14

PopupMenu の項目リストの最大幅をリセットするにはどうすればよいですか?

たとえば、実行時にいくつかの TMenuItems を popupmenu に追加するとします。

item1: [xxxxxxxxxxxxxxxxxxx]
item2: [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]

メニューは、最大のアイテムに合わせてサイズを自動的に調整します。しかし、Items.Clear を実行して、新しいアイテムを追加します。

item1: [xxxxxxxxxxxx                    ]

このように、キャプションの後に大きな空白ができてしまいます。

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;
4

3 に答える 3

9

tl,dr: ImageList を添付します。


メニュー項目がWM_MEASUREITEMメッセージを送信できる場合は、幅が再計算されます。

OwnerDrawプロパティを設定してそれをTrue実現します。これが最初の解決策です。ただし、Delphi の古いバージョンでは、メニュー項目の非デフォルトおよび非スタイルの描画が発生します。それは望ましくありません。

幸いなことにTMenu、メニュー (項目) が所有者によって描画されているかどうかを判断する特別な方法があります。

function TMenu.IsOwnerDraw: Boolean;
begin
  Result := OwnerDraw or (Images <> nil);
end;

したがって、Imagesプロパティを既存の ImageList に設定すると、同じ結果が得られます。ImageList に画像が存在する必要はないことに注意してください。また、画像が含まれている場合は、それらを使用する必要はなく、メニュー項目に使用できImageIndexます。-1もちろん、画像を含む ImageList問題なく動作します。

于 2014-11-05T11:39:25.130 に答える
3

回避策はありますが、非常に汚れています。クラッカー クラスを使用して、 TPopupMenu.Itemsメニュー項目プロパティのFHandleプライベート メンバーへのアクセスを取得します。

クラッカー クラスでは、目的のプライベート メンバーまでターゲット クラスのプライベート ストレージ レイアウトを再現し、型キャストを使用して、その型をコンテキスト内のターゲット型のインスタンスに「オーバーレイ」します。ターゲットの内部ストレージにアクセスします。

この場合、ターゲット オブジェクトはTMenuItemのインスタンスであるTPopupMenuのItemsプロパティです。 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;

注:この手法はターゲット クラスの内部ストレージ レイアウトの正確な再現に依存しているため、異なる Delphi バージョン間での内部レイアウトの変更に対応するために、クラッカー宣言に$IFDEFバリエーションを含める必要がある場合があります。上記の宣言はDelphi XE4 では正しいので、他の Delphi バージョンとの正確性についてTMenuItemソースと照合する必要があります。

そのクラッカー クラスを使用して、ユーティリティ プロシージャを提供して、これが提供するアクセスを使用して実行する厄介なトリックをまとめることができます。この場合、通常どおりメニュー項目をクリアできますが、クラッカー キャストを使用して自分自身でDestroyMenu()を呼び出してFHandleメンバー変数を 0 で上書きすることもできます。これは、FHandle メンバー変数が無効になり、 TPopupMenuに強制的にメニューを再作成させるために 0 にする必要があるためです。次に必要なとき:

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

    // Here be dragons...

    DestroyMenu(aMenu.Items.Handle);
    TMenuItemCracker(aMenu.Items).FHandle := 0;
  end;

サンプル コードでは、Button2ClickハンドラーのPopupMenu1.Items.Clearへの呼び出しをResetPopupMenu(PopupMenu1)への呼び出しに置き換えるだけです。

これが極端に危険なのは言うまでもありません。クラスのプライベート ストレージ内をハッキングするというまったくの狂気は別として、この特定のケースでは、たとえば、マージされたメニューのマージを解除することについては考慮されていません。

しかし、回避策があるかどうかを尋ねましたが、ここに少なくとも 1 つの回避策があります。:)

単純にTPopupMenuを破棄して再作成するよりも、これを多かれ少なかれ実用的または望ましいと考えるかどうかは、あなた次第です。クラスクラッキングは、他の方法では解決できないジャムから抜け出すのに役立つテクニックですが、間違いなく「最後の手段」と見なす必要があります。

于 2014-11-04T22:04:56.833 に答える