delphitlistviewsubitem

Center subitem images in a TListView


Is it possible to fix the drawing of subitem images in a TListView so that they are not cut-off on the left hand side as shown in the image?
enter image description here


Solution

  • Well, Pieter van Wyk, I did minimal example of how you can owner-draw your TListView component in order to center images in sub-items.

    Answer has been rewritten. In order to decrease size of answer I had deleted unused and wrong parts. Previous versions may be found in history of question editing.

    Picture below represents work of the new code.
    One orange row is a selected row. enter image description here
    Images on selected row have white color around it. It is not a bug - it is a source image with such fill.

    There is the code that allows to do the same thing as on the picture:

    procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect;
      State: TOwnerDrawState);
    var
      Bmp: TBitmap;
      Image: TBitmap;
      R: TRect;
      CenterH: Integer;
      CenterV: Integer;
      ImageIndex: Integer;
      ItemWidth: Integer;
      i: Integer;
    begin
      // Set initial legth of point at the end of which image will be drawn.  
      // Column 0 is a "fixed" column
      ItemWidth := Sender.Column[0].Width;
      R := Rect;
    
      Bmp := TBitmap.Create;
      try
        Image := TBitmap.Create;
        try
          Bmp.SetSize(R.Width, R.Height);
    
          // Make fill for item
          if Item.Selected then
            Bmp.Canvas.Brush.Color := clWebOrange
          else
            Bmp.Canvas.Brush.Color := clMoneyGreen;
          Bmp.Canvas.FillRect(Bmp.Canvas.ClipRect);
    
          // Output image associated with 'fixed' column
          TListView(Sender).SmallImages.GetBitmap(Item.ImageIndex, Image);
          CenterH := (Sender.Column[0].Width - Image.Width) div 2;
          CenterV := (R.Height - Image.Height) div 2;
          Bmp.Canvas.Draw(CenterH, CenterV, Image);
    
          // Output text
          Bmp.Canvas.TextOut(CenterH + Image.Width + 6, 6, Item.Caption);
    
          // Draw sub-items
          for i:=0 to Item.SubItems.Count - 1 do
            begin
              // Obtain index of image
              ImageIndex := Item.SubItemImages[i];
    
              // Get associated image
              TListView(Sender).SmallImages.GetBitmap(ImageIndex, Image);
    
              // Center image
              CenterH := (Sender.Column[i+1].Width - Image.Width) div 2;
              CenterV := (R.Height - Image.Height) div 2;
    
              // Output image
              Bmp.Canvas.Draw(ItemWidth + CenterH, CenterV, Image);
    
              // Increase point where image started to be drawn
              Inc(ItemWidth, Sender.Column[i+1].Width);
            end;
    
          // Draw ready item's image onto sender's canvas
          Sender.Canvas.Draw(R.Left, R.Top, Bmp);
        finally
          Image.Free;
        end;
      finally
        Bmp.Free;
      end;
    end;
    

    To apply this code you must activate OwnerDraw property.

    See this TListView.OwnerDraw Property that leads to docs.embarcadero. I also would like to show a quote from a link above:

    Set OwnerDraw to true to allow the list view to receive the OnDrawItem event instead of the default rendering of list items.

    P.S.
    After column has been resized, there could be some graphical artifacts - just try to resize column in a way to hide (minimal possible size of column) and show image (any size of column that will exceeds size of associated image) and you will see what I meant.

    P.S.S.
    Drawing a text of sub-items I leave to you as a homework ;)