问题描述
我知道类似的问题已经问过很多次了,但是我找不到在这种情况下有用的任何东西。我有一个使用wpf-notifyicon最小化运行到任务栏的应用程序。主窗口打开,进行身份验证,然后隐藏。一个进程在后台运行,我希望它向主线程发送更新。在大多数情况下,它都有效。但是,任务栏图标具有上下文菜单,允许用户打开应用程序设置。如果我打开然后关闭设置窗口,则下次我尝试更新气球时,会出现空引用错误System.NullReferenceException: 'Object reference not set to an instance of an object.' System.Windows.Application.MainWindow.get returned null.
就像我打开另一个窗口一样,主窗口丢失了,我不知道如何再次找到它。
这就是我更新气球的方式。这段代码位于通知服务中,可以从MainWindow视图模型内部以及其他服务内部调用。
// Show balloon update on the main thread
Application.Current.dispatcher.Invoke( new Action( () =>
{
var notifyIcon = ( TaskbarIcon )Application.Current.MainWindow.FindName( "NotifyIcon" );
notifyIcon.ShowBalloonTip( title,message,balloonIcon );
} ),dispatcherPriority.normal );
通知图标在XAML内部的主窗口中声明。
<tb:TaskbarIcon
x:Name="NotifyIcon"
IconSource="/Resources/Icons/card_16x16.ico"
ToolTipText="Processor"
MenuActivation="LeftOrRightClick"
DoubleClickCommand="{Binding ShowStatusCommand}">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="Settings" Command="{Binding ShowSettingsCommand}" />
<Separator />
<MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
<tb:TaskbarIcon.TrayToolTip>
<Border Background="Gray"
BorderBrush="DarkGray"
BorderThickness="1"
CornerRadius="3"
Opacity="0.8"
Width="180"
Height="20">
<TextBlock Text="{Binding ListeningMessage }" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</tb:TaskbarIcon.TrayToolTip>
</tb:TaskbarIcon>
如何安全地从后台线程更新气球图标?
更新1:
上下文菜单绑定到视图模型中的命令。打开设置窗口
<ContextMenu>
<MenuItem Header="Settings" Command="{Binding ShowSettingsCommand}" />
<Separator />
<MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}" />
</ContextMenu>
VM中的命令为:
public ICommand ShowSettingsCommand => new DelegateCommand
{
CommandAction = () =>
{
Application.Current.MainWindow = new Views.SettingsWindow( _logger,_hidservice,_certificateService );
Application.Current.MainWindow.Show();
}
};
public ICommand CancelSettingsCommand => new DelegateCommand
{
CommandAction = () => CloseAction()
};
// In Code Behind
vm.CloseAction = new Action( () => this.Close() );
解决方法
您将覆盖主窗口。你不应该那样做。只需为其创建一个新实例,然后调用Show()
。
public ICommand ShowSettingsCommand => new DelegateCommand
{
CommandAction = () =>
{
var settingsWindow = = new Views.SettingsWindow( _logger,_hidservice,_certificateService );
settingsWindow.Show();
}
};
,
不显示视图模型中的窗口。请改用事件处理程序。
另外,请勿覆盖Application.MainWindow
的值。
请勿使用Dispatcher
显示进度。从.NET 4.5开始,推荐的模式是使用IProgres<T>
。框架提供了默认实现Progress<T>
:
用于保存进度数据的进度模型
class ProgressArgs
{
public string Title { get; set; }
public string Message { get; set; }
public object Icon { get; set; }
}
主UI线程
private void ShowSettings_OnMenuClicked(object sender,EventArgs e)
{
// Pass this IProgress<T> instance to every class/thread
// that needs to report progress (execute the delegate)
IProgres<ProgressArgs> progressReporter = new Progress<ProgressArgs>(ReportPropgressDelegate);
var settingsWindow = new Views.SettingsWindow(_logger,_certificateService,progressReporter);
}
private void ReportPropgressDelegate(ProgressArgs progress)
{
var notifyIcon = (TaskbarIcon) Application.Current.MainWindow.FindName("NotifyIcon");
notifyIcon.ShowBalloonTip(progress.Title,progress.Message,progress.Icon);
}
背景线程
private void DoWork(IProgress<ProgressArgs> progressReporter)
{
// Do work
// Report progress
var progress = new ProgressArgs() { Title = "Title",Message = "Some message",Icon = null };
progressReporter.Report(progress);
}