喫茶XRAYトップページ

[スレッド全体
]

[8044] PupupMenuのメニューを画像にしてクリッカブルマップ返信 削除
2014/10/1 (水) 19:30:27 Mr.XRAY
ポップアップメニュー(TPopupMenu) の Item を文字列ではなく,
画像にする例です.
以下では,Item_0 という項目名に複数の画像を表示します.
ウェブページの「クリッカブルマップ」のような機能を実装しています.
オーナ描画で TPopupMenu の Canvas にビットマップを描画します.
クリックの位置は,マウスカーソルの座標で検出します.

動作確認は,Windows 7 U64(SP1) + Delphi XE(UP1) Pro です.

http://mrxray.on.coocan.jp/Delphi/zip/PopupMenu_ImageItem.zip


unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    PopupMenu1: TPopupMenu;
    Item_0: TMenuItem;
    Item_1: TMenuItem;
    Item_2: TMenuItem;
    Item_3: TMenuItem;
    N1: TMenuItem;
    ImageList1: TImageList;
    procedure Item_0AdvancedDrawItem(Sender: TObject; ACanvas: TCanvas;
      ARect: TRect; State: TOwnerDrawState);
    procedure Item_0MeasureItem(Sender: TObject; ACanvas: TCanvas; var Width,
      Height: Integer);
    procedure Item_0Click(Sender: TObject);
  private
    { Private 宣言 }
    RectImage : array [0..3] of TRect;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  PopupMenu1のItem_0項目のOnAdvancedDrawItemイベント処理
//  ImageListに格納してあるビットマップ画像を表示
//
//  PopupMenu1のOwnerDrawプロパティをTrueにしておく
//-----------------------------------------------------------------------------
procedure TForm1.Item_0AdvancedDrawItem(Sender: TObject; ACanvas: TCanvas;
  ARect: TRect; State: TOwnerDrawState);
var
  aBitmap   : TBitmap;
  ItemWidth : Integer;
  ItemHeigh : Integer;
  aLeft     : Integer;
  aPos      : TPoint;
  infMargin : Integer;
  i         : Integer;
begin
  ItemWidth := ImageList1.Width + 5;
  ItemHeigh := ImageList1.Height + 10;
  aPos      := PopupMenu1.PopupPoint;

  aBitmap := TBitmap.Create;
  try
    infMargin := -5;
    InflateRect(ARect, infMargin, infMargin);

    aLeft  := ARect.Left + 5;
    aPos.X := aPos.X + 5 - infMargin;
    for i := 0 to Length(RectImage) - 1 do begin
      RectImage[i] := Rect(aPos.X, aPos.Y, aPos.X + ItemWidth, aPos.Y + ItemHeigh);

      aBitmap.Assign(nil);
      ImageList1.GetBitmap(i, aBitmap);
      ACanvas.Draw(aLeft, ARect.Top, aBitmap);
      aLeft  := aLeft + ItemWidth;
      aPos.X := aPos.X + ItemWidth;
    end;
  finally
    aBitmap.Free;
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenu1のItem_0項目のOnMeasureItemイベント処理
//  項目の幅と高さを設定
//-----------------------------------------------------------------------------
procedure TForm1.Item_0MeasureItem(Sender: TObject; ACanvas: TCanvas; var Width,
  Height: Integer);
begin
  Height := ImageList1.Height + 10;
  Width  := (ImageList1.Width + 5) * Length(RectImage);
end;

//-----------------------------------------------------------------------------
//  PopupMenu1のItem_0項目のOnClickイベント処理
//  クリックした画像を,マウスクカーソルの位置から判定する
//-----------------------------------------------------------------------------
procedure TForm1.Item_0Click(Sender: TObject);
var
  aPos : TPoint;
  i    : Integer;
begin
  GetCursorPos(aPos);

  for i := 0 to Length(RectImage) - 1 do begin
    if PtInRect(RectImage[i], aPos) then begin
      ShowMessage(IntToStr(i + 1) +  ' 番目の画像クリック');
      break;
    end;
  end;
end;


最初の図は,設計時の画面です.
次の図はポップアップメニューを表示して 2 番目の項目をクリックするところてす.
最後の図が,クリックした結果です.
ボタンは動作とは関係ありません.


[8046] Re:PupupMenuのメニューを画像にしてクリッカブルマップ返信 削除
2014/10/1 (水) 19:41:41 Mr.XRAY
これは,下図のような FierFox のポップアップメニューを真似たものです.
こまかい描画は,必要であれば勝手に.
ヒントウィンドウ等も表示した方がいいかも知れません.
コンポーネントにした方がいいのかも知れません.

個人的には,複雑な仕様ならば,TForm から ポップアップメニューを作成
してしまった方がいいのではないか,と思っています.

頭は,fireFox 32.0.3 のものです.
Firefox のポップアップメニューは,そのクラス名から予想すると,
独自仕様と思われます.


[8050] Re2:PupupMenuのメニューを画像にしてクリッカブルマップ返信 削除
2014/10/2 (木) 11:31:17 Mr.XRAY
>ヒントウィンドウ等も表示した方がいいかも知れません.

残念ながら,TPopupMenu は Delphi のコントロールではなく,
Windows API の TrackPopupMenu を実行しています.
したがって,そのままではヒントウィドウを表示できません.
ヒントプロパティを実装すればできるかも知れませんが,確認していません.
TPopupMenu あるいは TMenuItem が TPanel のようにコンテナにできれば,
自由に他のコントロールが配置できるのですが...

下の図は,サンプルプログラム集の [261_マウスカーソル位置に画像を表示] を
実行してから,次のコードを実行したものです.
カエルの画像の代わりに,文字列を表示すれば,ヒントウィンドウの代わりに
なるかと思います.
文字列は,Item の画像の位置,つまりマウスカーソルの位置によって変更します.

そのためには,マウスカーソルの位置を知る必要があります.
そこで,マウスのローカルフックを実行しています.
フックは,アプリ開始と同時に開始して,終了時に終了してもいいのですが,
ポップアップメニュー表示開始時にフック開始とし,閉じたらフック終了に
することにしています.

TPopupMenu には,ポップアップウィンドウ以外の場所をクリックして閉じた時の
イベントがありません.
そこで,TPopupList.WndProc で,WM_EXITMENULOOP を検出しています.


unit Unit1;

interface

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

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;
    ImageList1: TImageList;
    Panel1: TPanel;
    procedure Item_0AdvancedDrawItem(Sender: TObject; ACanvas: TCanvas;
      ARect: TRect; State: TOwnerDrawState);
    procedure Item_0MeasureItem(Sender: TObject; ACanvas: TCanvas; var Width,
      Height: Integer);
    procedure Item_0Click(Sender: TObject);
    procedure PopupMenu1Popup(Sender: TObject);
  private
    { Private 宣言 }
    RectImage : array [0..3] of TRect;
  protected
    procedure WMExitMenuLoop(var Message: TWMExitMenuLoop);
      message WM_EXITMENULOOP;
  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));
    end;
    Result := CallNextHookEx(hMouseHook, nCode, wPar, lPar);
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenuを表示した時のイベント処理
//  マウスフックを開始する
//-----------------------------------------------------------------------------
procedure TForm1.PopupMenu1Popup(Sender: TObject);
begin
  if hMouseHook = 0 then
    hMouseHook := SetWindowsHookEx(WH_MOUSE, @MouseHookProc, 0, GetCurrentThreadID);
end;

//-----------------------------------------------------------------------------
//  PopupMenuが閉じた時のメッセージ処理
//  マウスフックを終了する
//-----------------------------------------------------------------------------
procedure TForm1.WMExitMenuLoop(var Message: TWMExitMenuLoop);
begin
  if hMouseHook <> 0 then begin
    UnhookWindowsHookEx(hMouseHook);
    hMouseHook := 0;
  end;
  Panel1.Caption := '閉じた';
end;

//-----------------------------------------------------------------------------
//  PopupMenu1のItem_0項目のOnAdvancedDrawItemイベント処理
//  ImageListに格納してあるビットマップ画像を表示
//
//  PopupMenu1のOwnerDrawプロパティをTrueにしておく
//-----------------------------------------------------------------------------
procedure TForm1.Item_0AdvancedDrawItem(Sender: TObject; ACanvas: TCanvas;
  ARect: TRect; State: TOwnerDrawState);
var
  aBitmap   : TBitmap;
  ItemWidth : Integer;
  ItemHeigh : Integer;
  aLeft     : Integer;
  aPos      : TPoint;
  infMargin : Integer;
  i         : Integer;
begin
  ItemWidth := ImageList1.Width + 5;
  ItemHeigh := ImageList1.Height + 10;
  aPos      := PopupMenu1.PopupPoint;

  aBitmap := TBitmap.Create;
  try
    infMargin := -5;
    InflateRect(ARect, infMargin, infMargin);

    aLeft  := ARect.Left + 5;
    aPos.X := aPos.X + 5 - infMargin;
    for i := 0 to Length(RectImage) - 1 do begin
      RectImage[i] := Rect(aPos.X, aPos.Y, aPos.X + ItemWidth, aPos.Y + ItemHeigh);

      aBitmap.Assign(nil);
      ImageList1.GetBitmap(i, aBitmap);
      ACanvas.Draw(aLeft, ARect.Top, aBitmap);
      aLeft  := aLeft + ItemWidth;
      aPos.X := aPos.X + ItemWidth;
    end;
  finally
    aBitmap.Free;
  end;
end;

//-----------------------------------------------------------------------------
//  PopupMenu1のItem_0項目のOnMeasureItemイベント処理
//  項目の幅と高さを設定
//-----------------------------------------------------------------------------
procedure TForm1.Item_0MeasureItem(Sender: TObject; ACanvas: TCanvas; var Width,
  Height: Integer);
begin
  Height := ImageList1.Height + 10;
  Width  := (ImageList1.Width + 5) * Length(RectImage);
end;

//-----------------------------------------------------------------------------
//  PopupMenu1のItem_0項目のOnClickイベント処理
//  クリックした画像を,マウスクカーソルの位置から判定する
//-----------------------------------------------------------------------------
procedure TForm1.Item_0Click(Sender: TObject);
var
  aPos : TPoint;
  i    : Integer;
begin
  if TMenuItem(Sender) = Item_0 then begin
    GetCursorPos(aPos);

    for i := 0 to Length(RectImage) - 1 do begin
      if PtInRect(RectImage[i], aPos) then begin
        ShowMessage(IntToStr(i + 1) +  ' 番目の画像クリック');
        break;
      end;
    end;

    if hMouseHook <> 0 then begin
      UnhookWindowsHookEx(hMouseHook);
      hMouseHook := 0;
    end;
  end;

  Panel1.Caption := '閉じた';
  end;
end;

//-----------------------------------------------------------------------------
//  ローカルマウスフックのメッセージを受信
//  マウスカーソルの座標値を取得して表示
//-----------------------------------------------------------------------------
procedure TForm1.WMApp100(var Message: TMessage);
var
  X : Integer;
  Y : Integer;
begin
  X := LOWORD(Message.LParam);
  Y := HIWORD(Message.LParam);
  Panel1.Caption := 'X: ' + IntToStr(X) + '   Y: ' + IntToStr(Y);
end;


//-----------------------------------------------------------------------------
//  PopupMenuが閉じるとWM_EXITMENULOOPメッセージを発行する
//  このメッセージを受信したら,アプリのアクティブなフォームにメッセージを送る
//-----------------------------------------------------------------------------

{ TPopupListEx }

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

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

end.


[8053] ローカルフックとグローバルフック返信 削除
2014/10/2 (木) 18:23:39 Mr.XRAY
> そこで,マウスのローカルフックを実行しています.

ローカルフックの時は,
 SetWindowsHookEx(WH_MOUSE, @MouseHookProc, 0, GetCurrentThreadID);

です.グローバルフックの時の引数は,
 SetWindowsHookEx(WH_MOUSE, @MouseHookProc, hInstance, 0);

となります.
グローバルフックというのは,システム全体で有効なフックのことです.
他のアプリケーションのマウス操作をフック(捕捉・検出)します.
第 1 引数が WH_MOUSE の時は,DLL にしないとグローバルフックにはなりません.


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