delphi

Delphi TListView & TJvListView - Subitems line in 255 characters


TlistView (and TJvlistView) - Subitems line in 255 characters. Is there a way to get around this restriction?

Dynamically I fill in the SubItems of 670 characters, but the line turns out only 255 characters.

unit Unit1;

interface

uses
  //Winapi.Windows,
  //Winapi.Messages,
  System.SysUtils, // IntToStr, FreeAndNil
  Vcl.Controls,    // alClient
  Vcl.Forms,
  CommCtrl,        // for LVSCW_AUTOSIZE
  ComCtrls;        // for TListView

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  ListView1: TListView;
  ListItem1: TListItem;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  form1.Height    := 115;
  form1.Width     := 350;
  form1.Position  := poScreenCenter;
  form1.Caption   := 'Subitems[2] in 255 characters';

  ListView1       := TListView.Create(Self);
  with ListView1 do
    begin
      Parent      := Self;
      Align       := alClient;
      ViewStyle   := vsReport;
      BorderWidth := 2;
      GridLines   := true;
    end;

  with ListView1.Columns do
    begin
      Add.Caption := 'Line № ';
      Add.Caption := 'Error ';
      Add.Caption := 'String ';
    end;

  try
    ListView1.Items.BeginUpdate;
    ListItem1 := ListView1.Items.Add;
    ListItem1.Caption := '22421 ';
    ListItem1.SubItems.Add('All Columns ' + IntToStr(ListView1.Columns.Count));
    ListItem1.SubItems.Add('<RHINOSTRING English="Exploding this mesh will create %d individual meshes.  This may be more than your system can safely manage using the available memory.  You can use Weld to make the mesh explode into fewer pieces, or see Help for more information.\n\nClick OK to proceed with Explode, or Cancel to leave the mesh as is.[[24836]]" Localized="Exploding this mesh will create %d individual meshes.  This may be more than your system can safely manage using the available memory.  You can use Weld to make the mesh explode into fewer pieces, or see Help for more information.\n\nClick OK to proceed with Explode, or Cancel to leave the mesh as is.[[24836]]" />');
    //uses CommCtrl;
    ListView1.Columns[0].Width := {LVSCW_AUTOSIZE or} LVSCW_AUTOSIZE_USEHEADER;
    ListView1.Columns[1].Width := {LVSCW_AUTOSIZE or} LVSCW_AUTOSIZE_USEHEADER;
    ListView1.Columns[2].Width := LVSCW_AUTOSIZE {or LVSCW_AUTOSIZE_USEHEADER};
  finally
    ListView1.Items.EndUpdate;
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(ListView1);
end;

end.

Solution

  • I don't think you can have Windows display long columns if you let Windows itself draw them.

    Indeed, the official documentation of the LVITEM structure says this:

    If the structure receives item attributes, pszText is a pointer to a buffer that receives the item text. Note that although the list-view control allows any length string to be stored as item text, only the first 260 TCHARs are displayed.

    But there is still a solution: Instead of having Windows draw the column, draw it yourself!

    Set the TListView control's OwnerDraw to True and add the following OnDrawItem handler:

    procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
      Rect: TRect; State: TOwnerDrawState);
    begin
    
      if Sender = nil then
        Exit;
    
      if Item = nil then
        Exit;
    
      if odSelected in State then
      begin
        Sender.Canvas.Font.Color := clHighlightText;
        Sender.Canvas.Brush.Color := clHighlight;
      end
      else
      begin
        Sender.Canvas.Font.Color := clWindowText;
        Sender.Canvas.Brush.Color := clWindow;
      end;
    
      Sender.Canvas.Brush.Style := bsSolid;
      Sender.Canvas.FillRect(Rect);
    
      var R := Rect;
      var S: string;
      Sender.Canvas.Brush.Style := bsClear;
    
      R.Offset(ScaleValue(5), 0);
    
      for var i := 0 to ListView1.Columns.Count - 1 do
      begin
        R.Width := Sender.Column[i].Width - ScaleValue(5);
        if i = 0 then
          S := Item.Caption
        else
          S := Item.SubItems[i - 1];
        Sender.Canvas.TextRect(R, S, [tfSingleLine, tfVerticalCenter, tfEndEllipsis]);
        R.Offset(Sender.Column[i].Width, 0);
      end;
    
      R := Rect;
      R.Inflate(-ScaleValue(1), -ScaleValue(1));
      if odFocused in State then
      begin
        Sender.Canvas.Pen.Color := clWindow;
        Sender.Canvas.Brush.Color := clWindow;
        Sender.Canvas.DrawFocusRect(R);
      end;
    
    end;
    

    To test this, I created a new VCL application, dropped a TListView on the main form, made it Align = alClient, BorderStyle = bsNone, DoubleBuffered = True, ViewStyle = vsReport, MultiSelect = True, and added three columns at design time.

    Then I populate it at run time:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      for var i := 1 to 1000 do
      begin
        var lm := ListView1.Items.Add;
        lm.Caption := 'Caption of item ' + i.ToString;
        lm.SubItems.Add('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. '+'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est LABORUM. This is item ' + i.ToString);
        lm.SubItems.Add('Second subitem of ' + i.ToString);
      end;
    end;
    

    Doing owner drawing requires quite a lot of attention to detail. My code above handles selections, focus rectangles, and horizontal scrolling.

    A screenshot of the list view control with a very wide column. A few rows are selected and a non-selected row has a focus rectangle.