source: tags/1.0.0/Packages/Common/UListViewSort.pas

Last change on this file was 7, checked in by chronos, 5 years ago
  • Added: Remember window dimensions after application restart.
File size: 16.8 KB
Line 
1unit UListViewSort;
2
3// Date: 2019-05-17
4
5{$mode delphi}
6
7interface
8
9uses
10 {$IFDEF Windows}Windows, CommCtrl, LMessages, {$ENDIF}Classes, Graphics, ComCtrls, SysUtils,
11 Controls, DateUtils, Dialogs, fgl, Forms, Grids, StdCtrls, ExtCtrls,
12 LclIntf, LclType, LResources;
13
14type
15 TSortOrder = (soNone, soUp, soDown);
16
17 TListViewSort = class;
18
19 TCompareEvent = function (Item1, Item2: TObject): Integer of object;
20 TListFilterEvent = procedure (ListViewSort: TListViewSort) of object;
21
22 { TListViewSort }
23
24 TListViewSort = class(TComponent)
25 private
26 FListView: TListView;
27 FOnCompareItem: TCompareEvent;
28 FOnFilter: TListFilterEvent;
29 FOnCustomDraw: TLVCustomDrawItemEvent;
30 {$IFDEF Windows}FHeaderHandle: HWND;{$ENDIF}
31 FColumn: Integer;
32 FOrder: TSortOrder;
33 FOldListViewWindowProc: TWndMethod;
34 FOnColumnWidthChanged: TNotifyEvent;
35 procedure DoColumnBeginResize(const AColIndex: Integer);
36 procedure DoColumnResized(const AColIndex: Integer);
37 procedure DoColumnResizing(const AColIndex, AWidth: Integer);
38 procedure SetListView(const Value: TListView);
39 procedure ColumnClick(Sender: TObject; Column: TListColumn);
40 procedure Sort(Compare: TCompareEvent);
41 procedure DrawCheckMark(Item: TListItem; Checked: Boolean);
42 procedure GetCheckBias(var XBias, YBias, BiasTop, BiasLeft: Integer;
43 const ListView: TListView);
44 procedure ListViewCustomDrawItem(Sender: TCustomListView;
45 Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
46 procedure ListViewClick(Sender: TObject);
47 procedure UpdateColumns;
48 procedure SetColumn(const Value: Integer);
49 procedure SetOrder(const Value: TSortOrder);
50 {$IFDEF WINDOWS}
51 procedure NewListViewWindowProc(var AMsg: TMessage);
52 {$ENDIF}
53 public
54 List: TFPGObjectList<TObject>;
55 Source: TFPGObjectList<TObject>;
56 constructor Create(AOwner: TComponent); override;
57 destructor Destroy; override;
58 function CompareTime(Time1, Time2: TDateTime): Integer;
59 function CompareInteger(Value1, Value2: Integer): Integer;
60 function CompareString(Value1, Value2: string): Integer;
61 function CompareBoolean(Value1, Value2: Boolean): Integer;
62 procedure Refresh;
63 published
64 property ListView: TListView read FListView write SetListView;
65 property OnCompareItem: TCompareEvent read FOnCompareItem
66 write FOnCompareItem;
67 property OnFilter: TListFilterEvent read FOnFilter
68 write FOnFilter;
69 property OnCustomDraw: TLVCustomDrawItemEvent read FOnCustomDraw
70 write FOnCustomDraw;
71 property OnColumnWidthChanged: TNotifyEvent read FOnColumnWidthChanged
72 write FOnColumnWidthChanged;
73 property Column: Integer read FColumn write SetColumn;
74 property Order: TSortOrder read FOrder write SetOrder;
75 end;
76
77 { TListViewFilter }
78
79 TListViewFilter = class(TWinControl)
80 private
81 FOnChange: TNotifyEvent;
82 FStringGrid1: TStringGrid;
83 procedure GridDoOnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
84 procedure GridDoOnResize(Sender: TObject);
85 public
86 constructor Create(AOwner: TComponent); override;
87 procedure UpdateFromListView(ListView: TListView);
88 function TextEntered: Boolean;
89 function TextEnteredCount: Integer;
90 function TextEnteredColumn(Index: Integer): Boolean;
91 function GetColValue(Index: Integer): string;
92 property StringGrid: TStringGrid read FStringGrid1 write FStringGrid1;
93 published
94 property OnChange: TNotifyEvent read FOnChange write FOnChange;
95 property Align;
96 property Anchors;
97 property BorderSpacing;
98 end;
99
100 { TListViewEx }
101
102 TListViewEx = class(TWinControl)
103 private
104 FFilter: TListViewFilter;
105 FListView: TListView;
106 FListViewSort: TListViewSort;
107 procedure ResizeHanlder;
108 public
109 constructor Create(TheOwner: TComponent); override;
110 destructor Destroy; override;
111 published
112 property ListView: TListView read FListView write FListView;
113 property ListViewSort: TListViewSort read FListViewSort write FListViewSort;
114 property Filter: TListViewFilter read FFilter write FFilter;
115 property Visible;
116 end;
117
118procedure Register;
119
120
121implementation
122
123procedure Register;
124begin
125 RegisterComponents('Common', [TListViewSort, TListViewFilter, TListViewEx]);
126end;
127
128{ TListViewEx }
129
130procedure TListViewEx.ResizeHanlder;
131begin
132end;
133
134constructor TListViewEx.Create(TheOwner: TComponent);
135begin
136 inherited Create(TheOwner);
137 Filter := TListViewFilter.Create(Self);
138 Filter.Parent := Self;
139 Filter.Align := alBottom;
140 ListView := TListView.Create(Self);
141 ListView.Parent := Self;
142 ListView.Align := alClient;
143 ListViewSort := TListViewSort.Create(Self);
144 ListViewSort.ListView := ListView;
145end;
146
147destructor TListViewEx.Destroy;
148begin
149 inherited Destroy;
150end;
151
152{ TListViewFilter }
153
154procedure TListViewFilter.GridDoOnKeyUp(Sender: TObject; var Key: Word;
155 Shift: TShiftState);
156begin
157 if Assigned(FOnChange) then
158 FOnChange(Self);
159end;
160
161procedure TListViewFilter.GridDoOnResize(Sender: TObject);
162begin
163 FStringGrid1.DefaultRowHeight := FStringGrid1.Height;
164end;
165
166constructor TListViewFilter.Create(AOwner: TComponent);
167begin
168 inherited Create(AOwner);
169 FStringGrid1 := TStringGrid.Create(Self);
170 FStringGrid1.Align := alClient;
171 FStringGrid1.Parent := Self;
172 FStringGrid1.Visible := True;
173 FStringGrid1.ScrollBars := ssNone;
174 FStringGrid1.FixedCols := 0;
175 FStringGrid1.FixedRows := 0;
176 FStringGrid1.RowCount := 1;
177 FStringGrid1.Options := [goFixedHorzLine, goFixedVertLine, goVertLine,
178 goHorzLine, goRangeSelect, goEditing, goAlwaysShowEditor, goSmoothScroll];
179 FStringGrid1.OnKeyUp := GridDoOnKeyUp;
180 FStringGrid1.OnResize := GridDoOnResize;
181end;
182
183procedure TListViewFilter.UpdateFromListView(ListView: TListView);
184var
185 I: Integer;
186 R: TRect;
187begin
188 with FStringGrid1 do begin
189 while Columns.Count > ListView.Columns.Count do Columns.Delete(Columns.Count - 1);
190 while Columns.Count < ListView.Columns.Count do Columns.Add;
191 for I := 0 to ListView.Columns.Count - 1 do begin
192 Columns[I].Width := ListView.Columns[I].Width;
193 if Selection.Left = I then begin
194 R := CellRect(I, 0);
195 Editor.Left := R.Left + 2;
196 Editor.Width := R.Width - 4;
197 end;
198 end;
199 end;
200end;
201
202function TListViewFilter.TextEntered: Boolean;
203begin
204 Result := TextEnteredCount > 0;
205end;
206
207function TListViewFilter.TextEnteredCount: Integer;
208var
209 I: Integer;
210begin
211 Result := 0;
212 for I := 0 to FStringGrid1.ColCount - 1 do begin
213 if FStringGrid1.Cells[I, 0] <> '' then begin
214 Inc(Result);
215 end;
216 end;
217end;
218
219function TListViewFilter.TextEnteredColumn(Index: Integer): Boolean;
220begin
221 Result := FStringGrid1.Cells[Index, 0] <> '';
222end;
223
224function TListViewFilter.GetColValue(Index: Integer): string;
225begin
226 if (Index >= 0) and (Index < StringGrid.Columns.Count) then
227 Result := StringGrid.Cells[Index, 0]
228 else Result := '';
229end;
230
231{ TListViewSort }
232
233{$IFDEF WINDOWS}
234procedure TListViewSort.NewListViewWindowProc(var AMsg: TMessage);
235var
236 vColWidth: Integer;
237 vMsgNotify: TLMNotify absolute AMsg;
238 Code: Integer;
239begin
240 // call the old WindowProc of ListView
241 FOldListViewWindowProc(AMsg);
242
243 // Currently we care only with WM_NOTIFY message
244 if AMsg.Msg = WM_NOTIFY then
245 begin
246 Code := NMHDR(PHDNotify(vMsgNotify.NMHdr)^.Hdr).Code;
247 case Code of
248 HDN_ENDTRACKA, HDN_ENDTRACKW:
249 DoColumnResized(PHDNotify(vMsgNotify.NMHdr)^.Item);
250
251 HDN_BEGINTRACKA, HDN_BEGINTRACKW:
252 DoColumnBeginResize(PHDNotify(vMsgNotify.NMHdr)^.Item);
253
254 HDN_TRACKA, HDN_TRACKW:
255 begin
256 vColWidth := -1;
257 if (PHDNotify(vMsgNotify.NMHdr)^.PItem<>nil)
258 and (PHDNotify(vMsgNotify.NMHdr)^.PItem^.Mask and HDI_WIDTH <> 0)
259 then
260 vColWidth := PHDNotify(vMsgNotify.NMHdr)^.PItem^.cxy;
261
262 DoColumnResizing(PHDNotify(vMsgNotify.NMHdr)^.Item, vColWidth);
263 end;
264 end;
265 end;
266end;
267{$ENDIF}
268
269procedure TListViewSort.DoColumnBeginResize(const AColIndex: Integer);
270begin
271end;
272
273procedure TListViewSort.DoColumnResizing(const AColIndex, AWidth: Integer);
274begin
275end;
276
277procedure TListViewSort.DoColumnResized(const AColIndex: Integer);
278begin
279 if Assigned(FOnColumnWidthChanged) then
280 FOnColumnWidthChanged(Self);
281end;
282
283procedure TListViewSort.ColumnClick(Sender: TObject; Column: TListColumn);
284begin
285 if Column.Index = Self.Column then begin
286 if FOrder = soUp then FOrder := soDown
287 else if FOrder = soDown then FOrder := soUp
288 else FOrder := soUp;
289 end else Self.Column := Column.Index;
290 Refresh;
291 UpdateColumns;
292end;
293
294procedure TListViewSort.SetOrder(const Value: TSortOrder);
295begin
296 FOrder := Value;
297 UpdateColumns;
298end;
299
300procedure TListViewSort.SetColumn(const Value: Integer);
301begin
302 FColumn := Value;
303 UpdateColumns;
304end;
305
306procedure TListViewSort.SetListView(const Value: TListView);
307begin
308 if FListView = Value then Exit;
309 if Assigned(FListView) then
310 ListView.WindowProc := FOldListViewWindowProc;
311 FListView := Value;
312 FListView.OnColumnClick := ColumnClick;
313 FListView.OnCustomDrawItem := ListViewCustomDrawItem;
314 FListView.OnClick := ListViewClick;
315 FOldListViewWindowProc := FListView.WindowProc;
316 {$IFDEF WINDOWS}
317 FListView.WindowProc := NewListViewWindowProc;
318 {$ENDIF}
319end;
320
321var
322 ListViewSortCompare: TCompareEvent;
323
324function ListViewCompare(const Item1, Item2: TObject): Integer;
325begin
326 Result := ListViewSortCompare(Item1, Item2);
327end;
328
329procedure TListViewSort.Sort(Compare: TCompareEvent);
330begin
331 // TODO: Because TFLGObjectList compare handler is not class method,
332 // it is necessary to use simple function compare handler with local variable
333 ListViewSortCompare := Compare;
334 if (List.Count > 0) then
335 List.Sort(ListViewCompare);
336end;
337
338procedure TListViewSort.Refresh;
339begin
340 if Assigned(FOnFilter) then FOnFilter(Self)
341 else if Assigned(Source) then
342 List.Assign(Source) else
343 List.Clear;
344 if ListView.Items.Count <> List.Count then
345 ListView.Items.Count := List.Count;
346 if Assigned(FOnCompareItem) and (Order <> soNone) then Sort(FOnCompareItem);
347 //ListView.Items[-1]; // Workaround for not show first row if selected
348 ListView.Refresh;
349 // Workaround for not working item selection on first row
350 //if not Assigned(ListView.Selected) then begin
351 // ListView.Items.Count := 0;
352 // ListView.Items.Count := List.Count;
353 //end;
354 //if ListView.Items.Count > 0 then
355 // ListView.Items[0].Selected := True;
356 //ListView.Selected := nil;
357 UpdateColumns;
358end;
359
360const
361 //W_64: Integer = 64; {Width of thumbnail in ICON view mode}
362 H_64: Integer = 64; {Height of thumbnail size}
363 CheckWidth: Integer = 14; {Width of check mark box}
364 CheckHeight: Integer = 14; {Height of checkmark}
365 CheckBiasTop: Integer = 2; {This aligns the checkbox to be in centered}
366 CheckBiasLeft: Integer = 3; {In the row of the list item display}
367
368function TListViewSort.CompareBoolean(Value1, Value2: Boolean): Integer;
369begin
370 if Value1 > Value2 then Result := 1
371 else if Value1 < Value2 then Result := -1
372 else Result := 0;
373end;
374
375function TListViewSort.CompareInteger(Value1, Value2: Integer): Integer;
376begin
377 if Value1 > Value2 then Result := 1
378 else if Value1 < Value2 then Result := -1
379 else Result := 0;
380end;
381
382function TListViewSort.CompareString(Value1, Value2: string): Integer;
383begin
384 Result := AnsiCompareStr(Value1, Value2);
385// if Value1 > Value2 then Result := -1
386// else if Value1 < Value2 then Result := 1
387// else Result := 0;
388end;
389
390function TListViewSort.CompareTime(Time1, Time2: TDateTime): Integer;
391begin
392 Result := DateUtils.CompareDateTime(Time1, Time2);
393end;
394
395constructor TListViewSort.Create(AOwner: TComponent);
396begin
397 inherited;
398 List := TFPGObjectList<TObject>.Create;
399 List.FreeObjects := False;
400end;
401
402destructor TListViewSort.Destroy;
403begin
404 List.Free;
405 inherited;
406end;
407
408procedure TListViewSort.DrawCheckMark(Item: TListItem; Checked:
409 Boolean);
410var
411 TP1: TPoint;
412 XBias, YBias: Integer;
413 PenColor: TColor;
414 BrushColor: TColor;
415 BiasTop, BiasLeft: Integer;
416 Rect1: TRect;
417 lRect: TRect;
418 ItemLeft: Integer;
419begin
420 XBias := 0;
421 YBias := 0;
422 BiasTop := 0;
423 BiasLeft := 0;
424 Item.Left := 0;
425 GetCheckBias(XBias, YBias, BiasTop, BiasLeft, ListView);
426 PenColor := ListView.Canvas.Pen.Color;
427 BrushColor := ListView.Canvas.Brush.Color;
428 //TP1 := Item.GetPosition;
429 lRect := Item.DisplayRect(drBounds); // Windows 7 workaround
430 TP1.X := lRect.Left;
431 TP1.Y := lRect.Top;
432 //ShowMessage(IntToStr(Item.Index) + ', ' + IntToStr(GetScrollPos(Item.ListView.Handle, SB_VERT)) + ' ' +
433 // IntToHex(Integer(Item), 8) + ', ' + IntToStr(TP1.X) + ', ' + IntToStr(TP1.Y));
434
435// if Checked then
436 ListView.Canvas.Brush.Color := clWhite;
437 ItemLeft := Item.Left;
438 ItemLeft := 23; // Windows 7 workaround
439
440 Rect1.Left := ItemLeft - CheckWidth - BiasLeft + 1 + XBias;
441 //ShowMessage(IntToStr(Tp1.Y) + ', ' + IntToStr(BiasTop) + ', ' + IntToStr(XBias));
442 Rect1.Top := Tp1.Y + BiasTop + 1 + YBias;
443 Rect1.Right := ItemLeft - BiasLeft - 1 + XBias;
444 Rect1.Bottom := Tp1.Y + BiasTop + CheckHeight - 1 + YBias;
445 //ShowMessage(IntToStr(Rect1.Left) + ', ' + IntToStr(Rect1.Top) + ', ' + IntToStr(Rect1.Right) + ', ' + IntToStr(Rect1.Bottom));
446
447 ListView.Canvas.FillRect(Rect1);
448 //if Checked then ListView.Canvas.Brush.Color := clBlack
449 ListView.Canvas.Brush.Color := clBlack;
450 ListView.Canvas.FrameRect(Rect1);
451 ListView.Canvas.FrameRect(Rect(Rect1.Left - 1, Rect1.Top - 1,
452 Rect1.Right + 1, Rect1.Bottom + 1));
453 if Checked then begin
454 ListView.Canvas.Pen.Color := clBlack;
455 ListView.Canvas.MoveTo(ItemLeft - BiasLeft - 2 + XBias - 2,
456 Tp1.Y + BiasTop + 3 + YBias);
457 ListView.Canvas.LineTo(ItemLeft - BiasLeft - (CheckWidth div 2) + XBias,
458 Tp1.Y + BiasTop + (CheckHeight - 4) + YBias);
459 ListView.Canvas.LineTo(ItemLeft - BiasLeft - (CheckWidth - 3) + XBias,
460 Tp1.Y + BiasTop + (CheckHeight div 2) + YBias - 1);
461
462 ListView.Canvas.MoveTo(ItemLeft - BiasLeft - 2 - 1 + XBias - 2,
463 Tp1.Y + BiasTop + 3 + YBias);
464 ListView.Canvas.LineTo(ItemLeft - BiasLeft - (CheckWidth div 2) - 1 + XBias,
465 Tp1.Y + BiasTop + (CheckHeight - 4) + YBias);
466 ListView.Canvas.LineTo(ItemLeft - BiasLeft - (CheckWidth - 3) - 1 + XBias,
467 Tp1.Y + BiasTop + (CheckHeight div 2) + YBias - 1);
468 end;
469 //ListView.Canvas.Brush.Color := ListView.Color;
470 ListView.Canvas.Brush.Color := BrushColor;
471 ListView.Canvas.Pen.Color := PenColor;
472end;
473
474procedure TListViewSort.GetCheckBias(var XBias, YBias, BiasTop, BiasLeft: Integer;
475 const ListView: TListView);
476begin
477 XBias := 0;
478 YBias := 0;
479 if ListView.ViewStyle = vsICON then
480 begin
481 YBias := H_64 - CheckHeight;
482 XBias := 0;
483 end;
484 BiasTop := CheckBiasTop;
485 BiasLeft := CheckBiasLeft;
486 if ListView.ViewStyle <> vsReport then
487 begin
488 BiasTop := 0;
489 BiasLeft := 0;
490 end;
491end;
492
493procedure TListViewSort.ListViewCustomDrawItem(Sender: TCustomListView;
494 Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
495begin
496 if Assigned(Item) then begin
497 if ListView.Checkboxes then
498 DrawCheckMark(Item, Item.Checked);
499 if Assigned(FOnCustomDraw) then
500 FOnCustomDraw(Sender, Item, State, DefaultDraw);
501 end;
502end;
503
504procedure TListViewSort.ListViewClick(Sender: TObject);
505var
506 Item: TListItem;
507 Pos: TPoint;
508 DefaultDraw: Boolean;
509begin
510 Pos := ListView.ScreenToClient(Mouse.CursorPos);
511 Item := ListView.GetItemAt(Pos.X, Pos.Y);
512 //ShowMessage(IntToStr(Item.Index) + ', ' + IntToStr(Pos.X) + ', ' + IntToStr(Pos.Y));
513 if Assigned(Item) and (Pos.X < 20) then begin
514
515 Item.Checked := not Item.Checked;
516 //ShowMessage(IntToStr(Item.Index) + ', ' +BoolToStr(Item.Checked));
517 if Assigned(ListView.OnChange) then
518 ListView.OnChange(Self, Item, ctState);
519 DefaultDraw := False;
520 ListViewCustomDrawItem(ListView, Item, [], DefaultDraw);
521 //ListView.UpdateItems(Item.Index, Item.Index);
522 end;
523end;
524
525procedure TListViewSort.UpdateColumns;
526{$IFDEF Windows}
527const
528 HDF_SORTUP = $0400;
529 HDF_SORTDOWN = $0200;
530 SortOrder: array[TSortOrder] of Word = (0, HDF_SORTUP, HDF_SORTDOWN);
531var
532 Item: THDItem;
533 I: Integer;
534begin
535 if Assigned(FListView) then begin
536 FHeaderHandle := ListView_GetHeader(FListView.Handle);
537 for I := 0 to FListView.Columns.Count - 1 do begin
538 {$push}{$warn 5057 off}
539 FillChar(Item, SizeOf(THDItem), 0);
540 {$pop}
541 Item.Mask := HDI_FORMAT;
542 Header_GetItem(FHeaderHandle, I, Item);
543 Item.fmt := Item.fmt and not (HDF_SORTDOWN or HDF_SORTUP);
544 if (Column <> -1) and (I = Column) then
545 Item.fmt := Item.fmt or SortOrder[FOrder];
546 Header_SetItem(FHeaderHandle, I, Item);
547 end;
548 end;
549end;
550{$ELSE}
551begin
552end;
553{$ENDIF}
554
555end.
Note: See TracBrowser for help on using the repository browser.