Resource Dictionaries and Styling
When an application has several windows with an editor, their settings belong in one place: a resource dictionary that holds the styles of the TextControl and its bars, the color sets and the value converters, merged into App.xaml so that every window uses it. This is ordinary WPF, with one rule that decides what goes where: a style can set dependency properties only, so some settings of TX Text Control live in the dictionary as objects that a window assigns.
What each color and style property of TX Text Control does is described in Styles, Resources, and Themes. Read on for how to organize those settings for a whole application.
What a Style Can Set
A Setter in a Style needs a dependency property. For the controls of TX Text Control, these are:
| Control | Dependency properties a style can set |
|---|---|
| TextControl | ViewMode, ZoomFactor, BorderThickness, AcceptsTab, ControlChars, IsUndoEnabled, IsTabStop, SelectObjects, DocumentTargetMarkers, DrawingMarkerLines, TextFrameMarkerLines, the scroll bar visibilities, the font properties and the other properties inherited from Control |
| StatusBar | FontFamily, FontSize, FontStyle, FontWeight and the inherited properties |
| Sidebar | Its own properties, such as TitleFontSize, TitleForeground, ShowPinButton, ShowCloseButton and DialogStyle |
| RulerBar | The inherited properties only |
The reference marks only some of the properties of the TextControl with "This is a dependency property.", among them ZoomFactor, AcceptsTab, IsUndoEnabled and the scroll bar visibilities; ViewMode, SelectObjects and the listed properties of the Sidebar are dependency properties in the assembly as well, although the reference doesn't say so. The same properties can be the target of a binding, as Data Binding explains.
Properties such as DisplayColors of the TextControl, BorderStyle and DisplayColors of the bars, or ScaleUnit of the ruler are ordinary .NET properties. A style can't set them; set them on the element in XAML, with a StaticResource from the dictionary, or in code.
Create the Resource Dictionary
Add a folder Themes to the project and in it a resource dictionary, EditorResources.xaml (Add > Resource Dictionary (WPF)):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPF="clr-namespace:TXTextControl.WPF;assembly=TXTextControl.WPF">
<!-- Every TextControl of the application -->
<Style TargetType="WPF:TextControl">
<Setter Property="ViewMode" Value="PageView" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="ZoomFactor" Value="100" />
</Style>
<!-- Every StatusBar of the application -->
<Style TargetType="WPF:StatusBar">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontSize" Value="12" />
</Style>
<!-- Every Sidebar of the application -->
<Style TargetType="WPF:Sidebar">
<Setter Property="TitleFontSize" Value="14" />
<Setter Property="ShowPinButton" Value="False" />
</Style>
<!-- Color sets, assigned with StaticResource or in code -->
<WPF:TextControlColors x:Key="LightEditorColors"
DesktopColor="#F5F6F7"
DarkShadowColor="#F5F6F7"
LightShadowColor="#F5F6F7" />
<WPF:TextControlColors x:Key="DarkEditorColors"
DesktopColor="#FF2B2B2B"
DarkShadowColor="#FF2B2B2B"
LightShadowColor="#FF2B2B2B" />
<WPF:StatusBarColors x:Key="StatusColors"
BackColor="#FFF0F0F0"
ForeColor="#FF333333" />
<!-- Converters for bindings anywhere in the application -->
<WPF:MeasureConverter x:Key="PointConverter" Unit="Point" Decimals="1" />
<WPF:ColorConverter x:Key="DrawingColorConverter" />
</ResourceDictionary>
The dictionary works as follows:
- A
Stylewith aTargetTypeand nox:Keyis an implicit style: it applies to every element of that type in the scope of the dictionary, unless the element sets the property itself. - The light color set repeats the desktop and shadow colors of the TX Words ribbon sample, which gives the area around the pages one flat color. TextControlColors has further properties for the page, the text and form fields.
- StatusBarColors is used only when the
BorderStyleof the status bar isColorScheme. - The converters are the ones described in Value Converters, declared once for all windows.
Merge It Into the Application
App.xaml makes the dictionary part of the application resources:
<Application x:Class="MyWPFApp.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="Themes/EditorResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Every window of the application now finds the styles, the color sets and the converters by their keys. A window, a panel or a single control can still override a value: a style in Window.Resources replaces the application style for that window, and an attribute on the element wins over both.
Use the Resources in a Window
The following MainWindow.xaml takes the colors and the converter from the dictionary. The TextControl gets PageView, no border and 100 percent zoom from the implicit style without a single attribute for them:
<Window x:Class="MyWPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPF="clr-namespace:TXTextControl.WPF;assembly=TXTextControl.WPF"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<ToolBar DockPanel.Dock="Top">
<CheckBox x:Name="darkCheckBox" Content="Dark desktop"
Checked="DarkCheckBox_Changed" Unchecked="DarkCheckBox_Changed" />
<TextBox Width="50"
Text="{Binding ElementName=textControl1, Path=InputFormat.FontSize,
Converter={StaticResource PointConverter}, Mode=TwoWay}" />
</ToolBar>
<WPF:StatusBar x:Name="statusBar1" DockPanel.Dock="Bottom"
BorderStyle="ColorScheme"
DisplayColors="{StaticResource StatusColors}" />
<WPF:TextControl x:Name="textControl1"
StatusBar="statusBar1"
DisplayColors="{StaticResource LightEditorColors}" />
</DockPanel>
</Window>
StaticResource looks the resource up once, when the window is loaded, and works for the ordinary .NET properties DisplayColors and BorderStyle as well. DynamicResource, which follows later changes of a resource, requires a dependency property, so it can't be used for these properties.
Switch Colors at Run Time
Because DisplayColors isn't a dependency property, a new color set is assigned in code. The handler of the check box in MainWindow.xaml.cs looks up the other set in the resources:
using System.Windows;
using TXTextControl.WPF;
namespace MyWPFApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DarkCheckBox_Changed(object sender, RoutedEventArgs e)
{
// DisplayColors is not a dependency property: assign the resource in code
string key = darkCheckBox.IsChecked == true ? "DarkEditorColors" : "LightEditorColors";
textControl1.DisplayColors = (TextControlColors)FindResource(key);
}
}
}
FindResource searches the resources of the window, then those of the application, and throws an exception when the key doesn't exist; TryFindResource returns null instead. Every property the new TextControlColors object doesn't set returns to its default value when the object is assigned to DisplayColors, so define every color that differs from the default in each set.
Run the application and check Dark desktop: the area around the pages turns dark gray.
Next Steps
- Styles, Resources, and Themes: every color of the editor, the template parts and the icons of the ribbon.
- Value Converters: the converters of the dictionary in bindings.
- WPF Layout and Resizing: where the styled controls go in the window.
- Localization and Resource Kit: texts and icons in other languages.