WPF 更新 UI 来自后台线程
WPF Update UI From Background Thread
我知道类似的问题已被问过很多次,但我无法找到在这种情况下有效的任何方法。我有一个使用 wpf-notifyicon 最小化到任务栏运行的应用程序。主要 window 打开、验证然后隐藏。一个进程在后台运行,我希望它向主线程发送更新。大部分时间它都有效。但是,任务栏图标有一个上下文菜单,允许用户打开应用程序设置。如果我打开然后关闭设置 window,下次我尝试更新气球时,我会收到空引用错误 System.NullReferenceException: 'Object reference not set to an instance of an object.' System.Windows.Application.MainWindow.get returned null.
就像我打开另一个window,主window丢失了,我不知道如何再次找到它。
这就是我更新气球的方式。此代码位于通知服务中,并从主窗口视图模型内部和其他服务内部调用。
// 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 内声明为主 window。
<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:
上下文菜单绑定到视图模型中的命令。打开设置window
<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();
}
};
要关闭设置window,我在window代码后面有一个动作
public ICommand CancelSettingsCommand => new DelegateCommand
{
CommandAction = () => CloseAction()
};
// In Code Behind
vm.CloseAction = new Action( () => this.Close() );
你被覆盖了主要 window。你不应该那样做。只需创建它的一个新实例并调用 Show()
.
public ICommand ShowSettingsCommand => new DelegateCommand
{
CommandAction = () =>
{
var settingsWindow = = new Views.SettingsWindow( _logger, _hidservice, _certificateService );
settingsWindow.Show();
}
};
不显示视图模型中的 window。请改用事件处理程序。
也不要覆盖 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, _hidservice, _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);
}
我知道类似的问题已被问过很多次,但我无法找到在这种情况下有效的任何方法。我有一个使用 wpf-notifyicon 最小化到任务栏运行的应用程序。主要 window 打开、验证然后隐藏。一个进程在后台运行,我希望它向主线程发送更新。大部分时间它都有效。但是,任务栏图标有一个上下文菜单,允许用户打开应用程序设置。如果我打开然后关闭设置 window,下次我尝试更新气球时,我会收到空引用错误 System.NullReferenceException: 'Object reference not set to an instance of an object.' System.Windows.Application.MainWindow.get returned null.
就像我打开另一个window,主window丢失了,我不知道如何再次找到它。
这就是我更新气球的方式。此代码位于通知服务中,并从主窗口视图模型内部和其他服务内部调用。
// 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 内声明为主 window。
<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:
上下文菜单绑定到视图模型中的命令。打开设置window
<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();
}
};
要关闭设置window,我在window代码后面有一个动作
public ICommand CancelSettingsCommand => new DelegateCommand
{
CommandAction = () => CloseAction()
};
// In Code Behind
vm.CloseAction = new Action( () => this.Close() );
你被覆盖了主要 window。你不应该那样做。只需创建它的一个新实例并调用 Show()
.
public ICommand ShowSettingsCommand => new DelegateCommand
{
CommandAction = () =>
{
var settingsWindow = = new Views.SettingsWindow( _logger, _hidservice, _certificateService );
settingsWindow.Show();
}
};
不显示视图模型中的 window。请改用事件处理程序。
也不要覆盖 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, _hidservice, _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);
}