Cara menyalin bagian dari Treeview ke Menu

Saya mencoba menyalin bagian dari Treeview ke menu popup, dan saya tidak beruntung sama sekali. Sepertinya saya tidak bisa menjalankan rekursi dan saya tahu saya mungkin melakukan semuanya dengan salah.

Ambil contoh gambar ini (yang merupakan screenshot runtime dari kode di bawah):

masukkan deskripsi gambar di sini

Saya perlu menu dibuat dengan hubungan yang sama dengan Treeview, tapi saya tidak ingin item Root ditambahkan. Inilah yang saya inginkan agar terlihat seperti ini:

masukkan deskripsi gambar di sini

Perhatikan bahwa item pertama bukanlah ikon pengaturan (Root), dan item tersebut berada pada level seperti Treeview.

Ini adalah kode yang saya miliki:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
  Menus, StdCtrls, Buttons;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ImageList1: TImageList;
    MenuItem1: TMenuItem;
    PopupMenu1: TPopupMenu;
    TreeView1: TTreeView;
    procedure MyMenuItemClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    procedure TreeViewToMenu(TreeView: TTreeView; BaseNode: TTreeNode; OutMenu: TMenu);
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

procedure TForm1.MyMenuItemClick(Sender: TObject);
begin
  ShowMessage('You selected ' + TMenuItem(Sender).Name + ' - Tag: ' +
    IntToStr(TMenuItem(Sender).Tag));
end;

procedure TForm1.TreeViewToMenu(TreeView: TTreeView; BaseNode: TTreeNode; OutMenu: TMenu);
var
  I: Integer;
  MenuItem: TMenuItem;
begin
  MenuItem := TMenuItem.Create(nil);
  with MenuItem do
  begin
    Caption := BaseNode.Text;
    ImageIndex := BaseNode.ImageIndex;
    OnClick := @MyMenuItemClick;
  end;

  for I := 0 to BaseNode.Count - 1 do
  begin
    MenuItem.Tag := I;
    TreeViewToMenu(TreeView, BaseNode[I], OutMenu);
  end;

  OutMenu.Items.Add(MenuItem);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Pt: TPoint;
  I: Integer;
  Node: TTreeNode;
begin
  Pt.X := Button1.Left + 1;
  Pt.Y := Button1.Top + Button1.Height + 1;
  Pt := ClientToScreen(Pt);

  PopupMenu1.Items.Clear;
  TreeViewToMenu(TreeView1, TreeView1.Items[0], PopupMenu1);

  PopupMenu1.Popup(Pt.X, Pt.Y);
end;

end.

Saya juga mencoba menambahkan properti Tag MenuItem sehingga saya dapat mengidentifikasi setiap item menu berdasarkan tagnya.

Saya pikir rekursi pada dasarnya berarti memanggil kembali prosedur dari dalam prosedur, jadi ini berulang, apa pun yang bisa saya lakukan dengan bantuan.

Terima kasih.


person Community    schedule 14.09.2013    source sumber


Jawaban (2)


Tidak ada masalah dengan pemahaman Anda tentang panggilan rekursif, tetapi Anda tidak ingin menambahkan item untuk node akar, jadi Anda harus menambahkan item dan melakukan rekursif untuk setiap anak dari setiap node yang diteruskan ke prosedur. Berikut salah satu contoh penerapannya:

type
  TForm1 = class(TForm)
    ..
  private
    procedure TreeViewToMenu(BaseNode: TTreeNode; OutMenu: TComponent);
    ..

procedure TForm1.TreeViewToMenu(BaseNode: TTreeNode; OutMenu: TComponent);
var
  i: Integer;
  Node: TTreeNode;
  MenuItem: TMenuItem;
begin
  for i := 0 to BaseNode.Count - 1 do begin
    Node := BaseNode.Item[i];

    MenuItem := TMenuItem.Create(nil);
    MenuItem.Caption := Node.Text;
    MenuItem.ImageIndex := Node.ImageIndex;
    MenuItem.Tag := i;
    if Node.Count = 0 then
      MenuItem.OnClick := MyMenuItemClick;

    if OutMenu is TPopupMenu then
      TMenu(OutMenu).Items.Add(MenuItem)
    else if
      OutMenu is TMenuItem then
        TMenuItem(OutMenu).Add(MenuItem)
      else
        raise Exception.Create('Invalid class type');

    TreeViewToMenu(Node, MenuItem);

  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  ..
begin
  ..
  TreeViewToMenu(TreeView1.Items[0], PopupMenu1);
  ..

Perhatikan bahwa saya mengubah deklarasi TreeViewToMenu karena (1) TreeView tidak digunakan dan (2) kami menambahkan item ke TPopupMenu atau TMenuItem, maka saya mendeklarasikan 'OutMenu' sebagai TComponent yang akan menerima keduanya.

person Sertac Akyuz    schedule 14.09.2013
comment
Saat bereksperimen dengan membuat TMenuItems saat runtime, saya menemukan masalah yang mengerikan seiring bertambahnya ukuran menu. Setiap panggilan Add menyebabkan pembuatan ulang menu secara penuh. Solusi dengan pembantu kelas dimungkinkan yang menetapkan ComponentState = [csLoading] menghindari ratusan pembuatan ulang item menu. Pembuatan pohon yang terdiri dari 100 item akan 100x lebih lambat dibandingkan pembuatan pohon yang terdiri dari 10 item, dan seterusnya, O(N^2). - person Warren P; 14.09.2013
comment
@Warren - Terima kasih untuk itu. Saya pernah mengalami masalah serupa - jika saya ingat benar yang disebabkan oleh RethinkHotkeys - yang mana saya tidak dapat menemukan solusi dan mengubah desain. - person Sertac Akyuz; 14.09.2013
comment
+1 Ini sama persis dengan situasi OP. Tapi itu hanya akan berfungsi jika ada item root master itu. - person NGLN; 14.09.2013
comment
@NGLN - Tepatnya ia berfungsi dengan node mana pun, tetapi ia tidak menambahkan node itu tetapi turunannya dan seterusnya. Terima kasih dan +1 atas jawaban Anda setidaknya mengingatkan saya bahwa TPopupMenu.Items sebenarnya adalah TMenuItem, sebenarnya saya tidak perlu mengubah deklarasinya. Saya tidak akan mengubah jawabannya, karena jawaban Anda menunjukkan bagaimana hal itu harus dilakukan. - person Sertac Akyuz; 15.09.2013
comment
@Sertac +1 Tentang TMenuItem, tapi maaf jika terlalu teliti: ketika tidak ada item root, Items[0].Count = 0 (Items[0].HasChildren = False) dan loop Anda tidak akan berjalan. Saya tahu, saya mencobanya, karena saya lupa item root di kode pengujian saya sendiri. - person NGLN; 15.09.2013
comment
Terima kasih telah menjelaskan perubahannya, ini berfungsi dengan baik. Dan +1 untuk alternatif dari @NGLN - person ; 15.09.2013
comment
@NGLN - Itu yang saya katakan ;) - .. tetapi ia tidak menambahkan simpul itu melainkan anak-anaknya dan seterusnya. Jika tidak ada anak maka ia tidak menambahkan apa pun. Saya tidak menentang Anda, saya mencoba mengatakan Anda dapat melewati node mana pun agar anak-anaknya diisi dalam menu. - person Sertac Akyuz; 15.09.2013

Seperti Sertac mengatakan, Anda menambahkan semua item menu ke root PopupMenu. Anda harus menambahkan item sub menu ke item menu terakhir yang dibuat.

Dengan ini pendekatan alternatif, memanfaatkan TTreeNode.GetFirstChild dan .GetNextSibling:

procedure TForm1.TreeViewToMenu(Node: TTreeNode; Menu: TMenuItem);
var
  MenuItem: TMenuItem;
begin
  while Node <> nil do
  begin
    MenuItem := TMenuItem.Create(nil);
    MenuItem.Caption := Node.Text;
    MenuItem.ImageIndex := Node.ImageIndex;
    Menu.Add(MenuItem);
    if Node.HasChildren then
      TreeViewToMenu(Node.GetFirstChild, MenuItem)
    else
      MenuItem.OnClick := MyMenuItemClick;
    Node := Node.GetNextSibling;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  PopupMenu1.Items.Clear;
  TreeViewToMenu(TreeView1.Items[1], PopupMenu1.Items);
end;

Perhatikan bahwa rutinitas dimulai di sini dengan indeks item 1, anak pertama dari item root Anda. Jika tidak ada item root, mulailah dengan indeks 0.

person NGLN    schedule 14.09.2013