喫茶XRAYトップページ

[▲前のスレッド]

[8065] ポップアップメニュー画面の上に別のポップアップメニュー返信 削除
2014/10/6 (月) 22:38:21 Mr.XRAY
ポップアップメニューを表示して,そのメニューのウィンドウの上で
マウスの右ボタンクリックで,別のポップアップメニューを表示するサンプルです.

(1) 新規にプロジェクトを作成して,保存します.
(2) Delphi のソースコードの Menu.pas をプロジェクトのフォルダにコピーします.
(3) Menu.pas に,以下の 1 行を追加します.
    TPM_RECURSE を追加すると右クリックで閉じなくなります.

    procedure TPopupMenu.Popup(X, Y: Integer);
      ...
      ...
      AFlags := AFlags or TPM_RECURSE;  //追加
      TrackPopupMenu(FItems.Handle, AFlags, X, Y, 0 { reserved }, PopupList.Window, nil);
    end;
 
(4) TPopupMenu コンポを 2 つフォームに配置して,適当に項目を作成します.
(5) TPanel コンポを 1 つ配置します.
(5) Form1 の PopupMenu プロパティを PopupMenu1 にします.
(6) テスト用のコードを以下のようにします.

面白そうなので,サブのポップアップメニューをいくつでも表示できるようにして
います.
動作確認は,Windows 7 U64(SP1) + Delphi XE(UP1) Pro です.


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Menus;

type
    TPopupListEx = class(TPopupList)
    protected
      procedure WndProc(var Message: TMessage) ; override;
    end;

  TForm1 = class(TForm)
    Button1: TButton;
    PopupMenu1: TPopupMenu;
    Item_0: TMenuItem;
    Item_1: TMenuItem;
    Item_2: TMenuItem;
    Item_3: TMenuItem;
    N1: TMenuItem;
    Panel1: TPanel;
    PopupMenu2: TPopupMenu;
    Item_20: TMenuItem;
    Item_21: TMenuItem;
    Item_22: TMenuItem;
    Item_23: TMenuItem;
  private
    { Private 宣言 }
  protected
    procedure WMExitMenuLoop(var Message: TWMExitMenuLoop);
      message WM_EXITMENULOOP;
    procedure WMEnterMenuLoop(var Message: TWMEnterMenuLoop);
      message WM_ENTERMENULOOP;
    procedure WMMenuSelect(var Message: TWMMenuSelect);
      message WM_MENUSELECT;
  public
    { Public 宣言 }
    procedure WMApp100(var Message: TMessage); message WM_APP+100;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hMouseHook : HHOOK;

//-----------------------------------------------------------------------------
//  マウスフックのコールバック関数
//  Form1にメッセージを送る
//  ポップアップメニュー上のマウスカーソル位置は,フックでないと検出不可
//-----------------------------------------------------------------------------
function MouseHookProc(nCode: Integer; wPar: WPARAM; lPar:LPARAM):
  Integer; stdcall;
var
  MousePos : TPoint;
begin
  if nCode < 0 then begin
    Result := CallNextHookEx(hMouseHook, nCode, wPar, lPar);
  end else begin
    if nCode = HC_ACTION then begin
      MousePos := PMouseHookStruct(lPar)^.pt;
      PostMessage(Form1.Handle, WM_APP+100, wPar, MakeLParam(MousePos.X, MousePos.Y));
      //PopupMenuのOnClickイベントを使用しない場合はResult := -1にしてExit;
      //このコードにすると,TMenuItem.OnClickは検出できなくなる
      Result := -1;
      exit;
    end;
    Result := CallNextHookEx(hMouseHook, nCode, wPar, lPar);
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenuが表示された時のメッセージ処理
//  マウスフックを開始する
//-----------------------------------------------------------------------------
procedure TForm1.WMEnterMenuLoop(var Message: TWMEnterMenuLoop);
begin
  if hMouseHook = 0 then begin
    hMouseHook := SetWindowsHookEx(WH_MOUSE, @MouseHookProc, 0, GetCurrentThreadID);
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenuが閉じた時のメッセージ処理
//  マウスフックを終了して,PopupMeneを全て閉じる
//-----------------------------------------------------------------------------
procedure TForm1.WMExitMenuLoop(var Message: TWMExitMenuLoop);
var
  MousePos : TPoint;
begin
  GetCursorPos(MousePos);

  //1つでも閉じれば全て閉じることにする
  SendMessage(Handle, WM_APP+100, WM_LBUTTONUP, MakeLParam(MousePos.X, MousePos.Y));
  if hMouseHook <> 0 then begin
    UnhookWindowsHookEx(hMouseHook);
    hMouseHook := 0;
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenuで項目を選択された時のメッセージ処理
//  選択されている項目名を表示
//  実際に選択したかで項目を取得するには,WM_LBUTTONUPがいいかも知れない
//  ただし,どの項目を選択したかは,このWM_MENUSELECTまたは
//  TPopupList.WndProc内でないと取得は困難だろう
//-----------------------------------------------------------------------------
procedure TForm1.WMMenuSelect(var Message: TWMMenuSelect);
var
  i        : Integer;
  MenuItem : TMenuItem;
begin
  if (Message.MenuFlag = $FFFF) and (Message.Menu = 0) then exit;

  Panel1.Caption := '';
  for i := 0 to ComponentCount - 1 do begin
    if Components[i].ClassName = 'TPopupMenu' then begin
      if TPopupMenu(Components[i]).Handle = Message.Menu then begin
        MenuItem := TPopupMenu(Components[i]).FindItem(Message.IDItem, fkCommand);
        Panel1.Caption := MenuItem.Name;
        Break;
      end;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  ローカルマウスフックのメッセージを受信
//-----------------------------------------------------------------------------
procedure TForm1.WMApp100(var Message: TMessage);
var
  X         : Integer;
  Y         : Integer;
  hPopupWnd : HWND;
begin
  X := LOWORD(Message.LParam);
  Y := HIWORD(Message.LParam);

  //左ボタンUP(項目を選択した時)はTPopupMenuを全て閉じることにする
  if (Message.WParam = WM_LBUTTONUP) then begin
    while True do begin
      hPopupWnd := FindWindow('#32768', nil);
      if hPopupWnd = 0 then break;

      SendMessage(hPopupWnd, WM_CLOSE, 0, 0);
      Application.ProcessMessages;
      Sleep(1);
    end;
  end else
  //右ボタンUPでは新しくTPopupMenuを表示する
  if (Message.WParam = WM_RBUTTONUP) then begin
    PopupMenu2.Popup(X, Y);
  end;
end;


//-----------------------------------------------------------------------------
//  PopupMenu内部のPopupListのメッセージ処理
//  これらのメッセージを受信したら,アプリのアクティブなフォームにメッセージを送る
//-----------------------------------------------------------------------------

{ TPopupListEx }

procedure TPopupListEx.WndProc(var Message: TMessage);
begin
  if Screen.Activeform <> nil then begin
    if Message.Msg = WM_ENTERMENULOOP then begin
      Screen.ActiveForm.Perform(WM_ENTERMENULOOP, Message.WParam, Message.LParam);
    end else
    if Message.Msg = WM_EXITMENULOOP then begin
      Screen.ActiveForm.Perform(WM_EXITMENULOOP, Message.WParam, Message.LParam);
    end;
    if Message.Msg = WM_MENUSELECT then begin
      Screen.ActiveForm.Perform(WM_MENUSELECT, Message.WParam, Message.LParam);
    end;
  end;
  inherited;
end;

initialization
  Popuplist.Free;
  PopupList:= TPopupListEx.Create;

end.


下図は,設計時と実行した画面です.
Panel1 に,(左ボタンDOWNまたは矢印キー等で)選択の項目名を表示します.


[8066] Re:ポップアップメニュー画面の上に別のポップアップメニュー返信 削除
2014/10/6 (月) 22:51:05 Mr.XRAY
前の [PupupMenuのメニューを画像にしてクリッカブルマップ]
のファイルに入れておきました (Menu.pas は含まれていません)

http://mrxray.on.coocan.jp/Delphi/zip/PopupMenu_ImageItem.zip
  (2,854 KB)

ネタ元は以下です.
[メニューの右クリック]
http://hpcgi3.nifty.com/e-papy/DelphiBBS/wwwlng.cgi?print+201407/14070017.txt


[8067] Re:ポップアップメニュー画面の上に別のポップアップメニュー返信 削除
2014/10/6 (月) 23:12:35 Mr.XRAY
マウスのフックのコールバック関数に以下のコードがあります.

>       //このコードにすると,TMenuItem.OnClickは検出できなくなる
>       Result := -1;
>       exit;


これは,次のウィンドウにマウスフックのメッセージを送らないためです.
その結果,TMenuItem.OnClick はマウスのメッセージを取得できなくなります.
代りに,マウスフックのメッセージで受け取るようにしています.

このようなコードをグロバールフックでやってしまうと...
他のウィンドウがマウスメッセージを受け取れなくなってしまうことがあります.
ご注意くださいませ.


[8068] Re:ポップアップメニュー画面の上に別のポップアップメニュー返信 削除
2014/10/7 (火) 08:55:31 Mr.XRAY
> 面白そうなので,サブのポップアップメニューをいくつでも表示できるようにして
> います.


どうだ!!
実用性は全くありませんが.
(備考: 合成画像ではありません)


[8069] Re:ポップアップメニュー画面の上に別のポップアップメニュー返信 削除
2014/10/7 (火) 10:07:19 Mr.XRAY
>     TPM_RECURSE を追加すると右クリックで閉じなくなります.

[WM_MENURBUTTONUP message (Windows)]
http://msdn.microsoft.com/en-us/library/windows/desktop/ms647610%28v=vs.85%29.aspx

の以下の記事によるものです.

The WM_MENURBUTTONUP message allows applications to provide
a context-sensitive menu―also known as a shortcut menu―for
the menu item specified in this message.
To display a context-sensitive menu for a menu item,
call the TrackPopupMenuEx function with TPM_RECURSE.

Delphi の Menu.pas の TPopupList.WndProc はこの WM_MENURBUTTONUP の
処理をしていません.そこで,マウスフックで WM_RBUTTONUP を検出して,
サブのポップアップメニューを表示するようにしています.

以下の TPM_RECURSE の箇所も参照してください.
[TrackPopupMenu 関数]
http://msdn.microsoft.com/ja-jp/library/cc364838.aspx


[8070] Re:ポップアップメニュー画面の上に別のポップアップメニュー返信 削除
2014/10/8 (水) 13:33:32 Mr.XRAY
> > 面白そうなので,サブのポップアップメニューをいくつでも表示できるようにして
> > います.
>
> どうだ!!
> 実用性は全くありませんが.


こんなことは実際にはしないと思います.で.真面目なコードに変更しました.
UP したファイルも整理しました.余分なプロジェクトを削除しています.
変更した Menu.pas は含まれていません.
メインのメニューが TMainMenu の場合は修正が必要です.

http://mrxray.on.coocan.jp/Delphi/zip/PopupMenu_ImageItem.zip
(1,122 KB)


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Menus;

type
    TPopupListEx = class(TPopupList)
    protected
      procedure WndProc(var Message: TMessage) ; override;
    end;

  TForm1 = class(TForm)
    Button1: TButton;
    PopupMenu1: TPopupMenu;
    Item_0: TMenuItem;
    Item_1: TMenuItem;
    Item_2: TMenuItem;
    Item_3: TMenuItem;
    N1: TMenuItem;
    PopupMenu2: TPopupMenu;
    Item_20: TMenuItem;
    Item_21: TMenuItem;
    Item_22: TMenuItem;
    Item_23: TMenuItem;
  protected
    procedure WMExitMenuLoop(var Message: TWMExitMenuLoop);
      message WM_EXITMENULOOP;
    procedure WMEnterMenuLoop(var Message: TWMEnterMenuLoop);
      message WM_ENTERMENULOOP;
    procedure WMMenuSelect(var Message: TWMMenuSelect);
      message WM_MENUSELECT;
  private
    { Private 宣言 }
    FSelectMenuItem    : TMenuItem;
    FClickMainMenuItem : TMenuItem;
    FClickSubMenuItem  : TMenuItem;
    FShowSubPopup      : Boolean;
  public
    { Public 宣言 }
    procedure WMApp100(var Message: TMessage); message WM_APP+100;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  hMouseHook : HHOOK;

//-----------------------------------------------------------------------------
//  マウスフックのコールバック関数
//  Form1にメッセージを送る
//  ポップアップメニュー上のマウスカーソル位置は,フックでないと検出不可
//-----------------------------------------------------------------------------
function MouseHookProc(nCode: Integer; wPar: WPARAM; lPar:LPARAM):
  Integer; stdcall;
var
  MousePos : TPoint;
begin
  if nCode < 0 then begin
    Result := CallNextHookEx(hMouseHook, nCode, wPar, lPar);
  end else begin
    if nCode = HC_ACTION then begin
      MousePos := PMouseHookStruct(lPar)^.pt;
      PostMessage(Form1.Handle, WM_APP+100, wPar, MakeLParam(MousePos.X, MousePos.Y));
      Result := -1;
      exit;
    end;
    Result := CallNextHookEx(hMouseHook, nCode, wPar, lPar);
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenuが表示された時のメッセージ処理
//  マウスフックを開始する
//-----------------------------------------------------------------------------
procedure TForm1.WMEnterMenuLoop(var Message: TWMEnterMenuLoop);
begin
  if FShowSubPopup = False then begin
    FClickMainMenuItem := nil;
    FClickSubMenuItem  := nil;
    if hMouseHook = 0 then begin
      hMouseHook := SetWindowsHookEx(WH_MOUSE, @MouseHookProc, 0, GetCurrentThreadID);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenuが閉じた時のメッセージ処理
//  マウスフックを終了
//  クリックしたメニュー項目の名前(Captionプロパティの値)を表示
//
//  フックを解除しないでダイアログを表示すると本サンプルではハングアップする
//  サブのポップアップを閉じたら,メインのメニューも閉じる
//  メニューを表示したままダイアログを表示すると,メニューが最上位に表示される
//  メニューは常にZオーダーが最上位のウィンドウとして表示される
//  ダイアログを表示しなければメインのメニューは表示したままでもよい
//  その場合,マウスフックは解除しない
//  マウスフックを解除すると,サブのポップメニューが表示できなくなる
//-----------------------------------------------------------------------------
procedure TForm1.WMExitMenuLoop(var Message: TWMExitMenuLoop);
var
  PopupPos : TPoint;
  MainName : String;
  SubName  : String;
  StrText  : String;
begin
  if hMouseHook <> 0 then begin
    UnhookWindowsHookEx(hMouseHook);
    hMouseHook := 0;

    MainName := '';
    SubName  := '';
    if FShowSubPopup then begin
      if FClickSubMenuItem = nil then FClickMainMenuItem := nil;
    end;
    if FClickMainMenuItem <> nil then MainName := FClickMainMenuItem.Name;
    if FClickSubMenuItem  <> nil then SubName  := FClickSubMenuItem.Name;
    StrText := 'Main: ' + MainName + sLineBreak + 'Sub: ' + SubName;

    PopupPos := PopupMenu1.PopupPoint;
    SendMessage(Handle, WM_APP+100, WM_LBUTTONUP, MakeLParam(PopupPos.X, PopupPos.Y));

    //コンポーネントにする場合は,ここでOnClickイベントを実行する
    //TMainMenu,TPopupMenuのSenderはTMenuItemの1つだけ
    //メインメニューのどの項目からサブのポップアップを表示したかは別のプロパティ
    //として実装する必要があるだろう
    MessageBox(Handle, PChar(StrText), '選択項目', MB_ICONINFORMATION);
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenuで項目が選択された時のメッセージ処理
//-----------------------------------------------------------------------------
procedure TForm1.WMMenuSelect(var Message: TWMMenuSelect);
var
  FindKind  : TFindItemKind;
  ItemIndex : Integer;
begin
  if (Message.MenuFlag = $FFFF) and (Message.Menu = 0) then exit;

  FindKind := fkCommand;
  //サブメニュー
  //本サンプルでは使用していない
  if (Message.MenuFlag and MF_POPUP) <> 0 then begin
    FindKind := fkHandle;
  end;

  ItemIndex := -1;
  if FindKind = fkHandle then begin
    if Message.Menu <> 0 then begin
      ItemIndex := GetSubMenu(Message.Menu, Message.IDItem);
    end;
  end else begin
    ItemIndex := Message.IDItem;
  end;

  FSelectMenuItem := nil;
  //TMainMenu等を使用している場合は,その中を検索する
  if PopupMenu1.Handle = Message.Menu then begin
    FSelectMenuItem := PopupMenu1.FindItem(ItemIndex, FindKind);
  end else
  if PopupMenu2.Handle = Message.Menu then begin
    FSelectMenuItem := PopupMenu2.FindItem(ItemIndex, FindKind);
  end;
end;

//-----------------------------------------------------------------------------
//  ローカルマウスフックのメッセージを受信
//
//  このメッセージ内でダイアログ等を表示しないこと
//  本サンプルのフックのコールバック関数は,Form1のハンドルに対してだけ
//  メッセージを送信するようにしている
//  そのため,ダイアログ等を表示してしまうと,メッセージが受信できなくなる
//  つまり,このメッセージ処理が実行されなくなってしまう
//-----------------------------------------------------------------------------
procedure TForm1.WMApp100(var Message: TMessage);
var
  X      : Integer;
  Y      : Integer;
  hPopup : HWND;
begin
  X := LOWORD(Message.LParam);
  Y := HIWORD(Message.LParam);

  if (Message.WParam = WM_LBUTTONUP) or (Message.WParam = WM_RBUTTONUP) then begin
    if (Message.WParam = WM_RBUTTONUP) and (FShowSubPopup = False) then begin
      FClickMainMenuItem := FSelectMenuItem;
      FShowSubPopup := True;
      PopupMenu2.Popup(X, Y);
      FShowSubPopup := False;
    end else begin
      while True do begin
        hPopup := FindWindow('#32768', nil);
        if hPopup = 0 then break;
        SendMessage(hPopup, WM_CLOSE, 0, 0);
        Application.ProcessMessages;
        Sleep(1);
      end;

      if FClickMainMenuItem = nil then begin
        FClickMainMenuItem := FSelectMenuItem;
      end else begin
        FClickSubMenuItem := FSelectMenuItem;
      end;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenu内部のPopupListのメッセージ処理
//  これらのメッセージを受信したら,アプリのアクティブなフォームにメッセージを送る
//-----------------------------------------------------------------------------

{ TPopupListEx }

procedure TPopupListEx.WndProc(var Message: TMessage);
begin
  if Screen.Activeform <> nil then begin
    if Message.Msg = WM_ENTERMENULOOP then begin
      Screen.ActiveForm.Perform(WM_ENTERMENULOOP, Message.WParam, Message.LParam);
    end else
    if Message.Msg = WM_EXITMENULOOP then begin
      Screen.ActiveForm.Perform(WM_EXITMENULOOP, Message.WParam, Message.LParam);
    end;
    if Message.Msg = WM_MENUSELECT then begin
      Screen.ActiveForm.Perform(WM_MENUSELECT, Message.WParam, Message.LParam);
    end;
  end;
  inherited;
end;

initialization
  Popuplist.Free;
  PopupList:= TPopupListEx.Create;

end.



[▼次のスレッド]
INCM/CMT
Cyclamen v3.82