Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Jul 16, 2015

The DispatcherTimer

In WinForms, there's a control called the Timer, which can perform an action repeatedly within a given interval. WPF has this possibility as well, but instead of an invisible control, we have the DispatcherTimer control. It does pretty much the same thing, but instead of dropping it on your form, you create and use it exclusively from your Code-behind code.
The DispatcherTimer class works by specifying an interval and then subscribing to the Tick event that will occur each time this interval is met. The DispatcherTimer is not started before you call the Start() method or set the IsEnabled property to true.
Let's try a simple example where we use a DispatcherTimer to create a digital clock: 


<Window x:Class="WpfTutorialSamples.Misc.DispatcherTimerSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DispatcherTimerSample" Height="150" Width="250">
    <Grid>
        <Label Name="lblTime" FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</Window>
 
 
 
using System;
using System.Windows;
using System.Windows.Threading;
namespace WpfTutorialSamples.Misc
{
        public partial class DispatcherTimerSample : Window
        {
                public DispatcherTimerSample()
                {
                        InitializeComponent();
                        DispatcherTimer timer = new DispatcherTimer();
                        timer.Interval = TimeSpan.FromSeconds(1);
                        timer.Tick += timer_Tick;
                        timer.Start();
                }

                void timer_Tick(object sender, EventArgs e)
                {
                        lblTime.Content = DateTime.Now.ToLongTimeString();
                }
        }
}
 
the XAML part is extremely simple - it's merely a centered label with a large font size, used to display the current time. Code-behind is where the magic happens in this example. In the constructor of the window, we create a DispatcherTimer instance. We set the Interval property to one second, subscribe to the Tick event and then we start the timer. In the Tick event, we simply update the label to show the current time.
Of course, the DispatcherTimer can work at smaller or much bigger intervals. For instance, you might only want something to happen every 30 seconds or 5 minutes - just use the TimeSpan.From* methods, like FromSeconds or FromMinutes, or create a new TimeSpan instance that completely fits your needs.
To show what the DispatcherTimer is capable of, let's try updating more frequently... A lot more frequently! 


using System;
using System.Windows;
using System.Windows.Threading;
namespace WpfTutorialSamples.Misc
{
        public partial class DispatcherTimerSample : Window
        {
                public DispatcherTimerSample()
                {
                        InitializeComponent();
                        DispatcherTimer timer = new DispatcherTimer();
                        timer.Interval = TimeSpan.FromMilliseconds(1);
                        timer.Tick += timer_Tick;
                        timer.Start();
                }

                void timer_Tick(object sender, EventArgs e)
                {
                        lblTime.Content = DateTime.Now.ToString("HH:mm:ss.fff");
                }
        }
}
 
As you can see, we now ask the DispatcherTimer to fire every millisecond! In the Tick event, we use a custom time format string to show the milliseconds in the label as well. Now you have something that could easily be used as a stopwatch - just add a couple of buttons to the Window and then have them call the Stop(), Start() and Restart() methods on the timer.

Summary

There are many situations where you would need something in your application to occur at a given interval, and using the DispatcherTimer, it's quite easy to accomplish. Just be aware that if you do something complicated in your Tick event, it shouldn't run too often, like in the last example where the timer ticks each millisecond - that will put a heavy strain on the computer running your application.
Also be aware that the DispatcherTimer is not 100% precise in all situations. The tick operations are placed on the Dispatcher queue, so if the computer is under a lot of pressure, your operation might be delayed. The .NET framework promises that the Tick event will never occur too early, but can't promise that it won't be slightly delayed. However, for most use cases, the DispatcherTimer is more than precise enough.
If you need your timer to have a higher priority in the queue, you can set the DispatcherPriority by sending one of the values along on the DispatcherTimer priority.
 

  

Trigger animations

One of the things that became a LOT easier with WPF, compared to previous frameworks like WinForms, is animation. Triggers have direct support for using animations in response to the trigger being fired, instead of just switching between two static values.
For this, we use the EnterActions and ExitActions properties, which are present in all of the trigger types already discussed (except for the EventTrigger), both single and multiple. Here's an example: 

<Window x:Class="WpfTutorialSamples.Styles.StyleTriggerEnterExitActions"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleTriggerEnterExitActions" Height="200" Width="200" UseLayoutRounding="True">
    <Grid>
        <Border Background="LightGreen" Width="100" Height="100" BorderBrush="Green">
            <Border.Style>
                <Style TargetType="Border">
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <ThicknessAnimation Duration="0:0:0.400" To="3" Storyboard.TargetProperty="BorderThickness" />
                                        <DoubleAnimation Duration="0:0:0.300" To="125" Storyboard.TargetProperty="Height" />
                                        <DoubleAnimation Duration="0:0:0.300" To="125" Storyboard.TargetProperty="Width" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <ThicknessAnimation Duration="0:0:0.250" To="0" Storyboard.TargetProperty="BorderThickness" />
                                        <DoubleAnimation Duration="0:0:0.150" To="100" Storyboard.TargetProperty="Height" />
                                        <DoubleAnimation Duration="0:0:0.150" To="100" Storyboard.TargetProperty="Width" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
        </Border>
    </Grid>
</Window>
 

In this example, we have a green square. It has a trigger that fires once the mouse is over, in which case it fires of several animations, all defined in the EnterActions part of the trigger. In there, we animate the thickness of the border from its default 0 to a thickness of 3, and then we animate the width and height from 100 to 125. This all happens simultaneously, because they are a part of the same StoryBoard, and even at slightly different speeds, since we have full control of how long each animation should run.
We use the ExitActions to reverse the changes we made, with animations that goes back to the default values. We run the reversing animations slightly faster, because we can and because it looks cool.
The two states are represented on the two screenshots, but to fully appreciate the effect, you should try running the example on your own machine, using the source code above.

Summary

Using animations with style triggers is very easy, and while we haven't fully explored all you can do with WPF animations yet, the example used above should give you an idea on just how flexible both animations and styles are.
 

WPF MultiTrigger and MultiDataTrigger

In the previous chapter, we worked with triggers to get dynamic styles. So far they have all been based on a single property, but WPF also supports multi triggers, which can monitor two or more property conditions and only trigger once all of them are satisfied.
There are two types of multi triggers: The MultiTrigger, which just like the regular Trigger works on dependency properties, and then the MultiDataTrigger, which works by binding to any kind of property. Let's start with a quick example on how to use the MultiTrigger.

MultiTrigger 

<Window x:Class="WpfTutorialSamples.Styles.StyleMultiTriggerSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleMultiTriggerSample" Height="100" Width="250">
    <Grid>
        <TextBox VerticalAlignment="Center" HorizontalAlignment="Center" Text="Hover and focus here" Width="150">
            <TextBox.Style>
                <Style TargetType="TextBox">
                    <Style.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsKeyboardFocused" Value="True" />
                                <Condition Property="IsMouseOver" Value="True" />
                            </MultiTrigger.Conditions>
                            <MultiTrigger.Setters>
                                <Setter Property="Background" Value="LightGreen" />
                            </MultiTrigger.Setters>
                        </MultiTrigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>
    </Grid>
</Window>
 
In this example, we use a trigger to change the background color of the TextBox once it has keyboard focus AND the mouse cursor is over it, as seen on the screenshot. This trigger has two conditions, but we could easily have added more if needed. In the Setters section, we define the properties we wish to change when all the conditions are met - in this case, just the one (background color).

MultiDataTrigger

Just like a regular DataTrigger, the MultiDataTrigger is cool because it uses bindings to monitor a property. This means that you can use all of the cool WPF binding techniques, including binding to the property of another control etc. Let me show you how easy it is: 


<Window x:Class="WpfTutorialSamples.Styles.StyleMultiDataTriggerSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleMultiDataTriggerSample" Height="150" Width="200">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <CheckBox Name="cbSampleYes" Content="Yes" />
        <CheckBox Name="cbSampleSure" Content="I'm sure" />
        <TextBlock HorizontalAlignment="Center" Margin="0,20,0,0" FontSize="28">
            <TextBlock.Style>
                <Style TargetType="TextBlock">
                    <Setter Property="Text" Value="Unverified" />
                    <Setter Property="Foreground" Value="Red" />
                    <Style.Triggers>
                        <MultiDataTrigger>
                            <MultiDataTrigger.Conditions>
                                <Condition Binding="{Binding ElementName=cbSampleYes, Path=IsChecked}" Value="True" />
                                <Condition Binding="{Binding ElementName=cbSampleSure, Path=IsChecked}" Value="True" />
                            </MultiDataTrigger.Conditions>
                            <Setter Property="Text" Value="Verified" />
                            <Setter Property="Foreground" Value="Green" />
                        </MultiDataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </StackPanel>
</Window>
 
 

 

 

In this example, I've re-created the example we used with the regular DataTrigger, but instead of binding to just one property, I bind to the same property (IsChecked) but on two different controls. This allows us to trigger the style only once both checkboxes are checked - if you remove a check from either one of them, the default style will be applied instead.

Summary

As you can see, multi triggers are pretty much just as easy to use as regular triggers and they can be extremely useful, especially when developing your own controls.

Trigger, DataTrigger & EventTrigger

So far, we worked with styles by setting a static value for a specific property. However, using triggers, you can change the value of a given property, once a certain condition changes.
 Triggers come in multiple flavors: 
Property triggers, 
event triggers and 
data triggers. 

They allow you to do stuff that would normally be done in code-behind completely in markup instead, which is all a part of the ongoing process of separating style and code. 

Property trigger

The most common trigger is the property trigger, which in markup is simply defined with a <Trigger> element. It watches a specific property on the owner control and when that property has a value that matches the specified value, properties can change. In theory this might sound a bit complicated, but it's actually quite simple once we turn theory into an example: 


<Window x:Class="WpfTutorialSamples.Styles.StyleTriggersSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleTriggersSample" Height="100" Width="300">
    <Grid>
        <TextBlock Text="Hello, styled world!" FontSize="28" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock.Style>
                <Style TargetType="TextBlock">
                    <Setter Property="Foreground" Value="Blue"></Setter>
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Foreground" Value="Red" />
                            <Setter Property="TextDecorations" Value="Underline" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </Grid>
</Window>
 
In this style, we set the Foreground property to blue, to make it look like a hyperlink. We then add a trigger, which listens to theIsMouseOver property - once this property changes to True, we apply two setters: We change the Foreground to red and then we make it underlined. This is a great example on how easy it is to use triggers to apply design changes, completely without any code-behind code.
We define a local style for this specific TextBlock, but as shown in the previous articles, the style could have been globally defined as well, if we wanted it to apply to all TextBlock controls in the application. 

Data triggers

Data triggers, represented by the <DataTrigger> element, are used for properties that are not necessarily dependency properties. They work by creating a binding to a regular property, which is then monitored for changes. This also opens up for binding your trigger to a property on a different control. For instance, consider the following example: 


<Window x:Class="WpfTutorialSamples.Styles.StyleDataTriggerSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleDataTriggerSample" Height="200" Width="200">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <CheckBox Name="cbSample" Content="Hello, world?" />
        <TextBlock HorizontalAlignment="Center" Margin="0,20,0,0" FontSize="48">
            <TextBlock.Style>
                <Style TargetType="TextBlock">
                    <Setter Property="Text" Value="No" />
                    <Setter Property="Foreground" Value="Red" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ElementName=cbSample, Path=IsChecked}" Value="True">
                            <Setter Property="Text" Value="Yes!" />
                            <Setter Property="Foreground" Value="Green" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </StackPanel>
</Window>
 
 

 In this example, we have a CheckBox and a TextBlock. Using a DataTrigger, we bind the TextBlock to the IsChecked property of the CheckBox. We then supply a default style, where the text is "No" and the foreground color is red, and then, using a DataTrigger, we supply a style for when the IsChecked property of the CheckBox is changed to True, in which case we make it green with a text saying "Yes!" (as seen on the screenshot).
 

Event triggers

Event triggers, represented by the <EventTrigger> element, are mostly used to trigger an animation, in response to an event being called. We haven't discussed animations yet, but to demonstrate how an event trigger works, we'll use them anyway. Have a look on the chapter about animations for more details. Here's the example: 

<Window x:Class="WpfTutorialSamples.Styles.StyleEventTriggerSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleEventTriggerSample" Height="100" Width="300">
    <Grid>
        <TextBlock Name="lblStyled" Text="Hello, styled world!" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock.Style>
                <Style TargetType="TextBlock">
                    <Style.Triggers>
                        <EventTrigger RoutedEvent="MouseEnter">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0.300" Storyboard.TargetProperty="FontSize" To="28" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="MouseLeave">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0.800" Storyboard.TargetProperty="FontSize" To="18" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </Grid>
</Window>
 
 

The markup might look a bit overwhelming, but if you run this sample and look at the result, you'll see that we've actually accomplished a pretty cool animation, going both ways, in ~20 lines of XAML. As you can see, I use an EventTrigger to subscribe to two events: MouseEnter and MouseLeave. When the mouse enters, I make a smooth and animated transition to a FontSize of 28 pixels in 300 milliseconds. When the mouse leaves, I change the FontSize back to 18 pixels but I do it a bit slower, just because it looks kind of cool.

Summary

WPF styles make it easy to get a consistent look, and with triggers, this look becomes dynamic. Styles are great in your application, but they're even better when used in control templates etc. You can read more about that elsewhere in this tutorial.
In the next article, we'll look at multi triggers, which allow us to apply styles based on multiple properties.

Using WPF styles

In the previous chapter, where we introduced the concept of styles, we used a very basic example of a locally defined style, which targeted a specific type of controls - the TextBlock. However, styles can be defined in several different scopes, depending on where and how you want to use them, and you can even restrict styles to only be used on controls where you explicitly want it. In this chapter, I'll show you all the different ways in which a style can be defined.

Local control specific style

You can actually define a style directly on a control, like this: 

<Window x:Class="WpfTutorialSamples.Styles.ControlSpecificStyleSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ControlSpecificStyleSample" Height="100" Width="300">
    <Grid Margin="10">
        <TextBlock Text="Style test">
            <TextBlock.Style>
                <Style>
                    <Setter Property="TextBlock.FontSize" Value="36" />
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </Grid>
</Window>
 
 <Window x:Class="WpfTutorialSamples.Styles.SimpleStyleSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SimpleStyleSample" Height="200" Width="250">
    <StackPanel Margin="10">
        <StackPanel.Resources>
            <Style TargetType="TextBlock">
                <Setter Property="Foreground" Value="Gray" />
                <Setter Property="FontSize" Value="24" />
            </Style>
        </StackPanel.Resources>
        <TextBlock>Header 1</TextBlock>
        <TextBlock>Header 2</TextBlock>
        <TextBlock Foreground="Blue">Header 3</TextBlock>
    </StackPanel>
</Window>
This is great for the more local styling needs. For instance, it would make perfect sense to do this in a dialog where you simply needed a set of controls to look the same, instead of setting the individual properties on each of them.

Window-wide styles

The next step up in the scope hierarchy is to define the style(s) within the Window resources. This is done in exactly the same way as above for the StackPanel, but it's useful in those situations where you want a specific style to apply to all controls within a window (or a UserControl for that matter) and not just locally within a specific control. Here's a modified example: 

<Window x:Class="WpfTutorialSamples.Styles.WindowWideStyleSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WindowWideStyleSample" Height="200" Width="300">
    <Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Foreground" Value="Gray" />
            <Setter Property="FontSize" Value="24" />
        </Style>
    </Window.Resources>
    <StackPanel Margin="10">
        <TextBlock>Header 1</TextBlock>
        <TextBlock>Header 2</TextBlock>
        <TextBlock Foreground="Blue">Header 3</TextBlock>
    </StackPanel>
</Window>
 
As you can see, the result is exactly the same, but it does mean that you could have controls placed everywhere within the window and the style would still apply.

Application-wide styles

If you want your styles to be used all over the application, across different windows, you can define it for the entire application. This is done in the App.xaml file that Visual Studio has likely created for you, and it's done just like in the window-wide example:
App.xaml 

<Application x:Class="WpfTutorialSamples.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         StartupUri="Styles/WindowWideStyleSample.xaml">
    <Application.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Foreground" Value="Gray" />
            <Setter Property="FontSize" Value="24" />
        </Style>
    </Application.Resources>
</Application>
 
Window
 
 
<Window x:Class="WpfTutorialSamples.Styles.WindowWideStyleSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ApplicationWideStyleSample" Height="200" Width="300">
    <StackPanel Margin="10">
        <TextBlock>Header 1</TextBlock>
        <TextBlock>Header 2</TextBlock>
        <TextBlock Foreground="Blue">Header 3</TextBlock>
    </StackPanel>
</Window>
  

 

 

Explicitly using styles

You have a lot of control over how and where to apply styling to your controls, from local styles and right up to the application-wide styles, that can help you get a consistent look all over your application, but so far, all of our styles have targeted a specific control type, and then ALL of these controls have used it. This doesn't have to be the case though.
By setting the x:Key property on a style, you are telling WPF that you only want to use this style when you explicitly reference it on a specific control. Let's try an example where this is the case: 


<Window x:Class="WpfTutorialSamples.Styles.ExplicitStyleSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ExplicitStyleSample" Height="150" Width="300">
    <Window.Resources>
        <Style x:Key="HeaderStyle" TargetType="TextBlock">
            <Setter Property="Foreground" Value="Gray" />
            <Setter Property="FontSize" Value="24" />
        </Style>
    </Window.Resources>
    <StackPanel Margin="10">
        <TextBlock>Header 1</TextBlock>
        <TextBlock Style="{StaticResource HeaderStyle}">Header 2</TextBlock>
        <TextBlock>Header 3</TextBlock>
    </StackPanel>
</Window>
 
 
 Notice how even though the TargetType is set to TextBlock, and the style is defined for the entire window, only the TextBlock in the middle, where I explicitly reference the HeaderStyle style, uses the style. This allows you to define styles that target a specific control type, but only use it in the places where you need it.