Install or change a Resource theme in code

My questions are very specific to ThemeResources in the Windows 10 Store app. Unfortunately, some of the things available in the "classic" WPF are different or not available here.

What I'm trying to achieve for a lot of ui elements:

  • Allow the user to use the color of the system accent (in XAML, this will be {ThemeResource SystemAccentColor} as the value.)
  • Allow user to use custom / fixed color. (I could override the SystemAccentColor key in resourcedictionary)
  • Allows you to switch between system emphasis and custom color at runtime (I can snap to color instead of using a resource)

But I did not find a good solution to achieve all this. If I have my own resource dictionary with custom color, I won’t get rid of it when the user wants to return to the color accent system. And the use of the property that I am linking has a drawback that I do not understand if the user changes the accent color in the system settings while the application is running - using the {ThemeResource} markup.

Any ideas how to do this correctly? If it was possible to install ThemeResource code from the code, I could write some behavior for this, but it doesn't seem to be available.

+6
source share
5 answers

On Windows 10, the name "Accent Color" changes to "SystemControlHighlightAccentBrush" and ThemeResource

Usage example

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

To override it, just change its value in App.xaml

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

To switch, it is a little more complicated First, you need to set the entire 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 on the page or in the code behind you set the appropriate theme

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

or in c #

 TestTextBlock.RequestedTheme = ElementTheme.Dark; 
+4
source

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

XAML:

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

WITH#

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

The color of the element border will be the same as the accent color selected in the Windows settings, and it will change even when your application is launched and the user changes it.

+2
source

As soon as I ran into the same problem and I also did not find a way to programmatically change ThemeResource so that it changes along with the phone theme. However, there is a way to achieve what you want, but it is cumbersome and may require a lot of work if you want to implement this for many controls.

The main idea is to use VisualStates to change from / to ThemeResource - the states are defined in xaml, so this will work with ThemeResources. Then, in the code, you can trigger the change back to the phone theme value. Below is a sample button that changes the color of the theme / user.

 <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Button Name="ColorBtn" Content="Change users color to green rom red"/> <local:ExtendedButton x:Name="UserBtn" Content="Change to user theme" UserBackground="Red"> <local:ExtendedButton.Style> <Style TargetType="local:ExtendedButton"> <!--default style 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 the 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 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))); } } 

This is much higher than just changing one color, but it should work and maybe give you an idea. This is also DependencyProperties, so you can use binding if necessary.

+1
source

I use this to set newAccentColor until I find a way to do this without switching the theme. This updates all brushes received based on the 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
source

I have a solution based on a pair of "helper" classes. At first it’s just an object container with DependencyProperty Value , which can be linked 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 } 

The following is the basis of the solution: a "selector" that contains a group of links and selects from them based on the 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 see, this class contains a collection of links and associates its SelectedObject property with the Value corresponding link. This binding is updated when the SelectedIndex changes and when the link collection itself changes.

Obviously, these classes cannot be used in XAML, because they are parameterized by type T (which should be derived from DependencyObject ). However, theirs is just a subclass:

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

The trick is to put the BrushSelector in some available ResourceDictionary (like your Resources Page ) and then bind it to the 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 there is no need to specify <DependencyObjectCollection> when defining BrushSelector in XAML due to the [ContentProperty] attribute in the selector class.

A few other comments - firstly, I would prefer SelectedObject to be a read-only DependencyProperty , as it should never be set by markup or code outside the selector, but UWP does not yet support this. Secondly, the References property must be of the DependencyObjectCollection type and the property itself must be DependencyProperty otherwise the theme changes are not propagated correctly. Finally, you can even use your own theme resources, and if your application does not indicate an explicit theme, when you change the theme in the Windows control panel (for example, “Light” → “Dark” or vice versa), these colors will be updated as Well.

0
source

Source: https://habr.com/ru/post/1234560/


All Articles