Chapter 24. Styles


Although the Resources sections of XAML files are useful for defining miscellaneous objects that you refer to in markup, many resource sections are used primarily for the definition of Style objects. Styles are essentially collections of property values that are applied to elements. Styles are partially the compensation for not being able to use loops in XAML to create multiple elements with identical properties.

For example, suppose your page contains a bunch of buttons. You want these buttons to share some common properties. You might want the same Margin property to apply to all these buttons, for example, or the same font. You can define these characteristics in a style, and then use that same style for multiple elements. In this way, styles are similar in purpose and functionality to style sheets in Microsoft Word and Cascading Style Sheets in HTML. But the WPF implementation of styles is more powerful because changes in properties can also be specified, which are triggered by changes in other properties, or by events.

The Style class is defined in System.Windows. It derives from Object and has no descendents. The most important property of Style is named Setters. The Setters property is of type SetterBaseCollection, which is a collection of SetterBase objects. SetterBase is an abstract class from which Setter and EventSetter derive. These objects are called "setters" because they result in the setting of properties or event handlers.

Setter is the content property of Style, so Setter and EventSetter elements are children of the Style element:

<Style ...>     <Setter ... />     <EventSetter ... />     <Setter ... /> </Style> 


Within Style definitions, Setter objects typically show up much more often than EventSetter objects. A Setter basically associates a particular property with a value, and the two crucial properties of the Setter class are Property (of type DependencyProperty) and Value (of type object). In XAML, a Setter looks like this:

<Setter Property="Control.FontSize" Value="24" /> 


Although the Property attribute always references a dependency property, notice that the property is specified as FontSize rather than FontSizeProperty. The property name usually (but not always) needs to be preceded by the class in which the property is defined or inherited. In code, you need to use the actual dependency property name, and it's always preceded by a class name.

If you need to indicate a value of null for the Value attribute, use the markup extension x:Null:

Value="{x:Null}" 


The FrameworkElement and FrameworkContentElement classes define a property named Style of type Style, so it's possiblebut usually not particularly usefulto define a Style element that is local to an element to which the style applies. For example, the following stand-alone XAML file defines a Style local to a Button element.

ButtonWithLocalStyle.xaml

[View full width]

<!-- === ==================================================== ButtonWithLocalStyle.xaml (c) 2006 by Charles Petzold ============================================= ========== --> <Button xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Red"> <Button.Style> <Style> <Setter Property="Button.FontSize" Value="18pt" /> <Setter Property="Control.Foreground" Value="Blue" /> </Style> </Button.Style> Button with Local Style </Button>



The Style element here includes two Setter elements to define the button FontSize and Foreground properties. Notice that I've specified the first as Button.FontSize and the second as Control.Foreground. It doesn't really matter here which class name you use because Button inherits the Foreground property from Control. You could even use TextBlock.FontSize and TextBlock.Foreground and it would work the same. Even though there's no inheritance involved between TextBlock and Button, both TextBlock and Control define these two properties based on the original definitions in TextElement. (You can verify this by looking at the Owner class of these properties in the ExploreDependencyProperties program from Chapter 16.)

You'll also notice that the Style definition indicates that the Foreground property should be Blue but the Foreground property of the Button is assigned explicitly as Red near the top of the file. Which color is used? The button foreground is red because local settingswhich is the term used for properties set directly on the elementtake precedence over Style settings. But styles take precedence over properties inherited through the visual tree.

It is much more common for styles to be defined in resource sections so that they can be shared among multiple elements and controls. Like other resources, styles are identified and referenced by a text key, and they can potentially be used in any element lower in the visual tree. Styles defined in the Resources section of the Application object can be used throughout the application.

Here's a StackPanel that includes a Resources section with a Style definition. The Style is given a key name of "normal." The three buttons that are children of the StackPanel refer to this style with the StaticResource markup extension.

StyleWithMultipleButtons.xaml

[View full width]

<!-- === =========== ============================================= StyleWithMultipleButtons.xaml (c) 2006 by Charles Petzold ============================================= ============== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style x:Key="normal"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control.Foreground" Value="Blue" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> <Setter Property="Control.Padding" Value="20, 10, 20, 10" /> </Style> </StackPanel.Resources> <Button Style="{StaticResource normal}"> Button Number 1 </Button> <Button Style="{StaticResource normal}"> Button Number 2 </Button> <Button Style="{StaticResource normal}"> Button Number 3 </Button> </StackPanel>



Because elements and controls often share many of the same properties, you can define styles that are used for different types of elements. Here's a style that is shared by Button controls as well as a TextBlock element.

StyleWithMultipleElements.xaml

[View full width]

<!-- === =========== ============================================== StyleWithMultipleElements.xaml (c) 2006 by Charles Petzold ============================================= =============== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style x:Key="normal"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control.Foreground" Value="Blue" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> <Setter Property="Control.Padding" Value="20, 10, 20, 10" /> </Style> </StackPanel.Resources> <Button Style="{StaticResource normal}"> Button on top of the stack </Button> <TextBlock Style="{StaticResource normal}"> TextBlock in the middle of the stack </TextBlock> <Button Style="{StaticResource normal}"> Button on the bottom of the stack </Button> </StackPanel>



In fact, in this program you can even define a Setter for a property unique to the Button, like this one:

<Setter Property="Button.IsDefault" Value="true" /> 


The program still works. The TextBlock element just ignores that Setter because TextBlock doesn't have an IsDefault property.

As with other resources, you can use the same key for a Style definition in multiple resource sections. For any particular element, the applicable Style will be that which is first encountered going up the visual tree.

Here's a Grid that contains a StackPanel with three buttons. The Grid defines a style named "normal" with a FontSize of 24 and a Foreground of Blue. The Resources section of the StackPanel also contains a Style named "normal" with a Foreground of Red.

StylesWithSameKeys.xaml

[View full width]

<!-- === ================================================== StylesWithSameKeys.xaml (c) 2006 by Charles Petzold ============================================= ======== --> <Grid xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml"> <Grid.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control.Foreground" Value="Blue" /> </Style> </Grid.Resources> <StackPanel> <StackPanel.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="Control .Foreground" Value="Red" /> </Style> </StackPanel.Resources> <Button> Button Number 1 </Button> <Button> Button Number 2 </Button> <Button> Button Number 3 </Button> </StackPanel> </Grid>



The buttons will use the style defined in the StackPanel. The foreground color is red and the FontSize is the defaultnot the FontSize defined in the earlier Style. Using the same name for multiple styles does not allow overriding selected properties while keeping the earlier definitions of others. (You'll see how to do that little trick shortly.) The choice here is between entire Style definitions.

If you can't express the value of a property with a text string in a Setter, you need to break out the Value property as a property element. The following XAML file has a Setter property for the Background property that requires a gradient brush.

StyleWithPropertyElement.xaml

[View full width]

<!-- === =========== ============================================= StyleWithPropertyElement.xaml (c) 2006 by Charles Petzold ============================================= ============== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style x:Key="normal"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> <Setter Property="Control.Background"> <Setter.Value> <LinearGradientBrush StartPoint="1,0" EndPoint="1,1"> <LinearGradientBrush .GradientStops> <GradientStop Color="LightBlue" Offset="0" /> <GradientStop Color="Aquamarine" Offset="1" /> </LinearGradientBrush .GradientStops> </LinearGradientBrush> </Setter.Value> </Setter> </Style> </StackPanel.Resources> <Button Style="{StaticResource normal}"> Button Number 1 </Button> <Button Style="{StaticResource normal}"> Button Number 2 </Button> <Button Style="{StaticResource normal}"> Button Number 3 </Button> </StackPanel>



Another solution is to make that gradient brush a resource, and to refer to the resource in the Style definition using the StaticResource markup extension. As you learned in Chapter 21, the Application, FrameworkElement, and FrameworkContentElement classes all define a Resources property of type ResourceDictionary. Style, too, defines this same Resources property, so you can put the resources right in the Style definition. This is the architecturally preferable approach for resources not used anywhere else. The following XAML file shows an example.

StyleWithResource.xaml

[View full width]

<!-- === ================================================= StyleWithResource.xaml (c) 2006 by Charles Petzold ============================================= ======= --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style x:Key="normal"> <Style.Resources> <LinearGradientBrush x:Key="gradbrush" StartPoint="1 ,0" EndPoint="1,1"> <LinearGradientBrush .GradientStops> <GradientStop Color="LightBlue" Offset="0" /> <GradientStop Color="Aquamarine" Offset="1" /> </LinearGradientBrush .GradientStops> </LinearGradientBrush> </Style.Resources> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> <Setter Property="Control.Background" Value="{StaticResource gradbrush}" /> </Style> </StackPanel.Resources> <Button Style="{StaticResource normal}"> Button Number 1 </Button> <Button Style="{StaticResource normal}"> Button Number 2 </Button> <Button Style="{StaticResource normal}"> Button Number 3 </Button> </StackPanel>



The Style class defines only six properties, and you've already seen two of them (Setters and Resources). Style also defines a property named TargetType that lets you specify the type of the element you want the style to be applied to. The syntax requires the markup extension of x:Type, which is enclosed in curly brackets and followed by the name of the class whose type you want to reference:

<Style TargetType="{x:Type Button}" ...>     ... </Style> 


You can think of x:Type as the XAML equivalent of typeof in C#. If you set the TargetType, you don't need to specify an x:Key value. A key is fabricated from the TargetType. Without an x:Key, the style shown in the preceding example applies to all Button controls in the element in which the Style is defined and all Button controls in any child elements. If that's not what you want, include an x:Key attribute as well and refer to that key in the Button elements.

When you use TargetType in the Style, the style will apply only to those elements that have the exact type. For example, you can't use Control in the TargetType and have the style apply to both Button controls and Label controls.

One advantage of using TargetType is that you don't need to fully qualify the property names within the Setter elements. Normally you need to use something like this:

<Setter Property="Button.FontSize" Value="24" /> 


When you use TargetType you can simplify that markup to this:

<Setter Property="FontSize" Value="24" /> 


This simplification may be worth adding the TargetType attribute even if you plan on also using an x:Key attribute in the style. The TargetType also clarifies your intentions to someone else looking at the file.

Here's a XAML file that defines two Style elements, the first with a FontSize of 24 and a Foreground of Blue that applies to Button objects, and the second with a Foreground of Red that applies to TextBlock objects. The StackPanel contains a Button with text content, a TextBlock, and a Button with TextBlock content. Can you anticipate the font size and foreground colors of these three elements?

StylesWithTargetTypes.xaml

[View full width]

<!-- === ===================================================== StylesWithTargetTypes.xaml (c) 2006 by Charles Petzold ============================================= =========== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="FontSize" Value="24" /> <Setter Property="Foreground" Value="Blue" /> </Style> <Style TargetType="{x:Type TextBlock}"> <Setter Property="Foreground" Value="Red" /> </Style> </StackPanel.Resources> <Button> Button with Text Content </Button> <TextBlock> TextBlock Text </TextBlock> <Button> <TextBlock> Button with TextBlock Content </TextBlock> </Button> </StackPanel>



The first button gets the style with a TargetType of Button. The button displays blue text with a font size of 24. The TextBlock element gets the style with the TargetType of TextBlock. The text is red and the font size is the default. The bottom button gets the first stylea Foreground property of Blue and a FontSize of 24. However, the TextBlock inside the button gets the second stylea Foreground property of Red. Through normal property inheritance, the TextBlock inherits the FontSize property of its Button parent. The button displays red text with a font size of 24. The style normally takes precedence over property inheritance, but the TextBlock has no style setting for the font size, so it inherits the property from its visual parent.

If you have a bunch of dialog boxes in an application, and these dialog boxes have some radio buttons on stack panels within group boxes, you know you need to apply a Margin property to each RadioButton to make it look reasonably attractive. This is an excellent application of a Style with a TargetType of RadioButton.

You can define keys along with TargetType specifications. In that case, a particular element gets a particular style when the type of the element matches the TargetType of the style and the keys match. For multiple styles defined in any particular resource section, the keys must be unique.

For any particular element, only one Style applies. This is the Style encountered first when searching up the visual tree where the key matches, or the class of the element matches the TargetType, or both, if the Style has both. Obviously, sometimes you'll want to define a new Style that is based on an existing Style but has some different or additional property definitions. In such a case you can define a style with the BasedOn attribute referencing an existing style. Here's the stack of three buttons again, but notice that a Style with the key of "hotbtn" has been defined with a Foreground of Red. The Button in the middle uses this style rather than the "normal" style.

BasedOnStyle.xaml

[View full width]

<!-- =============================================== BasedOnStyle.xaml (c) 2006 by Charles Petzold ============================================= == --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style x:Key="normal"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control.Foreground" Value="Blue" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> <Setter Property="Control.Padding" Value="20, 10, 20, 10" /> </Style> <Style x:Key="hotbtn" BasedOn="{StaticResource normal}"> <Setter Property="Control.Foreground" Value="Red" /> </Style> </StackPanel.Resources> <Button Style="{StaticResource normal}"> Button Number 1 </Button> <Button Style="{StaticResource hotbtn}"> Button Number 2 </Button> <Button Style="{StaticResource normal}"> Button Number 3 </Button> </StackPanel>



The BasedOn syntax is the same as the syntax used in referencing a Style by an element, and the same rules apply: The new style is based on the first style encountered in the visual tree with a matching key.

It would not be entirely correct to say that the second button uses the properties defined by the "normal" style except when those properties are overridden by the "hotbtn" style. It is better to think of the second button as using the properties defined by the "hotbtn" style, where the "hotbtn" style is initialized with properties originally defined for the "normal" style. An element has at most only one Style object, and for the second button that Style is "hotbtn." When writing XAML, it's easy to forget that you're still dealing with properties and objects. Style is a property defined by FrameworkElement, the Style property is null by default, and the Style object you set to this property has only one Setters collection that contains Setter objects for all the properties that the Style sets.

It is also possible to base a style on an existing style that has a TargetType attribute, but the BasedOn syntax is a bit messier:

BasedOn="{StaticResource {x:Type Button}}" 


The new Style must also have a TargetType that is the same as the class the style is based on, or a derivative of that class. Here's an example where the TargetType properties of the two styles are the same.

BasedOnTargetType.cs

[View full width]

<!-- === ================================================= BasedOnTargetType.xaml (c) 2006 by Charles Petzold ============================================= ======= --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control.Foreground" Value="Blue" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> </Style> <Style x:Key="hotbtn" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}"> <Setter Property="Control.Foreground" Value="Red" /> </Style> </StackPanel.Resources> <Button> Button Number 1 </Button> <Button Style="{StaticResource hotbtn}"> Button Number 2 </Button> <Button> Button Number 3 </Button> </StackPanel>



The second style definition requires an explicit x:Key attribute. Otherwise, there would be two conflicting styles.

However, try this: Change the TargetType of the first style to Control, and change the BasedOn of the second style to Control as well, but leave the TargetType of the second style as Button. This is valid. A style that combines a BasedOn property and a TargetType property can refer to the same class or a class it inherits from. Now the second style has a TargetType of Button and a x:Key of "hotbtn", so the second button uses that style. The first and third buttons get no style. If you eliminate the x:Key definition from the second style and the Style attribute from the second button, all three buttons get the second style.

If you're using TargetType so that particular types of elements always get a particular style, you can define a hierarchy of styles that parallel the hierarchy of classes. Here's an example.

TargetTypeDerivatives.xaml

[View full width]

<!-- === ===================================================== TargetTypeDerivatives.xaml (c) 2006 by Charles Petzold ============================================= =========== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style TargetType="{x:Type Control}"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control.Foreground" Value="Blue" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> </Style> <Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Control}}"> <Setter Property="Control.Foreground" Value="Red" /> </Style> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource {x:Type Control}}"> <Setter Property="Control.Foreground" Value="Green" /> </Style> <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type Control}}"> </Style> </StackPanel.Resources> <Button> Button Control </Button> <Label> Label Control </Label> <TextBox> TextBox Control </TextBox> </StackPanel>



The last Style element for the TextBox doesn't define any new properties, so the TextBox gets a Foreground of Blue. Of course, the latter Style elements can define properties specific to the individual control types.

Styles have certain inherent restrictions. For example, you might want to define a Style for a StackPanel so that it always contains several of the same types of elements:

<!-- This doesn't work! --> <Style TargetType="{x:Type StackPanel}">     <Setter Property="Children">         <Setter.Value>             ...         </Setter.Value>     </Setter> </Style> 


The main problem here is that the Children property defined by Panel is not backed by a dependency property; hence, you cannot use a Style to set that property. You might want to consider defining a new class derived from StackPanel for jobs of this nature, or perhaps a template (as discussed in the next chapter).

Here's a piece of XAML that attempts to define a style for a Button so that it always contains an Image object:

<!-- This doesn't work! --> <Style TargetType="{x:Type Button}">     <Setter Property="Content">         <Setter.Value>             <Image ... />         </Setter.Value>     </Setter> </Style> 


The problemas the error message indicatesis that "Setter does not support values derived from Visual or ContentElement." Any object referred to in a particular Style is created only once to be part of that Style, and hence is shared among all elements that use that Style. An element like Imageand indeed, any element derived from Visual or ContentElementcan have only one parent, and that would not be the case if the Style object were shared. Again, consider a custom class or a template if you run into this problem.

The Value attribute of a Setter element can be a data binding, as the following XAML demonstrates.

SetterWithBinding.xaml

[View full width]

<!-- === ================================================= SetterWithBinding.xaml (c) 2006 by Charles Petzold ============================================= ======= --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="FontSize" Value="{Binding ElementName=scroll , Path=Value}" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="Margin" Value="24" /> </Style> </StackPanel.Resources> <ScrollBar Name="scroll" Orientation="Horizontal" Margin="24" Minimum="11" Maximum="100" Value="24" /> <Button> Button Number 1 </Button> <Button> Button Number 2 </Button> <Button> Button Number 3 </Button> </StackPanel>



The Style element for Button contains a Setter for the FontSize property in which the Value attribute is a Binding referencing the Value property of the ScrollBar named scroll. As you manipulate the scrollbar, the buttons get smaller and larger.

Although I've been demonstrating styles with simple controls and TextBlock elements, styles also play an important role in drawing graphics. Here's a XAML file that defines a Style with a TargetType of Ellipse. The Style defines the outline of each ellipse and its size, leaving the individual Ellipse elements to focus on the location and color.

GraphicsStyles.xaml

[View full width]

<!-- ================================================= GraphicsStyles.xaml (c) 2006 by Charles Petzold ============================================= ==== --> <Canvas xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <Canvas.Resources> <Style TargetType="{x:Type Ellipse}"> <Setter Property="Stroke" Value="Black" /> <Setter Property="StrokeThickness" Value="3" /> <Setter Property="Width" Value="96" /> <Setter Property="Height" Value="96" /> </Style> </Canvas.Resources> <Ellipse Canvas.Left="100" Canvas.Top="50" Fill="Blue" /> <Ellipse Canvas.Left="150" Canvas.Top="100" Fill="Red" /> <Ellipse Canvas.Left="200" Canvas.Top="150" Fill="Green" /> <Ellipse Canvas.Left="250" Canvas.Top="100" Fill="Cyan" /> <Ellipse Canvas.Left="300" Canvas.Top="50" Fill="Magenta" /> </Canvas>



Suppose you need to draw a series of horizontal lines, perhaps similar to the ruled lines of the pad in the YellowPad program from Chapter 22. The Style element would include a Setter for the Stroke color, of course, and because each of the horizontal lines begins and ends at the same X coordinate, the Style could include Setter elements for both X1 and X2 as well.

<Style TargetType="Line">     <Setter Property="Stroke" Value="Blue" />     <Setter Property="X1" Value="100" />     <Setter Property="X2" Value="300" /> </Style> 


The individual Line elements could then be as simple as this:

<Line Y1="100" Y2="100" /> <Line Y1="125" Y2="125" /> <Line Y1="150" Y2="150" /> ... 


That's not too bad. But if you ever needed to alter the location of these linesperhaps move them down by 12 unitsyou'd need to manually change the values of both Y1 and Y2, which is about twice as much work as you probably want. Considering that these are horizontal lines, and the values of Y1 and Y2 are always the same, might there be a way to specify just one value in each Line element?

The solution is a Binding using RelativeSource, which I discussed toward the end of the previous chapter. You can define a Setter for the Y2 property that indicates it should be the same as the Y1 property:

<Setter Property="Y2"         Value="{Binding RelativeSource={RelativeSource self}, Path=Y1}" /> 


You then need only specify the value Y1 in the individual Line elements, and that value is also used for Y2.

The following XAML file uses this technique to draw a grid of horizontal and vertical lines. Both the "horz" and "vert" styles are based on a Style with an x:Key of "base" that defines the line colors.

DrawGrid.xaml

[View full width]

<!-- =========================================== DrawGrid.xaml (c) 2006 by Charles Petzold =========================================== --> <Canvas xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <Canvas.Resources> <Style x:Key="base" TargetType="Line"> <Setter Property="Stroke" Value="Blue" /> </Style> <Style x:Key="horz" TargetType="Line" BasedOn="{StaticResource base}"> <Setter Property="X1" Value="100" /> <Setter Property="X2" Value="300" /> <Setter Property="Y2" Value="{Binding RelativeSource={RelativeSource self}, Path=Y1}" /> </Style> <Style x:Key="vert" TargetType="Line" BasedOn="{StaticResource base}"> <Setter Property="Y1" Value="100" /> <Setter Property="Y2" Value="300" /> <Setter Property="X2" Value="{Binding RelativeSource={RelativeSource self}, Path=X1}" /> </Style> </Canvas.Resources> <Line Style="{StaticResource horz}" Y1="100" /> <Line Style="{StaticResource horz}" Y1="125" /> <Line Style="{StaticResource horz}" Y1="150" /> <Line Style="{StaticResource horz}" Y1="175" /> <Line Style="{StaticResource horz}" Y1="200" /> <Line Style="{StaticResource horz}" Y1="225" /> <Line Style="{StaticResource horz}" Y1="250" /> <Line Style="{StaticResource horz}" Y1="275" /> <Line Style="{StaticResource horz}" Y1="300" /> <Line Style="{StaticResource vert}" X1="100" /> <Line Style="{StaticResource vert}" X1="125" /> <Line Style="{StaticResource vert}" X1="150" /> <Line Style="{StaticResource vert}" X1="175" /> <Line Style="{StaticResource vert}" X1="200" /> <Line Style="{StaticResource vert}" X1="225" /> <Line Style="{StaticResource vert}" X1="250" /> <Line Style="{StaticResource vert}" X1="275" /> <Line Style="{StaticResource vert}" X1="300" /> </Canvas>



Although you should really consider a for loop in C# when drawing repetitive elements of this type, the numeric repetition in the individual elements has been reduced to such an extent that using XAML can almost be justified.

A Style property is defined not only for FrameworkElement, but also for FrameworkContentElement, which means that you can define styles for classes that derive from TextElement. This lets you define styles for use in formatting items in a FlowDocument. Here's the first paragraph from Alice's Adventures in Wonderland, with two Paragraph styles defined and referenced.

DocumentStyles.xaml

[View full width]

<!-- ================================================= DocumentStyles.xaml (c) 2006 by Charles Petzold ============================================= ==== --> <Page xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml" Title="I. Down the Rabbit-Hole"> <Page.Resources> <Style TargetType="{x:Type Paragraph}" x: Key="Normal"> <Setter Property="TextIndent" Value="0 .25in" /> </Style> <Style TargetType="{x:Type Paragraph}" x: Key="ChapterHead"> <Setter Property="TextAlignment" Value="Center" /> <Setter Property="FontSize" Value="16pt" /> </Style> </Page.Resources> <FlowDocumentReader> <FlowDocument> <Paragraph Style="{StaticResource ChapterHead}"> Chapter I </Paragraph> <Paragraph Style="{StaticResource ChapterHead}"> Down the Rabbit-Hole </Paragraph> <Paragraph Style="{StaticResource Normal}"> Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, &#x201C;and what is the use of a book,&#x201D; thought Alice, &#x201C;without pictures or conversations?&#x201D; </Paragraph> <Paragraph Style="{StaticResource Normal}"> ... </Paragraph> </FlowDocument> </FlowDocumentReader> </Page>



Of course, you probably don't need convincing that text styles are quite valuable, as is the ability to base one style on another. Entire technologies (such as Cascading Style Sheets) are based on this premise.

Although Setter is the most common child of Style, you can also use the EventSetter element to set an event handler of a particular routed event. This is yet another way in which you can share an event handler among multiple elements. The EventSetterDemo project contains a XAML file and a C# file. The XAML file shown here contains a Style with an EventSetter element.

EventSetterDemo.xaml

[View full width]

<!-- === =============================================== EventSetterDemo.xaml (c) 2006 by Charles Petzold ============================================= ===== --> <Window xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" x:0" width="14" height="9" align="left" src="/books/4/266/1/html/2/images/ccc.gif" />.EventSetterDemo" Title="EventSetter Demo"> <Window.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="FontSize" Value="24" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="Margin" Value="24" /> <EventSetter Event="Click" Handler="ButtonOnClick" /> </Style> </Window.Resources> <StackPanel> <Button> Button Number 1 </Button> <Button> Button Number 2 </Button> <Button> Button Number 3 </Button> </StackPanel> </Window>



The C# part of the Window class contains the event handler referred to in the EventSetter element.

EventSetterDemo.cs

[View full width]

//------------------------------------------------ // EventSetterDemo.cs (c) 2006 by Charles Petzold //------------------------------------------------ using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Petzold.EventSetterDemo { public partial class EventSetterDemo : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new EventSetterDemo()); } public EventSetterDemo() { InitializeComponent(); } void ButtonOnClick(object sender, RoutedEventArgs args) { Button btn = args.Source as Button; MessageBox.Show("The button labeled " + btn.Content + " has been clicked", Title); } } }



I mentioned earlier that Style defines six properties. I've now discussed Setters, Resources, TargetType, and BasedOn. The read-only IsSealed property becomes true when the Seal method is called, which happpens when the style is in use. The last property, named Triggers, lets you control how elements or controls respond to changes in properties of the element, or to changes in a data binding, or to events.

When first encountering setters and triggers, it is easy to become confused. Both setters and triggers generally involve properties being set. A Setter effectively sets properties when the element is first created. A Trigger sets properties only when something happensthat is, when something "triggers" the property to change.

The Triggers property of Style is of type TriggerCollection, which is a collection of objects of type TriggerBase. The classes that derive from the abstract TriggerBase class are shown here:

Object
    DispatcherObject (abstract)

      DependencyObject

      TriggerBase (abstract)

             DataTrigger

             EventTrigger

             MultiDataTrigger

             MultiTrigger

             Trigger

The class that I will not be discussing in this chapter is EventTrigger, which can cause a change to a control or element when a specified event occurs. Generally EventTrigger precipitates a graphical animation, so I cover this in Chapter 30. (You may have noticed that FrameworkElement itself defines a property named Triggers. I won't be discussing that property in this chapter because the only Trigger objects that can go in that collection are of type EventTrigger.)

The most common of the TriggerBase derivatives is Trigger, which specifies how a control or element should react to a particular property change. Very often, these properties involve user input, such as the property IsMouseOver. The Trigger reacts by changing some other property through a Setter definition. Here's a typical Trigger definition as it might appear within a Style element:

<Style.Triggers>     <Trigger Property="Control.IsMouseOver" Value="True">         <Setter Property="Control.FontStyle" Value="Italic" />         <Setter Property="Control.Foreground" Value="Blue" />     </Trigger> </Style.Triggers> 


Notice that the Style.Triggers property element is required within the Style definition. The Property and Value attributes of the Trigger element indicate that the Trigger will kick in when the IsMouseOver property becomes true. Although you aren't restricted to Boolean properties, they are certainly the most common. The property specified in a Trigger must be backed by a dependency property.

Like Style, Trigger has a property named Setters, and just like Style, this property is the content property of Trigger, and Trigger can contain more than one child Setter element. The two Setter objects in the Trigger element shown in the preceding example indicate that the control text is to be displayed with italics and in blue when the mouse is over the control. (Trigger also has a property named SourceName, but you don't use this property with styles. You use it with templates, which are covered in the next chapter.)

Here's the complete XAML file with two Trigger elements.

StyleWithTriggers.xaml

[View full width]

<!-- === ================================================= StyleWithTriggers.xaml (c) 2006 by Charles Petzold ============================================= ======= --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style x:Key="normal"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> <Style.Triggers> <Trigger Property="Control .IsMouseOver" Value="true"> <Setter Property="Control .FontStyle" Value="Italic" /> <Setter Property="Control .Foreground" Value="Blue" /> </Trigger> <Trigger Property="Button .IsPressed" Value="true"> <Setter Property="Control .Foreground" Value="Red" /> </Trigger> </Style.Triggers> </Style> </StackPanel.Resources> <Button Style="{StaticResource normal}"> Button Number 1 </Button> <Button Style="{StaticResource normal}"> Button Number 2 </Button> <Button Style="{StaticResource normal}"> Button Number 3 </Button> </StackPanel>



In addition to the Trigger based on the IsMouseOver property, another Trigger changes the Foreground color when the IsPressed property becomes True. In both cases, the properties return to their original states when the value of the property changes back.

In this particular case, the order of the two Trigger elements affects how the triggers work. If Trigger elements set the same properties, later Trigger elements override earlier Trigger elements. If you swap the two Trigger properties in the previous XAML file, the text never turns red because IsPressed is true only if IsMouseOver is also true, and the Trigger for IsMouseOver has final say in setting the text blue.

If you can't get the order just right, you might consider using a MultiTrigger. The MultiTrigger is similar to the Trigger except that it only kicks in when two or more conditions hold. Here's an example.

MultiTriggerDemo.xaml

[View full width]

<!-- === ================================================ MultiTriggerDemo.xaml (c) 2006 by Charles Petzold ============================================= ====== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style x:Key="normal"> <Setter Property="Control.FontSize" Value="24" /> <Setter Property="Control .HorizontalAlignment" Value="Center" /> <Setter Property="Control.Margin" Value="24" /> <Style.Triggers> <Trigger Property="Button .IsPressed" Value="True"> <Setter Property="Control .Foreground" Value="Red" /> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="Control.IsMouseOver" Value="True" /> <Condition Property="Button.IsPressed" Value="False" /> </MultiTrigger.Conditions> <Setter Property="Control .FontStyle" Value="Italic" /> <Setter Property="Control .Foreground" Value="Blue" /> </MultiTrigger> </Style.Triggers> </Style> </StackPanel.Resources> <Button Style="{StaticResource normal}"> Button Number 1 </Button> <Button Style="{StaticResource normal}"> Button Number 2 </Button> <Button Style="{StaticResource normal}"> Button Number 3 </Button> </StackPanel>



The Conditions property of MultiTrigger is an object of type ConditionCollection. Each Condition element indicates a Property and a Value. The button text is blue when the mouse is over the button, and red when the button is clicked. The button text is italic only when the mouse is over the button but the button is not pressed, because that's part of the MultiTrigger with those conditions.

The DataTrigger class is similar to Trigger except that it substitutes Binding for Property. The Binding generally refers to another element. DataTrigger can set a property when this Binding has a particular value as demonstrated in the following file.

StyleWithDataTrigger.xaml

[View full width]

<!-- === ==================================================== StyleWithDataTrigger.xaml (c) 2006 by Charles Petzold ============================================= ========== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style TargetType="{x:Type Button}"> <Setter Property="FontSize" Value="24" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="Margin" Value="24" /> <Style.Triggers> <DataTrigger Binding="{Binding ElementName=txtbox, Path=Text.Length}" Value="0"> <Setter Property="IsEnabled" Value="False" /> </DataTrigger> </Style.Triggers> </Style> </StackPanel.Resources> <TextBox Name="txtbox" HorizontalAlignment="Center" Width="2in" Margin="24" /> <Button> Button Number 1 </Button> <Button> Button Number 2 </Button> <Button> Button Number 3 </Button> </StackPanel>



The DataTrigger contains a binding with the TextBox element, referred to by its name of txtbox. The Path is Text.Length, which refers to the Length property of the Text property of the TextBox. When that value is 0, the IsEnabled property of the Button is set to false. The result is that the buttons are enabled only if there's some text in the TextBox. Dialog boxes commonly enable the OK button only when certain conditions are true, so this is an ideal application of a DataTrigger.

The MultiDataTrigger is to DataTrigger what MultiTrigger is to Trigger. The properties are set only when all binding conditions are met. Like MultiTrigger, MultiDataTrigger includes one or more Condition elements. The Condition class defines Property, Binding, and Value properties, perhaps suggesting that you can mix regular triggers and data triggers, but you can't. When defining a MultiTrigger, you use the Property and Value properties of Condition. When defining a MultiDataTrigger, you use the Binding and Value properties. Here's a XAML file in which the buttons are enabled only if two CheckBox controls are checked.

MultiDataTriggerDemo.xaml

[View full width]

<!-- === ==================================================== MultiDataTriggerDemo.xaml (c) 2006 by Charles Petzold ============================================= ========== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <StackPanel.Resources> <Style TargetType="{x:Type CheckBox}"> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="Margin" Value="12" /> </Style> <Style TargetType="{x:Type Button}"> <Setter Property="FontSize" Value="24" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="Margin" Value="12" /> <Setter Property="IsEnabled" Value="False" /> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding ElementName=chkbox1, Path=IsChecked}" Value="True" /> <Condition Binding="{Binding ElementName=chkbox2, Path=IsChecked}" Value="True" /> </MultiDataTrigger.Conditions> <Setter Property="IsEnabled" Value="True" /> </MultiDataTrigger> </Style.Triggers> </Style> </StackPanel.Resources> <CheckBox Name="chkbox1"> Check 1 </CheckBox> <CheckBox Name="chkbox2"> Check 2 </CheckBox> <Button> Button Number 1 </Button> <Button> Button Number 2 </Button> <Button> Button Number 3 </Button> </StackPanel>



As you've seen, styles give you the convenience of organizing the appearance of your controls in a very systematic way. Styles can be as simple as a few lines in a XAML file to reduce repetition among similar elements, or they can fill entire files and provide every control in your applications with a unique look. Whenever you're about to give two elements the same property value, use a style instead, and you'll be thankful if you ever need to change that value.




Applications = Code + Markup. A Guide to the Microsoft Windows Presentation Foundation
Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation (Pro - Developer)
ISBN: 0735619573
EAN: 2147483647
Year: 2006
Pages: 72

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net