Building a WPF Sudoku Game, Part 1: Introduction to WPF and XAML

  Building Sudoku using Windows Presentation Foundation and XAML, Microsoft's new declarative programming language. This is the 1st article from a series of 5 articles and focusses on introducing WPF and XAML.
Lucas Magder

Difficulty: Easy
Time Required: 1-3 hours
Cost: Free
Hardware: None
Download: Download (note: Tasos Valsamidis has an updated version that supports Expression Blend here)

Note: This article has been updated to work and compile with the RTM version of the Windows SDK.

Whenever I want to learn something new I find that just working through a tutorial is much easier and less painful then reading the documentation cold and I assume most people feel the same way, because come on, do you actually read the instructions for something before you try to use it? I sure don’t. To this end, I’ve decided the jump right into the action and walk though building a Windows Presentation Foundation application right away and since this is Coding4Fun and since, let’s face it, the world has enough enterprise-web 2.0-data-portal-dodads, were going to make a game. Unfortunately, I don’t think it’s realistic to jump right into making Halo 3 here, so I figured a more bite-sized game might be in order, something we might actually finish by the end of these tutorials, like say a Sudoku game.  (Hey, the upside is you might actually get away with playing Sudoku at work). Ok so what do we need to get started? (Install in this order)

You also need to be running Windows XP SP2, Windows Server 2003 SP1, or the Vista February CTP.

Of course, I just happened to have all that installed on my workstation (yeah right). But now that everything is loaded up lets get started. Fire up VC#, create a new project and select “WinFX Windows Application.” (If you can’t see this item you should make sure you’ve installed the Visual Studio Extensions since this is one of the things they add.). I entered “SudokuFX” as the project name, and that’s what you’ll see in the screenshots, but it’s up to you what you call it. Right now you should be starring at this:

<Windowx:Class="SudokuFX.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="SudokuFX"Height="300"Width="300"

    >

    <Grid>

       

    </Grid>

</Window>

This is XAML, Microsoft’s new declarative programming language. Classes in XAML can have both code (C# or VB.NET) and declarative (XAML) components. The x:Class attribute of the Window tag specifies the name of C# component of the class we are defining. The C# part should look like this:

namespace SudokuFX

{

    public partial class Window1 : Window

    {

        public Window1()

        {

            InitializeComponent();

           

        }

    }

}

When the program is compiled and run the two halves combine together to create a complete Window1 class, which derives from the System.Windows.Window class provided by WinFX. The only other non-obvious thing you need to know here is that all the objects declared in XAML can be accessed from code or vice-versa. In fact, you can write a class using the same API with no XAML at all! The nested elements are placed into collection stored as a property on their parent called Children, or in object that can contain only one other object they are assigned to the Content property. So in other words this XAML code creates an instance of Grid and places it in the window’s Content property.

So, how do we go about laying out the application? Well basically the goal layout is to have a title at the top, the game menu down the left, similar to the “tasks” pane in Windows explorer, the game timing info on the right, the move history on the bottom, and the board in the center. Of course this should all be resolution independent and dynamically resize and flow. (If you’ve used Windows Forms before, the sweat might be already beading on your forehead as your migraine develops, but don’t worry, dynamic resizing and flow is the default.) The best container for the job is the obviously-named DockPanel. The DockPanel is a container control that arranges it children based which side they are attached to; the last element added is then used to fill the remaining space. For example, replace the Grid tag with:

<DockPanel>

       <TextBlockBackground="Red"DockPanel.Dock ="Top"FontSize ="36">

Sudoku

</TextBlock>

       <StackPanelBackground="Green"DockPanel.Dock ="Left">

              <Button>A</Button>

       </StackPanel>

       <StackPanelBackground="Blue"DockPanel.Dock ="Right">

              <Button>B</Button>

       </StackPanel>

       <ListBoxBackground ="Gray"DockPanel.Dock="Bottom"/>

       <StackPanelBackground ="Yellow"/>

</DockPanel>

 

The DockPanel.Dock syntax is used to refer to a per-child but container-specific property. Another example of this is the Canvas.Left property, which is used with elements inside a Canvas, a container control that allows you to explicitly position elements. The StackPanel container arranges its children in vertical or horizontal stack, going either up or down or left or right. Right now I’ve added some buttons just pad out the panels so they don’t shrink away to nothing and some garishly horrible background colors we’ll remove later so it’s evident exactly how things are laid out. If you compile and run the program you get this monstrosity:

Ok, now its time to grind out some code to get a basic UI going. First lets layout the left panel: there’s going to be the main menu and the new game settings and lets throw them in expander controls just incase anyone who uses our program runs at 800x600 (It’s frightening I know, but I’ve seen it).

<StackPanelDockPanel.Dock ="Left">

<ExpanderIsExpanded ="True"Header ="Main Menu">

              <StackPanel>

                     <Button>New Game</Button>

                     <Button>Load Game</Button>

                     <Button>Save Game</Button>

                     <Button>Quit</Button>

              </StackPanel>

       </Expander>

       <ExpanderIsExpanded ="True"Header ="New Game Settings">

              <StackPanel>

                     <TextBlock>Board Size:</TextBlock>

                     <ComboBoxIsEditable ="False">

                           <ComboBoxItemIsSelected ="True">9x9</ComboBoxItem>

                           <ComboBoxItem>16x16</ComboBoxItem>

                           <ComboBoxItem>25x25</ComboBoxItem>

                           <ComboBoxItem>36x36</ComboBoxItem>

                     </ComboBox>

              </StackPanel>

       </Expander>

</StackPanel>

This gives you:

Ok, so maybe that doesn’t look too awesome right now, but wait there’s more the come! After fleshing out the rest of the UI in similar fashion and adding a dummy image for the board (we’ll make the board in a future tutorial) we get this:

 

 

Now, how do we improve the look and feel of this UI? Well, one way would be to tweak all the properties of the controls individually to make them look better; another would be to use some kind of skinning. Unfortunately, these techniques add clutter to your code and are difficult to maintain, what we can use instead is WPF’s notion of styles. WPF makes it quite easy to customize controls across a given scope, for example: the application as a whole, a particular dialog, or a single container, by defining a new set of default property values. You can do this by adding styles to the Resources collection of another object. In XAML, if you want to assign a more complex object or collection of objects to a property you can expand the property out from the Property="Value" syntax to the  

<Object.Property>

       <ValueObject/>

</Object.Property>

syntax. For example, this code assigns the color blue as the background color for all buttons in the application, unless it is explicitly set otherwise:

<Application.Resources>

       <StyleTargetType ="{x:Type Button}">

              <SetterProperty ="Background"Value ="Blue"/>

       </Style>

</Application.Resources>

Styles can also be named using the x:Key property. The reason why x:Key is used instead of x:Name is because <Application.Resources> is actually a list of key-value pairs. For example we could define:

<Application.Resources>

       <Stylex:Key ="BlueButton"TargetType ="{x:Type Button}">

              <SetterProperty ="Background"Value ="Blue"/>

       </Style>

</Application.Resources>

This creates a style which only applies to certain buttons, buttons that reference it with their Style property. For example:

<ButtonStyle="{StaticResource BlueButton}"/>

You can also define resources (there are also other types than styles) inside windows, panels, or other objects that have a Resources property. When code references a resource a search is made outwards up to the application level thus styles or other resources defined in <Window.Resources>, for example, only apply inside that window.

So far I haven’t explained the brace notation I’ve been using. Essentially, braces are used to declare references to other object but not instantiate them. For example, {x:Type Button} refers to the Button class, while {StaticResource BlueButton} searches the resource hierarchy for the item with the key “BlueButton”. The StaticResource directive indicates that this search is to be carried out when the object is first created, but DynamicResource can also be used to specify that the value should be continually updated.   

Now we can start adding a style to improve the look of the expander. I added this to the <Application.Resources> section:

<StyleTargetType ="{x:Type Expander}">

       <SetterProperty ="Background">

              <Setter.Value>

                     <LinearGradientBrushStartPoint ="0,0"EndPoint ="1,0">

                           <LinearGradientBrush.GradientStops>

                                  <GradientStopColor ="LightGray"Offset ="0"/>

                                  <GradientStopColor ="Gray"Offset ="1"/>                                          </LinearGradientBrush.GradientStops>

                     </LinearGradientBrush>

              </Setter.Value>

       </Setter>

<SetterProperty ="BorderBrush"Value ="DimGray"/>

       <SetterProperty ="BorderThickness"Value ="1"/>

       <SetterProperty ="Margin"Value ="5"/>

       <SetterProperty ="HorizontalContentAlignment"Value ="Stretch"/>

       <SetterProperty ="Foreground"Value ="White"/>

       <SetterProperty ="VerticalContentAlignment"Value ="Stretch"/>

</Style>

This is a pretty straightforward style, although it does nicely demonstrate the two property assignment syntaxes. The background is set to a horizontal gradient between two shades of gray and the rest of the properties are assigned to sensible defaults the work well with the background. I’ve also added an extra border around the inside of the control by adding an extra Border tag inside the expander’s content like so:

<ExpanderIsExpanded ="True"Header="Main Menu">

<BorderMargin ="5"Padding ="10"

Background ="#77FFFFFF"BorderBrush ="DimGray"BorderThickness ="1">

              <StackPanel>

                     <Button>New Game</Button>

                     <Button>Load Game</Button>

                     <Button>Save Game</Button>

                     <Button>Quit</Button>

              </StackPanel>

       </Border>

</Expander>

The Margin property specifies the spacing border on the outside, while the Padding property specifies the extra spacing on the inside although only container controls can have padding. Both values are specified by either four comma separated values for left, top, right, and bottom, respectively, or a single number if all four are the same (as they are here).

If you think this is kind of a kludge, you’re right. In fact I could have included the extra border inside the control itself using my custom style. To do this you can modify what’s called the control’s template, but that’s a whole other topic so I’ll just do this for now. Don’t worry, we’ll come back and fix this later. After whipping up some other styles, which you can check out if you download the code, and adding a nice gradient for the window background the application looks a lot better……ok, well at least it’s more colorful.

Finally, to close off this part of the tutorial, let’s add some simple event handling to make the “Quit” button work. This is actually very easy. Just define a method that conforms to the RoutedEventHandler delegate in your Window1 class. For example:

void QuitClicked(object sender, RoutedEventArgs e)

{

this.Close();

}

Then just set the Clicked property of the button to the name of your handler like this:

<ButtonClick ="QuitClicked">Quit</Button>

Now, if you click the quit button the program will exit. This is essentially all there is to handling simple events!

Ok, well that’s all for now, I hope this tutorial has at least given you a feel for how WPF applications work and how to start building an app. We’re just scratching the surface of what you can do with this framework and .NET 2.0! Stay tuned for the next parts of the tutorial that finish the application and cover cool stuff like:

  • Databinding, have the UI automatically display and manipulate your programs objects
  • Custom controls and control skinning, completely alter the look and feel of existing controls or create you own
  • Cool effects, reflections, transitions, and automatically running animations
  • Triggers, have the UI operate itself and control its appearance with no code
  • Creating a plug-in system, loading plug-ins written in any .NET language into a sandbox
  • Writing multithreaded code on .NET

Keep coding!

原文地址:https://www.cnblogs.com/ericfine/p/876519.html