1

My app is a system tray based popup form. I have a NotifyIcon in the system tray. When the notify icon is clicked, it toggles the main form. When the main form is deactivated, it hides itself. This is basically the standard behaviour of most system tray based app.

When the main form is shown, and the user clicks the notify icon, one would expect the main form to hide, which is also the standard behaviour. However, in Deactivate event handler, I hide the main form. Then, immediately after, in the Click event handler of the notify icon, we see that the main form is hidden, and we toggle it to be shown again. And this is the incorrect behaviour.

So, I wonder, if there's a way to know in Deactivate handler, if the reason we're losing the focus is that the user clicked the notify icon. Or, is there another clever way to handle this?

Thanks.

Deling Ren
  • 87
  • 4
  • Make `OnDeactivate` async, add `await Task.Delay(150);`. Call `Hide()` after. In the NotifyIcon's Click handler, check whether the Form is Visible. If it is, `Hide()` it, otherwise, `Show()` and `Activate()` it – Jimi Jul 25 '23 at 23:59
  • Not standard behavior, you found out why. Canonical example is Task Manager with its "Hide when minimized" option. https://stackoverflow.com/questions/1052913/how-to-detect-when-a-windows-form-is-being-minimized – Hans Passant Jul 26 '23 at 12:26

1 Answers1

2

In the Deactivate event, you can get the Rectangle of the NotifyIcon's Icon, then check if it Contains() the MousePosition, and then decide whether to Close() the MainForm.

I referenced this post: How to obtain the icon rectangle of a NotifyIcon

internal static class NotifyIconExtensions
{
    //Works with Shell32.dll (version 6.1 or later)
    [DllImport("shell32.dll")]
    static extern int Shell_NotifyIconGetRect([In] ref NOTIFYICONIDENTIFIER identifier, [Out] out RECT iconLocation);

    [StructLayout(LayoutKind.Sequential)]
    struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct NOTIFYICONIDENTIFIER
    {
        public uint cbSize;
        public IntPtr hWnd;
        public uint uID;
        public Guid guidItem;
    }

    public static bool TryGetRectangle(this NotifyIcon notifyIcon, out Rectangle rectangle)
    {
        rectangle = Rectangle.Empty;

        var type = notifyIcon.GetType();

        var idFieldName = "id";
        var windowFieldName = "window";

        // Due to the difference in field names between .NET and .NET Framework,
        // it requires conditional checking.
        var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
        if (!fields.Any(x => x.Name == idFieldName))
        {
            idFieldName = "_id";
            windowFieldName = "_window";
        }

        var idFieldInfo = type.GetField(idFieldName, BindingFlags.NonPublic | BindingFlags.Instance);
        if (idFieldInfo == null)
            return false;

        var iconId = (uint)idFieldInfo.GetValue(notifyIcon);

        var windowFieldInfo = type.GetField(windowFieldName, BindingFlags.NonPublic | BindingFlags.Instance);
        var nativeWindow = windowFieldInfo.GetValue(notifyIcon) as NativeWindow;
        if (nativeWindow == null)
            return false;

        var iconHandle = nativeWindow.Handle;

        var nid = new NOTIFYICONIDENTIFIER();
        nid.cbSize = (uint)Marshal.SizeOf(nid);
        nid.hWnd = iconHandle;
        nid.uID = iconId;

        int result = Shell_NotifyIconGetRect(ref nid, out var rect);

        if (result != 0)
            return false;

        rectangle = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
        return true;
    }
}

// in MainForm
protected override void OnDeactivate(EventArgs e)
{
    if (Program.notifyIcon.TryGetRectangle(out Rectangle rect))
    {
        Debug.WriteLine("{0}, {1}", MousePosition, rect);

        if (rect.Contains(MousePosition))
            return;
    }

    Close();
}

This will raise another issue: after the user clicks the NotifyIcon and triggers the Deactivate event of MainForm, if they click on another window, the Deactivate event of MainForm will not be triggered again.

Songpl
  • 132
  • 3