Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Building a WPF Sudoku Game, Part 2: The Board UI and Validation

  Building Sudoku using Windows Presentation Foundation and XAML, Microsoft's new declarative programming language. This is the 2nd article from a series of 5 articles and focusses on creating the Board UI and validations.

Difficulty: Easy
Time Required: 1-3 hours
Cost: Free
Software: Visual C# 2005 Express Edition .NET Framework 3.0 Runtime Components Windows Vista RTM SDK Visual Studio Extensions for the .NET Framework 3.0 November 2006 CTP
Hardware:
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.

Welcome to the second part of my Windows Presentation Foundation tutorial! If you've missed the first part you probably want to check it out here (link) since we'll be building on what we did last. In this tutorial I'll be covering creating a custom control for our Sudoku board and databinding it to the game logic.

 

First, I think it would be a good idea to go over just what databinding in WPF means, since it's used quite differently than in say Windows Forms or MFC. Hopefully this will give more insight in to why the internal classes are designed the way they are; you can databind anything but you can only easily databind some things. First off all, despite its name, databinding in no way involves databases, complex schemas, or any boring stuff like that. It's really all about tying your UI to your code. We've all written that WinForms or VB code that synchronizes a control like a listbox or treeview with an internal array, collection, or other data structure and we all remember how painful it is. Why!? Why can't the control just use my object for data storage instead of its own memory that I have to keep synced? Well, we don't have to worry about that anymore because that's how it works in WPF, in fact, controls only have their own storage if you explicitly request it and you'll find if you write clean code you'll almost never need it. So where am I going with this? How does it tie in? Well, if we structure our code correctly, we can have our Sudoku board control automatically display our Sudoku board object and easily enter and validate moves. To do this we are going to use a hierarchy of listboxes. Before you think I've gone off the deep end, it's important to note that in WPF listboxes are controls that list anything not just strings. So, lets start hammering out the data structures by looking at a typical Sudoku board for a 9x9 game:

 

5

3

 

 

7

 

 

 

 

6

 

 

1

9

5

 

 

 

 

9

8

 

 

 

 

6

 

8

 

 

 

6

 

 

 

3

4

 

 

8

 

3

 

 

1

7

 

 

 

2

 

 

 

6

 

6

 

 

 

 

2

8

 

 

 

 

4

1

9

 

 

5

 

 

 

 

8

 

 

7

9

 

Actually it's a 3x3 grid of 3x3 grids. But let's abstract this to any size. Starting at the deepest level we have a cell itself.

public class Cell
{
public bool ReadOnly = false;
public int? Value = null;
public bool IsValid = true;
}

It's simple, but gets the job done and maintains which cells are part of the original puzzle.  Unfortunately this won't work so well with databinding. First of all, databinding only works on properties, not fields and how can the control know when a property has changed? To make this work we have to implement the INotifyPropertyChanged interface and turn the fields into properties like this:

public class Cell: INotifyPropertyChanged 
{
bool readOnlyValue = false;
public bool ReadOnly
{
get
{
return readOnlyValue;
}
set
{
if (readOnlyValue != value)
{
readOnlyValue = value;
if (PropertyChanged != null) PropertyChanged(this,
new PropertyChangedEventArgs("ReadOnly"));
}
}
}
int? valueValue = null;
public int? Value
{
get
{
return valueValue;
}
set
{
if (valueValue != value)
{
valueValue = value;
if (PropertyChanged != null) PropertyChanged(this,
new PropertyChangedEventArgs("Value"));
}
}
}
bool isValidValue = true;
public bool IsValid
{
get
{
return isValidValue;
}
set
{
if (isValidValue != value)
{
isValidValue = value;
if (PropertyChanged != null) PropertyChanged(this,
new PropertyChangedEventArgs("IsValid"));
}
}
}
#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

#endregion
}

Ok, before you run away screaming, bear with me for a second. First, you only have to do this for properties that will be databound then changed after they are initially read. Second, there are other methods such as dependency properties that accomplish something similar that I'll discuss later. Dependency properties support animation, metadata, and all sorts other fun stuff but they have more overhead so this way is probably best if you just want the databindings to update. All that's going on here is firing off the PropertyChanged event when one of the properties changes, pretty simple and a good candidate for a code snippet or good old copy and paste.

Next, let's define the inner grid:

public class InnerGrid: INotifyPropertyChanged 
{
ObservableCollection<ObservableCollection<Cell>> Rows;
public ObservableCollection<ObservableCollection<Cell>> GridRows
{
get
{
return Rows;
}
}

 

Here we initialize the collections in the inner grid cell and populate then with Cells. What's neat here is that not only can the framework itself use the INotifyPropertyChanged interface but so can our code. We are adding a handler to the cell's event in order to revalidate ourselves when one of our child cells is altered.

 

    public InnerGrid(int size)
{
Rows = new ObservableCollection<ObservableCollection<Cell>>();
for (int i = 0; i < size; i++)
{
ObservableCollection<Cell> Col = new ObservableCollection<Cell>();
for (int j = 0; j < size; j++)
{
Cell c = new Cell();
c.PropertyChanged += new PropertyChangedEventHandler(c_PropertyChanged);
Col.Add(c);
}
Rows.Add(Col);
}
}

void c_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
bool valid = CheckIsValid();

foreach (ObservableCollection<Cell> r in Rows)
{
foreach (Cell c in r)
{
c.IsValid = valid;
}
}

isValidValue = valid;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsValid"));
}
}

 

Here we cache the result of the last IsValid value.  We can do this because we now have a notification event that is fired whenever there is a possibility of that value changing.

    bool isValidValue = true;
public bool IsValid
{
get
{
return isValidValue;
}
}
And finally, here we have a private validation method that actually does the work. This method simply checks if there are any duplicate cells in our inner grid square.
    private bool CheckIsValid()
{
bool[] used = new bool[Rows.Count * Rows.Count];
foreach (ObservableCollection<Cell> r in Rows)
{
foreach (Cell c in r)
{
if (c.Value.HasValue)
{
if (used[c.Value.Value-1])
{
return false; //this is a duplicate
}
else
{
used[c.Value.Value-1] = true;
}
}
}
}
return true;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}

 

As you can see I've defined the class to store its data in collections of type ObservableCollection, this is because ObservableCollection already implements INotifyCollectionChanged and INotifyPropertyChanged and that's great because I'm lazy. I've essentially defined an array of arrays containing the cells in the inner grid. This works great for databinding but it kind of sucks to access the cells from C# code. To solve this I also added an indexer to the class like so:

 

public Cell this[int row, int col]
{
get
{
if (row < 0 || row >= Rows.Count)
throw new ArgumentOutOfRangeException("row", row, "Invalid Row Index");
if (col < 0 || col >= Rows.Count)
throw new ArgumentOutOfRangeException("col", col, "Invalid Column Index");
return Rows[row][col];
}
}

The Board class is extremely similar to the  InnerGrid except it stores InnerGrids instead of Cells so I wont list the entire thing here (you can check it out, and the IsValid implementations if you download the source). I also changed the constructor and the indexer to drill-down through the two levels of containers. If you're not sure on how this would work you should probably check out the source before you continue just so we're on the same page.

Ok, so now we've written all these class it's time to hit the XAML and make a custom control. So we need to add a new WinFX user control to the project. I've modified some of the properties of the user control, but basically we start with this:

<UserControl x:Class="SudokuFX.SudokuBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

HorizontalAlignment ="Stretch"
HorizontalContentAlignment ="Stretch"
VerticalAlignment ="Stretch"
VerticalContentAlignment ="Stretch"
Background="{StaticResource ControlGradient}"
Foreground="White">
<UserControl/>

 

The basic idea here is that we'll use a series of nested ItemsControls to display our board. An ItemsControl is a generic panel that contains any number of other items, for example the ListBox control inherits from ItemsControl and adds support for things like scrolling and selection but we don't need those. We define the outer control that holds the rows on inner squares.

 

<ItemsControl  ItemTemplate ="{StaticResource OuterRowTemplate}" 
ItemsSource ="{Binding Path=GridRows}" x:Name ="MainList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns ="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

Ok, so this is where the fun starts. Every item that can be databound has a DataContext property that specifies the object it's bound to. We'll set this later from C# code but it's important to know that it's there because it's the basis for the binding syntax. When specifying a data binding, if no source is explicitly set the data context is used. In this code ItemsSource, the collection containing the child items is bound to the GridRows property of the data context, which will eventually be an instance of our Board class.

Another thing of note is the use of the UniformGrid class. By default the items control lays out items by stacking them from top to bottom. Instead of this behavior we would rather have the items stacked in a single column and stretched to fill the entire space evenly. The UniformGrid container does this and so I've substituted it for the default using the ItemsPanel property, but in general you could use any kind of standard or custom panel here, for example to arrange the items in a ring. Also you'll notice ItemTemplate points to a resource:

<DataTemplate x:Key ="OuterRowTemplate">
<ItemsControl ItemsSource ="{Binding}"
ItemTemplate ="{StaticResource InnerGridTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows ="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>

A data template describes how each item in the source list is interpreted as an item in the ItemsControl. The data context is the current item, which is the list of blocks in the row, which we bind to the ItemsSource of another ItemsControl. Currently, we have a vertical list of horizontal lists. Then, we do this all over again for the cells inside each block. You can also add other controls in the template and I've added a border to the InnerGridTemplate to show the boundary between cells.

Finally, we reach the innermost data template, the one for the Cell:

<DataTemplate x:Key ="CellTemplate">
<Border x:Name ="Border" BorderBrush ="DimGray" BorderThickness ="1">
<TextBlock HorizontalAlignment ="Center" VerticalAlignment ="Center"
FontWeight ="Bold" FontSize ="16" Text ="{Binding Path=Value}"/>
</Border>
</DataTemplate>

 

Here, we draw a lighter border around the cell then display the Value property of the current cell, which is the data context of the template. Finally, we want the entire thing to stay square as it resizes. Normally this would require a few lines of C# code but thanks to the insanely flexible databinding available in WPF we can do this with no code! All you need to do it to bind the Width property of the UserControl to its ActualHeight property, which contains the final height of the control after it's been laid out. We can do this with the RelativeSource property, to bind to object itself instead of the data context. This sounds complicated but it's actually really simple:

 

Width="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"

 

Now, we need to put the control onto our main window. First, we need to map out C# namespace, SudokuFX, to an XML namespace that we can reference from XAML. To do this we define a new namespace in the document root using this syntax:

xmlns:clr="clr-namespace:SudokuFX"

 

Where the xmlns:clr indicates the xml namespace. “clr” is in no way special an I could have called it

xmlns:stuff="clr-namespace:SudokuFX" or whatever I wanted. Once that's done I can use my control just like any other. I've replaced the stand-in canvas with our custom control tag:

 

<clr:SudokuBoard HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5"/>

If you run the app now you should see this:

 

Not that amazing, but we haven't bound any data yet. We can modify the C# component of the control to add a data source:

public partial class SudokuBoard : UserControl
{
public Board GameBoard = new Board(9);
public SudokuBoard()
{
InitializeComponent();
MainList.DataContext = GameBoard;
}
}

 

Now, it should look more like this (I've added the numbers just to show that it works):

 

You can see it rebuilds correctly to match the data, when I change from a 9x9 grid to a 16x16 grid:

 

Ok, so now that we've got that working, to close of this tutorial lets add the ability to change the numbers on the board. I've added a new collection property to the Cell class called PossibleValues that is populated with all the possible values when the class is created. Let's add a context menu that's bound to this new property so that the cell values can be changed. To do this the XAML needs to be changed like this:

 

<DataTemplate x:Key ="CellTemplate">
<Border x:Name ="Border" BorderBrush ="DimGray" BorderThickness ="1" Background="#00112233">
<TextBlock Focusable ="False" HorizontalAlignment ="Center" VerticalAlignment ="Center"
FontWeight ="Bold" FontSize ="16" Text ="{Binding Path=Value}">
</TextBlock>
<Border.ContextMenu>
<ContextMenu>
<ContextMenu.ItemContainerStyle>
<Style TargetType ="{x:Type MenuItem}">
<Setter Property ="Template">
<Setter.Value>
<ControlTemplate TargetType ="{x:Type MenuItem}">
<ContentPresenter x:Name="Header" ContentSource="Header"
RecognizesAccessKey="True" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ContextMenu.ItemContainerStyle>
<ListBox BorderThickness="0" Width ="35" Margin ="0"
SelectedItem ="{Binding Path=Value,Mode=TwoWay}"
HorizontalAlignment ="Stretch" VerticalAlignment ="Stretch"
DataContext ="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}"
ItemsSource="{Binding Path=PossibleValues}"/>
</ContextMenu>
</Border.ContextMenu>
</Border>
</DataTemplate>

The key here is the binding of the listbox to our data source. Because the context menu exists in a separate hierarchy the data context isn't automatically inherited. We can work around this by binding the new data context to the data content of the parent element just outside our sub-hierarchy. To do this we use the RelativeSource syntax to access our TemplatedParent. Next, we bind the ItemsSource property to the new property, which populates the listbox, and then we bind the SelectedItem property to the object's Value property. Because we specify a two-way binding, when the value of Value changes, so will the selected item in the listbox, and, when the use selects a new item through the UI, Value will updated also. A listbox is needed because a menu doesn't have the capability to retain item selections. To work around this I added the listbox to the menu as a single item, and then altered the MenuItem Template to remove all padding and selection logic. I haven't covered altering control templates yet, so don't worry about how exactly that part of the code works, we'll revisit it in the next tutorial.

Now, if you run the application, you'll see that you can edit the displayed grid:

This is really cool because, essentially, there is no code that ties the internal data classes to our UI! The UI automatically and dynamically displays and edits an instance of our custom class. How awesome is that?

Don't worry, we're not done yet! Come back for my next article were we take a break from data and spiff up the coolness of the app's look and feel. I'll be covering cool stuff like: 

  • Automatically selecting data templates based on properties of the object
  • Modifying control templates to completely customize the look, and operation of a control
  • Adding animations
  • Automatically starting and stopping animations and altering objects based on UI events or other conditions
  • Adding reflections and other imagery based on existing controls

See you next time!

Tags:

Follow the Discussion

  • John FrandoligJohn Frandolig

    where can I download this project (thus far) ? - or do I wait until the end ?

    thanks

  • MichaelMichael

    Even with updates for the released build of WCF, this project fails and my project fail to build.  The statement :

    xmlns:clr="clr-namespace:SudokuFX"

    causes issues and adding the statement:

    <clr:SudokuBoard...> causes major issues with the DockPanel.  How is anyone supposed to learn from this article when a) in your laziness you did not specify where to put the xmlns:clr statement and what you provide as an example will not build???

  • ncoksncoks

    thanks a lot at least u gave me an idea on what to do

  • PeregrinatiPeregrinati

    Hey, I'm having trouble running through this tutorial in Visual C# 2008 Express. I can get about half way through it It looks like every time I use a StaticResource in a UserControl and then use that control in a window the window's designer bugs out. It displays a "Could not create instance of [UserControl]" error, even thought the project compiles and runs fine, and the UserControl looks fine in the designer.

    I found it when working through the tutorial and I assigned a LinearGradientBrush called ControlGradient to the App resources, used in in SodukuBoard.xaml, then tried to put the SodukuBoard on Window1 - the designer for Window 1 now throws a "Could not create instance of SodukuBoard" error.

  • Clint RutkasClint I'm a "developer"

    Sudoku challenge:

    Part 1:

    http://blogs.msdn.com/coding4fun/archive/2006/11/06/999502.aspx

    Part 2:

    http://blogs.msdn.com/coding4fun/archive/2006/11/06/999781.aspx

    Part 3:

    http://blogs.msdn.com/coding4fun/archive/2006/11/30/1178193.aspx

    Part 4:

    http://blogs.msdn.com/coding4fun/archive/2006/11/30/1178206.aspx

    Part 5:

    http://blogs.msdn.com/coding4fun/archive/2006/11/30/1178235.aspx

  • CyronCyron

    I've just found this article but I'd like to start from the beginning. Where is the link to part 1?

  • LagDaemonLagDaemon

    Your code seems to have been chopped off on the right some how leaving many of the entries incomplete.  Otherwise this is a very nice introduction to theese ideas in WPF

  • Clint RutkasClint I'm a "developer"

    @LagDaemon:  True, I can go in and fix that.  You can also download the source at the top of the page.

  • MattMatt

    Building this. However, when I click on the context menu, nothing comes up. I am not sure if I am missing something. I tried running your code in VS2008 it works fine. however mine does everthing expect the part where a number should appear in a cell, when you select the context menu.

    i did a comparison, and everything looks intact. I even copied your SudokuBoard.xaml and still did not work.

    I am stumped.

  • domnisoaradianedomnisoarad​iane

    i ran into similar issues when i downloaded the code for part 1.  i couldn't even get the project file to load.  as far as i can tell, the staticreferrences have been removed since the winfx days, so you can just remove that keyword (and whatever else you have to to get it to compile).

    since i couldn't get the csproj to load, i created an empty project and dropped the xaml and c# code into that project (remember to add existing item so the solution knows about it).  you also must add referrences to windowsbase, windowscore, and presentationfoundationcore (vs will tell you which to add in the event that i forgot/misspelled those).

    finally, if you drop the code into a brand new project, vs will complain that you don't have a main method.  you must add this to the "secret" .g.cs file.  you can get to the file by viewing app.xaml.cs.  put your cursor on the name of the class (i.e. App), right click, and select "go to definition".  this should give you two choices, one of which is the .g.cs file. (there might be a better way to do all this, but this is all i could kludge together while watching tv Wink )

    to this file, add something like:

    /// <summary>

           /// Application Entry Point.

           /// </summary>

           [System.STAThreadAttribute()]

           [System.Diagnostics.DebuggerNonUserCodeAttribute()]

           public static void Main() {

               SudokuGame.App app = new SudokuGame.App();

               app.InitializeComponent();

               app.Run();

           }

    you may have to rebuild the solution to get it to work.  also, i only got it to work with my release executable (run without debug).

    if anyone has anything to add to this, please feel free!

    good luck!

    ~domni

  • NagarajNagaraj

    The download link at the top is not working.. do we have a dump somewhere else???

  • Clint RutkasClint I'm a "developer"

    @Nagaraj:  The Ch9 update broke this.  I fixed the link.

  • ge-forcege-force

    Where is the INotifyPropertyChanged interface defined?

  • Clint RutkasClint I'm a "developer"

    @ge-force it is part of WPF and .Net  http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.