问题
I have a ListView with ViewStyle = vsReport
and two popup menus:
- Column popup menu, which I want to open when user right-clicking the header bar
- Item popup menu, must open when the user right-clicking any list item/subitem or whitespace below items.
What is the most correct way to show that menus? Which events should I handle?
The problem is when I set ListView.PopupMenu
property, the popup menu appearing after right-clicking any point in ListView's client rectangle.
When I handle ListView.OnColumnRightClick
event, if fires only after clicking on column header, excluding free space of the header bar (on the right of columns).
Event LisView.OnMouseUp
fires only after right-clicking on whitespace below items.
回答1:
You don't have to use the PopupMenu
property of the listview, leave it unset and you can attach a handler to OnContextPopup
event and launch whatever popup menu you'd like depending on the position. Example:
procedure TForm1.ListViewContextPopup(Sender: TObject; MousePos: TPoint;
var Handled: Boolean);
var
HeaderRect: TRect;
Pos: TPoint;
begin
GetWindowRect(ListView_GetHeader(ListView.Handle), HeaderRect);
Pos := ListView.ClientToScreen(MousePos);
if PtInRect(HeaderRect, Pos) then
PopupMenuColumns.Popup(Pos.X, Pos.Y)
else
PopupMenuItems.Popup(Pos.X, Pos.Y);
end;
回答2:
You can simplify it considerably. Create your two popup menus (one each for the header row and the columns. Assign the TListView.PopupMenu
the column popup menu in the IDE.
Use this for the event handler for the ListView:
procedure TForm1.ListView1ContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean);
var
HeaderRect: TRect;
HeaderHeight: Integer;
Header: HWnd;
begin
ListView1.PopupMenu := ColumnMenu; // Default to ColumnMenu
Header := ListView_GetHeader(ListView1.Handle);
GetWindowRect(Header, HeaderRect);
HeaderHeight := HeaderRect.Bottom - HeaderRect.Top;
if MousePos.Y < HeaderHeight then
ListView1.PopupMenu := HeaderMenu;
end;
It's slightly different than @Sertac's approach, in not calling ClientToScreen
and PtInRect
- since we know the point is within the bounds of the ListView, a simple test of the height of the click is sufficient to know if we're in the header or column area. It also ensures that there is always at least one of the popup menus assigned to the ListView
at all times.
回答3:
This is how I solved it, but I don't like this solution. If you have a better one, please write down, I'll accept it as correct.
uses
CommCtrl;
procedure TForm1.FormCreate(Sender: TObject);
begin
ListView.PopupMenu := TPopupMenu.Create(Self);
ListView.PopupMenu.OnPopup := ListViewPopup;
end;
procedure TForm1.ListViewPopup(Sender: TObject);
var
Pos: TPoint;
SrcMenu: TPopupMenu;
I: Integer;
MenuItem: TMenuItem;
Header: HWND;
HeaderRect: TRect;
HeaderHeight: Integer;
begin
// Re-filling ListView's popup menu
ListView.PopupMenu.Items.Clear();
// Getting header height
Header := ListView_GetHeader(ListView.Handle);
GetWindowRect(Header, HeaderRect);
HeaderHeight := HeaderRect.Bottom - HeaderRect.Top;
Pos := ListView.ScreenToClient(ListView.PopupMenu.PopupPoint);
// Clicked on header?
if Pos.Y < HeaderHeight then
SrcMenu := PopupMenuColumns
else
SrcMenu := PopupMenuItems;
// Copying destired menu to ListView.PopupMenu
for I := 0 to SrcMenu.Items.Count - 1 do
begin
MenuItem := TMenuItem.Create(FListViewPopupMenu);
with SrcMenu.Items[I] do
begin
MenuItem.Action := Action;
MenuItem.Caption := Caption;
MenuItem.ShortCut := ShortCut;
MenuItem.Checked := Checked;
MenuItem.Enabled := Enabled;
MenuItem.OnClick := OnClick;
MenuItem.HelpContext := HelpContext;
MenuItem.Name := Name;
MenuItem.ImageIndex := ImageIndex;
end;
ListView.PopupMenu.Items.Add(MenuItem);
end;
ListView.PopupMenu.Images := SrcMenu.Images;
end;
来源:https://stackoverflow.com/questions/11259099/how-to-set-popup-menu-for-listview-header-bar-together-with-items-popup-menu