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()
时,会在MessageBox
class内部调用ShowCore()
方法,在内部处理这个参数。
...
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也会有同样的强制关闭,但是如果这样处理应该可以避免这个问题
我正在尝试使用 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()
时,会在MessageBox
class内部调用ShowCore()
方法,在内部处理这个参数。
...
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也会有同样的强制关闭,但是如果这样处理应该可以避免这个问题