Chapter 15
Advanced Desktop Programming

Wrox.com Code Downloads for this Chapter

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

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.

The Menu Control

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 Menu and a Toolbar control. The example here shows the use of the Menu control but using the Toolbar is very similar.

By default, the menu item appears as a horizontal bar from which you can drop down lists of items. The control is an Items control, so it is possible to change the default items contained in the content; however, you would normally use MenuItems in some form, as shown in the following example. Each MenuItem can contain other menu items, and you can build complex menus by nesting MenuItems within each other, but you should try to keep the menu structure as simple as possible.

You can control how the MenuItem displays using a number of properties (see Table 15.1).

Table 15.1 Displaying MenuItem Properties

Property Description
Icon Displays an icon by the left edge of the control
IsCheckable Displays a CheckBox by the left edge of the control
IsChecked Gets or sets the value of a CheckBox on a MenuItem

Routed Commands with Menus

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:

  1. The action that will cause an event to occur can be triggered from multiple locations in your application.
  2. The UI element should be accessible only under certain conditions, such as a Save button being disabled if there's nothing to save.
  3. You want to disconnect the code that handles the event from the code-behind file.

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.

Creating and Styling Controls

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:

  • Styles — Sets of properties that are applied to a control as a batch
  • Templates — The controls that are used to build the display for a control

There is some overlap here, as styles can contain templates.

Styles

WPF controls have a property called Style (inherited from FrameworkElement) that can be set to an instance of the Style class. The Style class is quite complex and is capable of advanced styling functionality, but at its heart it is essentially a set of Setter objects. Each Setter object is responsible for setting the value of a property according to its Property property (the name of the property to set) and its Value property (the value to set the property to). You can either fully qualify the name you use in Property to the control type (for example, Button.Foreground), or you can set the TargetType property of the Style object (for example, Button), so that it is capable of resolving property names.

The following code shows how to use a Style object to set the Foreground property of a Button control:

<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 Foreground 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.

Templates

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 Template property, which is an instance of the ControlTemplate class. The ControlTemplate class includes a TargetType 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 Grid, so this doesn't exactly limit what you can do.

Typically, you set the template for a class by using a style. This simply involves providing controls to use for the Template property in the following way:

<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, CheckBox controls use one template for a check box (CheckBox.Template) and one template to output text next to the check box (CheckBox.ContentTemplate).

Templates that require content presenters can include a ContentPresenter 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.

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.

Value Converters

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 int to string or bool and integer. But what happens when you want to convert something that is not included? Then you have to implement the converter yourself.

Let's look at one example that is very common: the inversed bool 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 ComboBox and TextBoxes on the dialog box. The value of IsChecked would be true, so binding that to IsEnabled of the other two controls will not work. Enter the InversedBoolConverter. This converter will simply inverse the bool value.

The IValueConverter Interface

In order to create a ValueConverter, you must implement the IValueConverter interface. This interface has two methods: Convert and ConvertBack. These might seem self-explanatory, but they are actually a bit complicated.

object Convert(object value, Type targetType,
    object parameter, CultureInfo culture);
object ConvertBack(object value, Type targetType,
    object parameter, CultureInfo culture);

You use the Convert method when converting to a target type and use the ConvertBack method for the reverse operation. The value parameter denotes the value to convert and the targetType 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.

ValueConversionAttribute

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. TheValueConversionAttribute takes two parameters, both of which are Type objects. This means that you explicitly set the types that the converter will convert to and from.

Triggers

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 TriggerBase class. One such trigger is the EventTrigger class, which contains a collection of actions, each of which is an object that derives from the base TriggerAction class. These actions are executed when the trigger is activated.

Not a lot of classes inherit from TriggerAction in WPF, but you can, of course, define your own. You can use EventTrigger to trigger animations using the BeginStoryboard action, manipulate storyboards using ControllableStoryboardAction, and trigger sound effects with SoundPlayerAction. As this latter trigger is mostly used in animations, you'll look at it in the next section.

Every control has a Triggers 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 Window object as shown earlier. The type of trigger you will use most often when you are styling controls is Trigger (although you will still use EventTrigger to trigger control animations). The Trigger class is used to set properties in response to changes to other properties, and is particularly useful when used in Style objects.

Trigger objects are configured as follows:

  • To define what property a Trigger object monitors, you use the Trigger.Property property.
  • To define when the Trigger object activates, you set the Trigger.Value property.
  • To define the actions taken by a Trigger, you set the Trigger.Setters property to a collection of Setter objects.

The Setter objects referred to here are exactly the same objects that you saw in the “Styles” section earlier.

For example, the following trigger examines the value of a property called MyBooleanValue, and when that property is true it sets the value of the Opacity property to 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 Trigger as you would use it in a Style object:

<Style TargetType="Button">
  <Style.Triggers>
    <Trigger Property="IsMouseOver" Value="true">
      <Setter Property="Foreground" Value="Yellow"/>
    </Trigger>
  </Style.Triggers>
</Style>

This code changes the Foreground property of a Button control to Yellow when the Button.IsMouseOver property is true. IsMouseOver 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 true if the mouse is over the control. This enables you to code for mouse rollovers. Other properties like this include IsFocused, to determine whether a control has focus; IsHitTestVisible, 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 IsPressed, which indicates whether a button is pressed. The last of these only applies to buttons that inherit from ButtonBase, whereas the others are available on all controls.

You can also achieve a great deal by using the ControlTemplate.Triggers property, which enables you to create templates for controls that include triggers. This is how the default Button 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.

Animations

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 Storyboard 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.

A Storyboard is contained in a resource dictionary, so you must identify it with an x:Key property.

Within the timeline of a storyboard, you can animate properties of any element in your application that is of type double, Point, or Color. 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.

Each of these three types has two associated timeline controls that you can use as children of Storyboard. These six controls are DoubleAnimation, DoubleAnimationUsingKeyFrames, PointAnimation, PointAnimationUsingKeyFrames, ColorAnimation, and ColorAnimationUsingKeyFrames. Every timeline control can be associated with a specific property of a specific control by using the attached properties Storyboard.TargetName and Storyboard.TargetProperty. For example, you would set these properties to MyRectangle and Width if you wanted to animate the Width property of a Rectangle control with a Name property of MyRectangle. You would use either DoubleAnimation or DoubleAnimationUsingKeyFrames to animate this property. You will see examples of using storyboards as this chapter progresses.

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.

Timelines without Key Frames

The timelines without key frames are DoubleAnimation, PointAnimation, and ColorAnimation. 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 [days.]hours:minutes:seconds in XAML code). Table 15.2 describes these properties.

Table 15.2 The Timeline Properties

Property Description
Name The name of the timeline, so that you can refer to it from other places.
BeginTime How long after the storyboard is triggered before the timeline starts.
Duration How long the timeline lasts.
AutoReverse Whether the timeline reverses when it completes and returns properties to their original values. This property is a Boolean value.
RepeatBehavior Set this to a specified duration to make the timeline repeat as indicated — an integer followed by x (for example, 5x) to repeat the timeline a set number of times; or use Forever to make the timeline repeat until the storyboard is paused or stopped.
FillBehavior How the timeline behaves if it completes while the storyboard is still continuing. You can use HoldEnd to leave properties at the values they are at when the timeline completes (the default), or Stop to return them to their original values.
SpeedRatio Controls the speed of the animation relative to the values specified in other properties. The default value is 1, but you can change it from other code to speed up or slow down animations.
From 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.
To 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.
By 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 From.

For example, the following timeline will animate the Width property of a Rectangle control with a Name property of MyRectangle between 100 and 200 over five seconds:

<Storyboard x:Key="RectangleExpander">
<DoubleAnimation Storyboard.TargetName="MyRectangle"
  Storyboard.TargetProperty="Width" Duration="00:00:05"
  From="100" To="200"/>
</Storyboard>

Timelines with Key Frames

The timelines with key frames are DoubleAnimationUsingKeyFrames, PointAnimationUsingKeyFrames, and ColorAnimationUsingKeyFrames. These timeline classes use the same properties as the timeline classes in the previous section, except that they don't have From, To, or By properties. Instead, they have a KeyFrames property that is a collection of key frame objects.

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:

  • Discrete — A discrete key frame causes the value being animated to jump to a specified value with no transition.
  • Linear — A linear key frame causes the value being animated to animate to a specified value in a linear transition.
  • Spline — A spline key frame causes the value being animated to animate to a specified value in a nonlinear transition defined by a cubic Bezier curve function.

There are therefore nine types of key frame objects: DiscreteDoubleKeyFrame, LinearDoubleKeyFrame, SplineDoubleKeyFrame, DiscreteColorKeyFrame, LinearColorKeyFrame, SplineColorKeyFrame, DiscretePointKeyFrame, LinearPointKeyFrame, and 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: KeySpline (see Table 15.3).

Table 15.3 Properties of the Spline Key Frame Classes

Property Usage
Name The name of the key frame, so that you can refer to it from other places.
KeyTime The location of the key frame expressed as an amount of time after the timeline starts.
Value The value that the property will reach or be set to when the key frame is reached.
KeySpline Two sets of two numbers in the form cp1x,cp1y,cp2x,cp2y 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 Ellipse in a square by animating its Center property, which is of type Point, as follows:

<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 x,y form in XAML code.

WPF User Controls

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 UserControl. 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.

You can add user controls to your project by using the Project 1 Add User Control menu item. This gives you a blank canvas (well, actually a blank Grid) to work from. User controls are defined using the top-level UserControl element in XAML, and the class in the code-behind derives from the System.Windows.Controls.UserControl class.

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.

Implementing Dependency Properties

You can add dependency properties to any class that inherits from System.Windows.DependencyObject. This class is in the inheritance hierarchy for many classes in WPF, including all the controls and UserControl.

To implement a dependency property to a class, you add a public, static member to your class definition of type System.Windows.DependencyProperty. The name of this member is up to you, but best practice is to follow the naming convention <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 DependencyProperty.Register() method:

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
string name The name of the property
Type propertyType The type of the property
Type ownerType The type of the class containing the property
PropertyMetadata typeMetadata Additional property settings: the default value of the property and callback methods to use for property change notifications and coercion
ValidateValueCallback validateValueCallback The callback method to use to validate property values

There are oth methods that you can use to register dependency properties, such as RegisterAttached(), 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.

For example, you could register the MyStringProperty dependency property using three parameters as follows:

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 DependencyObject, as follows:

    public string MyString
    {
        get { return (string)GetValue(MyStringProperty); }
        set { SetValue(MyStringProperty, value); }
    }

Here, the GetValue() and SetValue() methods get and set, respectively, the value of the MyStringProperty, 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.

If you want to set metadata for a property, then you must use an object that derives from PropertyMetadata, such as FrameworkPropertyMetadata, and pass this instance as the fourth parameter to Register(). There are 11 overloads of the FrameworkPropertyMetadata constructor, and they take one or more of the parameters shown in Table 15.5.

Table 15.5 Overloads for the FrameworkPropertyMetadata Constructor

Parameter Type Usage
object defaultValue The default value for the property.
FrameworkPropertyMetadataOptions flags A combination of the flags (from the FrameworkPropertyMetadataOptions enum) that you can use to specify additional metadata for a property. For example, you might use AffectsArrange 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.
PropertyChangedCallback propertyChangedCallback The callback method to use when the property value changes.
CoerceValueCallback coerceValueCallback The callback method to use if the property value is coerced.
bool isAnimationProhibited Specifies whether this property can be changed by an animation.
UpdateSourceTrigger defaultUpdateSourceTrigger When property values are data-bound, this property determines when the data source is updated, according to values in the UpdateSourceTrigger enum. The default value is PropertyChanged, which means that the binding source is updated as soon as the property changes. This is not always appropriate — for example, the TextBox.Text property uses a value of LostFocus for this property. This ensures that the binding source is not updated prematurely. You can also use the value Explicit to specify that the binding source should be updated only when requested (by calling the UpdateSource() method of a class derived from DependencyObject).

A simple example of using FrameworkPropertyMetadata is to use it to set the default value of a property:

   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.

Putting It All Together

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 Card user control to display the game visually. Finally, you will bind it all together in the game client.

Refactoring the Domain Model

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:

  • Card
  • Deck
  • Rank
  • Suit

In addition to these classes, the game needs a Player and a ComputerPlayer class, so you are going to add those. You also need to modify the Card and Deck classes a bit to make them work better in a Windows application.

There is a lot of work to do, so let's get started.

The View Models

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 GameOptions class. This class holds the state of the Options and StartGame 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.

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 deck from which the current player draw a card
  • A card that can be taken by the current player instead of drawing a card
  • A current player
  • A number of participating players

The view model should also be able to notify observers of changes, and that means implementing INotifyPropertyChanged again.

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.

Completing the Game

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 CardsInHand, which displays a player's hand, and GameDecks, which displays the main deck and the available card.

image What You Learned in this Chapter

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 Style 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 Style 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 IValueConverter 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.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset