呼び出したメニュー(TMenuItem)から、そのメニューの属するTPopupMenuを識別するには?

解決


おも  2008-10-27 15:37:04  No: 32361

複数のTPopupMenu内のメニューから同じprocedureを呼び出し、呼び出されたTPopupMenuを識別して処理を振り分けようとしています。この場合に、直接的にTPopupMenuを識別する方法はありますでしょうか?
当方で呼び出したメニューからオブジェクトツリーを遡るような方法を念頭に下記のようなテストは行いました。

なお、例えば、TPopupMenu1のメニューのTagには1、TPopupMenu2のメニューのTagには2をあらかじめ割り当てておいて、それにより識別するといったような間接的な方法での解決法はここでは望んでいません。

環境 : Delphi6Personal+Win2000;

〜試行内容〜
Form上にPanelを2つとButtonをおき、次のようなコードを用意しました。

procedure TForm1.Button1Click(Sender: TObject);
var
    Pop:TPopupMenu;
    Pop2:TPopupMenu;
    NewItem:TMenuItem;
begin
    Pop:=TPopupMenu.Create(Self);
    Pop.Name:='PopupMenu2';
    Panel1.PopupMenu:=Pop;
    NewItem:=TMenuItem.Create(Pop);
    NewItem.Caption:='テスト1';
    NewItem.Name:='test1';
    NewItem.OnClick:=TestClick;
    Pop.Items.Add(NewItem);

    Pop2:=TPopupMenu.Create(Self);
    Pop.Name:='PopupMenu3';
    Panel2.PopupMenu:=Pop2;
    NewItem:=TMenuItem.Create(Pop2);
    NewItem.Caption:='テスト2';
    NewItem.Name:='test2';
    NewItem.OnClick:=TestClick;
    Pop2.Items.Add(NewItem);
end;

procedure TForm1.TestClick(Sender: TObject);
var
    i:Integer;
    Mn:TMenuItem;
begin
    Mn:=(Sender as TMenuItem);
    for i:=0 to 10 do begin
        Mn:=Mn.Parent;
        ShowMessage(Mn.Name);
    end;
end;

実行後、Button1をクリックして、Panelに対してそれぞれPopupMenuを作成し、テストメニューをクリックしましたが、希望のPopupMenu.Nameは得られません。また、procedure TForm1.TestClick(Sender: TObject)部を以下のように変更しての試行も実施しましたが、希望の通りにはなりませんでした。

procedure TForm1.TestClick(Sender: TObject);
var
    i:Integer;
    Com:TComponent;
begin
    Com:=(Sender as TComponent);
    for i:=0 to 10 do begin
        Com:=Com.Owner;
        ShowMessage(Com.Name);
    end;
end;

それは不可能という回答でもかまいませんので、よろしくお願いします。


ofZ  2008-10-27 17:30:21  No: 32362

こちらで
procedure TForm1.TestClick(Sender: TObject);
var
  menu  :TMenuItem;
begin
  if Sender is TMenuItem then begin
    menu := TMenuItem(Sender);
    while menu <> nil do begin
      if menu.Parent = nil then begin
        if menu.Owner <> nil then begin
          ShowMessage(menu.Owner.Name);
        end;
        Break;
      end
      else begin
        menu := menu.Parent;
      end;
    end;
  end;
end;


う〜ん  2008-10-27 18:15:45  No: 32363

これだけじゃダメ?
procedure TForm1.TestClick(Sender: TObject);
begin
  ShowMessage(TMenuItem(Sender).Parent.Owner.Name);
end;


KHE00221  2008-10-27 19:55:09  No: 32364

1つのprocedureで処理振り分けるなら 
TPopupMenu で判断せず Sender で区別するだけで良いんじゃないの?


おも  2008-10-27 21:10:46  No: 32365

ofzさん、う〜んさん、KHE00221さん、レス有難うございます。

ofzさんの示してくださったコードが私が求めていたものドンピシャでした。

う〜んさん、KHE00221さんの抱かれた疑問への回答は下のようになります。

1.呼び出しメニューはTPopupMenu直下のものとは限らず、さらにサブメニューといった使い方も予定しています。

2.TPopupMenuも、例えば、TPanelの増減にしたがって、動的に処理するようなことを予定しており、
さらに、共通のprocedureを呼び出すTMenuItemも一つのTPopupMenuから一つではなく複数を予定しています。したがって、Senderのみで区別も可能と思われますが、TMenuItem.Nameの名前付けに制約がつき厳密に管理する必要があるのではないかと思います。ここでは、TMenuItem.Captionのみの緩やかな管理で使用したいと思っています。

ofzさん、う〜んさんの示されたコードを見れば、なんとなく、そういう構造になっていたのかとは分かりましたが、これはヘルプを見て普通に分かるものなのでしょうか(私はわかりませんでしたが^^)。それとも、Delphi6 Personalのヘルプでは分からないけど、他のヘルプだったらわかるとか、あるいは、製品版で?classes.pasを見たら分かるというものなのでしょうか。

一応、解決としておきますが、コメントして下さるとありがたいです。

なお、私が提示したprocedure TForm1.Button1Click(Sender: TObject);内のコードに間違いがありましたので訂正しておきます。すみませんでした。

Pop.Name:='PopupMenu3';

Pop2.Name:='PopupMenu3';


うんと  2008-10-27 22:33:40  No: 32366

>名前付けに制約がつき厳密に管理する必要があるのではないかと思います。

うーん、Name プロパティーにこだわっていますが、動的に生成するなら Name 
なんか必要ないし、まったく意味不明ですね。ひょっとして、インスタンスの
変数名と Name プロパティーを混同してませんか? Tag による番号付けの方が
はるかに合理的で、判断も簡単だと思います。


おも  2008-10-28 02:58:56  No: 32367

うんとさん、レス有難うございます。

前回挙げた例では確かにおっしゃるとおりTagを使用した方がよいようです。

ただ、少し背景を説明しておきます。
ソフトの変更を計画性なく、思いつくままに行っています。今回の具体的な変更は、PaintBoxを1つ持ったFormを動的に既に複数作成しており、そのFormのMainManuのメニューにはPaintBoxに関する他の形式のFormと共通したprocedureを呼び出している部分もあるのですが、PaintBoxを動的に複数にすることで、メインメニューだった部分を個別のPaintBoxに関連したPopupMenuに移そうということでした。この場合のメニューの識別としては、
1.同一Form内の別のPopupMenuでのメニューとの識別
2.同種のForm内のPopupMenuでのメニューとの識別
3.他種のForm内のメニューとの識別

といった感じになるかと思います。が、これも今回書き出してみて整理されたぐらいで、やりながら、修正していっている感じです。私としては、複数の条件で識別することが多く、計画性なくソフトの変更を行っているので、Tagを使用した場合に、その後の変更で競合する場合や、あるいは、呼び出したprocedure内では競合しなくても、さらにそこから呼び出したまた別のメニューとの共通のprocedure内で競合する可能性もあり、Tagの使用はこれまであまりありません。このようなケースでもきちんと整理すれば競合せず、Tagを使用する方がはるかに合理的で判断も簡単かも知れませんが、そこまで突き詰めるつもりもないので、多少無駄な処理があっても、汎用性を持たせる方向でやっています。

>>インスタンスの変数名と Name プロパティーを混同してませんか?

プロパティはしっくり来ますが、インスタンスは未だしっくり来ませんので混同しているかどうかもわかりません。今回のやりとりで動的作成時にはTMenuItem.Nameプロパティは指定しなくてもよいらしい(後で確認します)と分かりましたが、指定しなければいけないものと思って適当に連番でNameプロパティを指定していただけで、実際の識別にはほとんど利用したことはありません。ただ、TMenuItemのみで複数の条件を識別する場合には、Nameプロパティを使用するのが適当かと思っておりましたので、前回のような書き込みになりました。


TS  2008-10-28 07:41:32  No: 32368

本来の質問とはかけ離れますが。

イメージとしては各画像があるFormに各種のPopupMenuメニューが
ある感じですが、性質が同一のメニューならParentをそのFormが
アクティブになった時に変えるだけでは駄目ですか。


おも  2008-10-28 10:58:21  No: 32369

TSさん、レスありがとうございます。

Formがアクティブになった時にメニューのParentを変える

ということですか?
私にはなかった共通化?の発想のようでわくわくしています。発想がなかったので、メニューのParentを変更して共通化する具体的な方法が今ひとつなのですが、仮に下記のような場合にはどうなりますでしょうか。

一つの例として、

Form1:PaintBox1のみ

Form2:PaintBoxが動的に増えて、Form2自体も動的に作成

そういう仕様で実際に、

Form1:PaintBox1
Form2a:PaintBox1,PaintBox2
Form2b:PaintBox1,PaintBox2,PaintBox3

という状態で、それぞれのPaintBoxでの描画をBitmapで保存するメニューがあるとします。

私のやり方では、

・メニューの用意
Form1のMainManuにSaveImageメニュー
Form2aのPaintBox毎のPopupMenuにSaveImageメニュー
Form2bのPaintBox毎のPopupMenuにSaveImageメニュー

・メニュークリック
SaveImageから呼び出された共通のprocedureでメニューから識別し対象となるPaintBox決定

・イメージ保存専用procedureへTPaintBoxを渡す
渡されたTPaintBoxを元にその属するFormを最前面表示し、デスクトップー画像のうちPaintBox領域をイメージとして保存するprocedure

といった感じになります。メニューのParentを変更するならば、メニューの用意の部分がメニューの切替コード(この部分は逆に面倒な場合も?)に変更になるのかと思いますが、それ以降では結局同じプロセスを通過しないといけないようでメニュー切替により省略できた部分がないように思えます。

発想がなかったもので、見当違いのことを書いていたらご容赦ください。


ofZ  2008-10-28 17:37:19  No: 32370

Form2a.PaintBox1.PopupMenu := Form1.PaintBox1.PopupMenu;
このように同じインスタンスを割り当てしても、TPopupMenuのPopupComponentプロパティで、
どのコンポーネントからポップアップしたかわかります。

先に私が提示したコードで、PopupMenuが取得できるので、それに加え
PopupComponentを見てやれば、Form1のPaintBoxかForm2aのPaintBoxかという
個別の判定が不要になります。

PopupComponentがTPaintBoxであるかという判定は必要かと思いますが、
それぞれのPaintBoxで、同じ階層のメニューが必要なのであれば、
インスタンスを使い回すのもいいかと思います。


TS  2008-10-28 17:50:25  No: 32371

すでにofZさんが回答されていますが
procedure TForm2.PaintBox1Click(Sender: TObject);
begin
  PaintBox1.PopupMenu:=PopupMenu1;
end;
とかして
PopupMenu1.PopupComponentで判定


TS  2008-10-28 17:57:26  No: 32372

失礼しましたOnClickではなく普通に設定して下さい。


おも  2008-10-28 21:25:48  No: 32373

ofZさん、TSさん、レス有難うございます。

>>Form2a.PaintBox1.PopupMenu := Form1.PaintBox1.PopupMenu;

これは言われてみればなるほどです。オブジェクトインスペクタでも選択肢にでてきていますね。そして、確かにアクティブになったものがParentと言えますね。新鮮な発見です。有難うございます。

そのまま今回は適用できそうと思ったのですが、個別PopupMenu内のメニューによる処理で、そのPopupMenu内にさらに動的にメニューを作成するつもりなので、この方法を使うなら、この部分をなんらかの方法で回避しなければならなさそうです。

でも、応用範囲の広そうな方法で今後のプログラミングには大変役に立ちそうです。

TPopupMenu.PopupComponentプロパティは色々使えそうです。ネットの中の情報を頼りに我流でやっており、体系的には学習していないので、Delphiには備わっているのに使えていない機能がまだまだありそうです。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加