Search code examples
c#wpfwindowsuwptoast

C# WPF - Strange MainWindow focus loss when clicking on a Windows Toast Notification button


I have created a small .NET 6 WPF app to show this issue.

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
        }
    }

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var notifier = new Notifier();

            //Send Toast
            notifier.SendToast();

            //Close MainWindow
            Close();
        }
    }

I am using Microsoft.Toolkit.Uwp.Notifications NuGet package to send a Toast notification using ToastContentBuilder.

    public class Notifier
    {
        public Notifier()
        {
            SubscribeToToastCallback();
        }

        public void SendToast()
        {
            var toastContentBuilder = new ToastContentBuilder();
            toastContentBuilder.AddText("Text").AddButton("Test", ToastActivationType.Foreground, "");
            toastContentBuilder.Show();
        }

        private void SubscribeToToastCallback()
        {
            ToastNotificationManagerCompat.OnActivated += toastArgs =>
            {
                //On Toast button click, Show MainWindow activated
                Application.Current.Dispatcher.BeginInvoke(() =>
                {
                    if (Application.Current.MainWindow is null)
                    {
                        Application.Current.MainWindow = new MainWindow();
                        Application.Current.MainWindow.ShowActivated = true;
                    }
                    Application.Current.MainWindow.Show();
                });
            };
        }
    }

What I would expect to happen: Toast button gets clicked -> ToastNotificationManagerCompat.OnActivated gets fired -> my code to open MainWindow gets executed -> MainWindow is opened in foreground.

What actually happens: Toast button gets clicked -> ToastNotificationManagerCompat.OnActivated gets fired -> my code to open MainWindow gets executed -> MainWindow is opened in foreground -> ~0.5 seconds later MainWindow loses focus, and previously open app gets activated.

App Gets then Loses Focus

There is also an interesting exception:

You can reproduce this scenario 10 times in a row, but then if you click on the toast Body(which is interactable), it will activate my window correctly. Furthermore, it will also make the subsequent notifications activate window correctly, even if you click the button that used to cause focus loss in previous attempts.

Also, when MainWindow loses focus in this scenario, the MainWindow.Deactivated event (based on WA_INACTIVE message) is not fired. So the app doesn't know MainWindow lost focus.

I have tried changing the ToastActivationType.Foreground to Background, nothing changed.

All combinations of Show/Focus/Activate() don't help.

Tried setting LockSetForegroundWindow() using PInvoke, nothing changed.

The only thing that worked, but is extremely janky was: MainWindow opens -> set Topmost = true -> holding it for 1 second(while Windows tries to steal focus back) -> setting Topmost = false.

MainWindow.Topmost = true;
Task.Delay(1000).ContinueWith((_) => { Dispatcher.Invoke(() => { MainWindow.Topmost = false; }); });

Does anyone know why this scenario is happening? Is it a Windows bug that it's stealing focus after 0.5 seconds? And could there be any less janky solutions than the one I found using Topmost?

Thanks!

Environment: Windows 10 x64 Build 19045


Solution

  • Ok, I have found the proper solution. And it's a bit weird that when I initially posted this, this solution did not work. I'm not sure if Microsoft pushed an update or something that fixed it, but here's how to get focus properly, you need to do both of these things:

    1. Make sure you use ToastActivationType.Foreground on any button that you want the app to open the app's MainWindow.

    2. In the Activated callback handler make sure to call both of these in this order:

       MainWindow.Show(); MainWindow.Activate();
      

    If you don't use ToastActivationType.Foreground Windows won't allow you to get Foreground.

    If you do this, Windows will still not allow you to get foreground:

    MainWindow.ShowActivated = true;
    MainWindow.Activate();