Proper way of activating my MainForm from a Notify

2020-05-09 09:22发布

问题:

I just want to replace the task bar button of my winforms application by a tray notification icon. That means, if the user left-clicks the icon, the form should be activated if it wasn't focused, otherwise minimized or hidden.

I read lots and lots of articles about properly using a NotifyIcon, and it seems I have to accept a hackish solution. So, what's the most proper way?

I got it mostly to run, but now I'm stuck at detecting if my form was active already - because when clicking the icon, the form looses focus, so I cannot check the Focused property.

The following code does not yet solve this, so if the form was just hidden by other windows, you have to click 2 times, because the first click minimizes.

How can it be improved?

    private void FormMain_Resize(object sender, EventArgs e)
    {
        if (WindowState == FormWindowState.Minimized)
            Hide();
    }

    private void notifyIcon_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
            if (WindowState == FormWindowState.Minimized) {
                WindowState = FormWindowState.Normal;
                Show();
                Activate();
            }
            else {
                Hide();
                WindowState = FormWindowState.Minimized;
            }
    }

(I also don't understand why the Click event fires on right-clicking, which already opens the context menu in my case...)

(And of course it would be nice to have a proper minimize animation, but there are other questions here, where this wasn't really solved)

(I know I said Focused, but if the form was already fully visible (but maybe not focused), and the user clicks the tray icon, he most likely wants to hide it)

回答1:

@LarsTech suggested a hack using a timer, and this works, thanks, but I still hope for better solutions or improvements.

Maybe interesting: I also hack-solved part of the animation problem - when there is no taskbar button, the animation originates from where the minimized form is located. You can make it visible by calling Show() after minimizing. It sits in the lower left of the screen and it is not possible to move it by setting i.e. the Left property. So I used winapi directly and it works! Unfortunately only for the restore animation, because I don't know how to set the minimized position before minimizing. Hide() disables the animation anyway, so I would have to delay it...

That all is so disappointing! Why is there no nice solution? I can never be sure if it will work in every scenario. For example on one machine it worked well with a 100ms timer, but on another I needed 200ms. So I suggest to have a least 500ms.

[DllImport("user32.dll", SetLastError = true)]
static extern bool MoveWindow(IntPtr hWnd, 
    int X, int Y, int nWidth, int nHeight, bool bRepaint);

private void FormMain_Resize(object sender, EventArgs e)
{
    if (!ShowInTaskbar && WindowState == FormWindowState.Minimized) {
        Hide();
        //Move the invisible minimized window near the tray notification area
        if (!MoveWindow(Handle, Screen.PrimaryScreen.WorkingArea.Left
                + Screen.PrimaryScreen.WorkingArea.Width - Width - 100,
                Top, Width, Height, false))
            throw new Win32Exception();
    }
}

private void notifyIcon_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
        if (WindowState == FormWindowState.Minimized || !timer.Enabled) {
            Show();
            WindowState = FormWindowState.Normal;
            Activate();
        }
        else {
            WindowState = FormWindowState.Minimized;
            //FormMain_Resize will be called after this
        }
}

private void FormMain_Deactivate(object sender, EventArgs e)
{
    timer.Start();
}

private void timer_Tick(object sender, EventArgs e)
{
    timer.Stop();
}

//other free goodies not mentioned before...

private void taskbarToolStripMenuItem_Click(object sender, EventArgs e)
{
    ShowInTaskbar = !ShowInTaskbar;
    taskbarToolStripMenuItem.Checked = ShowInTaskbar;
}

private void priorityToolStripMenuItem_Click(object sender, EventArgs e)
{
    //Set the process priority from ToolStripMenuItem.Tag
    //Normal = 32, Idle = 64, High = 128, BelowNormal = 16384, AboveNormal = 32768
    Process.GetCurrentProcess().PriorityClass
        = (ProcessPriorityClass)int.Parse((sender as ToolStripMenuItem).Tag.ToString());
    foreach (ToolStripMenuItem item in contextMenuStrip.Items)
        if (item.Tag != null)
            item.Checked = item.Equals(sender);
}

private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
    Close();
}