1{%MainUnit win32wscomctrls.pp}
2{ $Id$
3
4 *****************************************************************************
5  This file is part of the Lazarus Component Library (LCL)
6
7  See the file COPYING.modifiedLGPL.txt, included in this distribution,
8  for details about the license.
9 *****************************************************************************
10}
11
12{ TWin32WSCustomListView }
13
14type
15  TLVStyleType = (lsStyle, lsInvert, lsExStyle);
16
17const
18  LV_STYLES: array[TListViewProperty] of record
19    StyleType: TLVStyleType;
20    Style: Integer;
21  end = (
22    (StyleType: lsStyle;   Style: LVS_AUTOARRANGE),        // lvpAutoArrange
23    (StyleType: lsExStyle; Style: LVS_EX_CHECKBOXES),      // lvpCheckboxes
24    (StyleType: lsInvert;  Style: LVS_NOSORTHEADER),       // lvpColumnClick
25    (StyleType: lsExStyle; Style: LVS_EX_FLATSB),          // lvpFlatScrollBars
26    (StyleType: lsExStyle; Style: LVS_EX_HEADERDRAGDROP),  // lvpFullDrag
27    (StyleType: lsExStyle; Style: LVS_EX_GRIDLINES),       // lvpGridLines
28    (StyleType: lsInvert;  Style: LVS_SHOWSELALWAYS),      // lvpHideSelection
29    (StyleType: lsExStyle; Style: LVS_EX_TRACKSELECT),     // lvpHotTrack
30    (StyleType: lsInvert;  Style: LVS_SINGLESEL),          // lvpMultiSelect
31    (StyleType: lsStyle;   Style: LVS_OWNERDRAWFIXED),     // lvpOwnerDraw
32    (StyleType: lsInvert;  Style: LVS_EDITLABELS),         // lvpReadOnly,
33    (StyleType: lsExStyle; Style: LVS_EX_FULLROWSELECT),   // lvpRowSelect
34    (StyleType: lsInvert;  Style: LVS_NOCOLUMNHEADER),     // lvpShowColumnHeaders
35    (StyleType: lsExStyle; Style: LVS_EX_MULTIWORKAREAS),  // lvpShowWorkAreas
36    (StyleType: lsInvert;  Style: LVS_NOLABELWRAP),        // lvpWrapText
37    (StyleType: lsExStyle; Style: LVS_EX_LABELTIP)         // lvpToolTips
38  );
39
40
41type
42  // TODO: add iImage and iOrder to exiting TLvColumn
43  // this is a hack !!!
44  TLvColumn_v4_7 = record
45    lvc: TLvColumn;
46    iImage: Integer;
47    iOrder: Integer;
48  end;
49
50
51type
52  TCustomListViewAccess = class(TCustomListView);
53  TListColumnAccess = class(TListColumn);
54
55////////////////////////////////////////////////////////////////////////////////
56// Msg handlers
57////////////////////////////////////////////////////////////////////////////////
58
59var
60  ListViewWindProcInfo: record
61    ActiveListView: TCustomListView;
62    NoMouseUp: Boolean;
63  end;
64
65function ListViewParentMsgHandler(const AWinControl: TWinControl; Window: HWnd;
66      Msg: UInt; WParam: Windows.WParam; LParam: Windows.LParam;
67      var MsgResult: Windows.LResult; var WinProcess: Boolean): Boolean;
68
69type
70  PNMLVOwnerData = PLVDISPINFO;
71var
72  NMHdr: PNMHdr absolute LParam; // used by WM_NOTIFY
73
74  // Gets the cursor position relative to a given window
75  function GetClientCursorPos(ClientWindow: HWND) : TSmallPoint;
76  var
77    P: TPoint;
78  begin
79    Windows.GetCursorPos(P);
80    //if the mouse is not over the window is better to set to 0 to avoid weird behaviors
81    if Windows.WindowFromPoint(P) = ClientWindow then
82      Windows.ScreenToClient(ClientWindow, P)
83    else
84    begin
85      P.X:=0;
86      P.Y:=0;
87    end;
88    Result := Windows.PointToSmallPoint(P);
89  end;
90
91  procedure HandleListViewOwnerData(ALV: TCustomListViewAccess);
92  var
93    DataInfo: PNMLVOwnerData; // absolute NMHdr;
94    txt: String;
95    LVInfo: PWin32WindowInfo;
96    idx: Integer;
97    listitem: TListItem;
98  begin
99    LVInfo:= GetWin32WindowInfo(ALV.Handle);
100    DataInfo := PNMLVOwnerData(NMHdr);
101    if not Assigned(DataInfo) or (not ALV.OwnerData) then
102      Exit;
103    listitem := ALV.Items[DataInfo^.item.iItem];
104    if not Assigned(listitem) then
105      Exit;
106    if (DataInfo^.item.mask and LVIF_TEXT) <> 0 then
107    begin
108      if DataInfo^.item.iSubItem = 0 then
109        txt := listitem.Caption
110      else
111      begin
112        idx := DataInfo^.item.iSubItem - 1;
113        if idx < listitem.SubItems.Count then
114          txt := listitem.SubItems[idx]
115        else
116          txt := '';
117      end;
118      if DataInfo^.hdr.code = UInt(LVN_GETDISPINFOA) then
119      begin
120        LVInfo^.DispInfoTextA[LVInfo^.DispInfoIndex]:=Utf8ToAnsi(txt+#0);
121        DataInfo^.item.pszText := @(LVInfo^.DispInfoTextA[LVInfo^.DispInfoIndex][1]);
122      end
123      else
124      begin
125        LVInfo^.DispInfoTextW[LVInfo^.DispInfoIndex]:=UTF8Decode(txt+#0);
126        DataInfo^.item.pszText := PChar(LVInfo^.DispInfoTextW[LVInfo^.DispInfoIndex]);
127      end;
128      inc(LVInfo^.DispInfoIndex);
129      if LVInfo^.DispInfoIndex=LV_DISP_INFO_COUNT then LVInfo^.DispInfoIndex:=0;
130    end;
131    if (DataInfo^.item.mask and LVIF_IMAGE) <> 0 then
132    begin
133      if DataInfo^.item.iSubItem = 0 then
134        DataInfo^.item.iImage := listitem.ImageIndex
135      else
136      begin
137        idx := DataInfo^.item.iSubItem - 1;
138        if idx < listitem.SubItems.Count then
139          DataInfo^.item.iImage := listitem.SubItemImages[idx]
140        else
141          DataInfo^.item.iImage := -1;
142      end;
143      if Assigned(ALV.StateImages) then
144      begin
145        DataInfo^.item.state := IndexToStateImageMask(listitem.StateIndex + 1);
146        DataInfo^.item.stateMask := $F000; // States start from 12 bit
147        DataInfo^.item.mask := DataInfo^.item.mask or LVIF_STATE;
148      end;
149    end;
150  end;
151
152  procedure HandleListViewCustomDraw(ALV: TCustomListViewAccess);
153    function ConvState(const State: uint): TCustomDrawState;
154    begin
155      Result := [];
156      if state and CDIS_CHECKED <> 0 then Include(Result, cdsChecked);
157      if state and CDIS_DEFAULT <> 0 then Include(Result, cdsDefault);
158      if state and CDIS_DISABLED <> 0 then Include(Result, cdsDisabled);
159      if state and CDIS_FOCUS <> 0 then Include(Result, cdsFocused);
160      if state and CDIS_GRAYED <> 0 then Include(Result, cdsGrayed);
161      if state and CDIS_HOT <> 0 then Include(Result, cdsHot);
162      if state and CDIS_INDETERMINATE <> 0 then Include(Result, cdsIndeterminate);
163      if state and CDIS_MARKED <> 0 then Include(Result, cdsMarked);
164      if state and CDIS_SELECTED <> 0 then Include(Result, cdsSelected);
165    end;
166
167  const
168    CDRFRESULT: array[TCustomDrawResultFlag] of Integer = (
169      CDRF_SKIPDEFAULT,
170      CDRF_NOTIFYPOSTPAINT,
171      CDRF_NOTIFYITEMDRAW,
172      CDRF_NOTIFYSUBITEMDRAW,
173      CDRF_NOTIFYPOSTERASE,
174      CDRF_NOTIFYITEMERASE
175    );
176  var
177    DrawInfo: PNMLVCustomDraw absolute NMHdr;
178    Stage: TCustomDrawStage;
179    DrawResult: TCustomDrawResult;
180    ResultFlag: TCustomDrawResultFlag;
181    OldDC: HDC;
182  begin
183    MsgResult := CDRF_DODEFAULT;
184    WinProcess := False;
185    if not ALV.IsCustomDrawn(dtControl, cdPrePaint) then
186      exit;
187
188    case DrawInfo^.nmcd.dwDrawStage and $7 of //Get drawing state
189      CDDS_PREPAINT:  Stage := cdPrePaint;
190      CDDS_POSTPAINT: Stage := cdPostPaint;
191      CDDS_PREERASE:  Stage := cdPreErase;
192      CDDS_POSTERASE: Stage := cdPostErase;
193    else
194      Exit;
195    end;
196
197    OldDC := ALV.Canvas.Handle;
198    ALV.Canvas.Handle := DrawInfo^.nmcd.hdc;
199    ALV.Canvas.Font.Assign(ALV.Font);
200    ALV.Canvas.Brush.Assign(ALV.Brush);
201
202    if DrawInfo^.nmcd.dwDrawStage and CDDS_SUBITEM <> 0 then
203    begin
204      // subitem 0 is handled by dtItem
205      if DrawInfo^.iSubItem = 0 then Exit;
206      DrawResult := ALV.IntfCustomDraw(dtSubItem, Stage,
207        DrawInfo^.nmcd.dwItemSpec, DrawInfo^.iSubItem,
208        ConvState(DrawInfo^.nmcd.uItemState), nil);
209    end
210    else
211    if DrawInfo^.nmcd.dwDrawStage and CDDS_ITEM <> 0 then
212      DrawResult := ALV.IntfCustomDraw(dtItem, Stage, DrawInfo^.nmcd.dwItemSpec,
213        -1, ConvState(DrawInfo^.nmcd.uItemState), nil)
214    else
215      DrawResult := ALV.IntfCustomDraw(dtControl, Stage, -1, -1, [], @DrawInfo^.nmcd.rc); //Whole control
216
217    if DrawResult <> [] then
218      MsgResult := 0;
219
220    if not (cdrSkipDefault in DrawResult) and
221       (DrawInfo^.nmcd.dwDrawStage and CDDS_ITEMPREPAINT = CDDS_ITEMPREPAINT) then
222    begin
223      DrawInfo^.clrText := ColorToRGB(ALV.Canvas.Font.Color);
224      DrawInfo^.clrTextBk := ColorToRGB(ALV.Canvas.Brush.Color);
225    end;
226    ALV.Canvas.Handle := OldDC;
227
228    for ResultFlag := Low(ResultFlag) to High(ResultFlag) do
229    begin
230      if ResultFlag in DrawResult then
231        MsgResult := MsgResult or CDRFRESULT[ResultFlag];
232    end;
233  end;
234
235begin
236  Result := False;
237  case Msg of
238    WM_NOTIFY:
239    begin
240      case PNMHdr(LParam)^.code of
241        LVN_GETDISPINFOA, LVN_GETDISPINFOW:
242          HandleListViewOwnerData(TCustomListViewAccess(AWinControl));
243        NM_CUSTOMDRAW:
244          HandleListViewCustomDraw(TCustomListViewAccess(AWinControl));
245        LVN_BEGINDRAG, LVN_BEGINRDRAG: begin
246            if ListViewWindProcInfo.ActiveListView = AWinControl then
247              ListViewWindProcInfo.NoMouseUp := True;
248          end;
249      end;
250    end;
251  end;
252end;
253////////////////////////////////////////////////////////////////////////////////
254// Event code
255////////////////////////////////////////////////////////////////////////////////
256
257
258////////////////////////////////////////////////////////////////////////////////
259// Column code
260////////////////////////////////////////////////////////////////////////////////
261
262class procedure TWin32WSCustomListView.ColumnDoAutosize(const ALV: TCustomListView; const AIndex: Integer);
263var
264  CaptionSize: TSize;
265begin
266  if (ALV.Items.Count > 0) then
267    ListView_SetColumnWidth(ALV.Handle, AIndex, LVSCW_AUTOSIZE)
268  else
269  begin
270    // normally, we have to use ListView_GetStringWidth, but it doesn't work with
271    // Unicode, so we take a universal function to get the width of the caption
272    if GetTextExtentPoint32W(ALV.Canvas.Handle,
273                             PWideChar(UTF8ToUTF16(ALV.Column[AIndex].Caption)),
274                             UTF8Length(ALV.Column[AIndex].Caption),
275                             CaptionSize) then
276    begin
277      // to retrieve the column width that can contain the string without
278      // truncating it, you must add padding to the returned string width
279      // see msdn: ListView_GetStringWidth
280      // there is no way to get the needed padding size for a list view caption
281      // so we take the height of the current caption text, to be DPI aware
282      ListView_SetColumnWidth(ALV.Handle, AIndex, CaptionSize.cx + CaptionSize.cy);
283    end
284    else
285      ListView_SetColumnWidth(ALV.Handle, AIndex, TListColumnAccess(ALV.Column[AIndex]).GetStoredWidth);
286  end;
287end;
288
289class procedure TWin32WSCustomListView.ColumnDelete(const ALV: TCustomListView; const AIndex: Integer);
290var
291  hHdr, hLV: THandle;
292  Count: Integer;
293begin
294  if not WSCheckHandleAllocated(ALV, 'ColumnDelete')
295  then Exit;
296
297  hLV := ALV.Handle;
298  hHdr := GetHeader(hLV);
299  if hHdr = 0 then Exit; //???
300
301  Count := Header_GetItemCount(hHdr);
302  if Count <= Aindex then Exit;
303
304  // Move column to the last, otherwise our items get shuffeled
305  if AIndex <> Count - 1 then
306    ColumnMove(ALV, AIndex, Count - 1, nil);
307  ListView_DeleteColumn(hLV, Count - 1);
308end;
309
310class function TWin32WSCustomListView.ColumnGetWidth(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn): Integer;
311var
312  lvc: TLvColumn;
313begin
314  Result := -1;
315  // this implementation uses columnwidht = 0 for invisible
316  // so fallback to default (= AColumn.FWidth)
317  // Don't return AColumn.Width, this will cause a loop
318  if not AColumn.Visible then Exit;
319
320  if not WSCheckHandleAllocated(ALV, 'ColumnGetWidth')
321  then Exit;
322
323  // do not use ListView_GetColumnWidth since we can not detect errors
324  lvc.Mask := LVCF_WIDTH;
325  if ListView_GetColumn(ALV.Handle, AIndex, lvc) <> 0
326  then Result := lvc.cx;
327end;
328
329class procedure TWin32WSCustomListView.ColumnInsert(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn);
330var
331  lvc: TLvColumn;
332begin
333  if not WSCheckHandleAllocated(ALV, 'ColumnInsert')
334  then Exit;
335
336  lvc.Mask := LVCF_TEXT;
337
338  lvc.pszText := PChar(PWideChar(UTF8ToUTF16(AColumn.Caption)));
339  SendMessage(ALV.Handle, LVM_INSERTCOLUMNW, WPARAM(AIndex), LPARAM(@lvc));
340end;
341
342class procedure TWin32WSCustomListView.ColumnMove(const ALV: TCustomListView; const AOldIndex, ANewIndex: Integer; const AColumn: TListColumn);
343var
344  lvc, oldlvc: TLvColumn_v4_7;
345  buf, oldbuf: array[0..1024] of Char;
346  Count, idx: Integer;
347
348begin
349  if not WSCheckHandleAllocated(ALV, 'ColumnMove')
350  then Exit;
351
352  Count := AOldIndex - ANewIndex;
353
354  // Fetch old column values
355  oldlvc.lvc.Mask := LVCF_FMT or LVCF_IMAGE or LVCF_TEXT or LVCF_WIDTH;
356  oldlvc.lvc.pszText := @oldbuf[0];
357  oldlvc.lvc.cchTextMax := SizeOF(oldbuf);
358  ListView_GetColumn(ALV.Handle, AOldIndex, oldlvc.lvc);
359
360  idx := AOldIndex;
361  while Count <> 0 do
362  begin
363    // get next index
364    if Count < 0
365    then Inc(idx)
366    else Dec(idx);
367    // and data
368    lvc.lvc.Mask := LVCF_FMT or LVCF_IMAGE or LVCF_TEXT or LVCF_WIDTH;
369    lvc.lvc.pszText := @buf[0];
370    lvc.lvc.cchTextMax := SizeOF(buf);
371    ListView_GetColumn(ALV.Handle, idx, lvc.lvc);
372    // set data
373    ListView_SetColumn(ALV.Handle, ANewIndex + Count, lvc.lvc);
374
375    if Count < 0
376    then Inc(Count)
377    else Dec(Count);
378  end;
379  // finally copy original data to new column
380  ListView_SetColumn(ALV.Handle, ANewIndex, oldlvc.lvc);
381end;
382
383class procedure TWin32WSCustomListView.ColumnSetAlignment(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn; const AAlignment: TAlignment);
384const
385  JUSTIFICATION: array[TAlignment] of Integer = (
386    LVCFMT_LEFT,
387    LVCFMT_RIGHT,
388    LVCFMT_CENTER
389  );
390var
391  lvc: TLvColumn;
392begin
393  if not WSCheckHandleAllocated(ALV, 'ColumnSetAlignment')
394  then Exit;
395
396  lvc.Mask := LVCF_FMT;
397  ListView_GetColumn(ALV.Handle, AIndex, lvc);
398  lvc.fmt := (lvc.fmt and not LVCFMT_JUSTIFYMASK) or JUSTIFICATION[AAlignment];
399  ListView_SetColumn(ALV.Handle, AIndex, lvc);
400end;
401
402class procedure TWin32WSCustomListView.ColumnSetAutoSize(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn; const AAutoSize: Boolean);
403begin
404  if not WSCheckHandleAllocated(ALV, 'ColumnSetAutoSize')
405  then Exit;
406
407  if AAutoSize
408  then ColumnDoAutosize(ALV, AIndex)
409  else ListView_SetColumnWidth(ALV.Handle, AIndex, TListColumnAccess(AColumn).GetStoredWidth);
410end;
411
412class procedure TWin32WSCustomListView.ColumnSetCaption(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn; const ACaption: String);
413var
414  lvc: TLvColumn;
415begin
416  if not WSCheckHandleAllocated(ALV, 'ColumnSetCaption')
417  then Exit;
418
419  lvc.Mask := LVCF_TEXT;
420
421  lvc.pszText := PChar(PWideChar(UTF8ToUTF16(AColumn.Caption)));
422  SendMessage(ALV.Handle, LVM_SETCOLUMNW, WPARAM(AIndex), LPARAM(@lvc));
423end;
424
425class procedure TWin32WSCustomListView.ColumnSetImage(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn; const AImageIndex: Integer);
426var
427  lvc: TLvColumn_v4_7;
428begin
429  if not WSCheckHandleAllocated(ALV, 'ColumnSetImage')
430  then Exit;
431
432  // forst get the old lvc, since we have to tell the bloody thing that this
433  // column has an image otherwise we will have a crash on XP using comctl 6
434
435  lvc.lvc.Mask := LVCF_FMT;
436  ListView_GetColumn(ALV.Handle, AIndex, lvc.lvc);
437
438  if AImageIndex = -1
439  then begin
440    lvc.lvc.Mask := LVCF_FMT;
441    lvc.lvc.fmt := lvc.lvc.fmt and not (LVCFMT_IMAGE or LVCFMT_COL_HAS_IMAGES);
442  end
443  else begin
444    lvc.lvc.Mask := LVCF_IMAGE or LVCF_FMT;
445    lvc.lvc.fmt := lvc.lvc.fmt or LVCFMT_IMAGE or LVCFMT_COL_HAS_IMAGES;
446    lvc.iImage := AImageIndex;
447  end;
448
449  ListView_SetColumn(ALV.Handle, AIndex, lvc.lvc);
450end;
451
452class procedure TWin32WSCustomListView.ColumnSetMaxWidth(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn; const AMaxWidth: Integer);
453begin
454  if not WSCheckHandleAllocated(ALV, 'ColumnSetMaxWidth')
455  then Exit;
456
457  // TODO: in messageHandler
458end;
459
460class procedure TWin32WSCustomListView.ColumnSetMinWidth(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn; const AMinWidth: integer);
461begin
462  if not WSCheckHandleAllocated(ALV, 'ColumnSetMinWidth')
463  then Exit;
464
465  // TODO: in messageHandler
466end;
467
468class procedure TWin32WSCustomListView.ColumnSetWidth(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn; const AWidth: Integer);
469begin
470  if not WSCheckHandleAllocated(ALV, 'ColumnSetWidth')
471  then Exit;
472
473  if AColumn.AutoSize
474  then ColumnDoAutosize(ALV, AIndex)
475  else ListView_SetColumnWidth(ALV.Handle, AIndex, AWidth)
476end;
477
478class procedure TWin32WSCustomListView.ColumnSetVisible(const ALV: TCustomListView; const AIndex: Integer; const AColumn: TListColumn; const AVisible: Boolean);
479begin
480  if not WSCheckHandleAllocated(ALV, 'ColumnSetVisible')
481  then Exit;
482
483  // TODO: implement with LV_COLUMN.subitem (associate different columns and insert/delete last.
484
485  if AVisible
486  then if AColumn.AutoSize
487    then ColumnDoAutosize(ALV, AIndex)
488    else ListView_SetColumnWidth(ALV.Handle, AIndex, TListColumnAccess(AColumn).GetStoredWidth)
489  else ListView_SetColumnWidth(ALV.Handle, AIndex, 0);
490end;
491
492class procedure TWin32WSCustomListView.ColumnSetSortIndicator(
493  const ALV: TCustomListView; const AIndex: Integer;
494  const AColumn: TListColumn; const AAndicator: TSortIndicator);
495var
496  Hdr: HWND;
497  Itm: THDITEM;
498begin
499  if not WSCheckHandleAllocated(ALV, 'ColumnSetSortIndicator')
500  then Exit;
501
502  Hdr := ListView_GetHeader(ALV.Handle);
503  FillChar(itm, sizeof(itm),0);
504  itm.mask := HDI_FORMAT;
505  Header_GetItem(Hdr, AIndex, Itm);
506  case AAndicator of
507    siNone:        itm.fmt := itm.fmt and (not (HDF_SORTDOWN or HDF_SORTUP));
508    siAscending:   itm.fmt := (itm.fmt or HDF_SORTUP) and (not HDF_SORTDOWN);
509    siDescending:  itm.fmt := (itm.fmt or HDF_SORTDOWN) and (not HDF_SORTUP);
510  end;
511  Header_SetItem(Hdr, AIndex, Itm);
512end;
513
514////////////////////////////////////////////////////////////////////////////////
515// Item code
516////////////////////////////////////////////////////////////////////////////////
517
518class procedure TWin32WSCustomListView.ItemDelete(const ALV: TCustomListView; const AIndex: Integer);
519begin
520  if not WSCheckHandleAllocated(ALV, 'ItemDelete')
521  then Exit;
522
523  ListView_DeleteItem(ALV.Handle, AIndex);
524end;
525
526class function TWin32WSCustomListView.ItemDisplayRect(const ALV: TCustomListView; const AIndex, ASubItem: Integer; ACode: TDisplayCode):TRect;
527const
528  DISPLAYCODES: array[TDisplayCode] of DWORD=(LVIR_BOUNDS, LVIR_ICON, LVIR_LABEL, LVIR_SELECTBOUNDS);
529var
530  mes: uint;
531begin
532  Result := Rect(0,0,0,0);
533  if not WSCheckHandleAllocated(ALV, 'ItemDisplayRect')
534  then Exit;
535
536  if ASubItem = 0
537  then mes:=LVM_GETITEMRECT
538  else begin
539    mes:=LVM_GETSUBITEMRECT;
540    if ACode = drSelectBounds
541    then ACode := drBounds;
542  end;
543  Result.top := ASubItem;
544  Result.left := DISPLAYCODES[ACode];
545  SendMessage(ALV.Handle, mes, AIndex, lparam(@Result));
546end;
547
548class procedure TWin32WSCustomListView.LVItemAssign(const ALV: TCustomListView;
549  AItem: TListItem; const AIndex: Integer);
550var
551  i: Integer;
552  B: Boolean;
553begin
554  if ALV.CheckBoxes then
555    B := AItem.Checked
556  else
557    B := False;
558
559  // apply texts
560  ItemSetText(ALV, AIndex, AItem, 0, AItem.Caption);
561  for i := 0 to AItem.SubItems.Count - 1 do
562    ItemSetText(ALV, AIndex, AItem, i + 1, AItem.SubItems[i]);
563  // make sure no texts are left over
564  for i := AItem.SubItems.Count to ALV.ColumnCount-1 do
565    ItemSetText(ALV, AIndex, AItem, i + 1, '');
566
567  // set state image
568  ItemSetStateImage(ALV, AIndex,AItem,0, AItem.StateIndex);
569
570  // set image
571  ItemSetImage(ALV, AIndex, AItem, 0, AItem.ImageIndex);
572
573  // apply checkbox state
574  ItemSetChecked(ALV, AIndex, AItem, B);
575end;
576
577class procedure TWin32WSCustomListView.ItemExchange(const ALV: TCustomListView;
578  AItem: TListItem; const AIndex1, AIndex2: Integer);
579var
580  AItem1, AItem2: TListItem;
581begin
582  if not WSCheckHandleAllocated(ALV, 'ItemExchange') then
583    exit;
584
585  //We have to reassign TLvItem to AIndex1 and AIndex2
586  //or use RecreateWnd() which is very expensive
587
588  AItem1 := ALV.Items[AIndex2];
589  AItem2 := ALV.Items[AIndex1];
590
591  LVItemAssign(ALV, AItem1, AIndex2);
592  LVItemAssign(ALV, AItem2, AIndex1);
593end;
594
595class procedure TWin32WSCustomListView.ItemMove(const ALV: TCustomListView;
596  AItem: TListItem; const AFromIndex, AToIndex: Integer);
597var
598  i: Integer;
599begin
600  if not WSCheckHandleAllocated(ALV, 'ItemMove') then
601    exit;
602  if AFromIndex = AToIndex then
603    exit;
604  if AFromIndex > AToIndex then
605  begin
606    for i := AToIndex to AFromIndex do
607      LVItemAssign(ALV, ALV.Items[i], i);
608  end else
609    for i := AFromIndex to AToIndex do
610      LVItemAssign(ALV, ALV.Items[i], i);
611end;
612
613class function TWin32WSCustomListView.ItemGetChecked(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem): Boolean;
614begin
615  Result := False;
616  if not WSCheckHandleAllocated(ALV, 'ItemGetChecked')
617  then Exit;
618  // shr 12 will give teh stateimage index, however a value of
619  // 0 means no image and 1 means unchecked. All other 14 are checked (?)
620  // so shifting 13 will always result in something <> 0 when checked.
621  Result := SendMessage(ALV.Handle, LVM_GETITEMSTATE, AIndex, LVIS_STATEIMAGEMASK) shr 13 <> 0;
622end;
623
624class function TWin32WSCustomListView.ItemGetPosition(
625  const ALV: TCustomListView; const AIndex: Integer): TPoint;
626begin
627  Result := Point(0, 0);
628  if WSCheckHandleAllocated(ALV, 'ItemGetPosition') then
629    SendMessage(ALV.Handle, LVM_GETITEMPOSITION, AIndex, LPARAM(@Result));
630end;
631
632class function TWin32WSCustomListView.ItemGetState(const ALV: TCustomListView;
633  const AIndex: Integer; const AItem: TListItem; const AState: TListItemState;
634  out AIsSet: Boolean): Boolean;
635const
636  // lisCut, lisDropTarget, lisFocused, lisSelected
637  FLAGS: array[TListItemState] of Integer = (LVIS_CUT, LVIS_DROPHILITED, LVIS_FOCUSED, LVIS_SELECTED);
638begin
639  Result := False;
640
641  if not WSCheckHandleAllocated(ALV, 'ItemGetState')
642  then Exit;
643
644  AIsSet := 0 <> ListView_GetItemState(ALV.Handle, AIndex, FLAGS[AState]);
645  Result := True;
646end;
647
648class function TWin32WSCustomListView.ItemGetStates(const ALV: TCustomListView; const AIndex: Integer; out AStates: TListItemStates): Boolean;
649const
650  MASK = LVIS_CUT or LVIS_DROPHILITED or LVIS_FOCUSED or LVIS_SELECTED;
651var
652  Flags: Integer;
653begin
654  Result := False;
655
656  if not WSCheckHandleAllocated(ALV, 'ItemGetStates')
657  then Exit;
658
659  AStates := [];
660  Flags := ListView_GetItemState(ALV.Handle, AIndex, MASK);
661  if (Flags and LVIS_CUT) <> 0 then
662    Include(AStates, lisCut);
663  if (Flags and LVIS_DROPHILITED) <> 0 then
664    Include(AStates, lisDropTarget);
665  if (Flags and LVIS_FOCUSED) <> 0 then
666    Include(AStates, lisFocused);
667  if (Flags and LVIS_SELECTED) <> 0 then
668    Include(AStates, lisSelected);
669
670  Result := True;
671end;
672
673class procedure TWin32WSCustomListView.ItemInsert(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem);
674var
675  lvi: TLvItem;
676begin
677  if not WSCheckHandleAllocated(ALV, 'ItemInsert')
678  then Exit;
679
680  lvi.Mask := LVIF_TEXT or LVIF_PARAM;
681  lvi.iItem := AIndex;
682  lvi.iSubItem := 0;
683  lvi.lParam := LPARAM(AItem);
684
685  lvi.pszText := PChar(PWideChar(UTF8ToUTF16(AItem.Caption)));
686  SendMessage(ALV.Handle, LVM_INSERTITEMW, 0, LPARAM(@lvi));
687end;
688
689class procedure TWin32WSCustomListView.ItemSetChecked(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const AChecked: Boolean);
690begin
691  if not WSCheckHandleAllocated(ALV, 'ItemSetChecked')
692  then Exit;
693
694  if AChecked then
695    ListView_SetItemState(ALV.Handle, AIndex, IndexToStateImageMask(2), LVIS_STATEIMAGEMASK)
696  else
697    ListView_SetItemState(ALV.Handle, AIndex, IndexToStateImageMask(1), LVIS_STATEIMAGEMASK);
698end;
699
700class procedure TWin32WSCustomListView.ItemSetImage(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const ASubIndex, AImageIndex: Integer);
701var
702  lvi: TLvItem;
703begin
704  if not WSCheckHandleAllocated(ALV, 'ItemSetImage')
705  then Exit;
706
707  lvi.Mask := LVIF_IMAGE;
708  lvi.iItem := AIndex;
709  lvi.iSubItem := ASubIndex;
710  lvi.iImage := AImageIndex;
711
712  ListView_SetItem(ALV.Handle, lvi);
713end;
714
715class function TWin32WSCustomListView.ItemSetPosition(const ALV: TCustomListView; const AIndex: Integer; const ANewPosition: TPoint): Boolean;
716begin
717  if not WSCheckHandleAllocated(ALV, 'ItemSetPosition') then
718    Result := False
719  else
720    Result := SendMessage(ALV.Handle, LVM_SETITEMPOSITION,
721      AIndex, MAKELPARAM(ANewPosition.X, ANewPosition.Y)) <> 0;
722end;
723
724class procedure TWin32WSCustomListView.ItemSetStateImage(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const ASubIndex, AStateImageIndex: Integer);
725begin
726  if not WSCheckHandleAllocated(ALV, 'ItemSetStateImage')
727  then Exit;
728
729  ListView_SetItemState(ALV.Handle, AIndex, IndexToStateImageMask(AStateImageIndex + 1), LVIS_STATEIMAGEMASK);
730end;
731
732class procedure TWin32WSCustomListView.ItemSetState(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const AState: TListItemState; const AIsSet: Boolean);
733const
734  // lisCut, lisDropTarget, lisFocused, lisSelected
735  FLAGS: array[TListItemState] of Integer = (LVIS_CUT, LVIS_DROPHILITED, LVIS_FOCUSED, LVIS_SELECTED);
736begin
737  if not WSCheckHandleAllocated(ALV, 'ItemSetState')
738  then Exit;
739  {Don't change the state if it already has needed value}
740  if ((ListView_GetItemState(ALV.Handle, AIndex, FLAGS[AState]) and FLAGS[AState]) = FLAGS[AState]) = AIsSet then exit;
741
742  if AIsSet
743  then ListView_SetItemState(ALV.Handle, AIndex, FLAGS[AState], FLAGS[AState])
744  else ListView_SetItemState(ALV.Handle, AIndex, 0, FLAGS[AState]);
745end;
746
747class procedure TWin32WSCustomListView.ItemSetText(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const ASubIndex: Integer; const AText: String);
748var
749  _gnu_lvi : LV_ITEM;
750begin
751  if not WSCheckHandleAllocated(ALV, 'ItemSetText')
752  then Exit;
753
754  _gnu_lvi.iSubItem := ASubIndex;
755  _gnu_lvi.pszText := PChar(PWideChar(UTF8ToUTF16(AText)));
756
757  SendMessage(ALV.Handle, LVM_SETITEMTEXTW, WPARAM(AIndex), LPARAM(@_gnu_lvi));
758  // autosize is an *extreme* performance bottleneck, even if WM_SETREDRAW
759  // was set to false it will ignore this and still redraw all columns.
760  // We will therefore postpone all autosizing until EndUpdate where we do
761  // it only once per column.
762
763  if (ASubIndex >= 0) and (ASubIndex < ALV.ColumnCount) and ALV.Column[ASubIndex].AutoSize and (TCustomListViewAccess(ALV).GetUpdateCount = 0) then
764    ColumnDoAutosize(ALV, ASubIndex);
765end;
766
767class procedure TWin32WSCustomListView.ItemShow(const ALV: TCustomListView; const AIndex: Integer; const AItem: TListItem; const PartialOK: Boolean);
768begin
769  if not WSCheckHandleAllocated(ALV, 'ItemShow')
770  then Exit;
771
772  ListView_EnsureVisible(ALV.Handle, AIndex, Ord(PartialOK));
773end;
774
775////////////////////////////////////////////////////////////////////////////////
776// LV code
777////////////////////////////////////////////////////////////////////////////////
778
779procedure ListViewDrawItem(const AWinControl: TWinControl; Window: HWnd;
780  Msg: UInt; WParam: Windows.WParam; const DrawIS: TDrawItemStruct;
781  var ItemMsg: Integer; var DrawListItem: Boolean);
782begin
783  DrawListItem := (AWinControl is TListView) and
784      (TListView(AWinControl).ViewStyle = vsReport) and
785      (DrawIS.ctlType = ODT_LISTVIEW) and
786      (TListView(AWinControl).OwnerDraw);
787  ItemMsg := CN_DRAWITEM;
788end;
789
790function ListViewProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam;
791    LParam: Windows.LParam): LResult; stdcall;
792
793  function GetClientCursorPos(ClientWindow: HWND) : TPoint;
794  begin
795    Windows.GetCursorPos(Result);
796      Windows.ScreenToClient(ClientWindow, Result);
797  end;
798
799var
800  WindowInfo: PWin32WindowInfo;
801  ListItem: TListItem;
802  ListView: TCustomListView;
803  AMsg: UINT;
804  MPos: TPoint;
805begin
806  case Msg of
807    WM_CONTEXTMENU:
808      begin
809        if ListViewWindProcInfo.ActiveListView <> nil then begin
810          Result := 1;
811          exit;
812        end;
813      end;
814    WM_LBUTTONDOWN, WM_RBUTTONDOWN:
815      begin
816      (*
817         A ListView doesn't get a WM_LBUTTONUP, WM_RBUTTONUP message,
818         because it keeps the message in its own event loop,
819         see msdn article about "Default List-View Message Processing"
820         therefore we take this notification and create a
821         LM_LBUTTONUP, LM_RBUTTONUP message out of it.
822
823         - When the ListView starts dragging (LVN_BEGIN(R)DRAG), it will get the
824           mouse up
825         - The WindProg for the WM_LBUTTONUP, WM_RBUTTONUP does not always
826           return immediately (multi select). It will return on either mouse up
827           or BeginDrag.
828           The LCL mouse down event is executed with the same delay.
829         - Also see issue #33330
830      *)
831
832        WindowInfo := GetWin32WindowInfo(Window);
833        ListView := TListView(WindowInfo^.WinControl);
834        ListItem := ListView.GetItemAt(GET_X_LPARAM(LParam), GET_Y_LPARAM(LParam));
835
836        if Msg = WM_LBUTTONDOWN
837        then AMsg := LM_LBUTTONUP
838        else AMsg := LM_RBUTTONUP;
839
840        ListViewWindProcInfo.ActiveListView := ListView;
841        ListViewWindProcInfo.NoMouseUp := False;
842        try
843          Result := WindowProc(Window, Msg, WParam, LParam);
844        finally
845          ListViewWindProcInfo.ActiveListView:= nil;
846        end;
847
848        if (not ListViewWindProcInfo.NoMouseUp) and
849           (Assigned(ListItem) or ListView.MultiSelect)
850        then
851        begin
852          MPos := GetClientCursorPos(Window);
853          PostMessage(Window, AMsg, 0, Windows.MakeLParam(MPos.X, MPos.Y));
854        end;
855        exit;
856      end;
857  end;
858  Result := WindowProc(Window, Msg, WParam, LParam);
859end;
860
861class function TWin32WSCustomListView.CreateHandle(const AWinControl: TWinControl;
862  const AParams: TCreateParams): HWND;
863const
864  LISTVIEWSTYLES: array[TViewStyle] of DWORD = (LVS_ICON, LVS_SMALLICON, LVS_LIST, LVS_REPORT);
865  Arrangement: array[TIconArrangement] of DWord = (LVS_ALIGNTOP, LVS_ALIGNLEFT);
866var
867  Params: TCreateWindowExParams;
868begin
869  // general initialization of Params
870  PrepareCreateWindow(AWinControl, AParams, Params);
871  // customization of Params
872  with Params do
873  begin
874    pClassName := WC_LISTVIEW;
875    SubClassWndProc := @ListViewProc;
876    WindowTitle := StrCaption;
877    Flags := Flags or LISTVIEWSTYLES[TListView(AWinControl).ViewStyle] or
878      LVS_SINGLESEL or LVS_SHAREIMAGELISTS or
879      Arrangement[TListView(AWinControl).IconOptions.Arrangement];
880    if TCustomListView(AWinControl).OwnerData then
881      Flags := Flags or LVS_OWNERDATA;
882    if TListView(AWinControl).OwnerDraw then
883      Flags := Flags or LVS_OWNERDRAWFIXED;
884    if TCustomListView(AWinControl).BorderStyle = bsSingle then
885      FlagsEx := FlagsEx or WS_EX_CLIENTEDGE;
886  end;
887  // create window
888  FinishCreateWindow(AWinControl, Params, false);
889  Params.WindowInfo^.ParentMsgHandler := @ListViewParentMsgHandler;
890  Params.WindowInfo^.needParentPaint := false;
891  Params.WindowInfo^.DrawItemHandler := @ListViewDrawItem;
892  Result := Params.Window;
893  if TCustomListView(AWinControl).checkboxes
894    then UpdateExStyle(result,lvs_ex_SubitemImages or lvs_Ex_Checkboxes,lvs_ex_SubitemImages or lvs_Ex_Checkboxes) else
895  UpdateExStyle(Result, LVS_EX_SUBITEMIMAGES, LVS_EX_SUBITEMIMAGES);
896end;
897
898class procedure TWin32WSCustomListView.BeginUpdate(const ALV: TCustomListView);
899begin
900  if not WSCheckHandleAllocated(ALV, 'BeginUpdate')
901  then Exit;
902
903  SendMessage(ALV.Handle,WM_SETREDRAW,WPARAM(False),0);
904end;
905
906class procedure TWin32WSCustomListView.EndUpdate(const ALV: TCustomListView);
907var
908  ColIndex : Integer;
909begin
910  if not WSCheckHandleAllocated(ALV, 'EndUpdate')
911  then Exit;
912
913  // we have skipped all column resizing in ItemSetText()
914  // for performance reasons, so now we need to do it here.
915  //
916  // A further significant perfomance boost and reduced flickering
917  // can be achieved by setting the widget to invisible during the
918  // following operation (it ignores the state of WM_SETREDRAW for
919  // column resizing, but this way we we can really enforce it).
920  // ShowWindow() itself does not force an immediate redraw,
921  // so it won't flicker at all.
922  ShowWindow(ALV.Handle, SW_HIDE);
923  for ColIndex := 0 to TCustomListViewAccess(ALV).Columns.Count - 1 do
924    if ALV.Column[ColIndex].AutoSize
925    then ColumnDoAutosize(ALV, ColIndex);
926
927  SendMessage(ALV.Handle,WM_SETREDRAW,WPARAM(True),0);
928  if ALV.Visible then
929    ShowWindow(ALV.Handle, SW_SHOW);
930end;
931
932class function TWin32WSCustomListView.GetBoundingRect(const ALV: TCustomListView): TRect;
933begin
934  Result := Rect(0,0,0,0);
935  if not WSCheckHandleAllocated(ALV, 'GetBoundingRect')
936  then Exit;
937
938  ListView_GetViewRect(ALV.Handle, Result);
939end;
940
941class function TWin32WSCustomListView.GetDropTarget(const ALV: TCustomListView): Integer;
942begin
943  Result := -1;
944  if not WSCheckHandleAllocated(ALV, 'GetDropTarget')
945  then Exit;
946
947  Result := ListView_GetNextItem(ALV.Handle, -1, LVNI_ALL or LVNI_DROPHILITED);
948end;
949
950class function TWin32WSCustomListView.GetFocused(const ALV: TCustomListView): Integer;
951begin
952  Result := -1;
953  if not WSCheckHandleAllocated(ALV, 'GetFocused')
954  then Exit;
955
956  Result := ListView_GetNextItem(ALV.Handle, -1, LVNI_ALL or LVNI_FOCUSED);
957end;
958
959class function TWin32WSCustomListView.GetHitTestInfoAt( const ALV: TCustomListView; X, Y: Integer ) : THitTests;
960var
961  HitInfo: LV_HITTESTINFO;
962
963begin
964  Result := [];
965  if not WSCheckHandleAllocated(ALV, 'GetHitTestInfoAt')
966  then Exit;
967
968  with HitInfo do
969  begin
970    pt.X := X;
971    pt.Y := Y;
972    ListView_HitTest( ALV.Handle, HitInfo );
973
974    if ((flags and LVHT_ABOVE) <> 0) and (Y < 0) then
975      Include(Result, htAbove);
976
977    if (flags and LVHT_BELOW) <> 0 then
978      Include(Result, htBelow);
979
980    if (flags and LVHT_NOWHERE) <> 0 then
981      Include(Result, ComCtrls.htNowhere);
982
983    if (flags and LVHT_ONITEM) = LVHT_ONITEM then
984      Include(Result, htOnItem)
985
986    else
987      begin
988      if (flags and LVHT_ONITEMICON) <> 0 then
989        Include(Result, htOnIcon);
990
991      if (flags and LVHT_ONITEMLABEL) <> 0 then
992        Include(Result, htOnLabel);
993
994      if (flags and LVHT_ONITEMSTATEICON) <> 0 then
995        Include(Result, htOnStateIcon);
996
997      end;
998
999    if (flags and LVHT_TOLEFT) <> 0 then
1000      Include(Result, htToLeft);
1001
1002    if (flags and LVHT_TORIGHT) <> 0 then
1003      Include(Result, htToRight);
1004
1005  end;
1006end;
1007
1008class function TWin32WSCustomListView.GetHoverTime(const ALV: TCustomListView): Integer;
1009begin
1010  Result := -1;
1011  if not WSCheckHandleAllocated(ALV, 'GetHoverTime')
1012  then Exit;
1013
1014  Result := SendMessage(ALV.Handle, LVM_GETHOVERTIME, 0, 0);
1015end;
1016
1017class function TWin32WSCustomListView.GetItemAt(const ALV: TCustomListView; x,
1018  y: Integer): Integer;
1019var
1020  HitInfo: LV_HITTESTINFO;
1021begin
1022  Result := -1;
1023  if not WSCheckHandleAllocated(ALV, 'GetItemAt')
1024  then Exit;
1025
1026  HitInfo.pt.x:=x;
1027  HitInfo.pt.y:=y;
1028  ListView_HitTest(alv.Handle,HitInfo);
1029  if HitInfo.flags <> LVHT_NOWHERE
1030  then Result:=HitInfo.iItem;
1031end;
1032
1033class function TWin32WSCustomListView.GetSelCount(const ALV: TCustomListView): Integer;
1034begin
1035  Result := 0;
1036  if not WSCheckHandleAllocated(ALV, 'GetSelCount')
1037  then Exit;
1038
1039  Result := ListView_GetSelectedCount(ALV.Handle);
1040end;
1041
1042class function TWin32WSCustomListView.GetSelection(const ALV: TCustomListView): Integer;
1043begin
1044  Result := -1;
1045  if not WSCheckHandleAllocated(ALV, 'GetSelection')
1046  then Exit;
1047
1048  Result := ListView_GetNextItem(ALV.Handle, -1, LVNI_ALL or LVNI_SELECTED);
1049end;
1050
1051class function TWin32WSCustomListView.GetTopItem(const ALV: TCustomListView): Integer;
1052begin
1053  Result := -1;
1054  if not WSCheckHandleAllocated(ALV, 'GetTopItem')
1055  then Exit;
1056
1057  case GetWindowLong(ALV.Handle, GWL_STYLE) and LVS_TYPEMASK of
1058    LVS_LIST,
1059    LVS_REPORT: Result := ListView_GetTopIndex(ALV.Handle);
1060  else
1061    Result := -1;
1062  end;
1063end;
1064
1065class function TWin32WSCustomListView.GetViewOrigin(const ALV: TCustomListView): TPoint;
1066begin
1067  if not WSCheckHandleAllocated(ALV, 'GetViewOrigin')
1068  then begin
1069    Result := Point(0, 0);
1070    Exit;
1071  end;
1072
1073  ListView_GetOrigin(ALV.Handle, Result);
1074end;
1075
1076class function TWin32WSCustomListView.GetVisibleRowCount(const ALV: TCustomListView): Integer;
1077begin
1078  Result := 0;
1079  if not WSCheckHandleAllocated(ALV, 'GetVisibleRowCount')
1080  then Exit;
1081
1082  case GetWindowLong(ALV.Handle, GWL_STYLE) and LVS_TYPEMASK of
1083    LVS_LIST,
1084    LVS_REPORT: Result := ListView_GetCountPerPage(ALV.Handle);
1085  else
1086    Result := -1;
1087  end;
1088end;
1089
1090class procedure TWin32WSCustomListView.SelectAll(const ALV: TCustomListView;
1091  const AIsSet: Boolean);
1092begin
1093  if not WSCheckHandleAllocated(ALV, 'SelectAll') then
1094    exit;
1095  // Index param -1 means select all.
1096  if AIsSet then
1097    ListView_SetItemState(ALV.Handle, -1, LVIS_SELECTED, LVIS_SELECTED)
1098  else
1099    ListView_SetItemState(ALV.Handle, -1, 0, LVIS_SELECTED);
1100end;
1101
1102class function TWin32WSCustomListView.GetHeader(const AHandle: THandle): THandle;
1103begin
1104  Result := THandle(SendMessage(AHandle, LVM_GETHEADER, 0, 0));
1105  if Result <> 0 then Exit;
1106
1107  // probably old version, try the first child
1108  Result := GetWindow(AHandle, GW_CHILD);
1109end;
1110
1111// MWE: original from MS knowledgebase KB137520
1112(********************************************************************
1113    PositionHeader
1114
1115    Call this function when the ListView is created, resized, the
1116    view is changed, or a WM_SYSPARAMETERCHANGE message is received.
1117
1118 ********************************************************************)
1119class procedure TWin32WSCustomListView.PositionHeader(const AHandle: THandle);
1120var
1121  hwndHeader: HWND;
1122  dwStyle: PtrInt;
1123  rc: TRect;
1124  hdLayout: THDLAYOUT;
1125  wpos: Windows.TWINDOWPOS;
1126begin
1127  dwStyle := GetWindowLong(AHandle, GWL_STYLE);
1128
1129  if dwStyle and LVS_NOSCROLL = 0 then Exit; // nothing to do
1130  if dwStyle and LVS_REPORT = 0 then Exit;   // nothing to do
1131
1132  hwndHeader := GetHeader(AHandle);
1133  if hwndHeader = 0 then Exit; // nothing to do
1134
1135  Windows.GetClientRect(AHandle, rc);
1136  FillChar(hdLayout, SizeOf(hdLayout), 0);
1137  hdLayout.prc := @rc;
1138  hdLayout.pwpos := @wpos;
1139  Header_Layout(hwndHeader, hdLayout);
1140
1141  Windows.SetWindowPos(hwndHeader,
1142                       wpos.hwndInsertAfter,
1143                       wpos.x,
1144                       wpos.y,
1145                       wpos.cx,
1146                       wpos.cy,
1147                       wpos.flags or SWP_SHOWWINDOW);
1148
1149  ListView_EnsureVisible(AHandle, 0, 0);
1150end;
1151
1152class procedure TWin32WSCustomListView.SetAllocBy(const ALV: TCustomListView; const AValue: Integer);
1153begin
1154  if not WSCheckHandleAllocated(ALV, 'SetAllocBy')
1155  then Exit;
1156
1157  ListView_SetItemCount(ALV.Handle, AValue);
1158end;
1159
1160class procedure TWin32WSCustomListView.SetBorderStyle(const AWinControl: TWinControl; const ABorderStyle: TBorderStyle);
1161begin
1162  if not WSCheckHandleAllocated(AWinControl, 'TWin32WSCustomListView.SetBorder') then
1163    Exit;
1164  // changing border style by changing EXSTYLE here does not work correctly
1165  RecreateWnd(AWinControl);
1166end;
1167
1168class procedure TWin32WSCustomListView.SetColor(const AWinControl: TWinControl);
1169var
1170  Color: TColor;
1171begin
1172  if not WSCheckHandleAllocated(AWinControl, 'TWin32WSCustomListView.SetColor') then
1173    Exit;
1174  Color := AWinControl.Color;
1175  if Color = clDefault then
1176    Color := AWinControl.GetDefaultColor(dctBrush);
1177  Windows.SendMessage(AWinControl.Handle, LVM_SETBKCOLOR, 0, ColorToRGB(Color));
1178  Windows.SendMessage(AWinControl.Handle, LVM_SETTEXTBKCOLOR, 0, ColorToRGB(Color));
1179end;
1180
1181class procedure TWin32WSCustomListView.SetDefaultItemHeight(const ALV: TCustomListView; const AValue: Integer);
1182begin
1183  if not WSCheckHandleAllocated(ALV, 'SetDefaultItemHeight')
1184  then Exit;
1185
1186  // TODO ???
1187end;
1188
1189class procedure TWin32WSCustomListView.SetFont(const AWinControl: TWinControl; const AFont: TFont);
1190var
1191  Color: TColor;
1192begin
1193  // call inherited SetFont; need to do it this way,
1194  // because the compile time ancestor class is TWSCustomListView
1195  TWSWinControlClass(ClassParent).SetFont(AWinControl, AFont);
1196  Color := AFont.Color;
1197  if Color = clDefault then
1198   Color := AWinControl.GetDefaultColor(dctFont);
1199  Windows.SendMessage(AWinControl.Handle, LVM_SETTEXTCOLOR, 0, ColorToRGB(Color));
1200end;
1201
1202class procedure TWin32WSCustomListView.SetHotTrackStyles(const ALV: TCustomListView; const AValue: TListHotTrackStyles);
1203const
1204  MASK = LVS_EX_ONECLICKACTIVATE or LVS_EX_TWOCLICKACTIVATE or LVS_EX_UNDERLINEHOT or LVS_EX_UNDERLINECOLD;
1205var
1206  Style: Integer;
1207begin
1208  if not WSCheckHandleAllocated(ALV, 'SetHotTrackStyles')
1209  then Exit;
1210
1211  if htHandPoint in AValue
1212  then Style := LVS_EX_ONECLICKACTIVATE
1213  else if [htUnderlineHot, htUnderlineCold] * AValue <> []
1214  then Style := LVS_EX_TWOCLICKACTIVATE
1215  else Style := 0;
1216
1217  if htUnderlineHot in AValue
1218  then Style := Style or LVS_EX_UNDERLINEHOT;
1219
1220  if htUnderlineCold in AValue
1221  then Style := Style or LVS_EX_UNDERLINECOLD;
1222
1223  UpdateExStyle(ALV.Handle, MASK, Style);
1224end;
1225
1226class procedure TWin32WSCustomListView.SetHoverTime(const ALV: TCustomListView; const AValue: Integer);
1227begin
1228  if not WSCheckHandleAllocated(ALV, 'SetHoverTime')
1229  then Exit;
1230
1231  SendMessage(ALV.Handle, LVM_SETHOVERTIME, 0, AValue);
1232end;
1233
1234class procedure TWin32WSCustomListView.SetIconArrangement(
1235  const ALV: TCustomListView; const AValue: TIconArrangement);
1236const
1237  ArrangementMap: array[TIconArrangement] of DWord = (
1238    { iaTop  } LVS_ALIGNTOP,
1239    { iaLeft } LVS_ALIGNLEFT
1240  );
1241begin
1242  if not WSCheckHandleAllocated(ALV, 'SetIconArrangement')
1243  then Exit;
1244
1245  // LVM_ALIGN styles are not implemented in windows (according to w7 sdk) => change style
1246  UpdateStyle(ALV.Handle, LVS_ALIGNMASK, ArrangementMap[AValue]);
1247end;
1248
1249class procedure TWin32WSCustomListView.SetImageList(const ALV: TCustomListView;
1250  const AList: TListViewImageList; const AValue: TCustomImageListResolution);
1251const
1252  LIST_MAP: array[TListViewImageList] of WPARAM = (
1253   {lvilSmall} LVSIL_SMALL,
1254   {lvilLarge} LVSIL_NORMAL,
1255   {lvilState} LVSIL_STATE
1256  );
1257begin
1258  if not WSCheckHandleAllocated(ALV, 'SetImageList')
1259  then Exit;
1260
1261  if AValue <> nil then
1262    SendMessage(ALV.Handle, LVM_SETIMAGELIST, LIST_MAP[AList], AValue.Reference._Handle)
1263  else
1264    SendMessage(ALV.Handle, LVM_SETIMAGELIST, LIST_MAP[AList], 0);
1265end;
1266
1267class procedure TWin32WSCustomListView.SetItemsCount(const ALV: TCustomListView; const AValue: Integer);
1268begin
1269  if not WSCheckHandleAllocated(ALV, 'SetItemsCount')
1270  then Exit;
1271  SendMessage(ALV.Handle, LVM_SETITEMCOUNT, AValue, 0);
1272end;
1273
1274class procedure TWin32WSCustomListView.SetOwnerData(const ALV: TCustomListView; const AValue: Boolean);
1275begin
1276  if not WSCheckHandleAllocated(ALV, 'SetOwnerData')
1277  then Exit;
1278  RecreateWnd(ALV);
1279end;
1280
1281class procedure TWin32WSCustomListView.SetProperty(const ALV: TCustomListView; const AProp: TListViewProperty; const AIsSet: Boolean);
1282begin
1283  if not WSCheckHandleAllocated(ALV, 'SetProperty')
1284  then Exit;
1285
1286  case LV_STYLES[AProp].StyleType of
1287    lsStyle: begin
1288      if AIsSet
1289      then UpdateStyle(ALV.Handle, LV_STYLES[AProp].Style, LV_STYLES[AProp].Style)
1290      else UpdateStyle(ALV.Handle, LV_STYLES[AProp].Style, 0);
1291    end;
1292    lsInvert:
1293    begin
1294      // we are always using readonly on ws since ListView_GetEditControl
1295      // requires minimum windows 2000. Editing is implemented in LCL atm,
1296      // can be changed later to use ws for item editing.
1297      // http://msdn.microsoft.com/en-us/library/windows/desktop/bb761260%28v=vs.85%29.aspx
1298      if (AProp = lvpReadOnly) then
1299        UpdateStyle(ALV.Handle, LV_STYLES[AProp].Style, 0)
1300      else
1301      if AIsSet then
1302        UpdateStyle(ALV.Handle, LV_STYLES[AProp].Style, 0)
1303      else
1304        UpdateStyle(ALV.Handle, LV_STYLES[AProp].Style, LV_STYLES[AProp].Style);
1305    end;
1306    lsExStyle: begin
1307      if AIsSet
1308      then UpdateExStyle(ALV.Handle, LV_STYLES[AProp].Style, LV_STYLES[AProp].Style)
1309      else UpdateExStyle(ALV.Handle, LV_STYLES[AProp].Style, 0);
1310    end;
1311  end;
1312  if(aProp=lvpCheckboxes)and aIsSet
1313    then ReCreateWnd(ALV)
1314end;
1315
1316class procedure TWin32WSCustomListView.SetProperties(const ALV: TCustomListView; const AProps: TListViewProperties);
1317var
1318  Prop: TListViewProperty;
1319  Style, ExStyle, Mask, ExMask: Integer;
1320begin
1321  if not WSCheckHandleAllocated(ALV, 'SetProperties')
1322  then Exit;
1323
1324  Style := 0;
1325  ExStyle := 0;
1326  Mask := 0;
1327  ExMask := 0;
1328
1329  for Prop := Low(Prop) to High(Prop) do
1330  begin
1331    case LV_STYLES[Prop].StyleType of
1332      lsStyle,
1333      lsInvert:
1334      begin
1335        // we are always using readonly on ws since ListView_GetEditControl
1336        // requires minimum windows 2000. Editing is implemented in LCL atm,
1337        // can be changed later to use ws for item editing.
1338        // http://msdn.microsoft.com/en-us/library/windows/desktop/bb761260%28v=vs.85%29.aspx
1339        if Prop = lvpReadOnly then
1340          UpdateStyle(ALV.Handle, LVS_EDITLABELS, 0)
1341        else
1342        begin
1343          Mask := Mask or LV_STYLES[Prop].Style;
1344          if (LV_STYLES[Prop].StyleType = lsStyle) = (Prop in AProps)
1345          then Style := Style or LV_STYLES[Prop].Style
1346          else Style := Style and not LV_STYLES[Prop].Style;
1347        end;
1348      end;
1349      lsExStyle: begin
1350        ExMask := ExMask or LV_STYLES[Prop].Style;
1351
1352        if Prop in AProps
1353        then ExStyle := ExStyle or LV_STYLES[Prop].Style
1354        else ExStyle := ExStyle and not LV_STYLES[Prop].Style;
1355      end;
1356    end;
1357  end;
1358
1359  if Mask <> 0
1360  then UpdateStyle(ALV.Handle, Mask, Style);
1361  if ExMask <> 0
1362  then UpdateExStyle(ALV.Handle, ExMask, ExStyle);
1363end;
1364
1365class procedure TWin32WSCustomListView.SetScrollBars(const ALV: TCustomListView; const AValue: TScrollStyle);
1366begin
1367  if not WSCheckHandleAllocated(ALV, 'SetScrollBars')
1368  then Exit;
1369
1370  // we only can hide all scrollbars.
1371  if AValue = ssNone
1372  then UpdateStyle(ALV.Handle, LVS_NOSCROLL, LVS_NOSCROLL)
1373  else UpdateStyle(ALV.Handle, LVS_NOSCROLL, 0);
1374end;
1375
1376function ListCompare(lParam1, lParam2: LParam; lParamSort: LParam): Integer; stdcall;
1377var
1378  Item1: TListItem absolute lParam1;
1379  Item2: TListItem absolute lParam2;
1380begin
1381  Result := CompareValue(Item1.Index, Item2.Index);
1382end;
1383
1384class procedure TWin32WSCustomListView.SetSort(const ALV: TCustomListView;
1385  const AType: TSortType; const AColumn: Integer; const ASortDirection: TSortDirection);
1386begin
1387  if not WSCheckHandleAllocated(ALV, 'SetSort')
1388  then Exit;
1389  ListView_SortItems(ALV.Handle, @ListCompare, Windows.MAKELPARAM(Ord(AType), AColumn));
1390end;
1391
1392class procedure TWin32WSCustomListView.SetViewOrigin(const ALV: TCustomListView; const AValue: TPoint);
1393var
1394  dx, dy: Integer;
1395  Origin: TPoint;
1396begin
1397  if not WSCheckHandleAllocated(ALV, 'SetViewOrigin')
1398  then Exit;
1399
1400  ListView_GetOrigin(ALV.Handle, Origin);
1401
1402  dx := AValue.X - Origin.X;
1403  dy := AValue.Y - Origin.Y;
1404  if (dx <> 0) or (dy <> 0)
1405  then ListView_Scroll(ALV.Handle, dx, dy);
1406end;
1407
1408class procedure TWin32WSCustomListView.SetViewStyle(const ALV: TCustomListView; const Avalue: TViewStyle);
1409const
1410  //vsIcon, vsSmallIcon, vsList, vsReport
1411  STYLES: array[TViewStyle] of DWORD = (LVS_ICON, LVS_SMALLICON, LVS_LIST, LVS_REPORT);
1412begin
1413  if not WSCheckHandleAllocated(ALV, 'SetViewStyle')
1414  then Exit;
1415
1416  UpdateStyle(ALV.Handle, LVS_TYPEMASK, STYLES[AValue]);
1417end;
1418
1419class procedure TWin32WSCustomListView.UpdateStyle(const AHandle: THandle; const AMask, AStyle: Integer);
1420var
1421  OldStyle, NewStyle: PtrInt;
1422begin
1423  OldStyle := GetWindowLong(AHandle, GWL_STYLE);
1424
1425  NewStyle := (OldStyle and not AMask) or AStyle;
1426
1427  if OldStyle = NewStyle then Exit;
1428
1429  SetWindowLong(AHandle, GWL_STYLE, NewStyle);
1430
1431  // fix header if needed
1432  if (NewStyle and LVS_NOSCROLL)<> 0 then begin
1433    if (OldStyle and LVS_NOSCROLL = 0)
1434    or (NewStyle and LVS_REPORT <> 0)
1435    then PositionHeader(AHandle);
1436  end;
1437
1438  //Invalidate Listview, so that changes are made visible
1439  Windows.InvalidateRect(AHandle, nil, true);
1440end;
1441
1442class procedure TWin32WSCustomListView.UpdateExStyle(const AHandle: THandle; const AMask, AStyle: Integer);
1443var
1444  OldStyle, NewStyle: Integer;
1445begin
1446  OldStyle := SendMessage(AHandle, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0);
1447
1448  NewStyle := (OldStyle and not AMask) or AStyle;
1449
1450  if OldStyle = NewStyle then Exit;
1451
1452  SendMessage(AHandle, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, NewStyle);
1453
1454 //Invalidate Listview, so that changes are made visible
1455  Windows.InvalidateRect(AHandle, nil, true);
1456end;
1457
1458class function TWin32WSCustomListView.GetNextItem(const ALV: TCustomListView; const StartItem: TListItem;
1459  const Direction: TSearchDirection; const States: TListItemStates): TListItem;
1460var
1461  Flags, Index: Integer;
1462begin
1463  Result := nil;
1464
1465  if not WSCheckHandleAllocated(ALV, 'GetNextItem')
1466  then Exit;
1467
1468  Flags := 0;
1469  case Direction of
1470    sdAbove: Flags := LVNI_ABOVE;
1471    sdBelow: Flags := LVNI_BELOW;
1472    sdLeft: Flags := LVNI_TOLEFT;
1473    sdRight: Flags := LVNI_TORIGHT;
1474    sdAll: Flags := LVNI_ALL;
1475  end;
1476
1477  if StartItem <> nil then Index := StartItem.Index else Index := -1;
1478
1479  if lisCut in States then Flags := Flags or LVNI_CUT;
1480  if lisDropTarget in States then Flags := Flags or LVNI_DROPHILITED;
1481  if lisFocused in States then Flags := Flags or LVNI_FOCUSED;
1482  if lisSelected in States then Flags := Flags or LVNI_SELECTED;
1483
1484  Index := ListView_GetNextItem(ALV.Handle, Index, Flags);
1485  if Index <> -1 then Result := ALV.Items[Index];
1486end;
1487