I have a wpf app and I am messing with loading themes (light and dark), I made two simple resource dictionary files which are created in a shared assembly:
Dark Theme (same structure for the light theme, but with different color values):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush Color="#FF1E1E1E" x:Key="Background"/>
<SolidColorBrush x:Key="TextColorBrush" Color="White"/>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextColorBrush}"/>
</Style>
<Style TargetType="Grid">
<Setter Property="Background" Value="{StaticResource Background}"/>
</Style>
</ResourceDictionary>
In my main application, App.xaml I am referencing my 2 theme dictionaries as such
<Application x:Class="Foo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Foo.Core.WPF;component/Resources/Dictionary_DarkTheme.xaml" x:Name="DarkTheme"/>
<ResourceDictionary Source="pack://application:,,,/Foo.Core.WPF;component/Resources/Dictionary_LightTheme.xaml" x:Name="LightTheme"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
The way I am setting up the resources based on which theme I am choosing is done in the App.xaml.cs
public enum Skin { Light, Dark }
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public static Skin Skin { get; set; }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ChangeSkin(Skin.Light);
}
public void ChangeSkin(Skin newSkin)
{
Skin = newSkin;
if (Skin == Skin.Dark)
ApplyResources(Resources.MergedDictionaries[0].Source.ToString());
else if (Skin == Skin.Light)
ApplyResources(Resources.MergedDictionaries[1].Source.ToString());
}
private void ApplyResources(string src)
{
var dict = new ResourceDictionary() { Source = new Uri(src, UriKind.RelativeOrAbsolute) };
foreach (var mergeDict in dict.MergedDictionaries)
{
Resources.MergedDictionaries.Add(mergeDict);
}
foreach (var key in dict.Keys)
{
Resources[key] = dict[key];
}
}
}
And finally, my main window. Since I want these particular styles to be global I am not using any keys to identify them.
<Window x:Class="Foo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Label Content="hello"/>
</Grid>
</Window>
But my main issue is that the Label control doesn't show up in my application. I can see my background change color appropriately but my label control is just gone! What am I doing wrong? Many thanks in advance!
Do not add all theme ResourceDictionaries from the start to Application.Resources.MergedDictionaries, i.e. start with empty Application.Resources:
<Application x:Class="Foo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Then change the theme by replacing Application.Current.Resources.MergedDictionaries with the current theme:
private void ChangeSkin(Skin skin)
{
ResourceDictionary theme = null;
switch (skin)
{
case Skin.Light:
theme = new ResourceDictionary { Source = new Uri("pack://application:,,,/Foo.Core.WPF;component/Resources/Dictionary_LightTheme.xaml") };
break;
case Skin.Dark:
theme = new ResourceDictionary { Source = new Uri("pack://application:,,,/Foo.Core.WPF;component/Resources/Dictionary_DarkTheme.xaml") };
break;
}
if (theme != null)
{
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(theme);
}
}
When changing themes only means to replace Colors and Brushes, you may also move your Styles to Application.Resources and use DynamicResource in the Style Setters.
<Application x:Class="Foo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource TextColorBrush}"/>
</Style>
<Style TargetType="Grid">
<Setter Property="Background" Value="{DynamicResource Background}"/>
</Style>
</Application.Resources>
</Application>
Then your theme ResourceDictionaries would only contain Color and Brush resources:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush Color="#FF1E1E1E" x:Key="Background"/>
<SolidColorBrush x:Key="TextColorBrush" Color="White"/>
</ResourceDictionary>
Related
I need to change a color of application's TextBlocks at runtime in an Universal Windows App.
Universal Windows Apps don't support Dynamic Resources and I've been unsuccessfully exploring a few different ways to change color of TextBlock
<TextBlock Text="Test" Style="{StaticResource MyText}"/>
using the style
<Style x:Key="MyText" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextColor}" />
</Style>
My question is: How can I change the color of the TextBlock at runtime?
What follows are all attempts to change the color:
Initially, I followed this article+video Dynamically Skinning Your Windows 8 App and I stored TextColor in a separate dictionary file that I can swap in and out of MergedDictionaries
Day.xaml contains <SolidColorBrush x:Key="TextColor" Color="#FFDDEEFF" />
Night.xaml contains <SolidColorBrush x:Key="TextColor" Color="#FFFFDD99" />
In code:
ResourceDictionary _nightTheme = new ResourceDictionary() { Source = new Uri("ms-appx:///Themes/Night.xaml") };
ResourceDictionary _baseTheme = new ResourceDictionary() { Source = new Uri("ms-appx:///Themes/MyApp.xaml") };
// OnLaunched - I set a default theme to prevent exceptions
Application.Current.Resources.MergedDictionaries.Add(_dayTheme);
// Method that changes theme:
if (NightFall)
{
Application.Current.Resources.MergedDictionaries.Remove(_dayTheme);
Application.Current.Resources.MergedDictionaries.Add(_nightTheme);
}
else
{
Application.Current.Resources.MergedDictionaries.Remove(_nightTheme);
Application.Current.Resources.MergedDictionaries.Add(_dayTheme);
}
When this didn't work, I thought I need to clear the dictionaries:
ResourceDictionary _baseTheme = new ResourceDictionary() { Source = new Uri("ms-appx:///Themes/MyApp.xaml") };
// Method that changes theme:
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(_baseTheme);
if (NightFall)
{
Application.Current.Resources.MergedDictionaries.Add(_nightTheme);
}
else
{
Application.Current.Resources.MergedDictionaries.Add(_dayTheme);
}
I also tried to refresh the frame in the method that changes dictionaries, to no avail
var frame = Window.Current.Content as Frame;
frame.Navigate(frame.Content.GetType());
In another attempt I tried to create a dictionary at runtime and update it
ResourceDictionary _dynamicTheme = new ResourceDictionary();
// OnLaunched
_dynamicTheme.Add("TextColor", new SolidColorBrush(Windows.UI.Colors.Chocolate));
Application.Current.Resources.MergedDictionaries.Add(_dynamicTheme);
// Method that changes theme
_dynamicTheme.Remove("TextColor");
_dynamicTheme.Add("TextColor", new SolidColorBrush(NightFall ? Windows.UI.Colors.Chocolate : Windows.UI.Colors.Cornsilk));
Finally, I realized that perhaps StaticResource makes the color immutable, so I decided to give ThemeResource a try. I've modified my themes:
<Style x:Key="MyText" TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource MyTextColor}" />
</Style>
Day.xaml
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="MyTextColor" Color="#FFDDEEFF" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
Night.xaml
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="MyTextColor" Color="#FFFFDD99" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
I swapped the methods in and out of the Application.Current.Resources.MergedDictionaries just like in previous attempts.
Again, the color doesn't change, even if I fake-refresh the Frame
I was facing the same issue several month ago, i couldn't fix the problem until i came across the following blog post which propose a pretty good generic solution.
Basically what you need to do is :
First
add the following helper Frame class, which will replace your default Frame
public class ThemeAwareFrame : Frame
{
private static readonly ThemeProxyClass _themeProxyClass = new ThemeProxyClass();
public static readonly DependencyProperty AppThemeProperty = DependencyProperty.Register(
"AppTheme", typeof(ElementTheme), typeof(ThemeAwareFrame), new PropertyMetadata(default(ElementTheme), (d, e) => _themeProxyClass.Theme = (ElementTheme)e.NewValue));
public ElementTheme AppTheme
{
get { return (ElementTheme)GetValue(AppThemeProperty); }
set { SetValue(AppThemeProperty, value); }
}
public ThemeAwareFrame(ElementTheme appTheme)
{
var themeBinding = new Binding { Source = _themeProxyClass, Path = new PropertyPath("Theme"), Mode = BindingMode.OneWay };
SetBinding(RequestedThemeProperty, themeBinding);
AppTheme = appTheme;
}
sealed class ThemeProxyClass : INotifyPropertyChanged
{
private ElementTheme _theme;
public ElementTheme Theme
{
get { return _theme; }
set
{
_theme = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The idea behind the ThemeAwareFrame class as explained in by the blog post writer is:
I create a proxy class that will just be used to store the current theme, and,
if the theme is changed, to propagate it. It is a static field, so is
shared with all ThemeAwareFrame.
I add an AppTheme dependency property. When it will be changed, it
will changed in the proxy class.
In the ThemeAwareFrame constructor, I bind the ThemeRequested property
to the proxy class Theme property.
Second
Create your Light and Dark theme resources in the App.xaml :
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="MyTextColor" Color="DarkGray" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="MyTextColor" Color="White" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Third
in the App.Xaml.cs change the rootFrame to a ThemeAwareFrame instead of a simple Frame:
rootFrame = new ThemeAwareFrame(ElementTheme.Dark);
in the OnLaunched method :
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new ThemeAwareFrame(ElementTheme.Dark);
// TODO: change this value to a cache size that is appropriate for your application
rootFrame.CacheSize = 1;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// TODO: Load state from previously suspended application
}
//..
Forth
Use ThemeResource instead of staticResource when using a Theme related resource :
<Page.Resources>
<Style x:Key="MyText" TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource MyTextColor}" />
</Style>
</Page.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Test" Style="{StaticResource MyText}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Button Content="Dark Theme" Click="ChangeThemeToDarkClick" Grid.Row="1"></Button>
<Button Content="Light Theme" Click="ChangeThemeToLightClick" Grid.Row="2"></Button>
</Grid>
Finally
To change your app theme simply change the AppTheme property of your rootFrame like this:
private void ChangeThemeToLightClick(object sender, RoutedEventArgs e)
{
(Window.Current.Content as ThemeAwareFrame).AppTheme = ElementTheme.Light;
}
private void ChangeThemeToDarkClick(object sender, RoutedEventArgs e)
{
(Window.Current.Content as ThemeAwareFrame).AppTheme = ElementTheme.Dark;
}
I have this style in a Resource-File:
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="TextBox" x:Key="StandardTextBox"/>
<Setter Property="Foreground" Value="{StaticResource Color1}"/>
</Style>
</ResourceDictionary>
(Colors.xaml contains my brushes)
My Code to use the style:
ResourceDictionary TetxboxStyles = new ResourceDictionary();
TetxboxStyles.Source = (new Uri("TextboxStyles.xaml", UriKind.RelativeOrAbsolute));
Resources.MergedDictionaries.Add(TetxboxStyles);
tb_input.Style = (Style)Find("StandardTextBox");
This works without a problem but it doesn't work when I dynamically add the Colors-Resource via code instead of in the TextboxStyles-File:
ResourceDictionary TetxboxStyles = new ResourceDictionary();
TetxboxStyles.Source = (new Uri("TextboxStyles.xaml", UriKind.RelativeOrAbsolute));
//Adding the Colors.xaml Resource
ResourceDictionary Colors = new ResourceDictionary();
brushes.Source = (new Uri("Colors.xaml", UriKind.RelativeOrAbsolute));
TetxboxStyles.MergedDictionaries.Add(Colors);
Resources.MergedDictionaries.Add(TetxboxStyles);
tb_input.Style = (Style)Find("StandardTextBox");
Output-Error:
System.Windows.Markup.XamlParseException
"{DependencyProperty.UnsetValue}"
I replaced StaticResource with DynamicResource and it works
I have this custom control with my own logic
public class BackButton : Button
{
public BackButton()
{
this.DefaultStyleKey = typeof(Button);
this.Click += (s, e) => {
Services.NavigationService.GoBack();
};
}
}
I want to apply to it the default style BackButtonStyle. I do not want to edit StandardStyles.xaml so it's in another ResourceDictionary
<Style TargetType="controls:BackButton" BasedOn="{StaticResource BackButtonStyle}"/>
Referenced in App.xaml
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Common/StandardStyles.xaml"/>
<ResourceDictionary Source="Common/FrameworkStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
It is building but this exception is thrown :
Message = "Cannot find a Resource with the Name/Key BackButtonStyle [Line: 15 Position: 44]"
What am i doing wrong ?
BackButtonStyle has target type as Button, so you can't use BackButtonStyle as base style for target type of controls:BackButton.
<Style x:Key="BackButtonStyle" TargetType="Button">
I change the style of my user control by changing resource dictionaries. In other words I have:
Dictionary1.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="Grid">
<Setter Property="Background" Value="Green"></Setter>
</Style>
<SolidColorBrush x:Key="Foo" Color="Blue"></SolidColorBrush>
</ResourceDictionary>
Dictionary2.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="Grid">
<Setter Property="Background" Value="Black"></Setter>
</Style>
<SolidColorBrush x:Key="Foo" Color="Orange"></SolidColorBrush>
</ResourceDictionary>
UserControl1:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="103" d:DesignWidth="101">
<Grid >
<Ellipse Fill="{DynamicResource Foo}" />
</Grid>
</UserControl>
Code Behind
namespace WpfApplication1
{
using System; using System.Windows; using System.Windows.Controls;
public partial class UserControl1 : UserControl
{
public enum ControlTheme
{
Theme1 , Theme2
}
public UserControl1 ( )
{
InitializeComponent( );
}
public void ChangeTheme ( ControlTheme theme )
{
Resources.MergedDictionaries.Clear( );
int dic = 2;
if ( theme == ControlTheme.Theme1 )
dic = 1;
ResourceDictionary rd = new ResourceDictionary( );
rd.Source = new Uri( #"pack://application:,,,/WpfApplication1;component/Dictionary" + dic + ".xaml" );
Resources.MergedDictionaries.Add( rd );
}
}
}
Now I am able to change themes dynamically by calling the method: ChangeTheme
The problem that I have now is that if I place:
<UserControl.Resources>
<ResourceDictionary Source="Dictionary1.xaml" ></ResourceDictionary>
</UserControl.Resources>
on UserControl1 The method ChangeTheme no longer works. I am looking for a method that does something like:
//PseudoCode
var itemToRemove = this.UserControlResources.resources.where(x=> x.isDictionary==true);
this.UserControlResources.Remove(itemToRemove);
You are setting your a Dictionary in the Xaml without any MergedDictionary's so when you create your Merged Dictinarys they are being overridden by the base Dictionary. You can try one of two things.
The first being is to Create a MergedDictionary in your UserControls Xaml. This will work without changing your CodeBehind.
i.e.
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
The second would be to assign your newly created ResourceDictionary to the UserControls Resources thus overwriting the pre-existing ResourceDictionary. This will work without changing your Xaml.
i.e.
public void ChangeTheme(ControlTheme theme)
{
int dic = 2;
if (theme == ControlTheme.Theme1)
dic = 1;
ResourceDictionary rd = new ResourceDictionary();
rd.Source = new Uri(#"pack://application:,,,/WpfApplication1;component/Dictionary" + dic + ".xaml");
this.Resources.Clear();
this.Resources = rd;
}
I've got a simple custom control :
namespace Application.Custom_Controls
{
public class ReadOnlyTextBox : TextBox
{
public ReadOnlyTextBox()
{
this.DefaultStyleKey = typeof(ReadOnlyTextBox);
this.IsReadOnly = true;
}
}
}
And a custom style to make the control look like a TextBlock (in App.xaml).
<Application
x:Class="Application.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:tb = "clr-namespace:Application.Custom_Controls"
>
<!--Application Resources-->
<Application.Resources>
<Style x:Key="ReadOnlyTextBox" TargetType="tb:ReadOnlyTextBox">
//...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="tb:ReadOnlyTextBox">
//...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But when I'm using it in my application it does'nt display at all.
It diplays as a normal TextBox if I remove this.DefaultStyleKey = typeof(ReadOnlyTextBox);.
How to apply this style to my custom control in code behind ?
By the way, this style works well in xaml with Style="{StaticResource ReadOnlyTextBox}", but I can't use xaml in this case.
Thanks in advance.
this.Style = (Style)Application.Current.Resources["ReadOnlyTextBox"];
Add this line to the constructor of the ReadOnlyTextBox