问题
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