0

I want to display a form when the cursor enters the icon, and it disappears shortly after the cursor leaves the icon, similar to the Process Hacker software.

(It displays a form above the system tray that displays information about running applications when the cursor enters the icon, with additional options such as attaching the form that will not disappear, opening settings and more, and the form disappears a few seconds after leaving the icon)

I saw this post

Edit:

I do not need to add these events as properties (like the built-in OnMouseMove, and as in TTrayIconEx), I just want to add a handler procedure that will receive the messages sent from the icon when the cursor hovers over it or leaves it. For example, the messages Remy Lebeau mentioned in his reply (NIN_POPUPOPEN, NIN_POPUPCLOSE)

Is this possible, and how?

updating:

In fact, I used this code as experience:

unit MainFormtest;

interface

uses
  ShellAPI, Windows, Messages, SysUtils, Classes,
  Vcl.Controls, Vcl.Forms, Vcl.ExtCtrls, Vcl.Menus;

type
  TTesterForm = class(TForm)
    DelayHide: TTimer;
    TestPopupMenu: TPopupMenu;
    PMClose: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure DelayHideTimer(Sender: TObject);
    procedure FormMouseEnter(Sender: TObject);
    procedure FormMouseLeave(Sender: TObject);
    procedure FormHide(Sender: TObject);
    procedure PMCloseClick(Sender: TObject);
    procedure TestPopupMenuPopup(Sender: TObject);
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  public
    TrayIconForTest: TNotifyIconData;
    procedure TrayMouseMessage(var Msg: TMessage); Message WM_SYSTEM_TRAY_MESSAGE;
  end;

var
  TesterForm: TTesterForm;

implementation

{$R *.dfm}

procedure TTesterForm.FormCreate(Sender: TObject);
begin
  with TrayIconForTest do
  begin
    cbSize := TNotifyIconData.SizeOf;
    Wnd := Handle;
    uID := $20;
    uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP;
    uCallBackMessage := WM_SYSTEM_TRAY_MESSAGE;
    hIcon := Application.Icon.Handle;
    szTip := 'this is test icon';
    uVersion := 4;
    dwInfoFlags := NIIF_USER;
    hBalloonIcon := Application.Icon.Handle;
  end;
  Shell_NotifyICon(NIM_ADD, @TrayIconForTest);
  Shell_NotifyIcon(NIM_SETVERSION, @TrayIconForTest);
end;

procedure TTesterForm.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle := Params.ExStyle and not WS_EX_APPWINDOW;
  Params.WndParent := Application.Handle;
end;

procedure TTesterForm.TestPopupMenuPopup(Sender: TObject);
begin
  DelayHide.Enabled := False;
end;

procedure TTesterForm.TrayMouseMessage(var Msg: TMessage);
begin
  case LOWORD(Msg.Lparam) of
    NIN_POPUPOPEN:
    begin
      TesterForm.Show;
      DelayHide.Enabled := False;
      TestPopupMenu.CloseMenu;
    end;
    NIN_POPUPCLOSE:
      DelayHide.Enabled := True;
    WM_RBUTTONDOWN, WM_LBUTTONDOWN:
      TestPopupMenu.Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);
  end;
end;

procedure TTesterForm.DelayHideTimer(Sender: TObject);
begin
  TesterForm.Hide;
end;

procedure TTesterForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Shell_NotifyIcon(NIM_DELETE, @TrayIconForTest);
  Application.Terminate;
end;

procedure TTesterForm.FormHide(Sender: TObject);
begin
  DelayHide.Enabled := False;
end;

procedure TTesterForm.FormMouseEnter(Sender: TObject);
begin
  DelayHide.Enabled := False;
end;

procedure TTesterForm.FormMouseLeave(Sender: TObject);
begin
  DelayHide.Enabled := True;
end;

procedure TTesterForm.PMCloseClick(Sender: TObject);
begin
  testerForm.Close;
end;

end.

But he suffers from several problems:

  • When I press the right / left button to open the menu, the message NIN_POPUPCLOSE is not read again until the cursor enters the icon again and exits.
  • The menu does not close when a mouse button is pressed anywhere (so I added TestPopupMenu.CloseMenu to close the menu when the cursor re-enters).
  • The form window tends to display in the back when targeting another application window. I tried applying TesterForm.BringToFront and also SetForegroundWindow and it did not help.

Can you direct me to fix the code?

yonni
  • 248
  • 2
  • 11

1 Answers1

3

TTrayIcon, and TTrayIconEx provided in the other post you mention, do not support what you want.

However, the underlying Win32 tray icon API, Shell_NotifyIcon(), does. After adding your icon with NIM_ADD, you have to use NIM_SETVERSION setting NOTIFYICONDATA.uVersion to NOTIFYICON_VERSION_4 or higher to enable the API to send NIN_POPUPOPEN and NIN_POPUPCLOSE notification messages to your tray icon's owner window (note: this works only on Vista+):

  • NIN_POPUPOPEN. Sent when the user hovers the cursor over an icon to indicate that the richer pop-up UI should be used in place of a standard textual tooltip.
  • NIN_POPUPCLOSE. Sent when a cursor no longer hovers over an icon to indicate that the rich pop-up UI should be closed.

UPDATE:

  • The menu does not close when a mouse button is pressed anywhere (so I added TestPopupMenu.CloseMenu to close the menu when the cursor re-enters).

This is a well-known issue (multiple questions on StackOverflow about it), and is covered in the Remarks section of the documentation for the Win32 TrackPopupMenu() function (which TPopupMenu.Popup() calls internally):

To display a context menu for a notification icon, the current window must be the foreground window before the application calls TrackPopupMenu or TrackPopupMenuEx. Otherwise, the menu will not disappear when the user clicks outside of the menu or the window that created the menu (if it is visible). If the current window is a child window, you must set the (top-level) parent window as the foreground window.

However, when the current window is the foreground window, the second time this menu is displayed, it appears and then immediately disappears. To correct this, you must force a task switch to the application that called TrackPopupMenu. This is done by posting a benign message to the window or thread, as shown in the following code sample:

SetForegroundWindow(hDlg);

// Display the menu
TrackPopupMenu(   hSubMenu,
                  TPM_RIGHTBUTTON,
                  pt.x,
                  pt.y,
                  0,
                  hDlg,
                  NULL);

PostMessage(hDlg, WM_NULL, 0, 0);

In this case, hDlg can be the Handle of your TForm, eg:

WM_RBUTTONDOWN, WM_LBUTTONDOWN: begin
  SetForegroundWindow(Handle);
  with Mouse.CursorPos do
    TestPopupMenu.Popup(X, Y);
  PostMessage(Handle, WM_NULL, 0, 0);
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks, Remy! Well, the second problem is indeed solved, but still the first problem is limiting in that the form does not close automatically (after the delay) if the icon is clicked to display the menu, even if the mouse has left it. And the third problem is that if the focus is on another application window, the form will appear behind it, which is inconvenient. – yonni May 19 '22 at 07:02
  • @yonni "*the form does not close automatically (after the delay) if the icon is clicked to display the menu, even if the mouse has left it*" - makes sense. `NIN_POPUPCLOSE` is a hover notification. If you click on the icon, you are no longer simply hovering over the icon, you are actively interacting with the icon. Just close the Form before/after displaying the menu. "*if the focus is on another application window, the form will appear behind it*" - hence the need for `SetForegroundWindow()` before displaying the menu. – Remy Lebeau May 19 '22 at 07:20
  • I got my problem, @Remy But if so: (1.) How can I close the form (with delay) if the mouse clicks off the menu (as if NIN_POPUPCLOSE message was received). (2.) The form appears on the back even if it is now reopened (while hovering over the icon) if there is another focused window. I could not get the form to appear in progress as it was presented, SetForegroundWindow only works if the form was already open and focused before (so this solved the second problem). The only option that worked was to set up the StayOnTop form. Thanks for all the help and information! – yonni May 19 '22 at 08:20
  • @yonni I can't answer that. I've never had any need to display a hover UI in my tray apps, only popup menus and main UIs on clicks. – Remy Lebeau May 19 '22 at 15:41