Kinnara / ModernWpf

Modern styles and controls for your WPF applications
MIT License
4.51k stars 449 forks source link

Setting ThemeManager.Current.AccentColor programmatically. #377

Open blakepell opened 3 years ago

blakepell commented 3 years ago

I have an issue, I'm likely missing something (I tried this through binding also with the bits from the sample app). The sample's apps color picker to set the accent is working.

Behavior: I set the ThemeManager.Current.AccentColor and it only sets if theme has not yet been loaded. If the Light theme loads initially, then I set it the color never changes. If the Light theme loads, I set the AccentColor, then I load the Dark theme it will show for the Dark theme but then the Dark themes accent color can't be changed (it's like the color is etched in stone after used in a theme). I observe that it is changing something still (for instance, the radio button color doesn't change, but when I hover over it I can see the accent color so I know -something- is happening).

I can include a repro project but it's pretty bare bones, here's my code:

App.xaml

<Application x:Class="WpfApp4.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp4"
             xmlns:ui="http://schemas.modernwpf.com/2019"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ui:ThemeResources>
                    <ui:ThemeResources.ThemeDictionaries>
                        <ResourceDictionary x:Key="Light" ui:ThemeDictionary.Key="Light">
                            <ResourceDictionary.MergedDictionaries>
                                <ResourceDictionary Source="/ModernWpf;component/ThemeResources/Light.xaml" />
                            </ResourceDictionary.MergedDictionaries>
                        </ResourceDictionary>
                        <ResourceDictionary x:Key="Dark" ui:ThemeDictionary.Key="Dark">
                            <ResourceDictionary.MergedDictionaries>
                                <ResourceDictionary Source="/ModernWpf;component/ThemeResources/Dark.xaml" />
                            </ResourceDictionary.MergedDictionaries>
                        </ResourceDictionary>
                    </ui:ThemeResources.ThemeDictionaries>
                </ui:ThemeResources>

                <!-- ModernWPF controls resources -->
                <ui:XamlControlsResources />

                <ui:IntellisenseResources Source="/ModernWpf;component/DesignTime/DesignTimeResources.xaml" />

            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.xaml

<Window x:Class="WpfApp4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp4"
        mc:Ignorable="d"
        xmlns:ui="http://schemas.modernwpf.com/2019"
        ui:WindowHelper.UseModernWindowStyle="True"
        ui:ThemeManager.IsThemeAware="True"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TabControl Margin="0,20,0,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
                    Style="{DynamicResource TabControlPivotStyle}"
                    Padding="12,0">
            <TabControl.Resources>
                <Thickness x:Key="PivotItemMargin">12,0,12,20</Thickness>
            </TabControl.Resources>

            <TabItem Header="General">
                <StackPanel Orientation="Vertical">
                    <StackPanel Orientation="Vertical">
                        <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                            <Label Background="{DynamicResource SystemAccentColorLight1Brush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemAccentColorLight2Brush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemAccentColorLight3Brush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemAccessBrush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemAccentColorDark1Brush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemAccentColorDark2Brush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemAccentColorDark3Brush}" Height="20" Width="20"></Label>
                        </StackPanel>

                        <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                            <Label Background="{DynamicResource SystemControlHighlightAltListAccentLowBrush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemControlHighlightAltListAccentMediumBrush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemControlHighlightAltListAccentHighBrush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemControlHighlightListAccentLowBrush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemControlHighlightListAccentMediumBrush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemControlHighlightListAccentHighBrush}" Height="20" Width="20"></Label>
                            <Label Background="{DynamicResource SystemControlHyperlinkTextBrush}" Height="20" Width="20"></Label>
                        </StackPanel>

                        <StackPanel Orientation="Horizontal">
                            <Button Content="Set Random Accent Color" Click="ButtonBaseRandomColor_OnClick"></Button>
                            <Button Content="Toggle Light/Dark" Click="ButtonBaseToggleTheme_OnClick" Margin="5,0,0,0"></Button>
                            <Label Background="{Binding RandomColor}" Height="20" Width="20" Margin="10,10,0,0"></Label>
                        </StackPanel>
                    </StackPanel>
                </StackPanel>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

MainWindow.xaml.cs

using ModernWpf;
using System;
using System.Windows;
using System.Windows.Media;

namespace WpfApp4
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        private void ButtonBaseToggleTheme_OnClick(object sender, RoutedEventArgs e)
        {
            if (ThemeManager.GetActualTheme(this) == ElementTheme.Dark)
            {
                ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
            }
            else
            {
                ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
            }
        }

        public static readonly DependencyProperty RandomColorProperty = DependencyProperty.Register(
            nameof(RandomColor), typeof(SolidColorBrush), typeof(MainWindow), new PropertyMetadata(default(SolidColorBrush)));

        public SolidColorBrush RandomColor
        {
            get => (SolidColorBrush) GetValue(RandomColorProperty);
            set => SetValue(RandomColorProperty, value);
        }

        private Random _rnd = new Random();

        private void ButtonBaseRandomColor_OnClick(object sender, RoutedEventArgs e)
        {
            int x = _rnd.Next(1, 7);

            switch (x)
            {
                case 1:
                    ThemeManager.Current.AccentColor = Colors.Green;
                    break;
                case 2:
                    ThemeManager.Current.AccentColor = Colors.DarkRed;
                    break;
                case 3:
                    ThemeManager.Current.AccentColor = Colors.Blue;
                    break;
                case 4:
                    ThemeManager.Current.AccentColor = Colors.Purple;
                    break;
                case 5:
                    ThemeManager.Current.AccentColor = Colors.DarkGoldenrod;
                    break;
                case 6:
                    ThemeManager.Current.AccentColor = Colors.Cyan;
                    break;
                case 7:
                    ThemeManager.Current.AccentColor = Colors.Fuchsia;
                    break;
            }

            this.RandomColor = new SolidColorBrush(ThemeManager.Current.AccentColor ?? Colors.Red);
        }
    }
}

Screenshots

AccentColor set to purple, but you can see the accent colors haven't changed.

image

Switch to dark, you can see, it picked up the purple:

image

AccentColor changed to green, but as you go between light and dark they stay on whatever the first set Accent color was for that theme.

image

blakepell commented 3 years ago

Maybe I don't understand what the AccentColor is meant to do. I was able to get the selected color on the Pivot tab to change but not by changing the theme managers accent color, I ended adding this (but I'm still curious what is the right way to change the accent color on the fly to something the user chooses that isn't in a full blown theme):

public static readonly DependencyProperty RandomColorProperty = DependencyProperty.Register(
    nameof(RandomColor), typeof(Color), typeof(MainWindow), new PropertyMetadata(default(Color)));

public Color RandomColor
{
    get => (Color) GetValue(RandomColorProperty);
    set => SetValue(RandomColorProperty, value);
}
<Window.Resources>
    <SolidColorBrush x:Key="TestAccentBrush" Color="{Binding RandomColor}"></SolidColorBrush>
    <StaticResource x:Key="PivotHeaderItemSelectedPipeFill" ResourceKey="TestAccentBrush" />
</Window.Resources>
blakepell commented 3 years ago

I ended up creating a ColorPaletteResources that I set the Accent on and kept a reference to. Then I added it to the Resources.MergedDictionaries for the Window. That seemed to do the trick in a relatively minimal amount of code. In testing I used a static property on the App.

Sharing for posterity (note: this is an proof of concept, probably not a best practice). I set the theme in the MainWindow.xaml, then in a button event I start and endless timer that updates the accent with a random color every 100ms.

App.xaml.cs

public static ColorPaletteResources ColorPalette { get; set; }

MainWindow.xaml

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/ModernWpf;component/ThemeResources/Dark.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

MainWindow.xaml.cs

private DispatcherTimer _timer;

private Random _rnd = new Random();

private void ButtonBaseRandomColor_OnClick(object sender, RoutedEventArgs e)
{
    if (!this.Resources.MergedDictionaries.Contains(App.ColorPalette))
    {
        this.Resources.MergedDictionaries.Add(App.ColorPalette);

        _timer = new DispatcherTimer();
        _timer.Interval = TimeSpan.FromMilliseconds(100);
        _timer.Tick += Timer_Tick;
        _timer.Start();
    }

    int x = _rnd.Next(1, 12);

    switch (x)
    {
        case 1:
            App.ColorPalette.Accent = Colors.Green;
            break;
        case 2:
            App.ColorPalette.Accent = Colors.DarkRed;
            break;
        case 3:
            App.ColorPalette.Accent = Colors.Blue;
            break;
        case 4:
            App.ColorPalette.Accent = Colors.Purple;
            break;
        case 5:
            App.ColorPalette.Accent = Colors.DarkGoldenrod;
            break;
        case 6:
            App.ColorPalette.Accent = Colors.Cyan;
            break;
        case 7:
            App.ColorPalette.Accent = Colors.Fuchsia;
            break;
        case 8:
            App.ColorPalette.Accent = Colors.Brown;
            break;
        case 9:
            App.ColorPalette.Accent = Colors.GreenYellow;
            break;
        case 10:
            App.ColorPalette.Accent = Colors.White;
            break;
        case 11:
            App.ColorPalette.Accent = Colors.Gray;
            break;
        case 12:
            App.ColorPalette.Accent = Colors.DeepPink;
            break;
    }
}

private void Timer_Tick(object sender, EventArgs e)
{
    ButtonBaseRandomColor_OnClick(sender, null);
}