即使在Items.clear之后,TPopupMenu仍保留最大宽度

hik*_*ari 14 delphi popupmenu menuitem delphi-xe7

如何重置PopupMenu项目列表的最大宽度?

比如你在运行时向popupmenu添加一些TMenuItems:

item1: [xxxxxxxxxxxxxxxxxxx]
item2: [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]
Run Code Online (Sandbox Code Playgroud)

菜单会自动调整大小以适合最大的项目.但是然后你做Items.Clear并添加一个新项目:

item1: [xxxxxxxxxxxx                    ]
Run Code Online (Sandbox Code Playgroud)

最终结果如此,标题后面有一个很大的空白区域.

除了重新创建popupmenu之外还有其他解决方法吗?

这里是重现这种异常的代码:

procedure TForm1.Button1Click(Sender: TObject);
var
  t: TMenuItem;
begin
  t := TMenuItem.Create(PopupMenu1);
  t.Caption := 'largelargelargelargelargelarge';
  PopupMenu1.Items.Add(t);
  PopupMenu1.Popup(200, 200);
end;

procedure TForm1.Button2Click(Sender: TObject);
var 
  t: TMenuItem;
begin
  PopupMenu1.Items.Clear;
  t := TMenuItem.Create(PopupMenu1);
  t.Caption := 'short';
  PopupMenu1.Items.Add(t);
  PopupMenu1.Popup(200, 200);
end;
Run Code Online (Sandbox Code Playgroud)

NGL*_*GLN 9

tl,dr:附加一个ImageList.


如果菜单项可以发送WM_MEASUREITEM消息,则将重新计算宽度.

设置OwnerDraw属性以True实现这一点,这是第一个解决方案.但对于较旧的Delphi版本,这将导致菜单项的非默认和非样式绘制.这是不可取的.

幸运的是,TMenu有一种特殊的方式来判断菜单(项目)是否是所有者绘制的:

function TMenu.IsOwnerDraw: Boolean;
begin
  Result := OwnerDraw or (Images <> nil);
end;
Run Code Online (Sandbox Code Playgroud)

因此,将Images属性设置为现有ImageList将实现相同的目的.请注意,ImageList中不需要图像.如果其中有图像,则不必使用它们并将其ImageIndex作为-1菜单项.当然,图像的ImageList 也可以.


Del*_*ics 3

有一个解决方法,但它非常非常肮脏:使用破解器类来获取对TPopupMenu.Items菜单项属性的FHandle私有成员的访问权限。

破解类涉及复制目标类的私有存储布局,直到并包括感兴趣的私有成员,并使用类型转换将该类型“覆盖”到上下文中的目标类型的实例上,然后允许您访问目标的内部存储。

在本例中,目标对象是TPopupMenuItems属性,它是TMenuItem的实例。 TMenuItem派生自TComponent ,因此为TMenuItem提供对FHandle访问的破解器类是:

type
  // Here be dragons...
  TMenuItemCracker = class(TComponent)
  private
    FCaption: string;
    FChecked: Boolean;
    FEnabled: Boolean;
    FDefault: Boolean;
    FAutoHotkeys: TMenuItemAutoFlag;
    FAutoLineReduction: TMenuItemAutoFlag;
    FRadioItem: Boolean;
    FVisible: Boolean;
    FGroupIndex: Byte;
    FImageIndex: TImageIndex;
    FActionLink: TMenuActionLink;
    FBreak: TMenuBreak;
    FBitmap: TBitmap;
    FCommand: Word;
    FHelpContext: THelpContext;
    FHint: string;
    FItems: TList;
    FShortCut: TShortCut;
    FParent: TMenuItem;
    FMerged: TMenuItem;
    FMergedWith: TMenuItem;
    FMenu: TMenu;
    FStreamedRebuild: Boolean;
    FImageChangeLink: TChangeLink;
    FSubMenuImages: TCustomImageList;
    FOnChange: TMenuChangeEvent;
    FOnClick: TNotifyEvent;
    FOnDrawItem: TMenuDrawItemEvent;
    FOnAdvancedDrawItem: TAdvancedMenuDrawItemEvent;
    FOnMeasureItem: TMenuMeasureItemEvent;
    FAutoCheck: Boolean;
    FHandle: TMenuHandle;
  end;
Run Code Online (Sandbox Code Playgroud)

注意:由于该技术依赖于目标类内部存储布局的精确复制,破解者声明可能需要包含$IFDEF变体,以适应不同 Delphi 版本之间内部布局的变化。上面的声明对于Delphi XE4是正确的,并且应该对照TMenuItem源来检查其他 Delphi 版本的正确性。

有了这个破解类,我们就可以提供一个实用程序来包装我们将使用它提供的访问权限来执行的令人讨厌的技巧。在这种情况下,我们可以像往常一样清除菜单项,但也可以使用破解程序调用自己调用DestroyMenu() ,用 0覆盖FHandle成员变量,因为它现在无效并且需要为 0 来强制TPopupMenu重新创建菜单下次需要时:

  procedure ResetPopupMenu(const aMenu: TPopupMenu);
  begin
    aMenu.Items.Clear;

    // Here be dragons...

    DestroyMenu(aMenu.Items.Handle);
    TMenuItemCracker(aMenu.Items).FHandle := 0;
  end;
Run Code Online (Sandbox Code Playgroud)

在示例代码中,只需将Button2Click处理程序中对PopupMenu1.Items.Clear 的调用替换为对ResetPopupMenu(PopupMenu1)的调用。

不用说,这是极其危险的。除了在类的私有存储内部进行黑客攻击的纯粹疯狂之外,例如,在这种特定情况下,没有考虑取消合并的菜单。

但您询问是否有解决方法,这里至少有一个。:)

您是否认为这比简单地销毁并重新创建TPopupMenu更实用或更理想,取决于您。类破解是一种可以帮助您摆脱困境的技术,否则这种困境可能无法解决,但绝对应该被视为“最后的手段”!