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.
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;
}
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.
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;
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.
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.