Search code examples
c#wpfnotifyicon

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 a a null reference error 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()  );

Solution

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