ListViewColProperties.pas 19 KB

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