TListbox — управление компоновкой изображений и текста?

Я поиграл с элементом управления TListBox, рисовал изображения, менял стили шрифта и т. д. Я хочу немного улучшить его и попробовать еще немного манипулировать элементами с помощью отступов и многоуровневых отступов.

Взгляните на это изображение для лучшего понимания:

введите здесь описание изображения

Идея состоит в том, что элементы в списке, расположенные между начальным и конечным элементами, должны иметь соответствующий отступ.

Итак, чтобы дать представление, я отредактировал скриншот в Paint, чтобы он выглядел примерно так:

введите здесь описание изображения

Как можно подойти к этому? Моя мысль состояла в том, чтобы перебрать список и вернуть в двух отдельных переменных количество начальных и конечных элементов, а затем каким-то образом определить, где находятся другие элементы и подходят ли они друг к другу, но моя логика никогда не бывает такой хорошей :(

Для простоты использования я предоставил ниже код, чтобы показать, как я рисую изображения и стили:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ImgList, ComCtrls;

type
  TForm1 = class(TForm)
    ImageList1: TImageList;
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    ListBox1: TListBox;
    TabSheet2: TTabSheet;
    ListBox2: TListBox;
    TabSheet3: TTabSheet;
    ListBox3: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1MeasureItem(Control: TWinControl; Index: Integer;
      var Height: Integer);
    procedure ListBox1DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure ListBox2MeasureItem(Control: TWinControl; Index: Integer;
      var Height: Integer);
    procedure ListBox2DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure ListBox3MeasureItem(Control: TWinControl; Index: Integer;
      var Height: Integer);
    procedure ListBox3DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

// assign quick identifiers to image indexes
const
  imgLayout      = 0;
  imgCalculator  = 1;
  imgComment     = 2;
  imgTime        = 3;
  imgStart       = 4;
  imgEnd         = 5;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  ListStyle: TListBoxStyle;
begin
  // set the listbox style here
  ListStyle := lbOwnerDrawVariable;
  ListBox1.Style := ListStyle;
  ListBox2.Style := ListStyle;
  ListBox3.Style := ListStyle;
end;

{******************************************************************************}

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  TextPosition: Integer;
  Images: TImageList;
begin
  TListBox(Control).Canvas.FillRect(Rect);
  Images := ImageList1;

  // draw the images
  if TListBox(Control).Items.Strings[Index] = 'Layout' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgLayout);
  end else
  if TListBox(Control).Items.Strings[Index] = 'Calculator' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top,
      imgCalculator);
  end else
  if TListBox(Control).Items.Strings[Index] = 'Comment' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgComment);
  end else
  if TListBox(Control).Items.Strings[Index] = 'Time' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgTime);
  end;

  // positions the text
  TextPosition := (Rect.Bottom - Rect.Top - TListBox(Control).Canvas.TextHeight
    (Text)) div 2;

  // displays the text
  TListBox(Control).Canvas.TextOut(Rect.Left + Images.Width + 8,
    Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
end;

procedure TForm1.ListBox1MeasureItem(Control: TWinControl; Index: Integer;
  var Height: Integer);
begin
  Height := ImageList1.Height;
end;

{******************************************************************************}

procedure TForm1.ListBox2DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  TextPosition: Integer;
  Images: TImageList;
begin
  TListBox(Control).Canvas.FillRect(Rect);
  Images := ImageList1;

  // draw the images
  if TListBox(Control).Items.Strings[Index] = 'Layout' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgLayout);
    TListBox(Control).Canvas.Font.Style := [fsBold];
  end else
  if TListBox(Control).Items.Strings[Index] = 'Calculator' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top,
      imgCalculator);
    TListBox(Control).Canvas.Font.Color := clBlue;
    TListBox(Control).Canvas.Font.Style := [fsItalic];
  end else
  if TListBox(Control).Items.Strings[Index] = 'Comment' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgComment);
    TListBox(Control).Canvas.Font.Color := clRed;
  end else
  if TListBox(Control).Items.Strings[Index] = 'Time' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgTime);
  end;

  // positions the text
  TextPosition := (Rect.Bottom - Rect.Top - TListBox(Control).Canvas.TextHeight
    (Text)) div 2;

  // displays the text
  TListBox(Control).Canvas.TextOut(Rect.Left + Images.Width + 8,
    Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
end;

procedure TForm1.ListBox2MeasureItem(Control: TWinControl; Index: Integer;
  var Height: Integer);
begin
  Height := ImageList1.Height;
end;

{******************************************************************************}

procedure TForm1.ListBox3DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  TextPosition: Integer;
  Images: TImageList;
begin
  TListBox(Control).Canvas.FillRect(Rect);
  Images := ImageList1;

  // draw the images
  if TListBox(Control).Items.Strings[Index] = 'Layout' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgLayout);
  end else
  if TListBox(Control).Items.Strings[Index] = 'Calculator' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top,
      imgCalculator);
  end else
  if TListBox(Control).Items.Strings[Index] = 'Comment' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgComment);
  end else
  if TListBox(Control).Items.Strings[Index] = 'Time' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgTime);
  end else
  if TListBox(Control).Items.Strings[Index] = 'Start' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top,
      imgStart);
    TListBox(Control).Canvas.Font.Style := [fsBold];
  end else
  if TListBox(Control).Items.Strings[Index] = 'End' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgEnd);
    TListBox(Control).Canvas.Font.Style := [fsBold];
  end;

  // positions the text
  TextPosition := (Rect.Bottom - Rect.Top - TListBox(Control).Canvas.TextHeight
    (Text)) div 2;

  // displays the text
  TListBox(Control).Canvas.TextOut(Rect.Left + Images.Width + 8,
    Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
end;

procedure TForm1.ListBox3MeasureItem(Control: TWinControl; Index: Integer;
  var Height: Integer);
begin
  Height := ImageList1.Height;
end;

{******************************************************************************}

end.

Я был бы признателен за некоторые советы о том, как я могу определить манипулирование элементами. Я знаю, что могу изменить расположение растрового изображения и текста, но это определяет, попадает ли элемент между группами или нет, и устанавливает ли он правильный уровень отступа.

Я надеюсь, что это имеет смысл, поэтому я разместил несколько макетов фотографий.

Спасибо :)

PS, я никогда не пишу маленькие сообщения извините!

ОБНОВЛЕНИЕ С РАБОЧЕЙ ДЕМО

Я принял ответ Сертака, который отлично работает благодаря Сертаку.

Чтобы помочь другим, кто может просматривать - и, поскольку я изучаю ООП, я хочу показать свой код, чтобы увидеть, хорош ли он :)

Я сделал 2 модуля, Lib.pas содержит классы для элементов списка, а Unit1.pas — это модуль Form1 (я сократил модуль 1, чтобы было понятнее, что происходит):

Библиотека

unit Lib;

interface

uses
  Classes, StdCtrls;

type
  TMyListData = class(TObject)
  public
    fCaption: string;
    fImageIndex: integer;
  public
    property Caption: string read fCaption write fCaption;
    property ImageIndex: integer read fImageIndex write fImageIndex;

    constructor Create;
    destructor Destroy; override;
  end;

type
  TLayoutItem     = class(TMyListData);
  TCalculatorItem = class(TMyListData);
  TCommentItem    = class(TMyListData);
  TTimeItem       = class(TMyListData);
  TStartItem      = class(TMyListData);
  TEndItem        = class(TMyListData);

const
  imgLayout       = 0;
  imgCalculator   = 1;
  imgComment      = 2;
  imgTime         = 3;
  imgStart        = 4;
  imgEnd          = 5;

procedure NewLayoutItem(aListBox: TListBox);
procedure NewCalculatorItem(aListBox: TListBox);
procedure NewCommentItem(aListBox: TListBox);
procedure NewTimeItem(aListBox: TListBox);
procedure NewStartItem(aListBox: TListBox);
procedure NewEndItem(aListBox: TListBox);
procedure DeleteItem(aListBox: TListBox; aIndex: integer);
procedure CalculateIndents(aListBox: TListBox);

implementation

{ TMyListData }

constructor TMyListData.Create;
begin
  inherited Create;
end;

destructor TMyListData.Destroy;
begin
  inherited;
end;

procedure NewLayoutItem(aListBox: TListBox);
var
  Obj: TLayoutItem;
begin
  Obj := TLayoutItem.Create;
  try
    Obj.Caption := 'Layout';
    Obj.ImageIndex := imgLayout;

    aListBox.AddItem(Obj.Caption, Obj);
  finally
    Obj.Free;
  end;

  CalculateIndents(aListBox);
end;

procedure NewCalculatorItem(aListBox: TListBox);
var
  Obj: TCalculatorItem;
begin
  Obj := TCalculatorItem.Create;
  try
    Obj.Caption := 'Calculator';
    Obj.ImageIndex := imgCalculator;

    aListBox.AddItem(Obj.Caption, Obj);
  finally
    Obj.Free;
  end;

  CalculateIndents(aListBox);
end;

procedure NewCommentItem(aListBox: TListBox);
var
  Obj: TCommentItem;
begin
  Obj := TCommentItem.Create;
  try
    Obj.Caption := 'Comment';
    Obj.ImageIndex := imgComment;

    aListBox.AddItem(Obj.Caption, Obj);
  finally
    Obj.Free;
  end;

  CalculateIndents(aListBox);
end;

procedure NewTimeItem(aListBox: TListBox);
var
  Obj: TTimeItem;
begin
  Obj := TTimeItem.Create;
  try
    Obj.Caption := 'Time';
    Obj.ImageIndex := imgTime;

    aListBox.AddItem(Obj.Caption, Obj);
  finally
    Obj.Free;
  end;

  CalculateIndents(aListBox);
end;

procedure NewStartItem(aListBox: TListBox);
var
  Obj: TStartItem;
begin
  Obj := TStartItem.Create;
  try
    Obj.Caption := 'Start';
    Obj.ImageIndex := imgStart;

    aListBox.AddItem(Obj.Caption, Obj);
  finally
    Obj.Free;
  end;

  CalculateIndents(aListBox);
end;

procedure NewEndItem(aListBox: TListBox);
var
  Obj: TEndItem;
begin
  Obj := TEndItem.Create;
  try
    Obj.Caption := 'End';
    Obj.ImageIndex := imgEnd;

    aListBox.AddItem(Obj.Caption, Obj);
  finally
    Obj.Free;
  end;

  CalculateIndents(aListBox);
end;


procedure DeleteItem(aListBox: TListBox; aIndex: integer);
begin
  aListBox.Items.Delete(aIndex);
  aListBox.Items.Objects[aIndex] := nil;

  CalculateIndents(aListBox);
end;

procedure CalculateIndents(aListBox: TListBox);
var
  i: Integer;
  Indent: Integer;
begin
  Indent := 0;

  for i := 0 to aListBox.Items.Count - 1 do
  begin
    if aListBox.Items[i] = 'End' then
      Dec(Indent);

    if Indent > -1 then
      aListBox.Items.Objects[i] := Pointer(Indent);

    if aListBox.Items[i] = 'Start' then
      Inc(Indent);
  end;

  for i := aListBox.Items.Count - 1 downto 0 do
  begin
    if (aListBox.Items[i] = 'End') and (Indent = -1) then
    begin
      DeleteItem(aListBox, i);
      Break;
    end;
  end;
end;

end.

Unit1.pas

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ImgList, ComCtrls, Buttons;

type
  TForm1 = class(TForm)
    ImageList1: TImageList;
    lbMain: TListBox;
    btnLayout: TBitBtn;
    btnCalculator: TBitBtn;
    btnComment: TBitBtn;
    btnTime: TBitBtn;
    btnStartGroup: TBitBtn;
    btnEndGroup: TBitBtn;
    btnDelete: TBitBtn;
    procedure FormCreate(Sender: TObject);
    procedure lbMainMeasureItem(Control: TWinControl; Index: Integer;
      var Height: Integer);
    procedure lbMainDrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure btnLayoutClick(Sender: TObject);
    procedure btnCalculatorClick(Sender: TObject);
    procedure btnCommentClick(Sender: TObject);
    procedure btnTimeClick(Sender: TObject);
    procedure btnStartGroupClick(Sender: TObject);
    procedure btnEndGroupClick(Sender: TObject);
    procedure btnDeleteClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  Lib;

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  // set the listbox style here
  lbMain.Style := lbOwnerDrawVariable;
end;

procedure TForm1.lbMainDrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  TextPosition: Integer;
  Images: TImageList;
begin
  TListBox(Control).Canvas.FillRect(Rect);
  Images := ImageList1;

  // draw the images
  if TListBox(Control).Items.Strings[Index] = 'Layout' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
                8 * Integer(TListBox(Control).Items.Objects[Index]),
                Rect.Top, imgLayout);
  end
  else if TListBox(Control).Items.Strings[Index] = 'Calculator' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
                8 * Integer(TListBox(Control).Items.Objects[Index]),
                Rect.Top, imgCalculator);
  end
  else if TListBox(Control).Items.Strings[Index] = 'Comment' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
                8 * Integer(TListBox(Control).Items.Objects[Index]),
                Rect.Top, imgComment);
  end
  else if TListBox(Control).Items.Strings[Index] = 'Time' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
                8 * Integer(TListBox(Control).Items.Objects[Index]),
                Rect.Top, imgTime);
  end
  else if TListBox(Control).Items.Strings[Index] = 'Start' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
                8 * Integer(TListBox(Control).Items.Objects[Index]),
                Rect.Top, imgStart);
  end
  else if TListBox(Control).Items.Strings[Index] = 'End' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
                8 * Integer(TListBox(Control).Items.Objects[Index]),
                Rect.Top, imgEnd);
  end;

  // positions the text
  TextPosition := (Rect.Bottom - Rect.Top - TListBox(Control).Canvas.TextHeight
    (Text)) div 2;

  // displays the text
  TListBox(Control).Canvas.TextOut(
    Rect.Left + Images.Width + 8 + 8 * Longint(TListBox(Control).Items.Objects[Index]),
    Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
end;

procedure TForm1.lbMainMeasureItem(Control: TWinControl; Index: Integer;
  var Height: Integer);
begin
  Height := ImageList1.Height;
end;

procedure TForm1.btnLayoutClick(Sender: TObject);
begin
  NewLayoutItem(lbMain);
end;

procedure TForm1.btnCalculatorClick(Sender: TObject);
begin
  NewCalculatorItem(lbMain);
end;

procedure TForm1.btnCommentClick(Sender: TObject);
begin
  NewCommentItem(lbMain);
end;

procedure TForm1.btnTimeClick(Sender: TObject);
begin
  NewTimeItem(lbMain);
end;

procedure TForm1.btnStartGroupClick(Sender: TObject);
begin
  NewStartItem(lbMain);
end;

procedure TForm1.btnEndGroupClick(Sender: TObject);
begin
  NewEndItem(lbMain);
end;

procedure TForm1.btnDeleteClick(Sender: TObject);
begin
  if lbMain.ItemIndex <> -1 then
  begin
    DeleteItem(lbMain, lbMain.ItemIndex);
  end;
end;

end.

введите здесь описание изображения

Это можно сделать лучше, т.е. присвоить индексы изображений на основе свойства Items.Objects[], но это работает отлично :)


person Community    schedule 05.12.2011    source источник
comment
Просто из любопытства, почему вы пытаетесь сделать это с помощью TListBox. Это кажется идеальным местом для использования вместо этого TTreeView; он имеет встроенную поддержку отступов, изображений в зависимости от типа контента и т. д. Кажется, что вы делаете много дополнительной работы без всякой причины. (Сначала я подумал, что вы, должно быть, используете FireMonkey, но просмотр кода не показывает, что это так.)   -  person Ken White    schedule 05.12.2011
comment
Если вам нужен полностью настраиваемый список внешнего вида, вы также можете использовать TScrollBox и создавать свои элементы на TFrame. Настройте простой управляемый массив для обработки элементов так же, как TListBox.Items (это легко сделать), и тогда не будет буквально никаких ограничений на то, что вы можете создать.   -  person LaKraven    schedule 05.12.2011
comment
@KenWhite К сожалению, у меня нет Delphi XE2, и я полностью осведомлен о Treeview, который обеспечит лучший способ отображения данных, как вы говорите. С другой стороны, однако, для простого отображения списка я подумал, что это было бы неплохо попробовать и сделать. Я просто экспериментировал с TListbox, и, прочитав еще немного об Fire Monkey после вашего комментария, я вижу, что Treeview нельзя использовать, поскольку это элемент управления Windows, поэтому выполнение этого со списком имеет свои преимущества в этом отношении.   -  person    schedule 05.12.2011
comment
Крейг, нет. Этот код не поможет вам в конечном итоге с FireMonkey - ListBox FM никоим образом не совместим с VCL. И TListBox VCL является оболочкой базового общего элемента управления Windows и не предназначен для использования так, как вы пытаетесь его использовать (поэтому вам это сложно). Однако похоже, что @Sertac хорошо справился с ответом (как обычно).   -  person Ken White    schedule 06.12.2011
comment
Но все это также зависит от хорошего представления о том, как это будет работать, прежде чем перестраивать его. Это техника, называемая созданием прототипа.   -  person Jerry Dodge    schedule 06.12.2011
comment
@Ken Я не слишком много знаю о Firemonkey, и я использую Delphi только как хобби. На самом деле меня не интересовала огненная обезьяна, но если список огненной обезьяны мог бы делать то же самое, что и выше, я подумал, что это может иметь некоторые преимущества, но теперь я вижу, что это не так. Что ж, sertac в любом случае проделал отличную работу, и я вижу его применение в стандартных приложениях VCL :)   -  person    schedule 07.12.2011
comment
@JerryDodge, ваш комментарий был адресован кому-то конкретно? Я не вижу ничего, что имело бы отношение к другим комментариям...   -  person Ken White    schedule 07.12.2011
comment
Я с нетерпением жду возможности использовать Firemonkey, это для HD-приложений. Однако невозможно «преобразовать» любое приложение Delphi в firemonkey, это почти невозможно.   -  person Jerry Dodge    schedule 07.12.2011
comment
@JerryDodge, тогда спасибо за определение того, что такое prototype. Однако кодирование всего приложения для одного API, не имеющего прямого отношения к другому, не является prototype. Прототип относится к внешнему виду, а не к основному коду. И я все еще не вижу никакого отношения к комментарию, тем более, что ваше заявление prototype неуместно. :)   -  person Ken White    schedule 07.12.2011
comment
@JerryDodge, конечно, нет. Но они должны, по крайней мере, иметь какое-то отношение к сообщению, которое вы комментируете (или, по крайней мере, к другим комментариям к этому сообщению). Как насчет того, чтобы оставить комментарий Больше собак, таких как Alpo. на какой-то случайный вопрос программирования, который не имеет ничего общего с собаками - это правильный комментарий? :) Несмотря на это, я думаю, что это зашло достаточно далеко; мы сейчас просто шумим.   -  person Ken White    schedule 07.12.2011
comment
Согласованный. Удаление неактуальных комментариев выше.   -  person Jerry Dodge    schedule 07.12.2011


Ответы (2)


Один из способов — перебирать элементы и изменять текст, чтобы указать отступ:

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  Indent: Integer;
begin

  ...

  Indent := 0;
  for i := 0 to ListBox3.Items.Count - 1 do begin
    if Pos('End', ListBox3.Items[i]) > 0 then
      Dec(Indent);
    if Indent > 0 then
      ListBox3.Items[i] := StringOfChar(#32, 2 * Indent) + ListBox3.Items[i];
    if Pos('Start', ListBox3.Items[i]) > 0 then
      Inc(Indent);
  end;
end;

Поскольку текст элементов изменяется, этот подход требует соответствующей проверки текста при рисовании:

procedure TForm1.ListBox3DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  TextPosition: Integer;
  Images: TImageList;
begin
  TListBox(Control).Canvas.FillRect(Rect);
  Images := ImageList1;

  // draw the images
  if Pos('Layout', TListBox(Control).Items.Strings[Index]) > 0 then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgLayout);
  end else
  if Pos('Calculator', TListBox(Control).Items.Strings[Index]) > 0 then
    ..

(При таком подходе отступ изображения будет немного сложнее, подсчитайте начальные пробелы в тексте элемента и т. д.)


Если объекты элементов еще не используются, несколько лучшим подходом может быть сохранение отступа в виде целого числа и использование этой информации при рисовании. Например. при повторении:

Indent := 0;
for i := 0 to ListBox3.Items.Count - 1 do begin
  if ListBox3.Items[i] = 'Start' then
    Inc(Indent);
  ListBox3.Items.Objects[i] := Pointer(Indent);
  if ListBox3.Items[i] = 'End' then
    Dec(Indent);
end;

При рисовании:

  ..
  if TListBox(Control).Items.Strings[Index] = 'Layout' then
  begin
    Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
                8 * Integer(TListBox(Control).Items.Objects[Index]),
                Rect.Top, imgLayout);

  ..
  // displays the text
  TListBox(Control).Canvas.TextOut(
    Rect.Left + Images.Width + 8 + 8 * Longint(TListBox(Control).Items.Objects[Index]),
    Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
  ..  
person Sertac Akyuz    schedule 05.12.2011
comment
+1, я еще не просмотрел весь код, но сохранение значения отступа в качестве указателя на каждый элемент - хорошая идея для новых элементов. Позвольте мне просмотреть остальную часть кода, спасибо, Сертак... - person ; 05.12.2011
comment
@Craig - › новые элементы › Добавление — это нормально, но при вставке потребуется повторение элементов после позиции вставки. - person Sertac Akyuz; 05.12.2011
comment
Я пишу демонстрацию на основе предоставленной вами информации (по мере того, как я изучаю ООП, я вижу, что вы, эксперты, думаете о коде моих объектов:) следите за обновлениями. - person ; 05.12.2011
comment
Я принял ваш ответ и опубликовал свою демонстрацию на основе вашей информации, надеюсь, это выглядит хорошо, спасибо :) - person ; 05.12.2011
comment
@Craig - Если вы использовали код, аналогичный указанному в вопросе, вы можете получить небольшие возражения :). Например, нет смысла использовать переменную ListStyle в FormCreate. Или нет смысла прикреплять разные обработчики разных элементов управления с одним и тем же кодом. т.е. вы можете прикрепить ListBox2.OnMeasureItem к тому же обработчику 'ListBox1' в OI. ;) - person Sertac Akyuz; 05.12.2011
comment
Я знаю, что вы можете назначить каждому обработчику в инспекторе объектов, я только что опубликовал его, чтобы вы могли видеть стили, установленные во время выполнения :) - person ; 05.12.2011
comment
@Craig - у меня есть ошибка, которая приводит к отступу «Конец» на 1 отступ слева от предполагаемого. Изменит ответ. - person Sertac Akyuz; 05.12.2011
comment
Да, я тоже это заметил, я также правильно выровнял Конец с Началом, я обновлю эту часть.. - person ; 05.12.2011
comment
@Craig - извините, что снова вас беспокою :), но моя настоящая ошибка заключалась в повторении элементов, порядок операторов должен быть таким: начало теста, назначение отступа, конец теста. Конечно, ваше исправление тоже работает, но в обратном случае не требуется по-разному обрабатывать конец при рисовании. - person Sertac Akyuz; 05.12.2011
comment
Не стесняйтесь редактировать мой оригинальный пост, если вам нужно, ваше решение будет лучше;) - person ; 05.12.2011
comment
@Craig - Нет, нет .. все в порядке, просто другой взгляд :) - person Sertac Akyuz; 05.12.2011
comment
Хорошо, большое спасибо за вашу помощь, это было очень полезно;) - person ; 05.12.2011
comment
Я обновил код в своем первом посте в последний раз, он обрабатывает конечный блок, он не будет пытаться добавить конечный блок, если он находится в первом отступе. Однако процедура CalculateIndents может быть изменена множеством различных способов. - person ; 06.12.2011

Я думаю, вам, вероятно, следует использовать вместо этого TTreeView, который уже поддерживает отступы дочерних элементов.

Чтобы ответить на ваш вопрос, я думаю, вы могли бы использовать рекурсию для рисования элементов в вашем TListBox. Используя рекурсию, легко увидеть, на сколько уровней вы находитесь.

Так работает большинство парсеров, таких как парсеры HTML.

Вот некоторый псевдокод, иллюстрирующий концепцию:

procedure DrawBranch(branch: TMyList; indent: Integer);
var
  i: Integer;
begin
  // Draw the current branch, using the indent value
  branch.Draw;
  // Iterate through all of the child branches
  for i := 0 to branch.Children.Count - 1 do
  begin
    // Each time we recurse further, we add 1 to the indent 
    DrawBranch(branch.Child[i], indent + 1);
  end;
end;

procedure DrawTree;
begin
  // Start the whole thing off with the root branch
  // We start the indent at 0
  DrawBranch(root, 0);
end;

В вашем случае вам понадобится «скрытый» корневой узел.

Вы бы использовали аналогичную логику, чтобы добавить свои элементы в TTreeView.

person Marcus Adams    schedule 05.12.2011