Windows Presentation Foundation

Hello, WPF

WPF from Scratch
Navigation Applications
Content Model
Layout
Controls
Data Binding
Dependency Properties
Resources
Styles and Control Templates
Graphics
Application Deployment
Where Are We?

Layout

Introduction
Layout Basics
DockPanel
StackPanel
Grid
Canvas
Viewbox
Text Layout
Common Layout Properties
When Content Doesn't Fit
Custom Layout
Where Are We?

Controls

Introduction
What Are Controls?
Handling Input
Built-In Controls
Where Are We?

Data Binding

Introduction
Without Data Binding
Data Binding
Binding to List Data
Data Sources
Master-Detail Binding
Where Are We?

Styles and Control Templates

Introduction
Without Styles
Inline Styles
Named Styles
Element-Typed Styles
Data Templates and Styles
Triggers
Control Templates
Where Are We?

Resources

Introduction
Creating and Using Resources
Resources and Styles
Binary Resources
Global Applications
Where Are We?

Graphics

Introduction
Graphics Fundamentals
Shapes
Brushes and Pens
Transformations
Visual-Layer Programming
Video and 3-D
Where Are We?

Animation

Animation Fundamentals
Timelines
Storyboards
Key Frame Animations
Creating Animations Procedurally
Where Are We?

Custom Controls

Introduction
Custom Control Basics
Choosing a Base Class
Custom Functionality
Templates
Default Visuals
Where Are We?

ClickOnce Deployment

A Brief History of Windows Deployment
ClickOnce: Local Install
The Pieces of ClickOnce
Publish Properties
Deploying Updates
ClickOnce: Express Applications
Choosing Local Install versus Express
Signing ClickOnce Applications
Programming for ClickOnce
Security Considerations
Where Are We?

Triggers

5.6. Triggers

So far, we've seen styles as a collection of Setter elements. When a style is applied, the settings described in the Setter elements are applied unconditionally (unless overridden by per-instance settings). On the other hand, triggers are a way to wrap one or more Setter elements in a condition so that, if the condition is true, the corresponding Setter elements are executed, and when the condition becomes false, the property value reverts to its pre-trigger value.

WPF comes with three kinds of things that you can check in a trigger condition: a dependency property, a .NET property, and an event. The first two directly change values based on a condition, as I described, while the last, an event trigger, is activated when an event happens and then starts (or stops) an animation that causes properties to change.

5.6.1. Property Triggers

The simplest form of a trigger is a property trigger, which watches for a dependency property to have a certain value. For example, if we wanted to light up a button in yellow as the user moves the mouse over it, we can do so by watching for the IsMouseOver property to have a value of TRue, as in Example 5-24.

Example 5-24. A simple property trigger
<Style TargetType="{x:Type Button}">
  ...
  <Style.Triggers>
    <Trigger Property="IsMouseOver" Value="True" >
      <Setter Property="Background" Value="Yellow" />
    </Trigger>

  </Style.Triggers>
</Style>

Triggers are grouped together under the Style.Triggers element. In this case, we've added a TRigger element to the button style. When the IsMouseOver property of our button is true, the Background value of the button will be set to yellow, as shown in Figure 5-8.

You'll notice in Figure 5-8 that only the button where the mouse is currently hovering has its background set to yellow, even though other buttons have clearly been under the mouse. There's no need to worry about setting a property back when the trigger is no longer truee.g., watching for IsMouseOver to be False. The WPF dependency-property system watches for the property trigger to become inactive and reverts to the previous value.

Figure 5-8. A property trigger in action


Property triggers can be set to watch any of the dependency properties on the control to which your style is targeted and to set any of the dependency properties on the control while the condition is true. In fact, you can use a single trigger to set multiple properties if you like, as in Example 5-25.

Example 5-25. Setting multiple properties with a single trigger
<Style TargetType="{x:Type Button}">
  ...
  <Style.Triggers>
    <Trigger Property="IsMouseOver" Value="True" >
      <Setter Property="Background" Value="Yellow" />
      <Setter Property="FontStyle" Value="Italic" />

    </Trigger>
  </Style.Triggers>
</Style>

In Example 5-25, we're setting the background to yellow and the font style to italic when the mouse is over a button.

One property that you're not allowed to set in a trigger is the Style property itself. If you try, you'll get the following error:

A Style object is not allowed to affect the Style property of the object to which it applies.

This makes sense. Since it's the Style that's setting the property, and that participates in "unsetting" it when the trigger is no longer true, what sense would it make to change the very Style that's providing this orchestration? This would be somewhat like switching out your skis after you've launched yourself off of a jump but before you've landed.


5.6.2. Multiple Triggers

While you can set as many properties as you like in a property trigger, there can be more than one trigger in a style. When grouped together under the Style.Triggers element, multiple triggers act independently of each other.

For example, we can update our code so that if the mouse is hovering over one of our buttons, it'll be colored yellow and if the button has focus (the tab and arrow keys move focus around), it'll be colored green, as in Example 5-26. Figure 5-9 shows the result of one cell having focus and another with the mouse hovering.

Example 5-26. Multiple property triggers
<Style TargetType="{x:Type Button}">
  ...
  <Style.Triggers>
    <Trigger Property="IsMouseOver" Value="True" >
      <Setter Property="Background" Value="Yellow" />
    </Trigger>

    <Trigger Property="IsFocused" Value="True" >
      <Setter Property="Background" Value="LightGreen" />
    </Trigger>
  </Style.Triggers>
</Style>

Figure 5-9. Multiple property triggers in action


If multiple triggers set the same property, the last one wins. For example, in Figure 5-9, if a button has focus and the mouse is over it, the background will be light green because the trigger for the IsFocused trigger is last in the list of triggers.

5.6.3. Multi-Condition Property Trigger

If you'd like to check more than one property before a trigger condition is activatede.g., the mouse is hovering over a button and the button content is empty you can combine multiple conditions with a multiple-condition property trigger, as in Example 5-27.

Example 5-27. A multi-property trigger
<Style TargetType="{x:Type Button}">
  ...
  <Style.Triggers>
    <MultiTrigger>
      <MultiTrigger.Conditions>
        <Condition Property="IsMouseOver" Value="True" />

        <Condition Property="Content" Value="{x:Null}" />
      </MultiTrigger.Conditions>
      <Setter Property="Background" Value="Yellow" />
    </MultiTrigger>
  </Style.Triggers>
</Style>


Multi-condition property triggers check all of the properties' values to be set as specified, not just one of them. Here, we're watching for both a mouse hover and for the content to be null,[*] reflecting the game logic that only clicking on an empty cell will result in a move.

[*] The null value is set via a XAML markup extension, which you can read more about in Appendix A.

Figure 5-10 shows the yellow highlight on an empty cell when the mouse hovers, and Figure 5-11 shows the yellow highlight absent when the mouse hovers over a full cell.

Figure 5-10. Multi-condition property trigger with hovering and null content


Figure 5-11. Multi-condition property trigger not triggering as content is not null


Property triggers are great for noticing when the user is interacting with a control displaying your program's state. However, we'd also like to be able to notice when the program's state itself changes, such as when a particular player makes a move, and update our style settings accordingly. For that, we have data triggers .

5.6.4. Data Triggers

Unlike property triggers, which check only WPF dependency properties, data triggers can check any old .NET object property. While property triggers are generally used to check WPF visual-element properties, data triggers are normally used to check the properties of non-visual objects used as content, such as our PlayerMove objects in Example 5-28.

Example 5-28. Two data triggers
<Window.Resources>
  <Style TargetType="{x:Type Button}">
    ...
  </Style>

  <Style x:Key="CellTextStyle" TargetType="{x:Type TextBlock}">
    ...
    <Style.Triggers>
      <DataTrigger Binding="{Binding Path=PlayerName}" Value="X">
        <Setter Property="Foreground" Value="Red" />
      </DataTrigger>

      <DataTrigger Binding="{Binding Path=PlayerName}" Value="O">
        <Setter Property="Foreground" Value="Green" />
      </DataTrigger>
    </Style.Triggers>
  </Style>
  <Style x:Key="MoveNumberStyle" TargetType="{x:Type TextBlock}">

    ...
  </Style>
  ...
  <DataTemplate DataType="{x:Type l:PlayerMove}">
    <Grid>
      <TextBlock
        TextContent="{Binding Path=PlayerName}"
        Style="{StaticResource CellTextStyle}" />
      <TextBlock
        TextContent="{Binding Path=MoveNumber}"
        Style="{StaticResource MoveNumberStyle}" />

    </Grid>
  </DataTemplate>
</Window.Resources>

DataTrigger elements go under the Style.Triggers element just like property triggers and, just like property triggers, there can be more than one of them active at any one time. While a property trigger operates on the properties of the visual elements displaying the content, a data trigger operates on the content itself. In our case, the content of each of the cells is a PlayerMove object. In both of our data triggers, we're binding to the PlayerName property. If the value is X, we're setting the foreground to red and if it's O, we're setting it to green.

Take care where you put the data trigger. In our example, we've got the Button-type style and the named CellTextStyle style as potential choices. I've written this chapter twice now and both times I've initially put the data trigger on the button style instead of on the content in the data template. Data triggers are based on content, so make sure you put them into your content styles, not your control styles.


We haven't had per-player colors since we moved to data templates after setting styles programmatically in Figure 5-5, but data triggers bring us that feature right back, along with all of the other features we've been building up, as shown in Figure 5-12.

Unlike property triggers, which rely on the change notification of dependency properties, data triggers rely on an implementation of the standard property-change notification patterns that are built into .NET and are discussed in Chapter 4e.g., INotifyPropertyChanged. Since each PlayerMove object is constant, we don't need to implement this pattern, but if you're using data triggers, chances are that you will need to implement it on your custom content classes.

One other especially handy feature of data triggers is that there's no need for an explicit check for null content. If the content is null, the trigger condition is automatically false, which is why the application isn't crashing trying to dereference a null PlayerMove to get to the PlayerName property.

Figure 5-12. Data triggers in action


5.6.5. Multi-Condition Data Triggers

Just as property triggers can be combined into "and" conditions using the MultiTrigger element, data triggers can be combined using the MultiDataTrigger element. For example, if we wanted to watch for the 10th move of the game, make sure it's player "O," and do something special, we can do so as shown in Example 5-29.

Example 5-29. A multi-data trigger
<?Mapping XmlNamespace="sys" ClrNamespace="System" Assembly="mscorlib" ?>
...
<Window ... xmlns:sys="sys">
  <Style x:Key="MoveNumberStyle" TargetType="{x:Type TextBlock}">
    ...
    <Style.Triggers>
      <MultiDataTrigger>

        <MultiDataTrigger.Conditions>
          <Condition Binding="{Binding Path=PlayerName}" Value="O" />
          <Condition Binding="{Binding Path=MoveNumber}">
            <Condition.Value>
              <sys:Int32>10</sys:Int32>

            </Condition.Value>
          </Condition>
        </MultiDataTrigger.Conditions>
        <Setter Property="Background" Value="Yellow" />
      </MultiDataTrigger>
    </Style.Triggers>

  </Style>
  ...
</Window>

The only thing about Example 5-29 that may seem a little strange is the use of the mapping syntax to bring in the System namespace from the mscorlib .NET assembly. We do this so that we can pass 10 as an Int32 instead of as a string; otherwise, the multi-condition data trigger won't match our MoveNumber property correctly. The multi-condition data trigger in Example 5-29 sets the background of the move number to yellow to connote a cause for celebration for this special move that regular tic-tac-toe doesn't have, but you can use multi-condition data triggers for celebrations of your own kinds.

5.6.6. Event Triggers

While property triggers check for values on dependency properties and data triggers check for values on CLR properties, event triggers watch for events. When an event happens, such as a Click event, an event trigger responds by raising an animation-related action. While animation is challenging enough to deserve its own chapter (Chapter 8), Example 5-30 illustrates a simple animation that will transition a cell from solid yellow to white over five seconds when an empty cell is clicked.

Example 5-30. An event trigger
<Style TargetType="{x:Type Button}">
  ...
  <Setter Property="Background" Value="White" />
  <Style.Storyboards>
    <ParallelTimeline Name="CellClickedTimeline" BeginTime="{x:Null}">
      <SetterTimeline Path="(Button.Background).(SolidColorBrush.Color)">

        <ColorAnimation From="Yellow" To="White" Duration="0:0:5" />
      </SetterTimeline>
    </ParallelTimeline>
  </Style.Storyboards>
  <Style.Triggers>
    <EventTrigger RoutedEvent="Click">

      <EventTrigger.Actions>
        <BeginAction TargetName="CellClickedTimeline" />
      </EventTrigger.Actions>
    </EventTrigger>
  </Style.Triggers>
</Style>


Adding an animation to a style requires two things. The first is a storyboard with a named timeline that describes what you want to happen. In our case, we're animating the button's background brush color from yellow to white over five seconds.

For any property being animated with a nested path, there needs to be an explicit property setting that creates the top level of the nesting. In Example 5-30, this means that we need a Setter element for the Background property. If the top level of the nesting isn't created, there won't be anything to animate at runtime.


The second thing you need is an event trigger to start the timeline. In our case, when the user clicks on a button with the CellButtonStyle style applied (all of them, in our case), we begin the action described by the named timeline in the storyboard.

As of this writing, if you have an event trigger and a multi-condition property trigger animating the same propertye.g., the Background of a Button, make sure you put the multi-data trigger in the XAML file before the event trigger; otherwise, you'll get a nonsensical error at runtime.


The results of the animation, showing various shades of yellow, through past clicks can be seen in Figure 5-13.

Figure 5-13. The event trigger and our fading yellow animation (Color Plate 10)


Property and data triggers let you set properties when properties change. Event triggers let you trigger events when events happen. While both are pretty differente.g., you can't set a property with an event trigger or raise an event with a property or data triggerboth let you add a degree of interactivity to your applications in a wonderfully declarative way with little or no code.

For the full scoop on event triggers, you'll want to read Chapter 8.


©2008 FAQ - WPF Labs - Discuss - Terms of Use - Privacy Policy - About WPF
- Interview Questions - Sharepoint Articles - Interview Questions Resource Library - All about LINQ - MS Knowledgebase Articles - Electronics and Hardware discussions