Search code examples
delphidelphi-xe6vcl-styles

How can I make a TListView header caption centered in a VCL styled application?


I'm having a list view control (ListView here), and I'm filling it by the code like this:

var
  Item: TListItem;
  Column: TListColumn;
begin    
  ListView.ViewStyle := vsReport;

  Column := ListView.Columns.Add;
  Column.Width := 200;
  Column.Alignment:= taCenter;
  Column.Caption:= 'Column 1';

  Column:= ListView.Columns.Add;
  Column.Width := 200;
  Column.Alignment := taCenter;
  Column.Caption := 'Column 2';

  Item := ListView.Items.Add;
  Item.Caption := 'Item 1';
  Item.SubItems.Add('Subitem 1');
end;

The problem is that when I use a VCL style in my application, the text is not centered in the list view header:

enter image description here

How can I make the header caption centered in a VCL styled application?


Solution

  • The style hook responsible for drawing the column header never checks for the text alignment of the column and always draws text with left alignment, clearly an oversight.

    First create a descendant of Vcl.ComCtrls.TListViewStyleHook and a class helper for the ancestor so that we can access the private variable we will need.

    TListViewStyleHookHelper = class helper for TListViewStyleHook
        function getFHeaderHandle: HWnd;
      end;
    
    TListViewStyleHookEx = class(Vcl.ComCtrls.TListViewStyleHook)
      strict protected
        procedure DrawHeaderSection(Canvas: TCanvas; R: TRect; Index: Integer;
          const Text: string; IsPressed, IsBackground: Boolean); override;
      end;
    

    Fixing the method:

    uses
      Winapi.Commctrl;
    
    
    function TListViewStyleHookHelper.getFHeaderHandle: HWnd;
    begin
      Result := Self.FHeaderHandle;
    end;
    
    
    procedure TListViewStyleHookEx.DrawHeaderSection(Canvas: TCanvas; R: TRect;
      Index: Integer; const Text: string; IsPressed, IsBackground: Boolean);
    var
      Item: THDItem;
      ImageList: HIMAGELIST;
      DrawState: TThemedHeader;
      IconWidth, IconHeight: Integer;
      Details: TThemedElementDetails;
      LListView: TListView;
      DT_Align: Integer;
    begin
      FillChar(Item, SizeOf(Item), 0);
      Item.mask := HDI_FORMAT;
      Header_GetItem(getFHeaderHandle, Index, Item);
      if IsBackground then
        DrawState := thHeaderItemNormal
      else if IsPressed then
        DrawState := thHeaderItemPressed
      else
        DrawState := thHeaderItemNormal;
    
      Details := StyleServices.GetElementDetails(DrawState);
      StyleServices.DrawElement(Canvas.Handle, Details, R);
    
      ImageList := SendMessage(getFHeaderHandle, HDM_GETIMAGELIST, 0, 0);
      Item.mask := HDI_FORMAT or HDI_IMAGE;
      InflateRect(R, -2, -2);
      IconWidth := 0;
      if (ImageList <> 0) and Header_GetItem(getFHeaderHandle, Index, Item) then
      begin
        if Item.fmt and HDF_IMAGE = HDF_IMAGE then
        begin
          ImageList_Draw(ImageList, Item.iImage, Canvas.Handle, R.Left, R.Top,
            ILD_TRANSPARENT);
          ImageList_GetIconSize(ImageList, IconWidth, IconHeight);
          Inc(R.Left, IconWidth + 5);
        end;
      end;
      if IconWidth = 0 then
        Inc(R.Left, 2);
    
      DT_Align := 0;
    
      if Control is TListView then
      begin
        LListView := TListView(Control);
          if (Index > -1) and (Index < LListView.Columns.Count) then
            case LListView.Columns[Index].Alignment of
              taLeftJustify:
                DT_Align := 0;
              taRightJustify:
                DT_Align := 2;
              taCenter:
                DT_Align := 1;
            end;
      end;
    
      DrawControlText(Canvas, Details, Text, R, DT_VCENTER or DT_Align or
        DT_SINGLELINE or DT_END_ELLIPSIS);
    
    end;
    

    And finally we have to register our extended style hook for the TListView control:

    Initialization
     TCustomStyleEngine.RegisterStyleHook(TListView, TListViewStyleHookEx);
    
    Finalization
     TCustomStyleEngine.UnRegisterStyleHook(TListView, TListViewStyleHookEx);
    

    example image