NoesisGUI

Styles and Templates Tutorial

github Tutorial Data

Styles

A style, represented by the Style class, is a pretty simple entity. Its main function is to group together property values that could otherwise be set individually. You can share this group of values among multiple elements.

StylingTutorialImg1.jpg

The texts in the previous image are customized by setting six properties. Without a Style, you would need to duplicate these identical assignments on all the texts, as shown next:

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <TextBlock Text="John Doe" FontSize="18" FontWeight="Bold"
      Foreground="Crimson" Background="LightYellow" Padding="20,5" Margin="5"/>
    <TextBlock Text="251 Amazing Road" FontSize="18" FontWeight="Bold"
      Foreground="Crimson" Background="LightYellow" Padding="20,5" Margin="5"/>
    <TextBlock Text="Great City, ST 23145" FontSize="18" FontWeight="Bold"
      Foreground="Crimson" Background="LightYellow" Padding="20,5" Margin="5"/>
    <TextBlock Text="(555) 123.4224" FontSize="18" FontWeight="Bold"
      Foreground="Crimson" Background="LightYellow" Padding="20,5" Margin="5"/>
</StackPanel>

But with a Style, you can add a level of indirection setting them in one place and pointing each TextBlock to this new element, as shown in the following code snippet.

 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <StackPanel.Resources>
         <Style x:Key="TextStyle">
             <Setter Property="TextBlock.FontSize" Value="18"/>
             <Setter Property="TextBlock.FontWeight" Value="Bold"/>
             <Setter Property="TextBlock.Foreground" Value="Crimson"/>
             <Setter Property="TextBlock.Background" Value="LightYellow"/>
             <Setter Property="TextBlock.Padding" Value="20,5"/>
             <Setter Property="TextBlock.Margin" Value="5"/>
         </Style>
     </StackPanel.Resources>
     <TextBlock Text="John Doe" Style="{StaticResource TextStyle}"/>
     <TextBlock Text="251 Amazing Road" Style="{StaticResource TextStyle}"/>
     <TextBlock Text="Great City, ST 23145" Style="{StaticResource TextStyle}"/>
     <TextBlock Text="(555) 123.4224" Style="{StaticResource TextStyle}"/>
 </StackPanel>

Style uses a collection of Setters to set the target properties. Creating a Setter is just a matter of specifying the name of a dependency property and its desired value.

If you want to enforce that a Style can only be applied to a particular type, you can set its TargetType property accordingly. Also you no longer need to prefix the property names inside Setters with the type name. Applying a TargetType to a Style gives you another feature as well. If you omit its Key, the Style gets implicitly applied to all elements of that target type within the same scope. This is typically called a typed style as opposed to a named style. The scope of a typed Style is determined by the location of the Style resource.

 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <StackPanel.Resources>
         <Style TargetType="{x:Type TextBlock}">
             <Setter Property="FontSize" Value="18"/>
             <Setter Property="FontWeight" Value="Bold"/>
             <Setter Property="Foreground" Value="Crimson"/>
             <Setter Property="Background" Value="LightYellow"/>
             <Setter Property="Padding" Value="20,5"/>
             <Setter Property="Margin" Value="5"/>
         </Style>
     </StackPanel.Resources>
     <TextBlock Text="John Doe"/>
     <TextBlock Text="251 Amazing Road"/>
     <TextBlock Text="Great City, ST 23145"/>
     <TextBlock Text="(555) 123.4224"/>
 </StackPanel>

Styles can also define some values of an element based on one or more conditions through the use of Triggers. For example, if we want that text changes its foreground color and moves a bit to the right while the mouse pointer is over, we will add the following code:

 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <StackPanel.Resources>
         <Style TargetType="{x:Type TextBlock}">
             <Setter Property="FontSize" Value="18"/>
             <Setter Property="FontWeight" Value="Bold"/>
             <Setter Property="Foreground" Value="Crimson"/>
             <Setter Property="Background" Value="LightYellow"/>
             <Setter Property="Padding" Value="20,5"/>
             <Setter Property="Margin" Value="5"/>
             <Style.Triggers>
                 <Trigger Property="IsMouseOver" Value="True">
                     <Setter Property="Foreground" Value="Blue"/>
                     <Setter Property="Margin" Value="10,5,0,5"/>
                 </Trigger>
             </Style.Triggers>
         </Style>
     </StackPanel.Resources>
     <TextBlock Text="John Doe"/>
     <TextBlock Text="251 Amazing Road"/>
     <TextBlock Text="Great City, ST 23145"/>
     <TextBlock Text="(555) 123.4224"/>
 </StackPanel>

And even better, we can use some animation to make the transition smoother. This is possible thanks to EnterActions and ExitActions trigger properties.

 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <StackPanel.Resources>
         <Style TargetType="{x:Type TextBlock}">
             <Setter Property="FontSize" Value="18"/>
             <Setter Property="FontWeight" Value="Bold"/>
             <Setter Property="Foreground" Value="Crimson"/>
             <Setter Property="Background" Value="LightYellow"/>
             <Setter Property="Padding" Value="20,5"/>
             <Setter Property="Margin" Value="5"/>
             <Style.Triggers>
                 <Trigger Property="IsMouseOver" Value="True">
                     <Trigger.EnterActions>
                         <BeginStoryboard>
                             <Storyboard>
                                 <ColorAnimation Duration="0:0:0.1" To="Blue"
                                   Storyboard.TargetProperty="Foreground.Color"/>
                                 <ThicknessAnimation Duration="0:0:0.1" To="10,5,0,5"
                                   Storyboard.TargetProperty="Margin"/>
                             </Storyboard>
                         </BeginStoryboard>
                     </Trigger.EnterActions>
                     <Trigger.ExitActions>
                         <BeginStoryboard>
                             <Storyboard>
                                 <ColorAnimation Duration="0:0:0.2" To="Crimson"
                                   Storyboard.TargetProperty="Foreground.Color"/>
                                 <ThicknessAnimation Duration="0:0:0.2" To="5"
                                   Storyboard.TargetProperty="Margin"/>
                             </Storyboard>
                         </BeginStoryboard>
                     </Trigger.ExitActions>
                 </Trigger>
             </Style.Triggers>
         </Style>
     </StackPanel.Resources>
     <TextBlock Text="John Doe"/>
     <TextBlock Text="251 Amazing Road"/>
     <TextBlock Text="Great City, ST 23145"/>
     <TextBlock Text="(555) 123.4224"/>
 </StackPanel>
StylingTutorialImg2.jpg

Templates

A template allows you to completely replace a control's visual tree with anything you can imagine while keeping all of its functionality intact. The source code for every control is completely separated from its default visual tree representations. Control templates are represented by the ControlTemplate class that derives from the abstract FrameworkTemplate class which defines the VisualTree content property, that is, the tree of elements that specify the desired look of a control.

Next we are going to use templates to redesign the appearance of a common Button control.

<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Ellipse Stroke="Black" Fill="Silver"/>
            </Grid>
        </ControlTemplate>
    </Grid.Resources>
    <Button Template="{StaticResource ButtonTemplate}" Width="100" Height="100" Content="Rounded!"/>
</Grid>

Now we have a simple circle representing our button although the content, the text in this case, disappeared. If you are creating a control template that is meant to be broadly reusable, you need to do some work to respect various properties of the target control. We could add a TextBlock to the control template and use data binding to show the Button content. Of course, a Button can contain non-text Content, so using a TextBlock to display it, creates an artificial limitation. To ensure that all types of Content get displayed properly in the template, we can use a ContentPresenter, that was specifically designed for that purpose.

 <Grid
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <Grid.Resources>
         <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
             <Grid>
                 <Ellipse Stroke="Black" Fill="Silver"/>
                 <ContentPresenter Content="{TemplateBinding Content}"
                   HorizontalAlignment="Center" VerticalAlignment="Center"/>
             </Grid>
         </ControlTemplate>
     </Grid.Resources>
     <Button Template="{StaticResource ButtonTemplate}" Width="100" Height="100" Content="Rounded!"/>
 </Grid>

With this template, the button has lost some interactivity feedback, user has no visual reference to know if the button is responding to mouse input or not. As with styles, templates can spefify triggers, so we will use them to generate different representations for the different states of the button.

 <Grid
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <Grid.Resources>
         <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
             <Grid>
                 <Ellipse x:Name="Circle" Stroke="Black" Fill="Silver"/>
                 <ContentPresenter Content="{TemplateBinding Content}"
                   HorizontalAlignment="Center" VerticalAlignment="Center"/>
             </Grid>
             <ControlTemplate.Triggers>
                 <Trigger Property="IsMouseOver" Value="True">
                     <Setter TargetName="Circle" Property="Stroke" Value="Red"/>
                     <Setter TargetName="Circle" Property="Fill" Value="Pink"/>
                 </Trigger>
                 <Trigger Property="IsPressed" Value="True">
                     <Setter TargetName="Circle" Property="Stroke" Value="Green"/>
                     <Setter TargetName="Circle" Property="Fill" Value="PaleGreen"/>
                 </Trigger>
             </ControlTemplate.Triggers>
         </ControlTemplate>
     </Grid.Resources>
     <Button Template="{StaticResource ButtonTemplate}" Width="100" Height="100" Content="Rounded!"/>
 </Grid>
StylingTutorialImg3.jpg

At the beginning of this section we explained that controls are lookless and have an implementation that is completely independent from their visuals. But this is not entirely true. Some controls have built-in logic to control their visual behavior, but only when the template performs the correct commitment to enable this logic. The secret commitment performed by several controls is to look for elements in the control template's visual tree with specific names (always of the form PART_XXX) and sometimes with specific types. If such elements are found, extra behavior is applied to them. The specific names and behaviors depend on the control.

For example, if a ProgressBar control template has elements named PART_Indicator and PART_Track, the control ensures that the width of PART_Indicator remains the correct percentage of the width of PART_Track, based on ProgressBar's Value, Minimum and Maximum properties.

Themes

Although all the control templates thus far are applied directly to elements for simplicity, it is more common to set a Control's Template property inside a Style and then apply that style to the desired elements. Besides the convenience of combining a template with arbitrary property settings, there are important advantages to doing this:

  • It gives you the effect of default templates. For example, when a typed Style gets applied to elements by default, and that Style contains a custom control template, it gets applied without any explicit markings on those elements!
<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  HorizontalAlignment="Center" VerticalAlignment="Center">
    <StackPanel.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Ellipse x:Name="Circle" Stroke="Black" Fill="Silver"
                 Width="100" Height="50"/>
                <ContentPresenter Content="{TemplateBinding Content}"
                  HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Circle" Property="Stroke" Value="Red"/>
                    <Setter TargetName="Circle" Property="Fill" Value="Pink"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="Circle" Property="Stroke" Value="Green"/>
                    <Setter TargetName="Circle" Property="Fill" Value="PaleGreen"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Template" Value="{StaticResource ButtonTemplate}"/>
        </Style>
    </StackPanel.Resources>
    <Button Content="First"/>
    <Button Content="Second" Margin="0,5"/>
    <Button Content="Third"/>
</StackPanel>
StylingTutorialImg4.jpg
  • It also enables you to provide default yet overridable property values that control the look of the template. In other words, it enables you to respect the templated parent's properties but still provide your own default values.
<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  HorizontalAlignment="Center" VerticalAlignment="Center">
    <StackPanel.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Ellipse x:Name="Circle"
                  Stroke="{TemplateBinding BorderBrush}"
                  Fill="{TemplateBinding Background}"/>
                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
                  Content="{TemplateBinding Content}"
                  Margin="{TemplateBinding Padding}"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Circle" Property="Stroke" Value="Red"/>
                    <Setter TargetName="Circle" Property="Fill" Value="Pink"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="Circle" Property="Stroke" Value="Green"/>
                    <Setter TargetName="Circle" Property="Fill" Value="PaleGreen"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
        <Style TargetType="{x:Type Button}">
            <Setter Property="BorderBrush" Value="Black"/>
            <Setter Property="Background" Value="Silver"/>
            <Setter Property="Padding" Value="10"/>
            <Setter Property="Template" Value="{StaticResource ButtonTemplate}"/>
        </Style>
    </StackPanel.Resources>
    <Button Content="First"/>
    <Button Content="Second" Margin="0,5"/>
    <Button Content="Third" BorderBrush="Blue" Background="SkyBlue" Padding="30"/>
</StackPanel>
StylingTutorialImg5.jpg

Skining or Theming refers to the act of changing an applications's appearance. The best approach is creating a ResourceDictionary containing the styles and templates, following the tips previously explained, for all the controls that appear in the application. Then we can set this dictionary in the Resources property of the Application, Window or desired level. For example:

<Application
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="XamlPlayer.App"
  StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="ThemeResources.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <!-- Resources scoped at the Application level should be defined here. -->
        </ResourceDictionary>
    </Application.Resources>
</Application>

Note

The location of the XAML indicated in the Source property is specified using an URI.

Themes are complex to write and it requires time to obtain the desired results. Several themes are provided in noesisGUI to be taken as reference. To use them you simply must merge the desired one into a ResourceDictionary. The following sample displays three buttons, each one with a different theme.

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  HorizontalAlignment="Center" VerticalAlignment="Center">

    <Button Content="Noesis Style" Width="200" Height="60" Margin="0,5">
      <Button.Resources>
          <ResourceDictionary>
              <ResourceDictionary.MergedDictionaries>
                  <ResourceDictionary Source="Themes/NoesisStyle.xaml"/>
              </ResourceDictionary.MergedDictionaries>
          </ResourceDictionary>
      </Button.Resources>
    </Button>

    <Button Content="Simple Style" Width="200" Height="60" Margin="0,5">
      <Button.Resources>
          <ResourceDictionary>
              <ResourceDictionary.MergedDictionaries>
                  <ResourceDictionary Source="Themes/SimpleStyle.xaml"/>
              </ResourceDictionary.MergedDictionaries>
          </ResourceDictionary>
      </Button.Resources>
    </Button>

    <Button Content="Windows Style" Width="200" Height="60" Margin="0,5">
      <Button.Resources>
          <ResourceDictionary>
              <ResourceDictionary.MergedDictionaries>
                  <ResourceDictionary Source="Themes/WindowsStyle.xaml"/>
              </ResourceDictionary.MergedDictionaries>
          </ResourceDictionary>
      </Button.Resources>
    </Button>

</StackPanel>
StylingTutorialImg6.jpg

By default, and to optimize resources, only a minimalistic global theme is loaded when noesisGUI is initialized. If you want to override it with a different one, it can be done as shown in the following code:

Ptr<ResourceDictionary> theme = Noesis::GUI::LoadXaml<ResourceDictionary>("MyTheme.xaml");
Noesis::GUI::SetTheme(theme.GetPtr());

Note

If you find that controls in your application are displayed differently from XamlPlayer, this is probably the reason. XamlPlayer overrides the default theme. The same happens with Microsoft Blend that by default uses the theme installed in the operating system.

© 2017 Noesis Technologies