source: trunk/Packages/Common/UListViewSort.pas

Last change on this file was 215, checked in by chronos, 3 years ago
  • Modified: Build under Lazarus 2.2.0.
  • Modified: Updated Common package.
File size: 17.1 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 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 Destroy;
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(const 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(ListViewCompare);
352end;
353
354procedure TListViewSort.Refresh;
355begin
356 if Assigned(FOnFilter) then FOnFilter(Self)
357 else if Assigned(Source) then
358 List.Assign(Source) else
359 List.Clear;
360 if ListView.Items.Count <> List.Count then
361 ListView.Items.Count := List.Count;
362 if Assigned(FOnCompareItem) and (Order <> soNone) then Sort(FOnCompareItem);
363 //ListView.Items[-1]; // Workaround for not show first row if selected
364 ListView.Refresh;
365 // Workaround for not working item selection on first row
366 //if not Assigned(ListView.Selected) then begin
367 // ListView.Items.Count := 0;
368 // ListView.Items.Count := List.Count;
369 //end;
370 //if ListView.Items.Count > 0 then
371 // ListView.Items[0].Selected := True;
372 //ListView.Selected := nil;
373 UpdateColumns;
374end;
375
376const
377 //W_64: Integer = 64; {Width of thumbnail in ICON view mode}
378 H_64: Integer = 64; {Height of thumbnail size}
379 CheckWidth: Integer = 14; {Width of check mark box}
380 CheckHeight: Integer = 14; {Height of checkmark}
381 CheckBiasTop: Integer = 2; {This aligns the checkbox to be in centered}
382 CheckBiasLeft: Integer = 3; {In the row of the list item display}
383
384function TListViewSort.CompareBoolean(Value1, Value2: Boolean): Integer;
385begin
386 if Value1 > Value2 then Result := 1
387 else if Value1 < Value2 then Result := -1
388 else Result := 0;
389end;
390
391function TListViewSort.CompareInteger(Value1, Value2: Integer): Integer;
392begin
393 if Value1 > Value2 then Result := 1
394 else if Value1 < Value2 then Result := -1
395 else Result := 0;
396end;
397
398function TListViewSort.CompareString(Value1, Value2: string): Integer;
399begin
400 Result := AnsiCompareStr(Value1, Value2);
401// if Value1 > Value2 then Result := -1
402// else if Value1 < Value2 then Result := 1
403// else Result := 0;
404end;
405
406function TListViewSort.CompareTime(Time1, Time2: TDateTime): Integer;
407begin
408 Result := DateUtils.CompareDateTime(Time1, Time2);
409end;
410
411constructor TListViewSort.Create(AOwner: TComponent);
412begin
413 inherited;
414 List := TFPGObjectList<TObject>.Create;
415 List.FreeObjects := False;
416end;
417
418destructor TListViewSort.Destroy;
419begin
420 List.Free;
421 inherited;
422end;
423
424procedure TListViewSort.DrawCheckMark(Item: TListItem; Checked:
425 Boolean);
426var
427 TP1: TPoint;
428 XBias, YBias: Integer;
429 PenColor: TColor;
430 BrushColor: TColor;
431 BiasTop, BiasLeft: Integer;
432 Rect1: TRect;
433 lRect: TRect;
434 ItemLeft: Integer;
435begin
436 XBias := 0;
437 YBias := 0;
438 BiasTop := 0;
439 BiasLeft := 0;
440 Item.Left := 0;
441 GetCheckBias(XBias, YBias, BiasTop, BiasLeft, ListView);
442 PenColor := ListView.Canvas.Pen.Color;
443 BrushColor := ListView.Canvas.Brush.Color;
444 //TP1 := Item.GetPosition;
445 lRect := Item.DisplayRect(drBounds); // Windows 7 workaround
446 TP1.X := lRect.Left;
447 TP1.Y := lRect.Top;
448 //ShowMessage(IntToStr(Item.Index) + ', ' + IntToStr(GetScrollPos(Item.ListView.Handle, SB_VERT)) + ' ' +
449 // IntToHex(Integer(Item), 8) + ', ' + IntToStr(TP1.X) + ', ' + IntToStr(TP1.Y));
450
451// if Checked then
452 ListView.Canvas.Brush.Color := clWhite;
453 ItemLeft := Item.Left;
454 ItemLeft := 23; // Windows 7 workaround
455
456 Rect1.Left := ItemLeft - CheckWidth - BiasLeft + 1 + XBias;
457 //ShowMessage(IntToStr(Tp1.Y) + ', ' + IntToStr(BiasTop) + ', ' + IntToStr(XBias));
458 Rect1.Top := Tp1.Y + BiasTop + 1 + YBias;
459 Rect1.Right := ItemLeft - BiasLeft - 1 + XBias;
460 Rect1.Bottom := Tp1.Y + BiasTop + CheckHeight - 1 + YBias;
461 //ShowMessage(IntToStr(Rect1.Left) + ', ' + IntToStr(Rect1.Top) + ', ' + IntToStr(Rect1.Right) + ', ' + IntToStr(Rect1.Bottom));
462
463 ListView.Canvas.FillRect(Rect1);
464 //if Checked then ListView.Canvas.Brush.Color := clBlack
465 ListView.Canvas.Brush.Color := clBlack;
466 ListView.Canvas.FrameRect(Rect1);
467 ListView.Canvas.FrameRect(Rect(Rect1.Left - 1, Rect1.Top - 1,
468 Rect1.Right + 1, Rect1.Bottom + 1));
469 if Checked then begin
470 ListView.Canvas.Pen.Color := clBlack;
471 ListView.Canvas.MoveTo(ItemLeft - BiasLeft - 2 + XBias - 2,
472 Tp1.Y + BiasTop + 3 + YBias);
473 ListView.Canvas.LineTo(ItemLeft - BiasLeft - (CheckWidth div 2) + XBias,
474 Tp1.Y + BiasTop + (CheckHeight - 4) + YBias);
475 ListView.Canvas.LineTo(ItemLeft - BiasLeft - (CheckWidth - 3) + XBias,
476 Tp1.Y + BiasTop + (CheckHeight div 2) + YBias - 1);
477
478 ListView.Canvas.MoveTo(ItemLeft - BiasLeft - 2 - 1 + XBias - 2,
479 Tp1.Y + BiasTop + 3 + YBias);
480 ListView.Canvas.LineTo(ItemLeft - BiasLeft - (CheckWidth div 2) - 1 + XBias,
481 Tp1.Y + BiasTop + (CheckHeight - 4) + YBias);
482 ListView.Canvas.LineTo(ItemLeft - BiasLeft - (CheckWidth - 3) - 1 + XBias,
483 Tp1.Y + BiasTop + (CheckHeight div 2) + YBias - 1);
484 end;
485 //ListView.Canvas.Brush.Color := ListView.Color;
486 ListView.Canvas.Brush.Color := BrushColor;
487 ListView.Canvas.Pen.Color := PenColor;
488end;
489
490procedure TListViewSort.GetCheckBias(var XBias, YBias, BiasTop, BiasLeft: Integer;
491 const ListView: TListView);
492begin
493 XBias := 0;
494 YBias := 0;
495 if ListView.ViewStyle = vsICON then
496 begin
497 YBias := H_64 - CheckHeight;
498 XBias := 0;
499 end;
500 BiasTop := CheckBiasTop;
501 BiasLeft := CheckBiasLeft;
502 if ListView.ViewStyle <> vsReport then
503 begin
504 BiasTop := 0;
505 BiasLeft := 0;
506 end;
507end;
508
509procedure TListViewSort.ListViewCustomDrawItem(Sender: TCustomListView;
510 Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
511begin
512 if Assigned(Item) then begin
513 if ListView.Checkboxes then
514 DrawCheckMark(Item, Item.Checked);
515 if Assigned(FOnCustomDraw) then
516 FOnCustomDraw(Sender, Item, State, DefaultDraw);
517 end;
518end;
519
520procedure TListViewSort.ListViewClick(Sender: TObject);
521var
522 Item: TListItem;
523 Pos: TPoint;
524 DefaultDraw: Boolean;
525begin
526 Pos := ListView.ScreenToClient(Mouse.CursorPos);
527 Item := ListView.GetItemAt(Pos.X, Pos.Y);
528 //ShowMessage(IntToStr(Item.Index) + ', ' + IntToStr(Pos.X) + ', ' + IntToStr(Pos.Y));
529 if Assigned(Item) and (Pos.X < 20) then begin
530
531 Item.Checked := not Item.Checked;
532 //ShowMessage(IntToStr(Item.Index) + ', ' +BoolToStr(Item.Checked));
533 if Assigned(ListView.OnChange) then
534 ListView.OnChange(Self, Item, ctState);
535 DefaultDraw := False;
536 ListViewCustomDrawItem(ListView, Item, [], DefaultDraw);
537 //ListView.UpdateItems(Item.Index, Item.Index);
538 end;
539end;
540
541procedure TListViewSort.UpdateColumns;
542{$IFDEF Windows}
543const
544 HDF_SORTUP = $0400;
545 HDF_SORTDOWN = $0200;
546 SortOrder: array[TSortOrder] of Word = (0, HDF_SORTUP, HDF_SORTDOWN);
547var
548 Item: THDItem;
549 I: Integer;
550begin
551 if Assigned(FListView) then begin
552 FHeaderHandle := ListView_GetHeader(FListView.Handle);
553 for I := 0 to FListView.Columns.Count - 1 do begin
554 {$push}{$warn 5057 off}
555 FillChar(Item, SizeOf(THDItem), 0);
556 {$pop}
557 Item.Mask := HDI_FORMAT;
558 Header_GetItem(FHeaderHandle, I, Item);
559 Item.fmt := Item.fmt and not (HDF_SORTDOWN or HDF_SORTUP);
560 if (Column <> -1) and (I = Column) then
561 Item.fmt := Item.fmt or SortOrder[FOrder];
562 Header_SetItem(FHeaderHandle, I, Item);
563 end;
564 end;
565end;
566{$ELSE}
567begin
568end;
569{$ENDIF}
570
571end.
Note: See TracBrowser for help on using the repository browser.