How to create a dynamically changeable style with WPF in external library? - c#

I am trying to make a library (with WPF) that will contain some of my basic templates for windows , buttons and some another new costume controls and templates for later use in projects .
I am still new to WPF . I understood that i should create a new ResourceDictionary in my external library and put there all my styles and their dynamic variables . And later make a MergedResourceDictionary in my main application project that will give me access to use my usual styles and controls .
Well i success to create a basic experimental style with a template which uses two variables as DynamicResource , Just for testing i made a <Brush x:Key="brush_back_standard">RoyalBlue</Brush> . This is used for regular back coloring . Then i declared to use this brush dynamically in the style itself : <Grid Background="{DynamicResource brush_back_standard}">, And It works well .
My problem begins when i am trying to change the brush value a runtime : Style.Resources[ "brush_back_standard" ] = Brushes.Aqua;. To simulate the case that the user will change the theme in the settings of the application later at runtime . So that didn't work at all , and i got the exception : ResourceDictionary is read-only and cannot be modified. . So if it's really readonly then it's sort of useless to me because the user cannot change the template at runtime . I need to find some way to let the user change anything in the window template at run time .
Full style code (external library) :
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib">
<Style x:Key="window_standard"
TargetType="{x:Type Window}">
<Style.Resources>
<system:Double x:Key="thickness_shadow">10</system:Double>
<Brush x:Key="brush_back_standard">RoyalBlue</Brush>
</Style.Resources>
<Setter Property="WindowStyle"
Value="None" />
<Setter Property="AllowsTransparency"
Value="True" />
<Setter Property="BorderThickness"
Value="{DynamicResource thickness_shadow}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid Background="{DynamicResource brush_back_standard}">
<AdornerDecorator>
<ContentPresenter />
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode"
Value="CanResizeWithGrip">
<Setter TargetName="WindowResizeGrip"
Property="Visibility"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Accessing the library in my main application project :
<Application x:Class="diamond.sandbox.executer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="window_main.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/diamond.core;component/xaml/standard.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
The window_main.xaml which is magically affected by the style :
<Window x:Class="diamond.sandbox.window_main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="window_main" Height="250" Width="500"
Style="{DynamicResource window_standard}"
WindowStartupLocation="CenterScreen"
Loaded="initialise">
Initialise method (After window_main is loaded) :
private void initialise( object p_sender , RoutedEventArgs p_args )
{
Style.Resources[ "brush_back_standard" ] = Brushes.Aqua;
}

Well, you can't modify a Style (not even its Resources) at all because it has been frozen. The Style is a Freezable, and will be frozen by WPF when added to a ResourceDictionary. Instead, you should just modify the Window's Resources:
private void initialise( object p_sender , RoutedEventArgs p_args )
{
Resources[ "brush_back_standard" ] = Brushes.Aqua;
}

Related

How to easily allow users to update Styles used be elements in XAML (UWP)

This is for a Windows 10 UWP. I need to allow users to update values on Styles that are associated with elements used throughout the application (i.e allow users to change the font size of various textblocks, background color stackpanels etc.) .
I currently have all my Styles in a separately file.
My App.xaml is as below:
<Application
x:Class="MyTestApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Styles/Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
My Styles.xaml (partial) is as below:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:MyTestApp.Views"
xmlns:x1="using:System">
<Style x:Key="HeaderTextBlocks" TargetType="TextBlock" BasedOn="{StaticResource TitleTextBlockStyle}">
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Margin" Value="10,4,0,0"/>
</Style>
<Style x:Key="RegularTextBlocks" TargetType="TextBlock" BasedOn="{StaticResource TitleTextBlockStyle}">
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Margin" Value="0,0,0,0"/>
</Style>
</ResourceDictionary>
I refer to these styles on controls throughout the application using like this:
<TextBlock Style="{StaticResource HeaderTextBlocks}" />
I have created a Settings page (settings.xaml) which has textboxes for the users to update various style settings.
But I am not sure how I could bind these to the settings on the various styles on the styles.xaml file so that the styles are updated and the controls referring to the styles are updated when the user changes the value.
<TextBox Header="Font Size of Header TextBlocks" Text="{x:Bind HeaderTextBlocks.FontSize ???, Mode=TwoWay}" />
<TextBox Header="Font Size of Regular TextBlocks" Text="{x:Bind RegularTextBlocks.FontSize???, Mode=TwoWay}" />
Could someone please point me in the right direction? I am trying to do this with minimal (or no code behind) as possible.
Unfortunately this kind of user-defined styling is not easily available in UWP. You can however implement a kind of styling solution using data binding.
First step is to create a class like CustomUISettings which implements INotifyPropertyChanged and has properties like HeaderFontSize, etc.
Now on app start create an instance of this class and add it as app resource:
Application.Current.Resources["CustomUISettings"] = new CustomUISettings();
Now you can bind to the properties in this class anywhere in your code:
<TextBox FontSize="{Binding HeaderFontSize, Source={StaticResource CustomUISettings}}" />
You must use the classic {Binding} markup extension, because {x:Bind} does not support Source setting.
To modify the UI settings you can just retrieve the instance anywhere and set the properties as you see fit:
var customUISettings = (CustomUISettings)Application.Current.Resources["CustomUISettings"];
customUISettings.HeaderFontSize = 50;
You must make sure that all properties in CustomUISettings class fire the PropertyChanged event. You can see how to implement INotifyPropertyChanged interface for example here.

How to use ResourceDictionary from separate WpfControlLibrary to set style to RadioButton in Prism 6 Module?

In my Prism 6 WPF modular application I use WPF ControlLibrary project called 'CommonControlLibrary' which has 'SwitchButtonStyle.xaml' file comprising the ResourceDictionary.The ResourceDictionary is for styling of RadioButtons in my application. Below is my application structure:
Below I partially display the ResourceDictionary.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CommonControlLibrary"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" >
<!--Style for view-switching radiobuttons-->
<Style x:Key="MainViewRadioButtonStyle" TargetType="RadioButton">
<Setter Property="Background" Value="{x:Null}"/>
<Setter Property="Foreground" Value="#FF483D8B"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="BorderBrush" Value="#FF6A5ACD" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid>
. . . . . . . . . . . . . . . .
There are two Prism 6 Modules in my application 'Authorisation' and 'Calibration' (see picture above). Each module has corresponding View and RadioButton to switch to that View. Below I display XAML for RadioButton switching to 'Calibration' View:
<UserControl x:Class="Calibration.Views.CalibrationNavigationItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<RadioButton GroupName="MainNavigation" IsChecked="{Binding Path=IsChecked, Mode=TwoWay}"
AutomationProperties.AutomationId="CalibrationRadioButton">
Calibration
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<prism:InvokeCommandAction Command="{Binding NavigateToCalibrationCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</RadioButton>
</Grid>
Both of the RadioButtons are identical in XAML and each of them needs to be styled with MainViewRadioButtonStyle from ResourceDistionary that is in 'SwitchButtonStyle.xaml that is in 'CommonControlLibrary'. How can I include the reference to the ResourceDictionary in XAML of each RadioButton to apply 'MainViewRadioButtonStyle' style to the RadioButton that is in Prism 6 Module? Please show me it on example of 'Calibration' RadioButton XAML.
1) On top of the view add xaml code for a merged resource dictionary:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/CommonControlLibray;component/SwitchButtonStyle.xaml" />
2.) Add a Style attribute to you radio button:
<RadioButton Style={StaticResource MainViewRadioButtonStyle}" ...
or
Add an inplicit style for radio buttons:
<Style TargetType="RadioButton" BasedOn="{StaticResource MainViewRadioButtonStyle}" />
The implicit style will be applied to all RadioButtons on the view

How can I change the default style of WPF controls for a single assembly?

I've written a WPF plugin for some off-the-shelf product, and in this plugin I've used a theme/style to change the minimal width of all buttons like so:
<Style TargetType="Button">
<Setter Property="MinWidth" Value="80" />
</Style>
In the newest version of said off-the-shelf product they migrated from winforms to WPF themselves. Now when my plugin is loaded the style that previously just affected my plugged-in forms now affects all buttons in the application. This renders most UI's unusable.
I know I can use dictionary key based resources to make this style specific to my buttons, but this means I have to change each and every button in my plugin by hand, as well as not forget to set the style of each button in the future (and other elements this problem applies to). There are other options to make the style specific to a set of buttons, as seen in Is it possible to set a style in XAML that selectively affects controls? But I'm looking for a way to let my style affect only those of my plugin (so a bit more coarse than talked about in the referenced question). This plugin consists of multiple windows/views (tied together with Caliburn.Micro).
Is there a way to easily scope a style to for instance an assembly or namespace? I'd really like to define my resources just once. Currently it's defined at the Application.Resources level, if there's one more appropriate I'd like to hear that too.
With a ResourceDictionary, we can set default style wich will be applied without define style in Xaml.
DictionayNew.xaml :
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
xmlns:System="clr-namespace:System;assembly=mscorlib">
<!-- default button -->
<Style TargetType="{x:Type Button}">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="MinWidth" Value="80" />
</Style>
<!-- new button style -->
<Style x:Key="ActionButton" TargetType="{x:Type Button}">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="MinWidth" Value="75" />
<Setter Property="Height" Value="23" />
</Style>
<!-- new button style based on previous style -->
<Style x:Key="BigActionButton"
BasedOn="{StaticResource ActionButton}"
TargetType="{x:Type Button}">
<Setter Property="MinWidth" Value="150" />
<Setter Property="Height" Value="30" />
</Style>
</ResourceDictionary>
In your Xaml, use the dictionary :
<Window x:Class="CheckDoublonImageBing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DictionaryNew.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
</Grid>
</Window>
Then, use Button as usual or with new style
<Button Content="Refresh" />
<Button Content="Delete selected" Style="{DynamicResource ActionButton}" />
With no style defined, button will have default style defined in the dictionary.
EDIT :
You can set merged dictionary by code like this :
ResourceDictionary myResourceDictionary = new ResourceDictionary();
myResourceDictionary.Source = new Uri("DictionayNew.xaml", UriKind.Relative);
Application.Current.Resources.MergedDictionaries.Add(myResourceDictionary);
You need to specify a Key for your Style and apply the Style to all your Buttons.
<Style TargetType="Button" x:Key="MyButtonStyle">
<Setter Property="MinWidth" Value="80" />
</Style>
<Button Style="{StaticResource MyButtonStyle}"/>
Without a Key the Style is used for all Buttons in the application (as you have already noticed).

How to add specific Style to Framework Elements contained on a User Control, with XAML?

I create a basic UserControl, called InputTag; it has two FrameworkElements a TextBlock and a Textbox.
Using the dependency properties I control the input for this control. The name of dependency properties are "Field" for the textblock, and "Value" for the Textbox.
The user control is compiled into a DLL (Class Library) and then loaded on a Windows Application.
Here is the XAML code for the UserControl:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="FieldName" Text="Tag"/>
<TextBox x:Name="FieldValue" Grid.Column="0" Text="0"/>
</Grid>
And in the windows app MainWindow.xaml I insert the control like this
<t:InputTag Field="Name" Value="" />
I want to add different styles to the Textblock and the TextBox using a Resource Dictionary, it should work like this
<t:InputTag Field="Name" Value="" Style="{StaticResource InputTagStyle}"/>
For this example lets say we had two different font sizes. My Resource dictionary looks like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:Terra.Controls;assembly=Terra">
<Style x:Key="InputTagStyle" TargetType="{x:Type t:InputTag}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Style="{StaticResource InputTagStyle}"/>
<TextBox Style="{StaticResource InputTextStyle}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<Style x:Key="InputTagStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="15"/>
</Style>
<Style x:Key="InputTextStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="12"/>
</Style>
</ResourceDictionary>
The problem is that I don't know how to access a subkey of my control, and it seems that if I use ControlTemplate is like creating new content to my control.
You need to refer to import the resources into your xaml as a pack URI format thus to import your styling keys in the section like this:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Terra;component/Styles/ButtonStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>

Specifying a custom Window's default appearance in WPF?

I would like to make a library of some of my commonly used WPF controls, and one of these controls is a CustomWindow which inherits from the Window class. How can I get my CustomWindow to use a default appearance that is defined in the library with it?
I can replace
<Window x:Class="..." />
with
<MyControls:CustomWindow x:Class="..." />
and it works for the window behavior, but not the appearnce.
EDIT
Here is a simplified version of what I have so far:
Custom Window Control. Located in the Control library.
public class CustomChromeWindow: Window
{
static CustomChromeWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomChromeWindow),
new FrameworkPropertyMetadata(typeof(CustomChromeWindow)));
}
}
Window Style. Located in a Generic.xaml, a ResourceDictionary in the Themes folder of the control library
<Style TargetType="local:CustomChromeWindow">
<Setter Property="WindowStyle" Value="None" />
<Setter Property="Background" Value="Red" />
</Style>
Test Window. Startup window of a separate project that references the control library
<local:CustomChromeWindow
x:Class="MyControlsTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyControls;assembly=MyControls"
Title="MainWindow" Height="350" Width="525"
>
<Grid>
<TextBlock Text="This is a Test" />
</Grid>
</local:CustomChromeWindow>
What I end up getting is a window with the regular WindowStyle and a black background.
Use this xaml:
<Window x:Class="MyNamespace.CustomWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MyControls="MyNamespace">
<Window.Style>
<Style TargetType="MyControls:CustomWindow">
...
</Style>
</Window.Style>
<ContentPresenter />
</Window>
You may want to design a new theme for the window. If so place following theme in (your library)\Themes\Generic.xaml resource file:
<Style TargetType="{x:Type MyControls:CustomWindow}">
<Setter Property="WindowStyle" Value="None" />
<Setter Property="AllowsTransparency" Value="True" />
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MyControls:CustomWindow}">
<Border>
...
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Try adding this to the AssemblyInfo.cs file in your class library
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
How about this (put this in the default Cctor) :
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow)
, new FrameworkPropertyMetadata(typeof(CustomWindow)));`

Categories