Skip to content

WPF NET 9.0 (latest preview)-based app that uses the built-in Fluent theme crashes when Theme is changed #9906

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
danielkornev opened this issue Oct 7, 2024 · 17 comments
Assignees
Labels
Investigate Requires further investigation by the WPF team. Priority:1 Work that is critical for the release, but we could probably ship without Win 11 Theming

Comments

@danielkornev
Copy link

danielkornev commented Oct 7, 2024

Description

This problem appears on two kinds of OS:

  • Windows 10 (AMD64, 24H2)
  • Windows 11 (ARM64, 24H2)

Here's the error I've got in the beginning on both machines:

Monday, October 7, 2024, 10/7/2024 12:48:28 PM
Exception [System.InvalidOperationException]: The calling thread cannot access this object because a different thread owns it.
System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
   at System.Windows.Threading.Dispatcher.<VerifyAccess>g__ThrowVerifyAccess|7_0()
   at System.Windows.ThemeManager.OnSystemThemeChanged()
   at System.Windows.SystemResources.SystemThemeFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

Sadly there was no way to figure out why this error happened. My app showed this message and then crashed. App uses Microsoft.VisualBasic library to get a Single Instance behavior, so catching the exception would get us to app.Run method.

After several experiments, the app now crashes w/o even this error after the theme of the apps in System Settings is changed to Light/Dark.

Reproduction Steps

  1. Create a WPF 9.0 app that uses Visual Basic assembly's Application to get a Single Instance Application
  2. Use Fluent theme as a ResourceDictionary across your app
  3. Switch theme of all apps in System Settings from Light to Dark or vice versa

Expected behavior

Theme changes where expected

Actual behavior

App crashes (initially with an error message and now silently)

Regression?

there were no problems prior to this moment

Known Workarounds

  1. don't use Fluent theme?
  2. Added these lines in SingleInstanceApplication's OnStartup override event handler:
#pragma warning disable WPF0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
                this.ThemeMode = ThemeMode.Light;
#pragma warning restore WPF0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

This seems to keep app working.

Impact

App cannot be given to the end users. It crashes every time user changes an app color in System Settings on Windows 11, and it crashes (in general) every time the theme changes on Windows 10/11.

Configuration

.NET 9 (preview, latest)
Windows 10: 24H2, amd64
Windows 11: 24H2 Canary, arm64

Other information

No response

@danielkornev
Copy link
Author

Updated workaround:

Added these lines in SingleInstanceApplication's OnStartup override event handler:

#pragma warning disable WPF0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
                this.ThemeMode = ThemeMode.Light;
#pragma warning restore WPF0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

@h3xds1nz
Copy link
Member

h3xds1nz commented Oct 7, 2024

Quick glance, this isn't invoked on the dispatcher, while all other calls from SystemThemeFilterMessage always are.

foreach (Window window in Application.Current.Windows)

@dipeshmsft
Copy link
Member

Hey @danielkornev, can you give a sample repro. I am afraid, I have not worked with Visual Basic a lot and may have missed somethings.

@danielkornev
Copy link
Author

danielkornev commented Oct 8, 2024 via email

@dipeshmsft
Copy link
Member

Okay, no issues. Just wanted one clarification on the above message. Switching theme while the application is running is also causing the application to crash ?

@danielkornev
Copy link
Author

danielkornev commented Oct 8, 2024 via email

@batzen
Copy link
Contributor

batzen commented Oct 9, 2024

If i am not mistaken the code in ThemeManager also ignores the fact that a Window might run on a different dispatcher thread.
Has anyone tried that yet?

@batzen
Copy link
Contributor

batzen commented Oct 9, 2024

ThemeManager also seems to ignore the fact that Application.Current might be null.
Has anyone tried that yet?

@dipeshmsft
Copy link
Member

ThemeManager also seems to ignore the fact that Application.Current might be null.
Has anyone tried that yet?

Regarding this, I have tried to check everywhere is Application.Current is null or not before performing application wide. In this issue, #9373 they have tried using Fluent without creating an Application instance, is that what you are asking about ?

@batzen
Copy link
Contributor

batzen commented Oct 9, 2024

@dipeshmsft That's one part. But I guess the ThemeManager causes a crash as soon as the Windows theme changes as it's trying to access Application.Current in that case.

@himgoyalmicro himgoyalmicro added Investigate Requires further investigation by the WPF team. Win 11 Theming Priority:1 Work that is critical for the release, but we could probably ship without labels Oct 9, 2024
@h3xds1nz
Copy link
Member

h3xds1nz commented Oct 9, 2024

If i am not mistaken the code in ThemeManager also ignores the fact that a Window might run on a different dispatcher thread. Has anyone tried that yet?

Yep, whole ThemeManager does not count on that, which doesn't correspond with how the rest is written.

@dipeshmsft
Copy link
Member

That's one part. But I guess the ThemeManager causes a crash as soon as the Windows theme changes as it's trying to access Application.Current in that case.

Can you send me a sample repro for this issue?

I will try to get it in before the GA happens

@batzen
Copy link
Contributor

batzen commented Oct 20, 2024

@dipeshmsft Just do this somewhere in your project, without having an application object, and then change some windows setting.
When having an application object the theme for the threaded window simply doesn't change, which i consider another bug.

    private static void StartNewThreadedWindow()
    {
        var thread = new Thread(StartOnThread);
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    }

    private static void StartOnThread(object? obj)
    {
        var window = new Window
        {
            ThemeMode = ThemeMode.System
        };
        window.Show();
        Dispatcher.Run();
    }

Usage of Application.Current seem to be guarded by calls to IsFluentThemeEnabled which checks for Application.Current != null.
So i was wrong with that assumption.

@danielkornev
Copy link
Author

danielkornev commented Nov 7, 2024

One more issue:

If you are in the situation like mine, and you want to use a custom title area, when theme switches, color of the icons for minimize, maximize/restore, and close buttons (on the right side of the app's custom title area) change to white/invisible (no idea). Curiously, mouse over still registers, custom snap layouts appear, and you can minimize/maximize/close window, but you have to switch visibility off/on in Hot Reload to make icons visible again.

Go figure.

Upd 1: maybe its because of this?

private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
    Dispatcher.Invoke(() =>
    {
        if (SystemParameters.HighContrast)
        {
            MinimizeButton.Visibility = Visibility.Visible;
            MaximizeButton.Visibility = Visibility.Visible;
            CloseButton.Visibility = Visibility.Visible;
        }
        else
        {
            MinimizeButton.Visibility = Visibility.Collapsed;
            MaximizeButton.Visibility = Visibility.Collapsed;
            CloseButton.Visibility = Visibility.Collapsed;
        }
    });
}

but if so, then why mouse over still works?

Upd 2: yep, this is precisely the reason. Once I've commented out this above, buttons stopped from disappearing when theme changes (and by theme I mean even a background change). Go figure!

@dipeshmsft
Copy link
Member

@danielkornev, this is actually a known thing. And seeing the code you have added, I am assuming you are using the WPF Gallery application.

When we use WindowChrome, we can extend the WPF's content into the non-client area of a window, allowing us to use the whole area of the window. However, by setting the CaptionHeight property of the WindowChrome, we allow a rectangular area at the top to be treated as a NON_CLIENT area and receives WM_NCHITTEST messages, irrespective of the Window's background or other elements drawn in that area (until specified otherwise using WindowChrome.IsHitTestVisibleInChrome property).

When we set UserAeroCaptionButtons the area which generally behaves as caption buttons, keep doing that, and that's why you are seeing the snap layout and other cursor movements being registered.

Now, regarding the button's not being visibile - in my understanding, Window's background is painted over the window's CompositionTarget, and anything drawn by the OS (like the default title bar buttons) is painted over by Window's background and other elements. In this case, when you said even on background change the buttons are getting invisible, I think the reason for this is that the window's background is opaque.

The code that you are referring above, it was added to show custom title bar buttons when we are in HighContrast mode. In this mode, the background of Window in opaque and we are not able to see the button's drawn by OS.

I hope I have not misunderstood your queries. I am curious about the hot-reload scenario though. I haven't explored the working on CompositionTarget, WindowChrome with Hot-Reload specifically.

@danielkornev
Copy link
Author

My point is that if you want to force specific Fluent theme (e.g., Light), you can avoid using this code from WPF Gallery app and then window management buttons will look ok:

private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
    Dispatcher.Invoke(() =>
    {
        if (SystemParameters.HighContrast)
        {
            MinimizeButton.Visibility = Visibility.Visible;
            MaximizeButton.Visibility = Visibility.Visible;
            CloseButton.Visibility = Visibility.Visible;
        }
        else
        {
            MinimizeButton.Visibility = Visibility.Collapsed;
            MaximizeButton.Visibility = Visibility.Collapsed;
            CloseButton.Visibility = Visibility.Collapsed;
        }
    });
}

@dipeshmsft
Copy link
Member

The code above is to deal with HighContrast theme's only, also when backdrop is disabled using the switch. There are some other scenario's where the code may not work ( like when transparency effects are switched off ). I am taking a look at those scenarios.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Investigate Requires further investigation by the WPF team. Priority:1 Work that is critical for the release, but we could probably ship without Win 11 Theming
Projects
Status: 🥅 Todo
Development

No branches or pull requests

5 participants