ListViewColProperties.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. unit ListViewColProperties;
  2. interface
  3. uses
  4. Classes, ComCtrls, Contnrs;
  5. const
  6. DefaultListViewMaxWidth = 1000;
  7. DefaultListViewMinWidth = 20;
  8. type
  9. TCustomListViewColProperty = class(TObject)
  10. Alignment: TAlignment;
  11. Caption: string;
  12. Width: Integer;
  13. Visible: Boolean;
  14. Order: Integer;
  15. constructor Create(AOrder: Integer);
  16. end;
  17. type
  18. TCustomListViewColProperties = class(TPersistent)
  19. private
  20. FChanged: Boolean;
  21. FMaxWidth: Integer;
  22. FMinWidth: Integer;
  23. FOnChange: TNotifyEvent;
  24. FUpdating: Integer;
  25. FProperties: TObjectList;
  26. FCreated: Boolean;
  27. function GetColumns: TListColumns;
  28. function GetCount: Integer;
  29. function GetOrderStr: string;
  30. procedure CheckBounds(Index: Integer);
  31. procedure SetMaxWidth(Value: Integer);
  32. procedure SetMinWidth(Value: Integer);
  33. procedure SetWidthsStr(Value: string; PixelsPerInch: Integer);
  34. function GetWidthsStr: string;
  35. procedure SetOrderStr(Value: string);
  36. protected
  37. FListView: TCustomListView;
  38. FListViewManaged: Boolean;
  39. function GetAlignments(Index: Integer): TAlignment;
  40. function GetParamsStr: string; virtual;
  41. function GetVisible(Index: Integer): Boolean;
  42. function GetWidths(Index: Integer): Integer;
  43. procedure SetAlignments(Index: Integer; Value: TAlignment);
  44. procedure SetVisible(Index: Integer; Value: Boolean);
  45. procedure SetWidths(Index: Integer; Value: Integer);
  46. function GetCaptions(Index: Integer): string;
  47. procedure Changed; virtual;
  48. procedure SetCaptions(Index: Integer; Value: string); virtual;
  49. procedure SetParamsStr(Value: string); virtual;
  50. procedure UpdateListView;
  51. procedure UpdateFromListView;
  52. procedure UpdateOrderFromListView;
  53. procedure UpdateListViewOrder;
  54. function GetProperties(Index: Integer): TCustomListViewColProperty;
  55. function GetIndexByOrder(Order: Integer): Integer;
  56. function ColumnsExists: Boolean;
  57. procedure SetRuntimeVisible(Index: Integer; Value: Boolean; SaveWidth: Boolean);
  58. function GetColumn(Index: Integer): TListColumn;
  59. procedure CreateProperties(ACount: Integer);
  60. property Columns: TListColumns read GetColumns stored False;
  61. property Count: Integer read GetCount stored False;
  62. public
  63. constructor Create(ListView: TCustomListView; ColCount: Integer);
  64. destructor Destroy; override;
  65. procedure EndUpdate;
  66. procedure BeginUpdate;
  67. procedure ListViewWndCreated;
  68. procedure ListViewWndDestroying;
  69. procedure ListViewWndDestroyed;
  70. property Alignments[Index: Integer]: TAlignment read GetAlignments write SetAlignments;
  71. property Captions[Index: Integer]: string read GetCaptions write SetCaptions;
  72. property MaxWidth: Integer read FMaxWidth write SetMaxWidth default DefaultListViewMaxWidth;
  73. property MinWidth: Integer read FMinWidth write SetMinWidth default DefaultListViewMinWidth;
  74. property Widths[Index: Integer]: Integer read GetWidths write SetWidths;
  75. property Visible[Index: Integer]: Boolean read GetVisible write SetVisible;
  76. procedure RecreateColumns;
  77. property OnChange: TNotifyEvent read FOnChange write FOnChange;
  78. property ParamsStr: string read GetParamsStr write SetParamsStr stored False;
  79. end; { TListViewColProperties }
  80. type
  81. TListViewColProperties = class(TCustomListViewColProperties)
  82. published
  83. property MaxWidth;
  84. property MinWidth;
  85. end; { TListViewColProperties }
  86. implementation
  87. uses
  88. SysUtils, CommCtrl, Windows, PasTools, Controls;
  89. { TODO : V ListView zamezit zmenu velikosti neviditelnych sloupecku }
  90. constructor TCustomListViewColProperty.Create(AOrder: Integer);
  91. begin
  92. Alignment := taLeftJustify;
  93. Caption := '';
  94. Width := 50;
  95. Visible := True;
  96. Order := AOrder;
  97. end;
  98. { TCustomListViewColProperties }
  99. constructor TCustomListViewColProperties.Create(
  100. ListView: TCustomListView; ColCount: Integer);
  101. var
  102. ACount: Integer;
  103. begin
  104. inherited Create;
  105. FCreated := False;
  106. FUpdating := 0;
  107. FChanged := False;
  108. FListViewManaged := (ColCount = 0);
  109. FListView := ListView;
  110. FProperties := TObjectList.Create;
  111. if FListViewManaged then ACount := GetColumns.Count
  112. else ACount := ColCount;
  113. CreateProperties(ACount);
  114. if not Assigned(FListView) then
  115. raise Exception.Create('NIL ListView pointer.');
  116. MaxWidth := DefaultListViewMaxWidth;
  117. MinWidth := DefaultListViewMinWidth;
  118. end;
  119. destructor TCustomListViewColProperties.Destroy;
  120. begin
  121. inherited;
  122. FProperties.Free;
  123. end;
  124. procedure TCustomListViewColProperties.SetWidthsStr(Value: string; PixelsPerInch: Integer);
  125. var
  126. ColStr: string;
  127. Index: Integer;
  128. begin
  129. Index := 0;
  130. BeginUpdate;
  131. try
  132. while (Value <> '') and (Index < Count) do
  133. begin
  134. ColStr := CutToChar(Value, ';', True);
  135. Widths[Index] := LoadDimension(StrToInt(CutToChar(ColStr, ',', True)), PixelsPerInch);
  136. Visible[Index] := Boolean(StrToInt(CutToChar(ColStr, ',', True)));
  137. Inc(Index);
  138. end;
  139. finally
  140. EndUpdate;
  141. end;
  142. end;
  143. function TCustomListViewColProperties.GetWidthsStr: string;
  144. var
  145. Index: Integer;
  146. begin
  147. Result := '';
  148. for Index := 0 to Count-1 do
  149. Result := Format('%s;%d,%d', [Result, SaveDimension(Widths[Index]), Integer(Visible[Index])]);
  150. Delete(Result, 1, 1);
  151. end;
  152. procedure TCustomListViewColProperties.BeginUpdate;
  153. begin
  154. Columns.BeginUpdate;
  155. Inc(FUpdating);
  156. end;
  157. procedure TCustomListViewColProperties.EndUpdate;
  158. begin
  159. Columns.EndUpdate;
  160. Dec(FUpdating);
  161. if FUpdating = 0 then
  162. begin
  163. // call Changed() even when FChange is false
  164. Changed;
  165. FChanged := False;
  166. end;
  167. end;
  168. procedure TCustomListViewColProperties.Changed;
  169. begin
  170. if FUpdating > 0 then FChanged := True
  171. else
  172. if Assigned(FOnChange) then FOnChange(Self);
  173. end;
  174. procedure TCustomListViewColProperties.CheckBounds(Index: Integer);
  175. begin
  176. if (Index < 0) or (Index >= Count) then
  177. raise Exception.Create('Index out of bounds.');
  178. end;
  179. function TCustomListViewColProperties.GetProperties(Index: Integer): TCustomListViewColProperty;
  180. begin
  181. Result := TCustomListViewColProperty(FProperties.Items[Index]);
  182. end;
  183. function TCustomListViewColProperties.GetIndexByOrder(Order: Integer): Integer;
  184. var
  185. I: Integer;
  186. begin
  187. for I := 0 to Count - 1 do
  188. begin
  189. if GetProperties(I).Order = Order then
  190. begin
  191. Result := I;
  192. Exit;
  193. end;
  194. end;
  195. raise Exception.Create('Column order out of bounds');
  196. end;
  197. function TCustomListViewColProperties.ColumnsExists: Boolean;
  198. begin
  199. Result := FListView.HandleAllocated;
  200. if Result and (not FCreated) and (not FListViewManaged) then
  201. UpdateListView;
  202. end;
  203. procedure TCustomListViewColProperties.SetAlignments(Index: Integer; Value: TAlignment);
  204. begin
  205. CheckBounds(Index);
  206. if Alignments[Index] <> Value then
  207. begin
  208. GetProperties(Index).Alignment := Value;
  209. if ColumnsExists then GetColumn(Index).Alignment := Value;
  210. Changed;
  211. end;
  212. end;
  213. procedure TCustomListViewColProperties.SetCaptions(Index: Integer; Value: string);
  214. begin
  215. CheckBounds(Index);
  216. if Captions[Index] <> Value then
  217. begin
  218. if ColumnsExists then GetColumn(Index).Caption := Value
  219. else GetProperties(Index).Caption := Value;
  220. Changed;
  221. end;
  222. end;
  223. function TCustomListViewColProperties.GetAlignments(Index: Integer): TAlignment;
  224. begin
  225. CheckBounds(Index);
  226. if ColumnsExists then Result := GetColumn(Index).Alignment
  227. else Result := GetProperties(Index).Alignment;
  228. end;
  229. function TCustomListViewColProperties.GetCaptions(Index: Integer): string;
  230. begin
  231. CheckBounds(Index);
  232. if ColumnsExists then Result := GetColumn(Index).Caption
  233. else Result := GetProperties(Index).Caption;
  234. end;
  235. procedure TCustomListViewColProperties.SetOrderStr(Value: string);
  236. var
  237. Order, Index: Integer;
  238. Properties: TCustomListViewColProperty;
  239. STemp: string;
  240. Phase: Boolean;
  241. begin
  242. BeginUpdate;
  243. try
  244. for Index := 0 to Count - 1 do
  245. GetProperties(Index).Order := -1;
  246. Phase := True;
  247. Order := 0;
  248. repeat
  249. Phase := not Phase;
  250. STemp := Value;
  251. while (STemp <> '') and (Order < Count) do
  252. begin
  253. Index := StrToInt(CutToChar(STemp, ';', True));
  254. Properties := GetProperties(Index);
  255. if (Properties.Visible = Phase) and
  256. (Properties.Order < 0) { robustness } then
  257. begin
  258. Properties.Order := Order;
  259. Inc(Order);
  260. end;
  261. end;
  262. until Phase;
  263. // this does not make sure hidden columns are first,
  264. // but it gets fixed on the next run
  265. for Index := 0 to Count - 1 do
  266. begin
  267. Properties := GetProperties(Index);
  268. if Properties.Order < 0 then
  269. begin
  270. Properties.Order := Order;
  271. Inc(Order);
  272. end;
  273. end;
  274. if ColumnsExists then
  275. UpdateListViewOrder;
  276. finally
  277. EndUpdate;
  278. end;
  279. end;
  280. procedure TCustomListViewColProperties.SetParamsStr(Value: string);
  281. var
  282. S: string;
  283. WidthsStr: string;
  284. OrderStr: string;
  285. PixelsPerInch: Integer;
  286. begin
  287. S := CutToChar(Value, '|', True);
  288. WidthsStr := CutToChar(S, '@', True);
  289. PixelsPerInch := LoadPixelsPerInch(S);
  290. OrderStr := CutToChar(Value, '|', True);
  291. // set first orders, only than the widths/visibility,
  292. // as setting visibility can reorder hidden/visible columns
  293. // as needed, while setting order cannot ensure this
  294. SetOrderStr(OrderStr);
  295. SetWidthsStr(WidthsStr, PixelsPerInch);
  296. end;
  297. procedure TCustomListViewColProperties.SetVisible(Index: Integer; Value: Boolean);
  298. var
  299. I: Integer;
  300. Properties: TCustomListViewColProperty;
  301. begin
  302. CheckBounds(Index);
  303. if Visible[Index] <> Value then
  304. begin
  305. Properties := GetProperties(Index);
  306. if ColumnsExists then
  307. UpdateOrderFromListView;
  308. if Value then
  309. begin
  310. // shown column is moved to the back
  311. for I := 0 to Count - 1 do
  312. begin
  313. if GetProperties(I).Order > Properties.Order then
  314. Dec(GetProperties(I).Order);
  315. end;
  316. Properties.Order := Count - 1;
  317. if ColumnsExists then
  318. UpdateListViewOrder;
  319. // show only after reordering column
  320. Properties.Visible := True;
  321. if ColumnsExists then
  322. SetRuntimeVisible(Index, True, True);
  323. end
  324. else
  325. begin
  326. // hide before reordering column
  327. Properties.Visible := False;
  328. if ColumnsExists then
  329. SetRuntimeVisible(Index, False, True);
  330. // hidden column is moved to the front,
  331. // unless column to the left is not hidden already
  332. // (or unless it is first already, in which case the
  333. // condition in the loop is never satisfied)
  334. if (Properties.Order > 0) and
  335. GetProperties(GetIndexByOrder(Properties.Order - 1)).Visible then
  336. begin
  337. for I := 0 to Count - 1 do
  338. begin
  339. if GetProperties(I).Order < Properties.Order then
  340. Inc(GetProperties(I).Order);
  341. end;
  342. Properties.Order := 0;
  343. end;
  344. if ColumnsExists then
  345. UpdateListViewOrder;
  346. end;
  347. Changed;
  348. // It does not refresh itself at last one on Win7 when DoubleBuffered
  349. if FListView.HandleAllocated then
  350. FListView.Invalidate;
  351. end;
  352. end;
  353. procedure TCustomListViewColProperties.SetRuntimeVisible(
  354. Index: Integer; Value: Boolean; SaveWidth: Boolean);
  355. begin
  356. with GetColumn(Index) do
  357. begin
  358. if Value then
  359. begin
  360. MaxWidth := FMaxWidth;
  361. MinWidth := FMinWidth;
  362. Width := GetProperties(Index).Width;
  363. end
  364. else
  365. begin
  366. if SaveWidth then
  367. GetProperties(Index).Width := Width;
  368. MaxWidth := 1;
  369. MinWidth := 0;
  370. Width := 0
  371. end;
  372. end;
  373. end;
  374. procedure TCustomListViewColProperties.SetWidths(Index: Integer; Value: Integer);
  375. begin
  376. CheckBounds(Index);
  377. if Value < FMinWidth then Value := FMinWidth
  378. else
  379. if Value > FMaxWidth then Value := FMaxWidth;
  380. if Widths[Index] <> Value then
  381. begin
  382. GetProperties(Index).Width := Value;
  383. if ColumnsExists and Visible[Index] then GetColumn(Index).Width := Value;
  384. Changed;
  385. end;
  386. end;
  387. function TCustomListViewColProperties.GetColumns: TListColumns;
  388. begin
  389. Result := TListView(FListView).Columns;
  390. end;
  391. function TCustomListViewColProperties.GetColumn(Index: Integer): TListColumn;
  392. begin
  393. Result := Columns[Index];
  394. end;
  395. function TCustomListViewColProperties.GetCount: Integer;
  396. begin
  397. Result := FProperties.Count;
  398. end;
  399. function TCustomListViewColProperties.GetOrderStr: string;
  400. var
  401. Index: Integer;
  402. begin
  403. Result := '';
  404. if ColumnsExists then
  405. UpdateOrderFromListView;
  406. for Index := 0 to Count - 1 do
  407. Result := Format('%s;%d', [Result, GetIndexByOrder(Index)]);
  408. Delete(Result, 1, 1);
  409. end;
  410. function TCustomListViewColProperties.GetParamsStr: string;
  411. begin
  412. // WORKAROUND
  413. // Adding an additional semicolon after the list,
  414. // to ensure that old versions that did not expect the pixels-per-inch part,
  415. // stop at the semicolon, otherwise they try to parse the
  416. // "last-column-width|pixels-per-inch" as integer and throw.
  417. // For the other instance of this hack, see GetListViewStr.
  418. // The new pixels-per-inch part is inserted after the widths part
  419. // as parsing of this was always robust to stop at "count" elements,
  420. // what order part was not (due to its logic of skipping hidden columns)
  421. Result := Format('%s;@%s|%s', [GetWidthsStr, SavePixelsPerInch, GetOrderStr]);
  422. end;
  423. function TCustomListViewColProperties.GetVisible(Index: Integer): Boolean;
  424. begin
  425. CheckBounds(Index);
  426. Result := GetProperties(Index).Visible;
  427. end;
  428. function TCustomListViewColProperties.GetWidths(Index: Integer): Integer;
  429. begin
  430. CheckBounds(Index);
  431. if ColumnsExists and Visible[Index] then Result := GetColumn(Index).Width
  432. else Result := GetProperties(Index).Width;
  433. end;
  434. procedure TCustomListViewColProperties.SetMaxWidth(Value: Integer);
  435. var
  436. Index: Integer;
  437. begin
  438. if FMaxWidth <> Value then
  439. begin
  440. FMaxWidth := Value;
  441. for Index := 0 to Count - 1 do
  442. begin
  443. if ColumnsExists and Visible[Index] then GetColumn(Index).MaxWidth := Value
  444. else
  445. if GetProperties(Index).Width > Value then GetProperties(Index).Width := Value;
  446. end;
  447. end;
  448. end;
  449. procedure TCustomListViewColProperties.SetMinWidth(Value: Integer);
  450. var
  451. Index: Integer;
  452. begin
  453. if FMinWidth <> Value then
  454. begin
  455. FMinWidth := Value;
  456. for Index := 0 to Count - 1 do
  457. begin
  458. if ColumnsExists and Visible[Index] then GetColumn(Index).MinWidth := Value
  459. else
  460. if GetProperties(Index).Width < Value then GetProperties(Index).Width := Value;
  461. end;
  462. end;
  463. end;
  464. procedure TCustomListViewColProperties.RecreateColumns;
  465. var
  466. Copy: TListColumns;
  467. begin
  468. Copy := TListColumns.Create(nil);
  469. try
  470. Copy.Assign(Columns);
  471. Columns.Assign(Copy);
  472. finally
  473. Copy.Free;
  474. end;
  475. end;
  476. procedure TCustomListViewColProperties.CreateProperties(ACount: Integer);
  477. var
  478. Index: Integer;
  479. begin
  480. for Index := 0 to ACount - 1 do
  481. FProperties.Add(TCustomListViewColProperty.Create(Index));
  482. end;
  483. procedure TCustomListViewColProperties.ListViewWndCreated;
  484. begin
  485. if FListViewManaged then
  486. begin
  487. if (FProperties.Count = 0) and (Columns.Count > 0) then
  488. CreateProperties(Columns.Count);
  489. UpdateFromListView;
  490. end
  491. else
  492. begin
  493. UpdateListView;
  494. end;
  495. end;
  496. procedure TCustomListViewColProperties.ListViewWndDestroying;
  497. begin
  498. UpdateFromListView;
  499. end;
  500. procedure TCustomListViewColProperties.ListViewWndDestroyed;
  501. begin
  502. if not FListViewManaged then
  503. FCreated := False;
  504. end;
  505. procedure TCustomListViewColProperties.UpdateListViewOrder;
  506. var
  507. Index: Integer;
  508. Properties: TCustomListViewColProperty;
  509. Temp: array of Integer;
  510. begin
  511. SetLength(Temp, Count);
  512. // Seemingly useless,
  513. // but probably only because we swallow HDN_ENDDRAG in TCustomIEListView.WMNotify,
  514. // what prevents VLC from actually reordering columns collection
  515. ListView_GetColumnOrderArray(FListView.Handle, Count, PInteger(Temp));
  516. for Index := 0 to Count - 1 do
  517. begin
  518. Properties := GetProperties(Index);
  519. Temp[Properties.Order] := Index;
  520. end;
  521. ListView_SetColumnOrderArray(FListView.Handle, Count, PInteger(Temp));
  522. end;
  523. procedure TCustomListViewColProperties.UpdateListView;
  524. var
  525. Index: Integer;
  526. Column: TListColumn;
  527. Properties: TCustomListViewColProperty;
  528. begin
  529. BeginUpdate;
  530. try
  531. for Index := 0 to Count-1 do
  532. begin
  533. if Index < Columns.Count then
  534. Column := GetColumn(Index)
  535. else
  536. Column := Columns.Add;
  537. Properties := GetProperties(Index);
  538. Column.Alignment := Properties.Alignment;
  539. Column.Caption := Properties.Caption;
  540. SetRuntimeVisible(Index, Properties.Visible, False);
  541. end;
  542. UpdateListViewOrder;
  543. finally
  544. FCreated := True;
  545. EndUpdate;
  546. end;
  547. end;
  548. procedure TCustomListViewColProperties.UpdateOrderFromListView;
  549. var
  550. Index: Integer;
  551. Temp: array of Integer;
  552. begin
  553. SetLength(Temp, Count);
  554. ListView_GetColumnOrderArray(FListView.Handle, Count, PInteger(Temp));
  555. for Index := 0 to Count - 1 do
  556. begin
  557. GetProperties(Temp[Index]).Order := Index;
  558. end;
  559. end;
  560. procedure TCustomListViewColProperties.UpdateFromListView;
  561. var
  562. Index: Integer;
  563. Column: TListColumn;
  564. Properties: TCustomListViewColProperty;
  565. begin
  566. Assert(FProperties.Count = Columns.Count);
  567. for Index := 0 to Count-1 do
  568. begin
  569. Column := GetColumn(Index);
  570. Properties := GetProperties(Index);
  571. Properties.Alignment := Column.Alignment;
  572. Properties.Caption := Column.Caption;
  573. if Properties.Visible then
  574. Properties.Width := Column.Width;
  575. end;
  576. UpdateOrderFromListView;
  577. end;
  578. end.