2014-10-18 18 views
13

如何重置PopupMenu項目列表的最大寬度?TPopupMenu保留最大寬度,即使在Items.clear後

說即在運行時添加一些TMenuItems的彈出菜單:

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; 
+2

我只能使用api調用,CreatePopupMenu,InsertMenu,TrackPopupMenu,DeleteMenu等等來複制它。只要句柄有效,就沒有'收縮'。因此,我認爲唯一的解決方案是釋放彈出菜單並在運行時重新創建它,這是調用'DestroyMenu'的唯一方法。 – 2014-10-18 03:36:19

+2

@hikari:感謝您的編輯。這個問題對於可用的代碼更有用,特別是對於可能在搜索中找到它的未來讀者。 – 2014-10-18 15:21:30

回答

3

有解決辦法,但它是非常,髒:使用餅乾類來獲得訪問FHandle私有成員TPopupMenu.Items菜單項屬性。

一個破解者類包括複製目標類的私有存儲佈局直至包含感興趣的私有成員,並使用類型轉換將該類型「覆蓋」到該目標類型的實例上然後允許您訪問目標的內部存儲。

在這種情況下,所述目標對象是TPopupMenuTMenuItem的一個實例的物品屬性。 TMenuItemTComponent所以裂化類的用於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; 

注:由於該技術依賴於內部存儲的精確再現目標類別的佈局,破解者聲明可能需要包括$ IFDEF變化以迎合變化在那不同的Delphi版本之間的外部佈局。上述聲明對於Delphi XE4是正確的,應該根據TMenuItem源進行檢查,以確保其他Delphi版本的正確性。

有了這個破解者類,我們就可以提供一個實用程序來處理我們將要使用這個提供的訪問執行的令人討厭的技巧。在這種情況下,我們可以像往常一樣清除菜單項,但也可以使用餅乾類型cast來覆蓋FHandle成員變量,因爲它現在無效並且需要爲0來強制執行,所以我們可以自己調用DestroyMenu() TPopupMenu重新創建菜單時,需要旁邊:

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

    // Here be dragons... 

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

在你的示例代碼簡單地以ResetPopupMenu(PopupMenu1)一個調用替換您的來電PopupMenu1.Items.ClearButton2Click處理程序。

不用說,這是極端危險的。例如,除了在班級的私人存儲空間內進行黑客攻擊之外,沒有考慮這種特殊情況下的未合併菜單。

但你問是否有解決方法,至少有一個。 :)

無論你認爲這或多或少比簡單銷燬和重新創建TPopupMenu取決於你自己。課堂破解是一種技術,可以幫助你擺脫困難,否則這些課程可能無法解決,但絕對應該被視爲「最後的手段」!

+1

這是一個很酷的伎倆,但它給了我一些毛骨悚然:-) – 2014-11-05 12:05:25

+0

+1如果你知道自己在做什麼並控制自己的來源,我認爲這並不危險。例如,針對較老的Delphi版本的TNT Unicode套裝嚴重依賴於這種破解私有字段的技術(每個版本都有適當的「$ IFDEF」)。你的代碼和我的D7一起工作的很好(當然我已經定義了一個與D7偏移相匹配的可擴展餅圖結構,比'OwnerDraw'好得多,它也會禁用主題,並且看起來很糟糕。順便說一句,你可以計算'FHandle'的偏移量只需在這個字段前加一個'Filler [offset bytes]' – kobik 2014-11-05 20:38:46

8

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 圖片也可以做得很好。

+0

肯定比破解類更安全:)但我覺得好奇的是,這些技術中的每一種產生的菜單都略微不同於其他任何一種*或* 「普通」菜單。設置OwnerDraw TRUE會產生一個*更小的菜單,並將虛擬ImageList放在一個稍大的菜單中(不僅僅是圖像邊距 - 也存在於普通彈出窗口中 - 也包括項目文本右側的額外填充)。很奇怪。 – Deltics 2014-11-05 19:25:50

+0

@Deltics帶有'OwnerDraw = True'的小得多的菜單我在這裏也注意到D7,但與XE2不同。我懷疑它是舊版本中的錯誤。右側的邊緣是熱鍵的空間。我懷疑一旦連接了ImageList,菜單就可以完全運行。 – NGLN 2014-11-05 19:32:22

+0

這會禁用主題,並且劑量看起來不太好。我想如果你想讓項目看起來很原生的話,大部分的所有者繪圖都應該手動完成。更好地簡單地銷燬並重新創建Popupmenu或使用Cracker類。 – kobik 2014-11-05 20:45:59

1

最後回答:但在10.1柏林至少我發現最簡單的解決方案是將OwnerDraw設置爲true,但不提供OnDrawItem,僅OnMeasureItem。這將保留菜單的樣式,但允許您在調用canvas.textextent((Sender as Tmenuitem).caption)後設置菜單項的寬度。

由於我必須將項目標題設置爲例如'打開:somefilename.txt',這允許菜單以最小的努力進行自定義。

相關問題