You can find the wrox.com
code downloads for this chapter at www.wrox.com/go/beginningvisualc#2015programming
on the Download Code tab. The code is in the Chapter 15 download and individually named according to the names throughout the chapter.
Until this point you have used Windows Presentation Foundation (WPF) in much the same way that you use the other major technology for creating windows applications in Visual Studio: Windows Forms. But that is about to change. WPF can style any control and use templates to change existing controls to look nothing like they do out-of-the-box. In addition to that, you are going to start working more and more by typing XAML. Although this might seem like a burden at first, the ability to move and fine-tune the display by setting properties will quickly become second nature, and you will find that there is quite a bit in XAML that cannot be done in the designer, such as creating animations.
Now it is time to continue where you left off in Chapter 14 and continue with the game client.
The main window of the application is where the game is played, and it therefore doesn't have many controls on it. You'll construct the game in this chapter, but before you start, there are three things that you must do. You need to add the main window to the project, add menus to the window, and bind the windows you already constructed to the menu items.
Most applications include menus and toolbars of some kind. Both are a means to the same end: to provide easy navigation of the application's content. Toolbars generally contain a subset of the same entries that the menus provide and can be thought of as shortcuts to the menu items.
Visual Studio ships with both a
and a Menu
control. The example here shows the use of the Toolbar
control but using the Toolbar is very similar.Menu
By default, the menu item appears as a horizontal bar from which you can drop down lists of items. The control is an
control, so it is possible to change the default items contained in the content; however, you would normally use Items
in some form, as shown in the following example. Each MenuItems
can contain other menu items, and you can build complex menus by nesting MenuItem
within each other, but you should try to keep the menu structure as simple as possible.MenuItems
You can control how the
displays using a number of properties (see Table 15.1).MenuItem
Table 15.1 Displaying MenuItem Properties
Property | Description |
|
Displays an icon by the left edge of the control |
|
Displays a by the left edge of the control |
|
Gets or sets the value of a on a
|
Routed commands were briefly discussed in Chapter 14, but now you are going to see them in action for the first time. Recall that these commands are akin to events in that they execute code when a user performs an action, and they can return a state indicating whether they can be executed at any given time.
There are at least three reasons why you would want to use routed commands instead of events:
If any of these scenarios matches yours, consider using routed commands. In the case of the game, some of the items in the menu should also potentially be available from a toolbar. In addition, the Save action should be available only when a game is in progress and it should potentially be available from both a menu and the toolbar.
It's time to step away from the client implementation of the game and start looking more at the game itself. One key feature of a graphical card game is…the cards. Obviously, you are not going to find a “Playing Card” control in the standard controls that ship with WPF, so you have to create it yourself.
One of the best features of WPF is the complete control it provides designers over the look and feel of user interfaces. Central to this is the capability to style controls however you want, in two or three dimensions. Until now, you have been using the basic styling for controls that is supplied with .NET, but the actual possibilities are endless.
This section describes two basic techniques:
There is some overlap here, as styles can contain templates.
WPF controls have a property called
(inherited from Style
) that can be set to an instance of the FrameworkElement
class. The Style
class is quite complex and is capable of advanced styling functionality, but at its heart it is essentially a set of Style
objects. Each Setter
object is responsible for setting the value of a property according to its Setter
property (the name of the property to set) and its Property
property (the value to set the property to). You can either fully qualify the name you use in Value
to the control type (for example, Property
), or you can set the Button.Foreground
property of the TargetType
object (for example, Style
), so that it is capable of resolving property names.Button
The following code shows how to use a
object to set the Style
property of a Foreground
control:Button
<Button>
Click me!
<Button.Style>
<Style TargetType="Button">
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="Purple"/>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Obviously, in this case it would be far easier simply to set the
property of the button in the usual way. Styles become much more useful when you turn them into resources, because resources can be reused. You will learn how to do this in the “WPF User Controls” section later in the chapter.Foreground
Controls are constructed using templates, which you can customize. A template consists of a hierarchy of controls used to build the display of a control, which may include a content presenter for controls such as buttons that display content.
The template of a control is stored in its
property, which is an instance of the Template
class. The ControlTemplate
class includes a ControlTemplate
property that you can set to the type of control for which you are defining a template, and it can contain a single control. This control can be a container such as TargetType
, so this doesn't exactly limit what you can do.Grid
Typically, you set the template for a class by using a style. This simply involves providing controls to use for the
property in the following way:Template
<Button>
Click me!
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
…
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Some controls may require more than one template. For example,
controls use one template for a check box (CheckBox
) and one template to output text next to the check box (CheckBox.Template
).CheckBox.ContentTemplate
Templates that require content presenters can include a
control at the location where you want to output content. Some controls — especially those that output collections of items — use alternative techniques, which aren't covered in this chapter.ContentPresenter
Again, replacing templates is most useful when combined with resources. However, as control styling is a very common technique, it is worth looking at how to do it in a Try It Out.
You may have wondered at some of the assignments that you have used in the examples so far: How, for example, can you assign the string value “true” to a Boolean property? You have learned that C# is type-safe and the compiler should not allow that kind of thing to happen! Happily, the reality is that it doesn't. XAML and WPF make extensive use of something called value converters, which can convert from one type to another behind the scenes.
WPF ships with converters for just about all the standard scenarios you can think of, so you can always convert from
to int
or string
and bool
. But what happens when you want to convert something that is not included? Then you have to implement the converter yourself.integer
Let's look at one example that is very common: the inversed
converter. Imagine that you have a check box on a dialog box. Depending on whether it's checked, another part of the dialog box will be disabled or enabled. Quite often, the answer must be reversed for this to make sense. Take a look at the Options dialog box, for example. It has a check box with the question, “Play against computer?” Selecting this option should disable the bool
and ComboBox
on the dialog box. The value of TextBoxes
would be true, so binding that to IsChecked
of the other two controls will not work. Enter the IsEnabled
. This converter will simply inverse the InversedBoolConverter
value.bool
In order to create a
, you must implement the ValueConverter
interface. This interface has two methods: IValueConverter
and Convert
. These might seem self-explanatory, but they are actually a bit complicated.ConvertBack
object Convert(object value, Type targetType,
object parameter, CultureInfo culture);
object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture);
You use the
method when converting to a target type and use the Convert
method for the reverse operation. The value parameter denotes the value to convert and the ConvertBack
is the type it should be converted to. The parameter can be used to set a helper. Exercise 15.1 at the end of this chapter requires you to use this parameter to create a specific value converter.targetType
In addition to implementing the interface, you can set an attribute on the class that implements the converter. This is not needed, but it is a great help both for tools and users of your converter.
takes two parameters, both of which are TheValueConversionAttribute
objects. This means that you explicitly set the types that the converter will convert to and from.Type
Events in WPF can include all manner of things, including button clicks, application startup and shutdown events, and so on. There are, in fact, several types of triggers in WPF, all of which inherit from a base
class. One such trigger is the TriggerBase
class, which contains a collection of actions, each of which is an object that derives from the base EventTrigger
class. These actions are executed when the trigger is activated.TriggerAction
Not a lot of classes inherit from
in WPF, but you can, of course, define your own. You can use TriggerAction
to trigger animations using the EventTrigger
action, manipulate storyboards using BeginStoryboard
, and trigger sound effects with ControllableStoryboardAction
. As this latter trigger is mostly used in animations, you'll look at it in the next section.SoundPlayerAction
Every control has a
property that you can use to define triggers directly on that control. You can also define triggers further up the hierarchy — for example, on a Triggers
object as shown earlier. The type of trigger you will use most often when you are styling controls is Window
(although you will still use Trigger
to trigger control animations). The EventTrigger
class is used to set properties in response to changes to other properties, and is particularly useful when used in Trigger
objects.Style
Trigger objects are configured as follows:
Trigger
object monitors, you use the Trigger.Property
property.Trigger
object activates, you set the Trigger.Value
property.Trigger
, you set the Trigger.Setters
property to a collection of Setter
objects.The
objects referred to here are exactly the same objects that you saw in the “Styles” section earlier.Setter
For example, the following trigger examines the value of a property called
, and when that property is MyBooleanValue
it sets the value of the true
property to Opacity
.0
:5
<Trigger Property="MyBooleanValue" Value="true">
<Setter Property="Opacity" Value="0.5"/>
</Trigger>
On its own, this code doesn't tell you very much, as it is not associated with any control or style. The following code is much more explanatory; it shows a
as you would use it in a Trigger
object:Style
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Foreground" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
This code changes the
property of a Foreground
control to Button
when the Yellow
property is Button.IsMouseOver
. true
is one of several extremely useful properties that you can use as a shortcut to find out information about controls and control state. As its name suggests, it is IsMouseOver
if the mouse is over the control. This enables you to code for mouse rollovers. Other properties like this include true
, to determine whether a control has focus; IsFocused
, which indicates whether it is possible to click on a control (that is, it is not obscured by controls further up the stacking order); and IsHitTestVisible
, which indicates whether a button is pressed. The last of these only applies to buttons that inherit from IsPressed
, whereas the others are available on all controls.ButtonBase
You can also achieve a great deal by using the
property, which enables you to create templates for controls that include triggers. This is how the default ControlTemplate.Triggers
template is able to respond to mouse rollovers, clicks, and focus changes with its template. This is also what you must modify to implement this functionality for yourself.Button
Animations are created by using storyboards. The absolute best way to define animations is, without a doubt, to use a designer such as Expression Blend. However, you can also define them by editing XAML code directly, and by implication from code-behind (as XAML is simply a way to build a WPF object model).
A storyboard is defined using a
object, which contains one or more timelines. You can define timelines by using key frames or by using one of several simpler objects that encapsulate entire animations. Complex storyboards may even contain nested storyboards.Storyboard
A
is contained in a resource dictionary, so you must identify it with an Storyboard
property.x:Key
Within the timeline of a storyboard, you can animate properties of any element in your application that is of type
, double
, or Point
. This covers most of the things that you may want to change, so it's quite flexible. There are some things that you can't do, such as replace one brush with another, but there are ways to achieve pretty much any effect you can imagine given these three types.Color
Each of these three types has two associated timeline controls that you can use as children of
. These six controls are Storyboard
, DoubleAnimation
, DoubleAnimationUsingKeyFrames
, PointAnimation
, PointAnimationUsingKeyFrames
, and ColorAnimation
. Every timeline control can be associated with a specific property of a specific control by using the attached properties ColorAnimationUsingKeyFrames
and Storyboard.TargetName
. For example, you would set these properties to Storyboard.TargetProperty
and MyRectangle
if you wanted to animate the Width
property of a Width
control with a Rectangle
property of Name
. You would use either MyRectangle
or DoubleAnimation
to animate this property. You will see examples of using storyboards as this chapter progresses.DoubleAnimationUsingKeyFrames
Next, you'll look at the simple, animation timelines without key frames, and then move on to look at the timelines that use key frames.
The timelines without key frames are
, DoubleAnimation
, and PointAnimation
. These timelines have identical property names, although the types of these properties vary according to the type of the timeline (note that all duration properties are specified in the form ColorAnimation
in XAML code). Table 15.2 describes these properties.[days.]hours:minutes:seconds
Table 15.2 The Timeline Properties
Property | Description |
|
The name of the timeline, so that you can refer to it from other places. |
|
How long after the storyboard is triggered before the timeline starts. |
|
How long the timeline lasts. |
|
Whether the timeline reverses when it completes and returns properties to their original values. This property is a value. |
|
Set this to a specified duration to make the timeline repeat as indicated — an integer followed by (for example, ) to repeat the timeline a set number of times; or use to make the timeline repeat until the storyboard is paused or stopped. |
|
How the timeline behaves if it completes while the storyboard is still continuing. You can use to leave properties at the values they are at when the timeline completes (the default), or to return them to their original values. |
|
Controls the speed of the animation relative to the values specified in other properties. The default value is , but you can change it from other code to speed up or slow down animations. |
|
The initial value to set the property to at the start of the animation. You can omit this value to use the current value of the property. |
|
The final value for the property at the end of the animation. You can omit this value to use the current value of the property. |
|
Use this value to animate from the current value of a property to the sum of the current value and the value you specify. You can use this property on its own or in combination with . |
For example, the following timeline will animate the
property of a Width
control with a Rectangle
property of Name
between 100 and 200 over five seconds:MyRectangle
<Storyboard x:Key="RectangleExpander">
<DoubleAnimation Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Width" Duration="00:00:05"
From="100" To="200"/>
</Storyboard>
The timelines with key frames are
, DoubleAnimationUsingKeyFrames
, and PointAnimationUsingKeyFrames
. These timeline classes use the same properties as the timeline classes in the previous section, except that they don't have ColorAnimationUsingKeyFrames
, From
, or To
properties. Instead, they have a By
property that is a collection of key frame objects.KeyFrames
These timelines can contain any number of key frames, each of which can cause the value being animated to behave in a different way. There are three types of key frames for each type of timeline:
There are therefore nine types of key frame objects:
, DiscreteDoubleKeyFrame
, LinearDoubleKeyFrame
, SplineDoubleKeyFrame
, DiscreteColorKeyFrame
, LinearColorKeyFrame
, SplineColorKeyFrame
, DiscretePointKeyFrame
, and LinearPointKeyFrame
.SplinePointKeyFrame
The key frame classes have the same three properties as the timeline classes examined in the previous section. The four spline key frame classes add one additional property:
(see Table 15.3).KeySpline
Table 15.3 Properties of the Spline Key Frame Classes
Property | Usage |
|
The name of the key frame, so that you can refer to it from other places. |
|
The location of the key frame expressed as an amount of time after the timeline starts. |
|
The value that the property will reach or be set to when the key frame is reached. |
|
Two sets of two numbers in the form that define the cubic Bezier function to use to animate the property. (Spline key frames only.) |
For example, you could animate the position of an
in a square by animating its Ellipse
property, which is of type Center
, as follows:Point
<Storyboard x:Key="EllipseMover">
<PointAnimationUsingKeyFrames Storyboard.TargetName="MyEllipse"
Storyboard.TargetProperty="Center" RepeatBehavior="Forever">
<LinearPointKeyFrame KeyTime="00:00:00" Value="50,50"/>
<LinearPointKeyFrame KeyTime="00:00:01" Value="100,50"/>
<LinearPointKeyFrame KeyTime="00:00:02" Value="100,100"/>
<LinearPointKeyFrame KeyTime="00:00:03" Value="50,100"/>
<LinearPointKeyFrame KeyTime="00:00:04" Value="50,50"/>
</PointAnimationUsingKeyFrames>
</Storyboard>
Point values are specified in
form in XAML code.x,y
WPF provides a set of controls that are useful in many situations. However, as with all the .NET development frameworks, it also enables you to extend this functionality. Specifically, you can create your own controls by deriving your classes from classes in the WPF class hierarchy.
One of the most useful controls you can derive from is
. This class gives you all the basic functionality that you are likely to require from a WPF control, and it enables your control to snap in beside the existing WPF control suite seamlessly. Everything you might hope to achieve with a WPF control — such as animation, styling, and templating — can be achieved with user controls.UserControl
You can add user controls to your project by using the Project Add User Control menu item. This gives you a blank canvas (well, actually a blank
) to work from. User controls are defined using the top-level Grid
element in XAML, and the class in the code-behind derives from the UserControl
class.System.Windows.Controls.UserControl
Once you have added a user control to your project, you can add controls to lay out the control and code-behind to configure the control. When you have finished doing that, you can use it throughout your application, and even reuse it in other applications.
One of the crucial things you need to know when creating user controls is how to implement dependency properties. Chapter 14 briefly discussed dependency properties, and now that you are getting closer to writing your own controls, it is time to take a closer look at them.
You can add dependency properties to any class that inherits from
. This class is in the inheritance hierarchy for many classes in WPF, including all the controls and System.Windows.DependencyObject
.UserControl
To implement a dependency property to a class, you add a public, static member to your class definition of type
. The name of this member is up to you, but best practice is to follow the naming convention System.Windows.DependencyProperty
:<PropertyName>Property
public static DependencyProperty MyStringProperty;
It might seem odd that this property is defined as static, as you end up with a property that can be uniquely defined for each instance of your class. The WPF property framework keeps track of things for you, so you don't have to worry about this for the moment.
The member you add must be configured by using the static
method:DependencyProperty.Register()
public static DependencyProperty MyStringProperty =
DependencyProperty.Register(…);
This method takes between three and five parameters, as shown in the Table 15.4 (these are shown in order, with the first three parameters being the mandatory ones).
Table 15.4 The Register( ) Method's Parameters
Parameter | Usage |
|
The name of the property |
|
The type of the property |
|
The type of the class containing the property |
|
Additional property settings: the default value of the property and callback methods to use for property change notifications and coercion |
|
The callback method to use to validate property values |
There are oth methods that you can use to register dependency properties, such as
, which you can use to implement an attached property. You won't look at these other methods in this chapter, but it's worth reading up on them.RegisterAttached()
For example, you could register the
dependency property using three parameters as follows:MyStringProperty
public class MyClass : DependencyObject
{
public static DependencyProperty MyStringProperty = DependencyProperty.Register(
"MyString",
typeof(string),
typeof(MyClass));
}
You can also include a .NET property that can be used to access dependency properties directly (although this isn't mandatory, as you will see shortly). However, because dependency properties are defined as static members, you cannot use the same syntax you would use with ordinary properties. To access the value of a dependency property, you have to use methods that are inherited from
, as follows:DependencyObject
public string MyString
{
get { return (string)GetValue(MyStringProperty); }
set { SetValue(MyStringProperty, value); }
}
Here, the
and GetValue()
methods get and set, respectively, the value of the SetValue()
, dependency property for the current instance. These two methods are public, so client code can use them directly to manipulate dependency property values. This is why adding a .NET property to access a dependency property is not mandatory.MyStringProperty
If you want to set metadata for a property, then you must use an object that derives from
, such as PropertyMetadata
, and pass this instance as the fourth parameter to FrameworkPropertyMetadata
. There are 11 overloads of the Register()
constructor, and they take one or more of the parameters shown in Table 15.5.FrameworkPropertyMetadata
Table 15.5 Overloads for the FrameworkPropertyMetadata Constructor
Parameter Type | Usage |
|
The default value for the property. |
|
A combination of the flags (from the enum) that you can use to specify additional metadata for a property. For example, you might use to declare that changes to the property might affect control layout. This would cause the layout engine for a window to recalculate control layout if the property changed. See the MSDN documentation for a full list of the options available here. |
|
The callback method to use when the property value changes. |
|
The callback method to use if the property value is coerced. |
|
Specifies whether this property can be changed by an animation. |
|
When property values are data-bound, this property determines when the data source is updated, according to values in the enum. The default value is , which means that the binding source is updated as soon as the property changes. This is not always appropriate — for example, the property uses a value of for this property. This ensures that the binding source is not updated prematurely. You can also use the value to specify that the binding source should be updated only when requested (by calling the method of a class derived from ). |
A simple example of using
is to use it to set the default value of a property:FrameworkPropertyMetadata
public static DependencyProperty MyStringProperty =
DependencyProperty.Register(
"MyString",
typeof(string),
typeof(MyClass),
new FrameworkPropertyMetadata("Default value"));
You have so far learned about three callback methods that you can specify, for property change notification, property coercion, and property value validation. These callbacks, like the dependency property itself, must all be implemented as public, static methods. Each callback has a specific return type and parameter list that you must use on your callback method.
Now it is time to get back on track and continue with the game client for Karli Cards. In the following Try It Out, you create a user control that can represent a playing card in the application.
At this point in the development of the game, you have two independent dialog boxes, a card library, and a main window that provides a blank space for the game to be displayed on. That still leaves quite a lot of work, but with the foundation built, it's time to start building the game. The classes in the CardLib describe the game “domain model,” that is, the objects that a game can be broken down into, which need to be refactored a bit to make it work better with a Windows application. Next you are going to write the game's “View Model,” which is a class that is able to control the display of the game. Then you will create two additional user controls that use the
user control to display the game visually. Finally, you will bind it all together in the game client.Card
As stated, the domain model is the code that describes the objects of the game. At the moment, you have these classes in the CardLib project that describe objects of the game:
In addition to these classes, the game needs a
and a Player
class, so you are going to add those. You also need to modify the ComputerPlayer
and Card
classes a bit to make them work better in a Windows application.Deck
There is a lot of work to do, so let's get started.
The purpose of a view model is to hold the state of the view that displays it. In the case of the Karli Cards, this means that you already have a view model class: the
class. This class holds the state of the GameOptions
and Options
windows. At the moment, you can't get the selected players from the options, so you have to add that ability. The view model of the Game Client window is missing, so that is the next task to do.StartGame
The view model for the execution of the game must reflect all the parts of the game as it is running. The parts of the game are:
The view model should also be able to notify observers of changes, and that means implementing
again.INotifyPropertyChanged
In addition to these abilities, the view model should also provide a way of starting a new game. You will do this by creating a new routed command for the menu. The command is created in the view model, but is called from the view.
You now have a complete game that you can't play because nothing is being displayed in the game client. For the game to run, you need two additional user controls that will be positioned on the game client using a dock panel.
The two user controls are called
, which displays a player's hand, and CardsInHand
, which displays the main deck and the available card.GameDecks
Topic | Key Concepts |
Styles | You can use styles to create styles for XAML elements that can be reused on many elements. Styles allow you to set the properties of an element. When you set the property of an element to point to a style you have defined, the properties of the element will use the values you specified in the property. |
Templates | Templates are used to define the content of a control. Using templates you can change how standard controls are displayed. You can also build complex custom controls with them. |
Value converters | Value converters are used to convert to and from two types. To create a value converter, you must implement the interface on a class. |
User controls | User controls are used to create code and XAML that can be reused easily in your own project. This code and XAML can also be exported for use in other projects. |