Context menu does not open in first right click after tray notification is clicked in C# WPF?

I am working on a .NET WPF project using Visual Studio 2022 and I added tray icon functionality to my app. I also show toast notification whenever my app is minimized to windows tray. I also added a context menu to show up when user right clicks onto tray icon. When toast notification appears and user clicks on the notification ballon, context menu does not open at the first right click event.

I tried to debug and solve the problem. I observe that first right click does not trigger Notifier_MouseDown event. I suppose it is a focus issue that when user clicks onto notification ballon, focus moves somewhere else and thats why it does not trigger at first click. However, I could not figure out how to solve this issue. Any help would be appreciated.

Work so far:

public MainWindow()
{
    InitializeComponent();

    notifyIcon = new NotifyIcon();
    ShowNotifications = true;
    notifyIcon.BalloonTipText = TextRes.Get("TrayNotifyBalloonText");
    notifyIcon.BalloonTipTitle = TextRes.Get("TrayAppTitle");
    notifyIcon.Text = TextRes.Get("TrayAppTitle");
    notifyIcon.DoubleClick += new EventHandler(NotifyIcon_Click);
    notifyIcon.MouseDown += new MouseEventHandler(Notifier_MouseDown);
    notifyIcon.BalloonTipClicked += new EventHandler(NotifyIcon_BalloonTipClicked);

    Hide();
    menu = (ContextMenu)this.FindResource("NotifierContextMenu");
    
    if (notifyIcon != null)
    {
        notifyIcon.ShowBalloonTip(60000);
    }
}

private void NotifyIcon_BalloonTipClicked(object sender, EventArgs e)
{
    Show();
    WindowState = m_storedWindowState;
}

private void Window_Closing(object sender, CancelEventArgs e)
{
    notifyIcon.BalloonTipText = ResourceHelper.GetResourceText("TrayNotifyBalloonText");

    e.Cancel = true;
    WindowState = WindowState.Minimized;
    Hide();
}

void OnClose(object sender, CancelEventArgs args)
{
    notifyIcon.Dispose();
    notifyIcon = null;
}

private WindowState m_storedWindowState = WindowState.Normal;
void OnStateChanged(object sender, EventArgs args)
{
    if (WindowState == WindowState.Minimized)
    {
        notifyIcon.BalloonTipText = ResourceHelper.TextRes.Get("TrayNotifyBalloonText");
    }
    else
        m_storedWindowState = WindowState;
}

void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs args)
{
    CheckTrayIcon();
}

void NotifyIcon_Click(object sender, EventArgs e)
{
    Show();
    WindowState = m_storedWindowState;
}

void CheckTrayIcon()
{
    ShowTrayIcon(true);
}

void ShowTrayIcon(bool show)
{
    if (notifyIcon != null)
        notifyIcon.Visible = show;
}

void Notifier_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Right)
    { 
        menu.IsOpen = true;
        IntPtr handle = ((HwndSource)PresentationSource.FromVisual(menu)).Handle;
        ApiHelper.SetForegroundWindow(handle);
    }
}

private void Menu_Open(object sender, RoutedEventArgs e)
{
    Show();
    WindowState = m_storedWindowState;
    IntPtr handle = ((HwndSource)PresentationSource.FromVisual(this)).Handle;
    ApiHelper.SetForegroundWindow(handle);
}

private void Menu_Close(object sender, RoutedEventArgs e)
{
    Application.Current.Shutdown();
}
<Window.Resources>
  <ContextMenu
    Focusable="{Binding FocusMenu}"
    x:Key="NotifierContextMenu"
    StaysOpen="False"
    Placement="MousePoint">
    <MenuItem Header="Open" Click="Menu_Open"/>
    <MenuItem Header="Close" Click="Menu_Close"/>
  </ContextMenu>
</Window.Resources>

Upvotes: 2

Views: 307

Answers (1)

Gabor
Gabor

Reputation: 3246

Try to use ContextMenuStrip instead of ContextMenu and assign it to the ContextMenuStrip property of the NotifyIcon instance.

Here is an example code that works for every right click event.

private NotifyIcon trayIcon;

// Called in the constructor after calling InitializeComponent() method.
private void ConfigureTrayIcon()
{
    var contextMenuStrip = new ContextMenuStrip();

    contextMenuStrip.Items.AddRange(
        new[]
        {
            new ToolStripMenuItem("Toggle", null, OnToggleRequested),
            new ToolStripMenuItem("Exit", null, OnExitRequested)
        }
    );

    trayIcon = new NotifyIcon()
    {
        Icon = Resources.Resources.ApplicationIcon,
        ContextMenuStrip = contextMenuStrip,
        Text = "Initial text",
        Visible = true
    };
}

private void OnExitRequested(object? sender, EventArgs e)
{
    trayIcon.Visible = false;
    Application.Exit();
}

Note the trayIcon.ContextMenuStrip property is set to the menu, so there is no need to call Win32 API methods, e.g. SetForegroundWindow().

The menu will be displayed automatically.

Upvotes: 2

Related Questions