2014-10-18 9 views
13

Jak zresetować maksymalną szerokość listy elementów PopupMenu?TPopupMenu zachowuje maksymalną szerokość, nawet po Elements.clear

Say tj dodasz kilka TMenuItems przy starcie do PopupMenu:

item1: [xxxxxxxxxxxxxxxxxxx] 
item2: [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] 

Menu automatycznie dostosowuje rozmiar w celu dopasowania do największego elementu. Ale wtedy robisz Items.Clear i dodaj nowy przedmiot:

item1: [xxxxxxxxxxxx     ] 

Kończy się tak, z dużej pustej przestrzeni po podpisie.

Czy istnieje jakieś obejście poza ponownym utworzeniem menu podręcznego?

Oto kod do odtwarzania tej anomalii:

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

Mogę to zduplikować tylko za pomocą wywołań api, CreatePopupMenu, InsertMenu, TrackPopupMenu, DeleteMenu itd. Nie ma "skurczu", o ile uchwyt jest ważny. Jako takie, moim zdaniem, jedynym rozwiązaniem jest uwolnienie menu podręcznego i odtworzenie go w czasie wykonywania, to jedyny sposób wywołania "DestroyMenu". –

+2

@hikari: Dzięki za edycję.Pytanie jest o wiele bardziej przydatne przy dostępnym kodzie, szczególnie dla przyszłych czytelników, którzy mogą znaleźć to w wyszukiwaniu. –

Odpowiedz

3

Istnieje obejście, ale jest bardzo, bardzo brudny: Użyj klasy cracker, aby uzyskać dostęp do prywatnego członek FHandle TPopupMenu.Items Właściwość elementu menu.

Klasa cracker polega na reprodukowaniu układu prywatnego magazynu klasy docelowej do prywatnego członka zainteresowania i włącznie z nim za pomocą rzutowania typu na "nałożenie" tego typu na instancję typu docelowego w kontekście, w którym następnie umożliwia dostęp do pamięci wewnętrznej celu.

W tym przypadku obiekt docelowy jest przedmioty własnością TPopupMenu który jest instancją TMenuItem. TMenuItem wywodzi TComponent więc klasy cracker, aby zapewnić dostęp do FHandle dla TMenuItem jest:

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; 

UWAGA: Ponieważ technika ta polega na dokładnej reprodukcji pamięci wewnętrznej układ docelowej klasy, deklaracja crackera może wymagać wprowadzenia odmiany $ IFDEF w celu uwzględnienia zmian w tym w Ternal układ między różnymi wersjami Delphi. Deklaracja powyżej jest poprawna dla Delphi XE4 i powinna być sprawdzona względem źródła TMenuItem dla poprawności w.r.t innych wersji Delphi.

Z tą klasą krakersów możemy dostarczyć program narzędziowy do podsumowania nieprzyjemnych sztuczek, które będziemy wykonywać przy użyciu dostępu, który zapewnia.W takim przypadku możemy normalnie usuwać elementy menu, ale także wywoływać: DestroyMenu() sami używając obsady crack, aby nadpisać zmienną składową FHandle z 0, ponieważ jest ona teraz nieważna i musi wynosić 0, aby wymusić TPopupMenu odtworzyć menu gdy obok potrzebne:

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

    // Here be dragons... 

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

w kodzie przykładowym po prostu zastąpić wywołanie PopupMenu1.Items.Clear w Button2Click obsługi z wezwaniem do ResetPopupMenu (PopupMenu1).

Nie trzeba dodawać, że jest to w najwyższym stopniu niebezpieczne. Niezależnie od zwykłego obłędu włamywania się do prywatnego przechowywania klasy, w tym konkretnym przypadku nie jest brane pod uwagę rozłączanie połączonych menu.

Ale zapytałeś, czy istnieje obejście, a tutaj jest co najmniej jeden. :)

Niezależnie od tego, czy uważasz to za mniej lub bardziej praktyczne, czy po prostu pożądane, niż po prostu zniszczenie i odtworzenie TPopupMenu należy do Ciebie. Łamanie klas jest techniką, która może być przydatna, aby wydostać cię z zakleszczenia, które w innym przypadku byłoby niemożliwe do rozwiązania, ale zdecydowanie powinno być uznane za "ostateczność"!

+1

To jest fajna sztuczka, ale to daje mi creepy :-) –

+0

+1 Nie sądzę, że to niebezpieczne, jeśli wiesz, co robisz i kontrolujesz własne źródła. na przykład kombinezon TNT Unicode dla starszych wersji Delphi mocno opiera się na tej technice łamania prywatnych pól (z właściwym '$ IFDEF' dla każdej wersji). Twój kod działa wspaniale z moim D7 (oczywiście zdefiniowałem strukturę crackera, która pasuje do przesunięć D7, znacznie lepiej niż "OwnerDraw", który wyłącza również motywy i wygląda bardzo źle.) BTW, możesz obliczyć przesunięcie 'FHandle' i po prostu umieść "Wypełniacz [przesunięcie bajtów]" przed tym polem – kobik

8

tl, dr: Dołącz listę ImageList.


Jeśli elementy menu mógł się wysłać wiadomość WM_MEASUREITEM, wówczas szerokość zostanie ponownie obliczona.

Ustawienie właściwości OwnerDraw na True zapewnia to, co jest pierwszym rozwiązaniem. Ale w starszych wersjach Delphi spowoduje to nie-domyślne i nieprogramowane rysowanie pozycji menu. To nie jest pożądane.

szczęście TMenu ma niezwykły sposób mówienia czy menu (przedmiotów) jest (są) właściciel rysowane:

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

Zatem ustawienie właściwości do istniejącej ImageList Images osiągną takie same. Zwróć uwagę, że w ImageList nie muszą być obrazy. A jeśli są w nim obrazy, nie musisz ich używać i pozostawić je dla pozycji menu. Oczywiście obrazy ImageList z obrazami również będą działały dobrze.

+0

Zdecydowanie dużo bezpieczniejsze niż złamanie klasy :) ale ciekawe jest to, że każda z tych technik daje nieco inny wygląd menu niż siebie nawzajem * lub * menu "zwykłe". Ustawienie OwnerDraw TRUE powoduje wyświetlenie * znacznie * mniejszego menu i atrapy ImageList w nieco większym (nie tylko marginesie dla obrazów - również obecnym na zwykłym wyskakującym okienku - ale również dodatkowe dopełnienie po prawej stronie tekstu pozycji). Bardzo dziwne. – Deltics

+0

@Deltics O wiele mniejsze menu z 'OwnerDraw = True' Zauważyłem tutaj również z D7, ale nie z XE2. Podejrzewam, że jest to błąd w starszych wersjach. Margines po prawej stronie to miejsce na klawisze skrótów. Podejrzewam, że po dołączeniu ImageList, menu może w pełni funkcjonować. – NGLN

+0

Powoduje wyłączenie motywów, a Dawka nie wygląda dobrze. Domyślam się, że większość rysunku właściciela powinna być wykonana ręcznie, jeśli chcesz, aby elementy wyglądały natywnie. Lepiej po prostu zniszcz i ponownie utwórz Popupmenu lub użyj klasy cracker. – kobik

1

Późna odpowiedź: ale w 10.1 Berlin przynajmniej stwierdzam, że najłatwiej jest ustawić właściwość OwnerDraw na wartość true, ale nie dostarczać OnDrawItem, tylko OnMeasureItem. Zachowuje to styl menu, ale pozwala ustawić szerokość pozycji menu po wywołaniu canvas.textextent((Sender as Tmenuitem).caption).

Ponieważ mam ustawić podpisy element, aby na przykład „otwarty: somefilename.txt” pozwala to menu do samodzielnego dostosowywania przy minimalnym wysiłku.