-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Background and motivation
- [New Look and Feel] As a WPF developer I want my controls to match the appearance of windows11 controls (Round corners, Accent color etc., Windows with Mica, Acrylic backgrounds, Snap Layout)
- [Changing Themes] As a WPF developer I want to switch the theme of my application from Light to Dark or vice versa.
- [Listen to System Theme Change] As a WPF developer I want to adapt to system theme changes, i.e., when the system theme switches my application should also change the theme to dark or light.
- [Listen to Accent Colour Change] As a WPF developer I want my application to reflect Accent Colour change as well. When the system's accent colour changes, the accent colour in my app should also change.
- [Element Level Control] As a WPF developer I want to have precise control on element level theming, I can choose to exclude a specific element from the theme changes.
- [Customize & Override Styles] As a WPF developer I should also be able to change or replace the theme or design of a specific element.
- [Opt-Out] As a WPF developer I want to have a feature override to opt out of the light/dark theme design, as I want to apply my own styles and there are issues due to my styles and default styles.
- [Easy to Migrate and Adopt] As an App author, I want to be able to upgrade to the new themes and styles without rewriting my markup. There should be a minimal cost for upgrading to new themes and styles.
API Proposal
ApplicationTheme Enum
- This enum declares the theme preference for an entire application. It has two values: Light and Dark. It is used to set a global theme for your application that applies to all UI elements. This is important when you want a consistent look and feel across your entire application.
public enum ApplicationTheme
{
/// <summary>
/// Use the **Light** default theme.
/// </summary>
Light = 0,
/// <summary>
/// Use the **Dark** default theme.
/// </summary>
Dark = 1
}ElementTheme Enum
- This Enum specifies a UI theme that should be used for individual UIElement parts of an app UI. It has three values: Default, Light, and Dark. The Default value means the element will use the Application.RequestedTheme value. This enum is used when you want to override the Application's theme for specific UI elements.
/// <summary>
/// Specifies a UI theme that should be used for individual UIElement parts of an app UI.
/// </summary>
public enum ElementTheme
{
/// <summary>
/// Use the Application.RequestedTheme value for the element. This is the default.
/// </summary>
Default = 0,
/// <summary>
/// Use the **Light** default theme.
/// </summary>
Light = 1,
/// <summary>
/// Use the **Dark** default theme.
/// </summary>
Dark = 2
}Having both enums is useful because it allows for flexibility in theme management. With ApplicationTheme, you can set a consistent theme across your entire application, and with ElementTheme, you can override this on a per-element basis if needed. This can be particularly useful for creating applications that need to support both light and dark themes, or for creating a unique visual identity by mixing themes.
ThemeManger API Surface
- Current: This is a static property that returns an instance of the ThemeManager class. It is used to access the ThemeManager functionality from any part of the application.
- ApplicationTheme: This is a dependency property that gets or sets the light-dark preference for the overall theme of an app. It can be Light, Dark, or null. When null, the system theme is used.
- ActualApplicationTheme: This property gets the actual theme being used by the application, which may be different from the ApplicationTheme if it is null. This property is read-only and its value is automatically updated by the class based on the system theme or the value of ApplicationTheme.
- AccentColor: This is a dependency property that gets or sets the accent colour of the application. The type of this property is Colour?, which means it can be any colour or null. If it is null, the system accent colour is used.
- ActualAccentColor: This property gets the actual accent colour being used by the application. This is a read-only property and its value is automatically updated based on the system accent colour or the value of AccentColor.
- GetRequestedTheme(FrameworkElement element): This is a static method that gets the UI theme used by a FrameworkElement for resource determination.
- SetRequestedTheme(FrameworkElement element, ElementTheme value): This is a static method that sets the UI theme for a FrameworkElement.
- GetActualTheme(FrameworkElement element): This static method returns the actual UI theme currently used by a FrameworkElement.
- AddActualThemeChangedHandler(FrameworkElement element, RoutedEventHandler handler): This static method adds a RoutedEventHandler that is invoked when the actual theme of a FrameworkElement changes.
- RemoveActualThemeChangedHandler(FrameworkElement element, RoutedEventHandler handler): This static method removes a RoutedEventHandler that was previously added with AddActualThemeChangedHandler.
- GetIsThemeAware(Window window): This static method gets a value showing whether a Window is aware of theme changes.
- SetIsThemeAware(Window window, bool value): This static method sets a value showing whether a Window should be aware of theme changes.
- GetHasThemeResources(FrameworkElement element): This static method gets a value showing whether a FrameworkElement has theme resources.
- SetHasThemeResources(FrameworkElement element, bool value): This static method sets a value showing whether a FrameworkElement should have theme resources.
public class ThemeManager
{
// Public Properties
public static ThemeManager Current { get; }
public ApplicationTheme? ApplicationTheme { get; set; }
public Color? AccentColor { get; set; }
public ApplicationTheme ActualApplicationTheme { get; }
public Color ActualAccentColor { get; }
// Public Methods
public static ElementTheme GetRequestedTheme(FrameworkElement element) { }
public static void SetRequestedTheme(FrameworkElement element, ElementTheme value) { }
public static ElementTheme GetActualTheme(FrameworkElement element) { }
public static bool GetIsThemeAware(Window window) { }
public static void SetIsThemeAware(Window window, bool value) { }
public static bool GetHasThemeResources(FrameworkElement element) { }
public static void SetHasThemeResources(FrameworkElement element, bool value) { }
public static void AddActualThemeChangedHandler(FrameworkElement element, RoutedEventHandler handler) { }
public static void RemoveActualThemeChangedHandler(FrameworkElement element, RoutedEventHandler handler) { }
// Events
public TypedEventHandler<ThemeManager, object> ActualApplicationThemeChanged;
public TypedEventHandler<ThemeManager, object> ActualAccentColorChanged;
}ThemeResources API Surface
- RequestedTheme: This gets or sets the preferred light-dark theme for the application. It uses the ApplicationTheme value from the ThemeManager class.
- AccentColor: This gets or sets the accent colour of the application. It uses the AccentColour value from the ThemeManager class.
- CanBeAccessedAcrossThreads: This gets or sets a Boolean value showing whether the resources can be accessed across multiple threads. If set to true, the resources are prepared in a thread-friendly manner during initialization.
public class ThemeResources
{
// Public Properties
public ApplicationTheme? RequestedTheme { get; set; }
public Color? AccentColor { get; set; }
public bool CanBeAccessedAcrossThreads { get; set; }
// Public Methods
public new void BeginInit() { }
public new void EndInit() { }
}ColorPaletteResources API Surface
The ColorPaletteResources class in the provided repository is a specialized resource dictionary that has colour resources used by XAML (Extensible Application Markup Language) elements. The class has colour related properties and below 2 properties:
• TargetTheme: This property gets or sets the target theme for the colour palette resources.
• Accent: This property gets or sets the Accent colour value.
public class ColorPaletteResources : ResourceDictionary, ISupportInitialize
{
// Public Properties
public ApplicationTheme? TargetTheme { get; set; }
public Color? Accent { get; set; }
public Color? AltHigh { get; set; }
public Color? AltLow { get; set; }
public Color? AltMedium { get; set; }
public Color? AltMediumHigh { get; set; }
public Color? AltMediumLow { get; set; }
public Color? BaseHigh { get; set; }
public Color? BaseLow { get; set; }
public Color? BaseMedium { get; set; }
public Color? BaseMediumHigh { get; set; }
public Color? BaseMediumLow { get; set; }
public Color? ChromeAltLow { get; set; }
public Color? ChromeBlackHigh { get; set; }
public Color? ChromeBlackLow { get; set; }
public Color? ChromeBlackMedium { get; set; }
public Color? ChromeBlackMediumLow { get; set; }
public Color? ChromeDisabledHigh { get; set; }
public Color? ChromeDisabledLow { get; set; }
public Color? ChromeGray { get; set; }
public Color? ChromeHigh { get; set; }
public Color? ChromeLow { get; set; }
public Color? ChromeMedium { get; set; }
public Color? ChromeMediumLow { get; set; }
public Color? ChromeWhite { get; set; }
public Color? ErrorText { get; set; }
public Color? ListLow { get; set; }
public Color? ListMedium { get; set; }
// Public Methods
public new void BeginInit() { }
public new void EndInit() { }
}API Usage
ThemeResources
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ThemeResources />
<XamlControlsResources />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>Customizing Themes
<ThemeResources>
<ThemeResources.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles/Themes/Light.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles/Themes/Dark.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ThemeResources.ThemeDictionaries>
</ThemeResources>ThemeManager
<Window ThemeManager.RequestedTheme="Dark" />ColorPaletteResources
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ThemeResources CanBeAccessedAcrossThreads="True">
<ThemeResources.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<ResourceDictionary.MergedDictionaries>
<ColorPaletteResources TargetTheme="Light" Accent="Black" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<ResourceDictionary.MergedDictionaries>
<ColorPaletteResources TargetTheme="Dark" Accent="White" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ThemeResources.ThemeDictionaries>
</ThemeResources>
<XamlControlsResources />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>Alternative Designs
Setting the application theme to Light/Dark
- APPROACH 1: FRAMEWORK ELEMENT HAS A THEME PROPERTY
- APPROACH 2: CREATE A NEW THEME RESOURCES MARKUP WHICH CAN BE INITIALIZED IN APP.XAML
<ThemeResources RequestedTheme=” Light” />Pros
Adding in the App.xaml will import the resource dictionary corresponding to the light theme and all the elements will be styled according to the brushes in the imported ThemeResource.
Cons
Addition of an extra layer of abstraction in between which could add to xaml verbosity.
Set AccentColour of the application & Listen to Accent Colour changes from System and Update accent colour of app.
-
APPROACH 1: Take the dependency of CSWinRT, Windows SDK (software development kits) to use the UISettings class which can ease listening to UI (user interfaces) Settings changes like Accent Colour change or System Theme Change. We could introduce an internal Helper class which interacts with the UISettings class and will raise SystemAccentColorChanged and SystemThemeChanged events. The event handlers will sit inside ThemeManger and will handle these events by applying the accent colour and theme.
Pros:
Not repeating code, reuse.
Cons:
Extra dependency addition (UISettings).
Addition of another class (ColorsHelper) -
APPROACH 2: SYSTEMTHEMEWATCHER
We can create a SystemThemeWatcher class which will watch by hooking into WndProc WININICHANGE message. Whenever the message arrives the ThemeManager can be called to update the theme/accent color.
Cons:
Extra class addition (SystemThemeWatcher)
Extra Plumbing needed.
Pros
No dependency, everything inside wpf.
Risks
No response
Metadata
Metadata
Assignees
Labels
Type
Projects
Status