NoesisGUI

Styles and Templates Tutorial

github Tutorial Data

NoesisGUI styling and templating refer to a suite of features (styles, templates, triggers, and storyboards) that allow developers and designers to create visually compelling effects and to create a consistent appearance for their product. Although developers and or designers can customize the appearance extensively on an application-by-application basis, a strong styling and templating model is necessary to allow maintenance and sharing of the appearance within and among applications. NoesisGUI provides that model.

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 would 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 that only applies based on the existence of certain elements in its visual tree. To find them it looks for specific names in the form of PART_XXX and sometimes with specific types also. If such elements are found, extra behavior is applied to them. The specific names and behaviors are different for each 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 PART_Track's width, 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

Global Style

A minimalistic default theme is provided by Noesis for all controls. In case you want to change the look of your application we recommend creating a ResourceDictionary containing the styles and templates for all the controls that appear in the application. This dictionary can be globally setup at initialization time by doing:

C++
 Ptr<ResourceDictionary> style = Noesis::GUI::LoadXaml<ResourceDictionary>("MyStyle.xaml");
 Noesis::GUI::SetApplicationResources(style);
C#
ResourceDictionary style = (ResourceDictionary)Noesis.GUI.LoadXaml("MyStyle.xaml");
Noesis.GUI.SetApplicationResources(style);

NOTE

In Unity, this property is exposed in 'Noesis Settings' panel as 'Application Resources'

When using the Application Framework, the Resources property of the Application can be used for the same purpose. MergedDictionaries is a useful property for adding a whole XAML to the target dictionary.

<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="AquaStyle.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <!-- Resources scoped at the Application level should be defined here. -->
            <SolidColorBrush x:Key="Background0" Color="#FF2F3F4F" />
            <SolidColorBrush x:Key="Background1" Color="SlateGray" />
            <SolidColorBrush x:Key="Foreground0" Color="White" />
            <SolidColorBrush x:Key="Foreground1" Color="SkyBlue" />
            <SolidColorBrush x:Key="Foreground2" Color="Black" />
            <SolidColorBrush x:Key="Border" Color="LightSlateGray" />

        </ResourceDictionary>
    </Application.Resources>
</Application>

NOTE

Styles are complex to write and it requires time to obtain the desired results. Several styles are provided in NoesisGUI to be taken as reference

Local Style

FrameworkElement is a base class that controls inherit from, and it has a Resources property that can be used for adding local resources. The following sample displays three buttons, each one with a different style.

<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="Nocturnal Style" Width="200" Height="60" Margin="0,5">
      <Button.Resources>
          <ResourceDictionary>
              <ResourceDictionary.MergedDictionaries>
                  <ResourceDictionary Source="Themes/NocturnalStyle.xaml"/>
              </ResourceDictionary.MergedDictionaries>
          </ResourceDictionary>
      </Button.Resources>
    </Button>

    <Button Content="Agile Style" Width="200" Height="60" Margin="0,5">
      <Button.Resources>
          <ResourceDictionary>
              <ResourceDictionary.MergedDictionaries>
                  <ResourceDictionary Source="Themes/AgileStyle.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

NOTE

Avoid local styles whenever possible. If the requested resource is not found in the local dictionary, the next lookup step is to check the global dictionary. That is the best place to put any app-specific resources that are referenced by multiple pages in your app's navigation structure.

Controls can only have one style set in its Style property, so if a few properties need to be locally overridden remember to define BasedOn in local style to inherit the rest of properties from the style defined in global dictionary. If BasedOn is omitted, property values will fallback to the default style defined by Noesis theme.

<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid.Resources>
        <Style x:Key="BigButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="FontSize" Value="30"/>
            <Setter Property="Padding" Value="20,10"/>
        </Style>
        <Style x:Key="RedButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource BigButtonStyle}">
            <Setter Property="Background" Value="Red"/>
        </Style>
        <Style x:Key="BlueButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource BigButtonStyle}">
            <Setter Property="Background" Value="Blue"/>
        </Style>
        <ControlTemplate x:Key="CircularButtonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Ellipse Fill="{TemplateBinding Background}"/>
                <ContentPresenter
                    HorizontalAlignment="Center" VerticalAlignment="Center"
                    Margin="{TemplateBinding Padding}"/>
            </Grid>
        </ControlTemplate>
        <Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Template" Value="{StaticResource CircularButtonTemplate}"/>
        </Style>
    </Grid.Resources>
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button Content="Button"/>
        <Button Content="Red Button" Style="{StaticResource RedButtonStyle}" Margin="0,10"/>
        <Button Content="Blue Button" Style="{StaticResource BlueButtonStyle}"/>
    </StackPanel>
</Grid>
StylingTutorialImg7.jpg

Notice that BigButtonStyle is based on any previously defined typed style that exists for the Button. This usually refers to the style defined in the global dictionary.

Moving our locally defined typed style for the Button before BigButtonStyle will make all buttons in the sample inherit from that style.

<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid.Resources>
        <ControlTemplate x:Key="CircularButtonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Ellipse Fill="{TemplateBinding Background}"/>
                <ContentPresenter
                    HorizontalAlignment="Center" VerticalAlignment="Center"
                    Margin="{TemplateBinding Padding}"/>
            </Grid>
        </ControlTemplate>
        <Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Template" Value="{StaticResource CircularButtonTemplate}"/>
        </Style>
        <Style x:Key="BigButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="FontSize" Value="30"/>
            <Setter Property="Padding" Value="20,10"/>
        </Style>
        <Style x:Key="RedButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource BigButtonStyle}">
            <Setter Property="Background" Value="Red"/>
        </Style>
        <Style x:Key="BlueButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource BigButtonStyle}">
            <Setter Property="Background" Value="Blue"/>
        </Style>
    </Grid.Resources>
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button Content="Button"/>
        <Button Content="Red Button" Style="{StaticResource RedButtonStyle}" Margin="0,10"/>
        <Button Content="Blue Button" Style="{StaticResource BlueButtonStyle}"/>
    </StackPanel>
</Grid>
StylingTutorialImg8.jpg
© 2017 Noesis Technologies