我想要按钮上方的弹出菜单:



Delphi用某种方式包装Win32菜单系统,该方式似乎排除了基础Win32 API提供的那一天不在VCL作者脑中的每种模式或标志。一个这样的例子似乎是TPM_BOTTOMALIGN,可以将其传递给TrackPopupMenu,但是,Delphi包装器似乎不仅在股票VCL中无法做到这一点,而且由于不恰当地使用私有和受保护的方法,也是不可能的(至少在我看来似乎是不可能的)在运行时准确地执行操作或通过重写操作。 VCL组件TPopupMenu的设计也不是很好,因为它应该有一个名为PrepareForTrackPopupMenu的虚拟方法,该方法除了调用TrackPopupMenuTrackPopupMenuEx之外还执行其他所有操作,然后允许某人重写实际调用的方法Win32方法。但这为时已晚。也许Delphi XE5可以正确完成Win32 API的基本覆盖。

我尝试过的方法:

方法A:使用METRICS或字体:

准确确定弹出菜单的高度,以便在调用popupmenu.Popup(x,y)之前可以减去Y值。结果:必须处理Windows主题的所有变体,并做出我似乎无法确定的假设。似乎不太可能在现实世界中取得良好的结果。这是基本字体指标方法的示例:

   height := aPopupMenu.items.count * (abs(font.height) + 6) + 34;


您可以考虑隐藏的项目,并且对于具有单个主题模式设置有效的单个版本的Windows,您可能会那样关闭,但并不完全正确。

方法B:让Windows执行:

尝试传递TPM_BOTTOMALIGN最终到达Win32 API调用TrackPopupMenu

到目前为止,我认为我可以做到,如果我修改VCL menus.pas ..我正在本项目中使用Delphi 2007。我对这个想法并不满意。

这是我正在尝试的代码类型:

procedure TMyForm.ButtonClick(Sender: TObject);
var
  pt:TPoint;
  popupMenuHeightEstimate:Integer;
begin
   // alas, how to do this accurately, what with themes, and the OnMeasureItem event
   // changing things at runtime.
      popupMenuHeightEstimate := PopupMenuHeight(BookingsPopupMenu);

      pt.X := 0;
      pt.Y := -1*popupMenuHeightEstimate;
      pt := aButton.ClientToScreen(pt);  // do the math for me.
      aPopupMenu.popup( pt.X, pt.Y );

end;


另外,我想这样做:

  pt.X := 0;
  pt.Y := 0;
  pt := aButton.ClientToScreen(pt);  // do the math for me.
  aPopupMenu.popupEx( pt.X, pt.Y, TPM_BOTTOMALIGN);


当然,VCL中不存在popupEx。也没有办法传递更多
VCC成员在1995年添加的TrackPopupMenu标记,
在1.0版中。

注意:我认为在显示菜单之前估算高度是不可能的,因此我们实际上应该通过TrackPopupMenu解决问题,而不是估算高度。

更新:直接调用TrackPopupMenu不起作用,因为VCL方法TPopupMenu.Popup(x,y)中的其余步骤对于我的应用程序绘制其菜单并使它看起来正确是必需的,但是如果没有邪恶,则无法调用它们诡计多端,因为它们是私有方法。修改VCL是一个令人毛骨悚然的提议,我也不希望这样。

最佳答案

有点骇人听闻,但也许可以解决。

声明TPopupMenu覆盖Popup的拦截器类:

type
  TPopupMenu = class(Vcl.Menus.TPopupMenu)
  public
    procedure Popup(X, Y: Integer); override;
  end;

procedure TPopupMenu.Popup(X, Y: Integer);
const
  Flags: array[Boolean, TPopupAlignment] of Word =
    ((TPM_LEFTALIGN, TPM_RIGHTALIGN, TPM_CENTERALIGN),
     (TPM_RIGHTALIGN, TPM_LEFTALIGN, TPM_CENTERALIGN));
  Buttons: array[TTrackButton] of Word = (TPM_RIGHTBUTTON, TPM_LEFTBUTTON);
var
  AFlags: Integer;
begin
  PostMessage(PopupList.Window, WM_CANCELMODE, 0, 0);
  inherited;
  AFlags := Flags[UseRightToLeftAlignment, Alignment] or
    Buttons[TrackButton] or
    TPM_BOTTOMALIGN or
    (Byte(MenuAnimation) shl 10);
  TrackPopupMenu(Items.Handle, AFlags, X, Y, 0 { reserved }, PopupList.Window, nil);
end;


诀窍是在菜单窗口中发布一条取消消息,以取消继承的TrackPopupMenu调用。

07-25 23:38