source: trunk/Packages/Common/ListViewSort.pas

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