Setting or modifying ThemeResource in code

后端 未结 5 655
野趣味
野趣味 2021-02-06 04:14

My questions is very specific to ThemeResources in a Windows 10 Store App. Unfortunately several things available in \"classic\" WPF are different or not available here.

相关标签:
5条回答
  • 2021-02-06 04:49

    I'm using this to set newAccentColor, until I can find a way to do it without toggling the theme. This updates all the derived brushes based on accent color:

    Application.Current.Resources["SystemAccentColor"] = newAccentColor;
    if (Window.Current.Content is FrameworkElement fe)
    {
          var requestedTheme = fe.RequestedTheme;
          fe.RequestedTheme = fe.RequestedTheme == ElementTheme.Light ? ElementTheme.Dark : ElementTheme.Light;
          fe.RequestedTheme = requestedTheme;
    }
    
    0 讨论(0)
  • 2021-02-06 04:50

    Once I've also faced the same problem and I also haven't found a way to programatically change ThemeResource so that it will change along with phone's theme. Nevertheless there is a way to achieve what you want, but it's cumbersome and may need a lot of work when you want to implement this to many controls.

    The basic idea is to use VisualStates to change from/to ThemeResource - the states are defined in xaml so this will work with ThemeResources. Then in code you can invoke the change back to phone's theme value. Here below is the sample button changing to theme's/user's color.

    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Button Name="ColorBtn" Content="Change users color to green rom red"/>
        <local:ExtendedButton x:Name="UserBtn" Content="Change to user's theme" UserBackground="Red">
            <local:ExtendedButton.Style>
                <Style TargetType="local:ExtendedButton">
                    <!--default style's setters-->
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="local:ExtendedButton">
                                <Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
                                    <VisualStateManager.VisualStateGroups>
                                        <VisualStateGroup>
                                            <VisualState x:Name="ThemeColor">
                                                <Storyboard>
                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="RootGrid">
                                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemColorControlAccentColor}"/>
                                                    </ObjectAnimationUsingKeyFrames>
                                                </Storyboard>
                                            </VisualState>
                                            <VisualState x:Name="UserColor"/>
                                        </VisualStateGroup>
                                        <!--rest of default visual states-->
                                    </VisualStateManager.VisualStateGroups>
                                    <ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </local:ExtendedButton.Style>
        </local:ExtendedButton>
    </StackPanel>
    

    and code behind:

    public class ExtendedButton : Button
    {
        public SolidColorBrush UserBackground
        {
            get { return (SolidColorBrush)GetValue(UserBackgroundProperty); }
            set { SetValue(UserBackgroundProperty, value); }
        }
    
        public static readonly DependencyProperty UserBackgroundProperty =
            DependencyProperty.Register("UserBackground", typeof(SolidColorBrush), typeof(ExtendedButton),
                new PropertyMetadata(new SolidColorBrush(Colors.Red), (s, e) =>
                { if ((s as ExtendedButton).IsUserTheme) (s as ExtendedButton).Background = e.NewValue as SolidColorBrush; }));
    
        // we need some property to indicate if to use user's theme or phone's
        public bool IsUserTheme
        {
            get { return (bool)GetValue(IsUserThemeProperty); }
            set { SetValue(IsUserThemeProperty, value); }
        }
    
        public static readonly DependencyProperty IsUserThemeProperty =
            DependencyProperty.Register("IsUserTheme", typeof(bool), typeof(ExtendedButton), new PropertyMetadata(false, (s, e) =>
            {
                if ((bool)e.NewValue)
                {
                    VisualStateManager.GoToState((s as ExtendedButton), "UserColor", false);
                    (s as ExtendedButton).Background = (s as ExtendedButton).UserBackground;                
                }
                else VisualStateManager.GoToState((s as ExtendedButton), "ThemeColor", false);
            }));
    }
    
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            Random random = new Random();
            UserBtn.Click += (s, e) => UserBtn.IsUserTheme = !UserBtn.IsUserTheme; ;
            ColorBtn.Click += (s, e) => UserBtn.UserBackground = new SolidColorBrush(Color.FromArgb(0xFF, (byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255)));
        }
    }
    

    It's a long way above just to change one color, but should work and maybe will give you an idea. Those are also DependencyProperties so you can use binding if needed.

    0 讨论(0)
  • 2021-02-06 04:59

    In Windows 10, the name "Accent Color" is changed to "SystemControlHighlightAccentBrush", and it's a ThemeResource

    Example using it

    <TextBlock Foreground="{ThemeResource SystemControlHighlightAccentBrush}"
                       Text="This is a sample text" />
    

    To override it, simply change value of it in App.xaml

    <Application.Resources>
        <SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Orange" />
    </Application.Resources>
    

    To switch, it's a little bit more difficult First, you need to set all the color for each theme in App.xaml

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary x:Key="Default">
                    <SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Orange" />
                </ResourceDictionary>
                <ResourceDictionary x:Key="Dark">
                    <SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Green" />
                </ResourceDictionary>
                <ResourceDictionary x:Key="Light">
                    <SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Blue" />
                </ResourceDictionary>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </Application.Resources>
    

    Then, in the page or in code behind, you set the corresponding theme

    <TextBlock x:Name="TestTextBlock"
                   Foreground="{ThemeResource SystemControlHighlightAccentBrush}"
                   RequestedTheme="Dark"
                   Text="This is a sample text" />
    

    or in C#

    TestTextBlock.RequestedTheme = ElementTheme.Dark;
    
    0 讨论(0)
  • 2021-02-06 04:59

    There is a way how to kinda set ThemeResource in code... I've tested it only on W10 Creators Update so it may not work on older versions, but you can create your own resource that is referencing the original ThemeResource you want to use and then use this resource:

    XAML:

    <SolidColorBrush x:Key="MyBorderBrush" Color="{ThemeResource SystemAccentColor}"/>
    

    C#:

    element.BorderBrush = (SolidColorBrush)Resources["MyBorderBrush"];
    

    The border color of element will be the same as the Accent Color selected in Windows Settings and it will change even when your app is running and user changes it.

    0 讨论(0)
  • 2021-02-06 05:01

    I have a solution based on a couple of 'helper' classes. First up is just a container object with a DependencyProperty Value that can be bound or set to {ThemeResource …}:

    public class DependencyObjectReference<T> : DependencyObject where T : DependencyObject
    {
        #region Properties
        public T Value
        {
            get { return (T)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        #endregion
    
        #region Static Data
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(nameof(Value),
                                        typeof(T),
                                        typeof(DependencyObjectReference<T>),
                                        new PropertyMetadata(default(T)));
        #endregion
    }
    

    Next is the meat of the solution: a 'selector' that contains a bunch of references and selects from them based on an index:

    [ContentProperty(Name = nameof(References))]
    public class DependencyObjectSelector<T> : DependencyObject where T : DependencyObject
    {
        #region Constructors
        public DependencyObjectSelector()
        {
            References = new DependencyObjectCollection();
        }
        #endregion
    
        #region Properties
        public DependencyObjectCollection References
        {
            get { return (DependencyObjectCollection)GetValue(ReferencesProperty); }
            set { SetValue(ReferencesProperty, value); }
        }
    
        public Int32 SelectedIndex
        {
            get { return (Int32)GetValue(SelectedIndexProperty); }
            set { SetValue(SelectedIndexProperty, value); }
        }
    
        public T SelectedObject
        {
            get { return (T)GetValue(SelectedObjectProperty); }
            set { SetValue(SelectedObjectProperty, value); }
        }
        #endregion
    
        #region Event Handlers
        private void Evt_OnVectorChangedReferences(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
        {
            UpdateSelectedObject();
        }
        #endregion
    
        #region Private Implementation Methods
        private void UpdateSelectedObject()
        {
            if  (
                References != null
                &&
                SelectedIndex >= 0
                &&
                SelectedIndex < References.Count
                &&
                References[SelectedIndex] is DependencyObjectReference<T>
                )
            {
                BindingOperations.SetBinding
                (
                    this,
                    SelectedObjectProperty,
                    new Binding
                    {
                        Source = References[SelectedIndex],
                        Path = new PropertyPath(nameof(DependencyObjectReference<T>.Value))
                    }
                );
            }
            else
            {
                ClearValue(SelectedObjectProperty);
            }
        }
    
        private void OnReferencesPropertyChanged(DependencyObjectCollection oldValue, DependencyObjectCollection newValue)
        {
            if (oldValue != null)
                oldValue.VectorChanged -= Evt_OnVectorChangedReferences;
    
            if (newValue != null)
                newValue.VectorChanged += Evt_OnVectorChangedReferences;
        }
    
        private static void ReferencesPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
        {
            DependencyObjectSelector<T>     _this = (DependencyObjectSelector<T>)dobj;
    
            _this.OnReferencesPropertyChanged(args.OldValue as DependencyObjectCollection, args.NewValue as DependencyObjectCollection);
        }
    
        private static void SelectedIndexPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
        {
            DependencyObjectSelector<T>     _this = (DependencyObjectSelector<T>)dobj;
    
            _this.UpdateSelectedObject();
        }
        #endregion
    
        #region Static Data
        public static readonly DependencyProperty ReferencesProperty =
            DependencyProperty.Register(nameof(References),
                                        typeof(DependencyObjectCollection),
                                        typeof(DependencyObjectSelector<T>),
                                        new PropertyMetadata(null, ReferencesPropertyChanged));
    
        public static readonly DependencyProperty SelectedIndexProperty =
            DependencyProperty.Register(nameof(SelectedIndex),
                                        typeof(Int32),
                                        typeof(DependencyObjectSelector<T>),
                                        new PropertyMetadata(-1, SelectedIndexPropertyChanged));
    
        public static readonly DependencyProperty SelectedObjectProperty =
            DependencyProperty.Register(nameof(SelectedObject),
                                        typeof(T),
                                        typeof(DependencyObjectSelector<T>),
                                        new PropertyMetadata(default(T)));
        #endregion
    }
    

    As you can see, this class holds a collection of references and binds its SelectedObject property to the Value of the appropriate reference. This binding is updated when SelectedIndex changes, and when the reference collection itself changes.

    These classes obviously can't be used in XAML as they are parameterized by type T (which must derive from DependencyObject). However, it's a simple matter to subclass them:

    public sealed class BrushReference : DependencyObjectReference<Brush>
    {
    }
    
    public sealed class BrushSelector : DependencyObjectSelector<Brush>
    {
    }
    

    The trick now is to place a BrushSelector in some accessible ResourceDictionary (such as your Page's Resources) and then bind to its SelectedObject property:

    <Page.Resources>
        <mynamespace:BrushSelector x:Key="MyBrushSelector" SelectedIndex="{x:Bind Path=MyViewModel.MyBrushIndex, Mode=OneWay}">
            <mynamespace:BrushReference Value="{ThemeResource SystemControlForegroundAccentColor}"/>
            <mynamespace:BrushReference Value="{ThemeResource SystemControlForegroundBaseHighBrush}"/>
            <mynamespace:BrushReference Value="Red"/>
            <mynamespace:BrushReference Value="Wheat"/>
        </mynamespace:BrushSelector>
    </Page.Resources>
    
    <!-- ... -->
    
    <TextBlock
        Text="..."
        Foreground="{Binding Source={StaticResource MyBrushSelector}, Path=SelectedObject}"
        />
    

    Note that it's not necessary to specify a <DependencyObjectCollection> when defining the BrushSelector in XAML because of the [ContentProperty] attribute on the selector class.

    A few other comments -- first, I'd prefer to have the SelectedObject be a read-only DependencyProperty, as it should never be set by markup or code outside the selector, but UWP doesn't yet support that. Second, the References property must be of type DependencyObjectCollection and the property itself must be a DependencyProperty or the theme changes don't propagate correctly. Finally, you can even use your own theme resources, and if your app doesn't specify an explicit theme, when you change the theme in Windows Control Panel (e.g., Light -> Dark or vice-versa), those colors will update as well.

    0 讨论(0)
提交回复
热议问题