7
votes

I have a ListView with ViewStyle = vsReport and two popup menus:

  1. Column popup menu, which I want to open when user right-clicking the header bar
  2. 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.

3
Do you want the PopupMenuItems menu popup only when you're over an item or even when you're over subitem ?TLama
@TLama yes, over subitems tooAndrew

3 Answers

8
votes

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;
3
votes

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.

0
votes

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;