简体   繁体   中英

WPF Update UI From Background Thread

I know similar questions have been asked many times but I have not been able to find anything that works in this situation. I have an application that runs minimized to the taskbar using wpf-notifyicon . The main window opens, authenticates and is then hidden. A process runs in the background and I would like it to send updates to the main thread. Most of the time it works. However, the taskbar icon has a context menu that allows the user to open application settings. If I open then close the settings window, the next time I try to update the balloon, I get aa null reference error System.NullReferenceException: 'Object reference not set to an instance of an object.' System.Windows.Application.MainWindow.get returned null. System.NullReferenceException: 'Object reference not set to an instance of an object.' System.Windows.Application.MainWindow.get returned null.

It's like once I open another window, the main window is lost and I can't figure out how to find it again.

This is how I am updating the balloon. This code is in a notification service and is called from inside the MainWindow View Model and from inside other services.

// 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 );

The notification icon is declared inside the XAML for the main 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>

How can I safely update the balloon icon from background threads?

Update 1:

The context menu is bound to commands in the view model. To open the settings window

<ContextMenu>
    <MenuItem Header="Settings" Command="{Binding ShowSettingsCommand}" />
    <Separator />
    <MenuItem Header="Exit" Command="{Binding ExitApplicationCommand}" />
</ContextMenu>

The command in the VM is:

    public ICommand ShowSettingsCommand => new DelegateCommand
    {
        CommandAction = () =>
        {
            Application.Current.MainWindow = new Views.SettingsWindow( _logger, _hidservice, _certificateService );
            Application.Current.MainWindow.Show();
        }
    };

To close the settings window, I have an action in the window code behind

public ICommand CancelSettingsCommand => new DelegateCommand
{
    CommandAction = () => CloseAction()
};


// In Code Behind
vm.CloseAction = new Action( () =>  this.Close()  );

You are overriden the main window. You should not do that. Just create a new instance of it and call Show() .

public ICommand ShowSettingsCommand => new DelegateCommand
{
    CommandAction = () =>
    {
        var settingsWindow = = new Views.SettingsWindow( _logger, _hidservice, _certificateService );
        settingsWindow.Show();
    }
};

Don't show the window from the view model. Use an event handler instead. Also don't override the value of Application.MainWindow .

Don't use Dispatcher to show progress. Since .NET 4.5 the recommended pattern is to use IProgres<T> . The frameworks provides a default implementation Progress<T> : Progress model to hold progress data

class ProgressArgs 
{
  public string Title { get; set; }
  public string Message { get; set; }
  public object Icon { get; set; }
}

Main UI thread

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);
}

Background thread

private void DoWork(IProgress<ProgressArgs> progressReporter)
{
  // Do work

  // Report progress
  var progress = new ProgressArgs() { Title = "Title", Message = "Some message", Icon = null };
  progressReporter.Report(progress);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM