WPF 任务栏图标上下文菜单

WPF TaskbarIcon ContextMenu

我正在尝试使用 TaskbarIcon 创建 WPF 应用程序, 我想如果我点击托盘栏中的图标,它会弹出一个上下文菜单, 如果我 select "Exit",那么它会显示一个消息框,询问我是否要关闭此应用程序。

这是问题所在,MessageBox 显示正确,但在我单击任何按钮之前它就立即消失了,我使用调试器检查 "Result" 值,我发现它始终是 "No"。有没有人遇到过这个问题?任何线索将不胜感激!!

这是我的 .xaml 代码:

<tb:TaskbarIcon x:Name="WpfTaskIcon" IconSource="/Themes/Images/TimeSync.ico"
                    ToolTipText="Hello world" >
<tb:TaskbarIcon.ContextMenu>
<ContextMenu Background="LightCoral">
    <MenuItem Header="Exit" Click="Exit_Click" />
    <MenuItem Header="Second menu Item" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>

这是我的 C# 代码:

private void Exit_Click(object sender, RoutedEventArgs e)
{
     MessageBoxResult result = System.Windows.MessageBox.Show(
       "Message_ConfirmationOfExit",
        "Title_Confirmation",
        MessageBoxButton.YesNo);
    if (result == MessageBoxResult.Yes)
    {
        this.Close();
    }
}

edt : 我添加了这个来初始化 MainWindow 的可见性:

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.Visibility = System.Windows.Visibility.Visible;
    MessageBox.Show("MainWindow loaded");
    MessageBoxResult result = System.Windows.MessageBox.Show(
       "Message_ConfirmationOfExit",
        "Title_Confirmation",
        MessageBoxButton.YesNo);
    if (result == MessageBoxResult.Yes)
    {
        this.Close();
    }
}

我在其他 WPF 方案中遇到过这个问题,原因是 还没有启动主 UI 线程 。如果您调用 MessageBox()MsgBox() 或 VB InputBox() 而没有启动主 UI 线程(=在加载第一个应用程序之前),预期的对话框将打开,但在一秒钟内消失。但是,它会自己创建丢失的 UI 线程(我想......我没有检查所有细节)因为在重复调用相同代码时消息框不会再消失。

解决方案 是让第一个窗体(通常是主窗体)在打开任何消息框之前初始化。不可见的初始化也很重要。

清单问题:

  • 是否加载了任何形式的应用程序(调用了其 Load 事件)?

解决方法(不好但有效):如果您没有时间对应用程序进行更大的更改,请先存储当前时间,然后再显示您的 消息框,如果您在 1500 毫秒内收到回复,请不要对其结果采取行动(无论如何它都不是来自用户),而是重新显示消息框。

可能来的有点晚了,不过我可能已经找到问题的原因了:

MessageBox使用了WindowAPI.

参考:http://pinvoke.net/default.aspx/user32/MessageBox.html

[DllImport("user32.dll", SetLastError = true, CharSet= CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

参数包括 IntPtr 类型的 hWnd

WPF中调用MessageBox.Show()时,会在MessageBoxclass内部调用ShowCore()方法,在内部处理这个参数。

参考:https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/MessageBox.cs 第 483 行

...


if ( (options & (MessageBoxOptions.ServiceNotification | MessageBoxOptions.DefaultDesktopOnly)) ! = 0) 
{
    // Demand UnmangedCode permissions if using ServiceNotification/DefaultDesktopOnly.
    // Details in DevDiv 163043.
    SecurityHelper.DemandUnmanagedCode()
    if (owner ! = IntPtr.Zero)
    {
        throw new ArgumentException(SR.Get(SRID.CantShowMBServiceWithOwner));
    }                
}
else
{                                    
    if (owner == IntPtr.Zero)
    {
        owner = UnsafeNativeMethods.GetActiveWindow();
    }                
}

...

我认为,在WPF中使用MessageBox时,当前活动的window被自动选择为hWnd,并且由于没有window,ContextMenu 用作 window.

我们可以使用 API:

来证明这一点
[DllImport("user32.dll")]
static extern IntPtr GetActiveWindow();

并在点击事件中使用:

private void MenuItem_Click(object sender, RoutedEventArgs e)
{
    var t = GetActiveWindow();
}

然后在spy++中找到t的值,就是ContextMenu.

点击ContextMenu上的MenuItem时,ContextMenu会关闭,此时MessageBox的所有者消失,MessageBox被迫关闭。

基于以上研究,我得到了以下解决方案。

1.Delay MessageBox 的外观,以便在关闭 ContextMenu 后显示。

await Task.Delay(200); 
MessageBox.Show("Hello, world!");

2.Call API 本身并将 hWnd 设置为 IntPtr.Zero.

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

private void MenuItem_Click(object sender, RoutedEventArgs e)
{
    MessageBox(IntPtr.Zero, "Hello, world!", "Test", 0);
}

3.ModifyMessageBoxOptions的取值根据WPF中MessageBox的源码

MessageBox.Show(
    "Hello, world!",
    "Test",
    MessageBoxButton.OK,
    MessageBoxImage.Error,
    MessageBoxResult.OK,
    MessageBoxOptions.ServiceNotification | MessageBoxOptions.DefaultDesktopOnly);

也许这可以为大家提供一些帮助。

==================

我觉得这样比较好:

添加一个变量到App.xaml.cs:

bool isNeedShowMessageBox = false;

按下 MenuItem 时:

private void MenuItem_Click(object sender, RoutedEventArgs e)
{
    isNeedShowMessageBox = true;
}

为上下文菜单添加事件:

<ContextMenu Closed="ContextMenu_Closed">

然后:

private void ContextMenu_Closed(object sender, RoutedEventArgs e)
{
    if (isNeedShowMessageBox)
    {
        MessageBox.Show();
        isNeedShowMessageBox=false;
    }
}

这似乎工作得很好。

在后续的尝试中我发现不仅是MessageBox,OpenFileDialog或者其他类似的windows也会有同样的强制关闭,但是如果这样处理应该可以避免这个问题