2014-10-18 11 views
13

Làm cách nào bạn có thể đặt lại chiều rộng tối đa cho danh sách các mục của PopupMenu?TPopupMenu giữ nguyên chiều rộng tối đa, ngay cả sau khi Mục. Xóa

Say tức là bạn thêm một vài TMenuItems khi chạy đến một popupmenu:

item1: [xxxxxxxxxxxxxxxxxxx] 
item2: [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] 

Thực đơn tự động điều chỉnh kích thước để phù hợp với mục lớn nhất. Nhưng sau đó bạn làm Items.Clear và thêm một mục mới:

item1: [xxxxxxxxxxxx     ] 

Nó kết thúc như thế, với một khoảng trống lớn sau khi chú thích.

Có cách nào khác ngoài việc tạo lại popupmenu không?

Đây mã để tái tạo bất thường này:

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

Tôi có thể sao chép điều này chỉ với các cuộc gọi api, CreatePopupMenu, InsertMenu, TrackPopupMenu, DeleteMenu vv .. Không có 'co' miễn là xử lý hợp lệ. Như vậy, ý kiến ​​của tôi là, giải pháp duy nhất là giải phóng menu popup và tạo lại nó trong thời gian chạy, đó là cách duy nhất để gọi 'DestroyMenu'. –

+2

@hikari: Cảm ơn bạn đã chỉnh sửa.Câu hỏi này hữu ích hơn nhiều với mã sẵn có, đặc biệt cho những người đọc trong tương lai có thể tìm thấy nó trong một tìm kiếm. –

Trả lời

3

Có workaround, nhưng nó là rất, rất bẩn: Sử dụng một lớp bánh để có được quyền truy cập vào các thành viên tin FHandle của TPopupMenu.Items thuộc tính mục menu.

Lớp cracker liên quan đến việc tái tạo bố cục lưu trữ riêng tư của lớp đích đến và bao gồm thành viên riêng tư quan tâm và sử dụng loại truyền cho "lớp phủ" nhập vào một thể hiện của loại mục tiêu trong ngữ cảnh sau đó cho phép bạn truy cập bộ nhớ trong của mục tiêu.

Trong trường hợp này, đối tượng mục tiêu là Items tài sản của TPopupMenu mà là một thể hiện của TMenuItem. TMenuItem xuất phát từ TComponent vì vậy lớp cracker để cung cấp quyền truy cập vào FHandle cho một TMenuItem là:

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; 

LƯU Ý: Kể từ khi kỹ thuật này dựa trên một tái tạo chính xác của bộ nhớ trong bố cục của lớp mục tiêu, tuyên bố trình bẻ khóa có thể cần phải bao gồm $ IFDEF các biến thể để phục vụ cho thay đổi trong đó bố trí ternal giữa các phiên bản Delphi khác nhau. Tuyên bố ở trên là chính xác cho Delphi XE4 và phải được kiểm tra đối với các nguồn TMenuItem cho sự chính xác với các phiên bản Delphi khác.

Với lớp cracker đó, chúng tôi có thể cung cấp một tiện ích proc để bọc các thủ thuật khó chịu mà chúng tôi sẽ thực hiện bằng cách sử dụng quyền truy cập này.Trong trường hợp này, chúng tôi có thể xóa các mục menu như bình thường, nhưng cũng có thể tự xóa các mục menu như là bình thường, nhưng cũng có thể tự gọi số DestroyMenu() bằng cách sử dụng công cụ cracker cast để ghi đè biến thành viên FHandle với 0 vì nó hiện không hợp lệ và cần phải là 0 để buộc TPopupMenu để tái tạo menu khi tiếp theo cần thiết:

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

    // Here be dragons... 

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

trong mã mẫu của bạn chỉ đơn giản là thay thế cuộc gọi của bạn để PopupMenu1.Items.Clear trong handler Button2Click của bạn với một cuộc gọi đến ResetPopupMenu (PopupMenu1).

Không cần phải nói rằng điều này là nguy hiểm trong cùng cực. Khá khác biệt với sự điên rồ tuyệt đối của việc xâm nhập vào kho lưu trữ riêng của một lớp, không có tài khoản nào được thực hiện trong trường hợp cụ thể này để hủy các menu đã hợp nhất, ví dụ.

Nhưng bạn đã hỏi liệu có cách giải quyết hay không và đây là ít nhất một. :)

Cho dù bạn xem xét điều này nhiều hơn hoặc ít thực tế hoặc mong muốn hơn là việc hủy và tái tạo TPopupMenu tùy thuộc vào bạn. Lớp nứt là một kỹ thuật có thể hữu ích cho bạn ra khỏi mứt mà nếu không có thể không thể giải quyết nhưng chắc chắn nên được coi là một "phương sách cuối cùng"!

+1

Đây là một mẹo hay, nhưng nó mang lại cho tôi những creep :-) –

+0

+1 Tôi không nghĩ rằng nó nguy hiểm nếu bạn biết những gì bạn đang làm và kiểm soát các nguồn của riêng bạn. ví dụ, TNT Unicode phù hợp với các phiên bản Delphi cũ hơn phụ thuộc rất nhiều vào kỹ thuật bẻ khóa các trường riêng (với '$ IFDEF' đúng cho mỗi phiên bản). Mã của bạn hoạt động tốt với D7 của tôi (tất nhiên tôi đã xác định một cấu trúc cracker suteible phù hợp với D7 offsets.hơn tốt hơn 'OwnerDraw' mà vô hiệu hóa các chủ đề cũng, và trông rất xấu BTW, bạn có thể tính toán bù đắp của' FHandle' và đơn giản là đặt một 'Filler [offset bytes]' trước trường này – kobik

8

tl, dr: Đính kèm danh sách hình ảnh.


Nếu các mục menu có thể nhận được thông báo WM_MEASUREITEM thì chiều rộng sẽ được tính lại.

Đặt thuộc tính OwnerDraw thành True đạt được điều đó, đó là giải pháp đầu tiên. Nhưng đối với các phiên bản Delphi cũ hơn, điều này sẽ dẫn đến bản vẽ không theo mặc định và không theo kiểu của các mục menu. Đó không phải là mong muốn.

May mắn thay, TMenu có một cách bất thường kể cho dù menu (items) là (là) chủ sở hữu rút ra:

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

Do đó thiết lập các Images thuộc tính vào ImageList hiện tại sẽ đạt được như vậy. Lưu ý rằng không cần phải có hình ảnh trong ImageList. Và nếu có hình ảnh trong đó, bạn không phải sử dụng chúng và để cho các ImageIndex-1 cho các mục menu. Tất nhiên, hình ảnh với hình ảnh cũng sẽ hoạt động tốt.

+0

Chắc chắn an toàn hơn nhiều so với việc bẻ khóa lớp :) nhưng tôi cảm thấy tò mò rằng mỗi kỹ thuật này mang lại một vẻ ngoài hơi khác so với mỗi trình đơn khác * hoặc * menu "đơn giản". Thiết lập các kết quả của OwnerDraw TRUE trong một menu * nhỏ hơn và ImageList giả trong một ảnh lớn hơn một chút (không chỉ là lề cho hình ảnh - cũng xuất hiện trên một cửa sổ bật lên đơn giản - mà còn thêm phần đệm bên phải của văn bản mục). Rất kỳ quặc. – Deltics

+0

@Deltics Menu nhỏ hơn nhiều với 'OwnerDraw = True' Tôi cũng chú ý ở đây với D7, nhưng không phải với XE2. Tôi nghi ngờ nó là một lỗi trong các phiên bản cũ. Lề bên phải là khoảng trống cho phím nóng. Tôi nghi ngờ rằng khi một ImageList được đính kèm, thì menu có thể hoạt động đầy đủ. – NGLN

+0

Việc này sẽ vô hiệu hóa các chủ đề, và Dường không đẹp. Tôi đoán hầu hết các chủ sở hữu bản vẽ nên được thực hiện bằng tay nếu bạn muốn các mục để nhìn bản địa. Id tốt hơn chỉ đơn giản là tiêu diệt và tái tạo Popupmenu hoặc sử dụng lớp cracker. – kobik

1

Trả lời trễ: nhưng ở 10.1 Berlin, ít nhất tôi thấy rằng giải pháp đơn giản nhất là đặt OwnerDraw thành true, nhưng không cung cấp OnDrawItem, chỉ OnMeasureItem. Điều này giữ nguyên kiểu dáng của menu, nhưng cho phép bạn đặt chiều rộng của các mục menu sau khi gọi số canvas.textextent((Sender as Tmenuitem).caption).

Vì tôi phải đặt chú thích mục thành ví dụ 'Mở: somefilename.txt' điều này cho phép menu tự tùy chỉnh với nỗ lực tối thiểu.

Các vấn đề liên quan