メニュー項目を移動するには?

解決


deldel  2010-12-08 20:25:34  No: 39675

スタートボタンをクリックすると表示されるポップアップメニュー内の項目は
ドラッグすることによって移動できますが、同じような処理を TPopupMenu で
実現できないでしょうか?
または、実現できているコンポーネントがないでしょうか?
(同じ質問が約5年前にあったのですが、解決できなかったようで・・・)

環境は、D5,D6,XP,Win7です。
宜しくお願い致します。


Fusa  2010-12-14 21:33:03  No: 39676

TPopupMenuでは難しいようですね。

出来なくはないような気がするけど、やっぱり出来なさそう。
と考えていたので答えるのを躊躇してました。

ポップアップするFormを作り
PanelをalTopにしてたくさん並べて
MouseMoveで色を変化させて
Drag&Dropで移動する。
そんなPopupMenuっぽいUIを実装する必要がありそうです。


D  2010-12-16 03:20:49  No: 39677

タイマーを使った力業。
D6 XP SP3
とりあえずメニューアイテムを3つくらい作ってOnClickイベントにN1OnClickを割り当ててテスト。

  private
    { Private 宣言 }
    F_DragItem: TMenuItem;

procedure TForm1.PopupMenu1Popup(Sender: TObject);
begin
  F_DragItem := nil;
  Timer1.Enabled := True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  i: Integer;
  l_MenuItem: TMenuItem;
  lpt_Pos:    TPoint;
  lrc_Rect:   TRect;
begin
  GetMenuItemRect(0, PopupMenu1.Handle, 0, lrc_Rect);
  if ((lrc_Rect.Right  - lrc_Rect.Left) <= 0)
  or ((lrc_Rect.Bottom - lrc_Rect.Top)  <= 0)
  then begin
    //ポップアップメニューは非表示
    Timer1.Enabled := False;
  end;

  if  (gfnbKeyState(VK_LBUTTON))
  and (F_DragItem = nil)
  then begin
    //ドラッグ開始かも知れない
    lpt_Pos := gfnptMousePosGet;
    for i := 0 to PopupMenu1.Items.Count-1 do begin
      l_MenuItem := PopupMenu1.Items[i];
      GetMenuItemRect(0, PopupMenu1.Handle, i, lrc_Rect);
      if (PtInRect(lrc_Rect, lpt_Pos)) then begin
        F_DragItem := l_MenuItem;
        Timer1.Enabled := False;
        Exit
      end;
    end;
  end;
end;

procedure TForm1.N1Click(Sender: TObject);
var
  l_MenuItem: TMenuItem;
begin
  l_MenuItem := TMenuItem(Sender);

  if  (F_DragItem <> nil)
  and (F_DragItem <> l_MenuItem)
  then begin
    //並べ替え
    F_DragItem.MenuIndex := l_MenuItem.MenuIndex;
  end;
end;


D  2010-12-16 03:25:24  No: 39678

訂正と追加。

>OnClickイベントにN1OnClickを割り当ててテスト。
  OnClickイベントにN1Clickを割り当ててテスト。

function gfnbKeyState(iKey: Integer): Boolean;
var
  li_Check: SHORT;
begin
  //左右ボタンを入れ替えている場合に対処
  if (GetSystemMetrics(SM_SWAPBUTTON) <> 0) then begin
    if (iKey = VK_LBUTTON) then begin
      iKey := VK_RBUTTON;
    end else if (iKey = VK_RBUTTON) then begin
      iKey := VK_LBUTTON;
    end;
  end;

  li_Check := GetAsyncKeyState(iKey);
  Result := BOOL(Hi(li_Check));
end;


D  2010-12-16 03:29:45  No: 39679

失礼。もう一つ追加。

function gfnptMousePosGet: TPoint;
//マウスカーソルの位置をスクリーン座標で返す
begin
  Result := Point(0, 0);
  GetCursorPos(Result);
end;


deldel  2010-12-17 18:15:42  No: 39680

Fusa様、D様、ありがとうございます。
Fusa様の方法はちょっと実装が辛そうです(^^;
D様の方法を試してみました。
ちょっと挙動が変でしたが、一応移動ができました。
(移動している最中では移動しているように見えないときがあるけど、
  実際は移動している)
ただ、サブメニューへの・からの、移動はこれではできないようで、
ちょっと色々試してみようと思います。
ありがとうございました。


KHE00221  2010-12-30 21:12:02  No: 39681

1)  サブメニューへ移動するとメニューが消える
2)  サブメニューを持ってる項目に移動すると消える時と消えないときがある

unit Unit1;

interface

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

type

  TPopupList = class(Menus.TPopupList)
  public
    procedure WndProc(var Message: TMessage); override;
  end;

  TForm1 = class(TForm)
    PopupMenu1: TPopupMenu;
    N221: TMenuItem;
    N34451: TMenuItem;
    N54351: TMenuItem;
    Memo1: TMemo;
    N61: TMenuItem;
    N111: TMenuItem;
    N121: TMenuItem;
    N131: TMenuItem;
    N211: TMenuItem;
    N222: TMenuItem;
    N231: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure PopupMenu1Popup(Sender: TObject);
  private
    { Private 宣言 }
  protected
  public
    { Public 宣言 }
  end;

const
    WH_MOUSE_LL = 14;

var
  Form1: TForm1;
  PopupList2 : TPopupList;
  SelectMenuID,DragSelectMenuID,DropSelectMenuID: Integer;
  isPopup : Boolean;
  isMouseDown: Boolean;
  isDragDrop : Boolean;

  SelectPopupMenu: TPopupMenu;
  DragMenuItem,DropMenuItem: TMenuItem;

implementation

{$R *.DFM}

var
  MouseHookHandle : HHOOK;

procedure TPopupList.WndProc(var Message: TMessage);
begin
    inherited;

    case Message.Msg of

      WM_ENTERMENULOOP://$0211;
      begin
        SelectMenuID := -1;
        DragSelectMenuID := -2;
        IsPopup := True;
        isMouseDown := False;
        isDragDrop := False;
        DropMenuItem := nil;
        DragMenuItem := nil;
      end;

      WM_MENUSELECT: //$11F
      begin
        SelectMenuID := Message.WParamLo;
      end;

      WM_EXITMENULOOP://$0212;
      begin
        isPopup := False;
      end;

    end;

end;

procedure GetMenuItemRect(Value: TComponent; Index: Integer; var Result: TMenuItem);
var
    Rect: TRect;
    P: TPoint;
begin

    P := Point(0, 0);
    GetCursorPos(P);

    if Value is TPopupMenu then
    begin
      Windows.GetMenuItemRect(0, TPopupMenu(Value).Handle, Index, Rect);
      if (PtInRect(Rect, P)) then
      begin
        Result := TPopupMenu(Value).Items[Index]
      end;
    end;

    if Value is TMenuItem then
    begin
      Windows.GetMenuItemRect(0 , TMenuItem(Value).Parent.Handle , Index, Rect);
      if (PtInRect(Rect, P)) then
      begin
        Result := TMenuItem(Value).Parent.Items[Index];
      end;
    end;

end;

procedure GetMenuItem(Value: TComponent; Index: Integer; var Result: TMenuItem);
var
    I,J: Integer;
begin

    if Value is TPopupMenu then
    begin
      for I:=0 to TPopupMenu(Value).Items.Count -1 do
      begin
        if Assigned(Result) = False then
        begin
          GetMenuItemRect (Value,I,Result);
          for J:=0 to TPopupMenu(Value).Items[I].Count - 1 do
          begin
            GetMenuItem(TPopupMenu(Value).Items[I].Items[J],J,Result);
          end;
        end;
      end;
    end;

    if Value is TMenuItem then
    begin
      if Assigned(Result) = False then
      begin
        GetMenuItemRect (Value,Index,Result);
        for I:=0 to TMenuItem(Value).Count -1 do
        begin
          GetMenuItem(TMenuItem(Value).Items[I],I,Result);
        end;
      end;
    end;

end;

function MouseHook(nCode: Integer; WParam: wParam; LParam: lParam): Integer; stdcall;

    function _GetMenuItem: TMenuItem;
    begin
      Result := nil;
      if Assigned(SelectPopupMenu) = True then
      begin
        GetMenuItem(SelectPopupMenu,0,Result);
      end;
    end;

    procedure ButtonDown;
    begin
      DragSelectMenuID := SelectMenuID;
      isMouseDown := True;
      isDragDrop := False;
      DragMenuItem := _GetMenuItem;
    end;

    procedure ButtonUp;
    var
        MI: TMenuItem;
        isParent: Boolean;
    begin
      Windows.SetCursor(Screen.Cursors[crDefault]);
      if isDragDrop = True then
      begin
        DropSelectMenuID := SelectMenuID;

        if DragSelectMenuID <> DropSelectMenuID then
        begin
          DropMenuItem := _GetMenuItem;
          if (Assigned(DragMenuItem) = True) and (Assigned(DropMenuItem) = True) then
          begin
            if DragMenuItem.Parent = DropMenuItem.Parent then
            begin
              if DropMenuItem.MenuIndex <> DragMenuItem.MenuIndex then
              begin
                DragMenuItem.MenuIndex := DropMenuItem.MenuIndex;
              end
            end
            else
            begin
              isParent := False;
              MI := DropMenuItem;
              while MI <> SelectPopupMenu.Items do
              begin
                if DragMenuItem = MI then isParent := True;
                MI := MI.Parent;
              end;
              if isParent = False then
              begin
                DragMenuItem.Parent.Delete(DragMenuItem.MenuIndex);
                DropMenuItem.Parent.Insert(DropMenuItem.MenuIndex,DragMenuItem);
              end
              else
              begin
                //子や孫にはコピーできない
              end;
            end;
          end;
          Result := 1;
        end;

      end;
      isMouseDown := False;
    end;

begin

  Result := 0;

  if IsPopup = True then
  begin
    if nCode = HC_ACTION then
    begin
      case WParam of
        WM_LBUTTONDOWN:
        begin
         if (GetSystemMetrics(SM_SWAPBUTTON) = 0) then ButtonDown;
        end;
        WM_RBUTTONDOWN:
        begin
         if (GetSystemMetrics(SM_SWAPBUTTON) <> 0) then ButtonDown;
        end;
        WM_MOUSEMOVE:
        begin
          if isMouseDown = True then
          begin
            SetCursor(Screen.Cursors[crDrag]);
            isDragDrop := True;
          end;
        end;
        WM_LBUTTONUP:
        begin
          if (GetSystemMetrics(SM_SWAPBUTTON) = 0) then ButtonUp;
          if isDragDrop = True then Result := 1;
        end;
        WM_RBUTTONUP:
        begin
          if (GetSystemMetrics(SM_SWAPBUTTON) <> 0) then ButtonUp;
          if isDragDrop = True then Result := 1;
        end;
      end;
    end;
  end;

  if Result = 0 then
  begin
    Result := CallNextHookEx(MouseHookHandle, nCode, wParam, lParam);
  end;

end;

procedure TForm1.FormCreate(Sender: TObject);
var
    I: Integer;
begin
    PopupList2 := TPopupList(PopupList);
    PopupList  := TPopupList.Create;
    for I:=0 to PopupList2.Count -1 do
    begin
      PopupList.Add(TPopupMenu(PopupList2.Get(I)));
    end;
    MouseHookHandle := SetWindowsHookEx(WH_MOUSE_LL,@MouseHook,hInstance,0);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
    PopupList.Free;
    PopupList := TPopupList(PopupList2);
    UnhookWindowsHookEx(MouseHookHandle);
end;

procedure TForm1.PopupMenu1Popup(Sender: TObject);
begin
    SelectPopupMenu := PopupMenu1;
end;

end.


deldel  2011-01-13 18:44:33  No: 39682

KHE00221様、ありがとうございました。
内容は全く分からないのですが、そのままコードを書いたところ、
D5,XPでは問題なく移動が出来ました。サブメニューの、への移動も完璧です。
これでプログラミングの幅が増えました。感謝しております。
これから理解に移ろうと思います(^^;

ただ、右上のボタン(最小化、最大化、終了)をクリックしますと、
約5秒位無反応でマウスカーソルも移動できず、その後正常な動作に移ります。
でも、ボタンを追加してクリックイベントでClose;などを実行すれば
すぐ反応するので問題ないと言えば問題ないのですが・・・。


KHE00221  2011-01-16 11:42:43  No: 39683

フックの開始と終了のタイミングを
WM_ENTERMENULOOP  と  WM_EXITMENULOOP  に
変更すれば直るかな


deldel  2011-01-17 18:19:43  No: 39684

KHE00221様
上記のようにタイミングを変更すると無反応が無くなりました。
これで全く問題なく使用できます。ありがとうございました。


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

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






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