8

我一直在玩 TListBox 控件,绘制图像和更改字体样式等。我想稍微提高一点,并尝试通过缩进和多级缩进来更多地操作项目。

看看这张图片以获得更好的想法:

在此处输入图像描述

这个想法是列表中位于开始和结束项之间的项目应该相应地缩进。

所以,为了给出一个想法,我在 Paint 中编辑了屏幕截图,所以它看起来像这样:

在此处输入图像描述

解决这个问题的方法是什么?我的想法是遍历列表框并在 2 个单独的变量中返回开始项和结束项的数量,然后以某种方式确定其他项的位置以及两者之间是否合适 - 但我的逻辑从来没有这么好:(

为了便于使用,我在下面提供了代码来展示我是如何绘制图像和样式的:

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,我从不写小帖子对不起!

使用工作演示更新

我已经接受了 Sertac 的回答,感谢 Sertac,我工作得很好。

为了帮助可能正在查看的其他人 - 因为我一直在学习 OOP,所以我想展示我的代码,看看它是否有好处:)

我制作了 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.

单元1.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[] 属性分配图像索引,但这完美无缺:)

4

2 回答 2

6

一种方法是遍历项目并修改文本以指示缩进:

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]);
  ..  
于 2011-12-05T15:04:07.377 回答
3

我认为您可能应该改用 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。

于 2011-12-05T14:33:23.803 回答