8

TListBox コントロールをいじったり、画像を描画したり、フォント スタイルを変更したりしています。もう少しレベルを上げて、インデントとマルチ レベルのインデントを使って項目を操作してみます。

より良いアイデアについては、この画像を見てください。

ここに画像の説明を入力

アイデアは、開始項目と終了項目の間に配置されたリスト内の項目をそれに応じてインデントする必要があるということです。

それで、アイデアを与えるために、ペイントでスクリーンショットを編集したので、次のようになります。

ここに画像の説明を入力

これにアプローチする方法は何でしょうか?私の考えは、リストボックスを反復処理し、開始項目と終了項目の量を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 を短くしました)。

Lib.pas

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[] プロパティに基づいて画像インデックスを割り当てますが、これは完全に機能します:)

4

2 に答える 2

6

1 つの方法は、アイテムを繰り返し処理し、テキストを変更してインデントを示すことです。

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 に答える