喫茶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.


[8052] マウスフックのコードを利用したコード返信 削除
2014/10/2 (木) 18:20:29 Mr.XRAY
上のコードではローカルなマウスフックを使用しています.
このフックのメッセージ処理を使用すれば,クリックした画像位置を検出できます.
そのようにしたコードです.
このコードにすれば,

procedure TForm1.Item_0Click(Sender: TObject);

の処理は不要です.

//-----------------------------------------------------------------------------
//  ローカルマウスフックのメッセージを受信
//  マウスカーソルの座標値を取得して表示
//  クリックした画像の番号を表示
//-----------------------------------------------------------------------------
procedure TForm1.WMApp100(var Message: TMessage);
var
  X      : Integer;
  Y      : Integer;
  hPopup : HWND;
  aRect  : TRect;
  i      : Integer;
begin
  X := LOWORD(Message.LParam);
  Y := HIWORD(Message.LParam);

  //PopupMen1のウィンドウの領域内の時だけ処理
  //Windwosのポップアップメニューのクラス名は#32768(16進数では$8000)
  hPopup := FindWindow(PChar(#32768), nil);
  GetWindowRect(hPopup, aRect);
  if PtInRect(aRect, Point(X, Y)) then begin
    Panel1.Caption := 'X: ' + IntToStr(X) + '   Y: ' + IntToStr(Y);

    if (Message.WParam = WM_LBUTTONDOWN)
         or (Message.WParam = WM_MBUTTONDOWN)
         or (Message.WParam = WM_RBUTTONDOWN) then begin
      if hMouseHook <> 0 then begin
        UnhookWindowsHookEx(hMouseHook);
        hMouseHook := 0;
      end;

      if (Message.WParam = WM_LBUTTONDOWN) or (Message.WParam = WM_RBUTTONDOWN) then begin
        for i := 0 to Length(RectImage) - 1 do begin
          if PtInRect(RectImage[i], Point(X, Y)) then begin
            ShowMessage(IntToStr(i + 1) +  ' 番目の画像クリック');
            break;
          end;
        end;
      end;

      Panel1.Caption := 'クリックで閉じた';
    end;
  end else begin
    Panel1.Caption := '';
  end;
end;


[8054] メニューの再描画返信 削除
2014/10/3 (金) 07:47:06 Mr.XRAY
> 上のコードではローカルなマウスフックを使用しています.
> このフックのメッセージ処理を使用すれば,クリックした画像位置を検出できます.
> そのようにしたコードです.


画像メニューの再描画のテストをしてみました.
マウスカーソル下の描画を変更する例です.
マウスカーソルが移動する度に再描画するとチラつきます.
そこで,OnMouseEnter, OnMouseLeave イベントのような機能を実装しています.
コードはダウンロードしたファイルで確認してください.

(再掲) 2,311 KB (EXE ファイル同梱)  2014年10月03日 PM 5時20分版
http://mrxray.on.coocan.jp/Delphi/zip/PopupMenu_ImageItem.zip


[8060] Re:メニューの再描画返信 削除
2014/10/4 (土) 14:08:12 Mr.XRAY
Delphi Forum にも投稿しておきました.

[TPopupMenu のメニューを画像にする]
http://ht-deko.minim.ne.jp/delphiforum/?vasthtmlaction=viewtopic&t=1540.0

やっぱり,途中にも画像が入れられるというのはいいですねぇ.


[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 にしないとグローバルフックにはなりません.


[8055] ネタ元 スーパーサイヤ人?返信 削除
2014/10/3 (金) 18:17:35 Mr.XRAY
> ポップアップメニュー(TPopupMenu) の Item を文字列ではなく,
> 画像にする例です.


大体まとまったので.ネタ元の紹介です.
http://hpcgi3.nifty.com/e-papy/DelphiBBS/wwwlng.cgi?print+201410/14100001.txt

テスト用のプロジェクトのソースがダウンロードできるようですが,
クリックしたら変な画面なのでやめました (^^;
Okiitan なんてのが付いてくるかも知れないので(笑)
どなたか勇気のある方がやってみて,大丈夫だったら挑戦してみます.

それにしても「超高校生級の初心者」ですか,
「スーパー高校生級」だけど初心者ですか?
昔,「スーパーサイヤ人」というのが登場するアニメがありましたが...
「スーパーマン」とかね....


[8056] スーパーサイヤ人返信 削除
2014/10/3 (金) 18:33:51 Mr.XRAY
> 昔,「スーパーサイヤ人」というのが登場するアニメがありましたが...

確認してみました.
[サイヤ人 - Wikipedia 超サイヤ人]
http://ja.wikipedia.org/wiki/%E3%82%B5%E3%82%A4%E3%83%A4%E4%BA%BA#.E8.B6.85.E3.82.B5.E3.82.A4.E3.83.A4.E4.BA.BA


[8057] Re:ネタ元 スーパーサイヤ人?返信 削除
2014/10/3 (金) 18:35:06 igy
▼ Mr.XRAYさん
> それにしても「超高校生級の初心者」ですか,

超高校級とは - はてなキーワード - はてなダイアリー
http://d.hatena.ne.jp/keyword/%C4%B6%B9%E2%B9%BB%B5%E9

他の人から言われるのは、わかりますが、自分で名乗るのは・・・


[8058] Re2:ネタ元 スーパーサイヤ人?返信 削除
2014/10/3 (金) 18:43:39 Mr.XRAY
▼ igyさん

> 超高校級とは - はてなキーワード - はてなダイアリー
> http://d.hatena.ne.jp/keyword/%C4%B6%B9%E2%B9%BB%B5%E9


ハハハッ.Delphi を使う人にもすごい人がいるんですね(半可通)


[8061] 勇気のある人いませんか?返信 削除
2014/10/4 (土) 18:50:25 Mr.XRAY
>大体まとまったので.ネタ元の紹介です.
>http://hpcgi3.nifty.com/e-papy/DelphiBBS/wwwlng.cgi?print+201410/14100001.txt


>テスト用のプロジェクトのソースがダウンロードできるようですが,
>クリックしたら変な画面なのでやめました (^^;
>Okiitan なんてのが付いてくるかも知れないので(笑)
>どなたか勇気のある方がやってみて,大丈夫だったら挑戦してみます.


勇気と度胸のある人いない?
「超高校生級」というのに興味を持ったのでレスしようと思っているんですが (^^;


[8062] Re:勇気のある人いませんか?返信 削除
2014/10/4 (土) 21:08:26 Mr.XRAY
清水の舞台から飛び降りるつもりでダウンロードしてみました.
特に問題は発生していません(^^;
Delphi 7 以前のコードのようです.
質問の文章にあった MenuChanged を使用してる部分が分かりました.

Timer1 イベントのコードを書きかえれは,チラつかなくなるようです.
動作確認は Windows 7 U64(SP1) + Delhpi XE(UP1) Pro です.


procedure TformMain.Timer1Timer(Sender: TObject);
var
  ...
  ...
  hPopupWnd : HWND;      //追加
  Flags     : Cardinal;  //追加
begin
  ...
  ...
//    mi.MenuChanged(True);  //削除.本人も激しくチラつくと書いている

    hPopupWnd := FindWindow(PChar(#32768), nil); //追加
    Flags := RDW_INVALIDATE or RDW_UPDATENOW;    //追加
    RedrawWindow(hPopupWnd, nil, 0, Flags);      //追加
  end;
end;

「超高校生級」でも難問だったようですが
問題が発生したら,いろいろ調べてみることですね.
MenuChanged は構造の変更があった場合なので,ウィンドウの更新ではなく,
全て再作成ということでしょう.

[Vcl.Menus.TMenu.MenuChanged - RAD Studio API Documentation]
http://docwiki.embarcadero.com/Libraries/XE7/ja/Vcl.Menus.TMenu.MenuChanged


こんなやつです.
TPanel 上の TPaintBox に描画している画像を TPopupMenu に表示するようです.
というか,TPanel のイメージを TMenuItem の Canvas に描画しています.


[8063] RedrawWindowの効果返信 削除
2014/10/5 (日) 13:00:12 Mr.XRAY
プロジェクトをダウンロードして,Delphi を起動して確認するのは面倒だ.
という方に,MenuChanged と RedrawWindow による違いを確認できるように,
実行ファイル (EXE) だけを UP しておきました.
興味のある方はどうぞ.
Windows 7 U64(SP1) + Delphi XE(UP1) Pro で作成しています.

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


[8064] FindWindow の引数を修正返信 削除
2014/10/5 (日) 20:02:49 Mr.XRAY
>     hPopupWnd := FindWindow(PChar(#32768), nil); //追加

まずい!!
引数で PChar の使用方法には要注意ですね.
一応期待通り動作はしますが...
既に UP しているファイルも以下に修正しました.
ついでに,少しコードの一部も修正・変更しています.

    hPopupWnd := FindWindow('#32768', nil); //追加


INCM/CMT
Cyclamen v3.82