Introduction
After writing a Sudoku game using VB.NET and WinForms, I decided to translate it to C#. To make things interesting, I decided to create the UI layer using WPF instead of WinForms. And since Silverlight is a subset of WPF, I wanted to see how easy it was to convert the finished WPF code to Silverlight as well.
This article covers the issues I encountered and the solutions and work-arounds that I came up with. For background information on the game and some of the decisions I made regarding game play as well as the programming concepts used like Singletons, Events, etc., please read the original article: Complete Sudoku Game for Windows using VB.Net 2013.
This article can be used as an introduction to programming in WPF and Silverlight. I will assume that the reader has some knowledge about C# and WinForms. For those who already know WPF and Silverlight, this article will be pretty boring. But feel free to read it, download the code and play with it. Any suggestions for improvement or alternative ways to do something are most welcome.
Background
Windows Presentation Foundation, or WPF, was first released with .NET 3.0 back in November of 2006. WPF is essentially a rendering engine with lots of powerful features to allow a developer to create visually stunning client applications for both standalone, browser-hosted, and phone applications. When building the WPF front-end, one uses XAML, or Extensible Application Mark-up Language, to describe the UI. The WPF engine will then interpret the XAML and output the UI to either a Windows application, browser, or Windows Store/Phone App depending on the project. Here is a simple diagram that shows what I mean.
In reality, Silverlight and Windows Store App is a subset of WPF. So, the same XAML that works for a Windows Application may not work when outputting to a browser as a Silverlight app or to a Windows Phone.
When building the UI in WPF, instead of setting properties for the controls on the Property page of the designer window, one sets properties directly using the XAML code window. This is completely different from writing a WinForms application, whether it is in VB.NET or C#. If entering XAML code is intimidating, the IDE also has a Properties tool window where one can modify the properties as well. When starting out writing WPF applications, it might be better to use the Properties tool window so that one can get familiar with all the properties that are available.
There are lots of tutorials, sample code, and help on-line to get you started with WPF. Here is a link to Microsoft's own website on WPF: Introduction to WPF. In this article, I'll go over some of the more salient points of WPF as it relates to writing the game.
For this project, instead of using the MVC programming pattern like I did in the other project, I tried to adhere to the MVVM, or Model-View-View Model pattern, which is the recommended pattern to use when writing WPF applications. Here is a diagram taken from Microsoft's website that details the flow between the different parts of the MVVM pattern. Note the extra "Data Binding" link between the View and the View Model compared to that in the MVC pattern.
In the MVC pattern, the business logic is generally kept in the Controller. In the MVVM pattern, the business logic is pushed down to the Model layer. Since the original code was written using the MVC pattern, I kept most of the business logic in the VM layer. There are lots of articles on-line that describe this programming pattern in greater detail, as well as the differences between the MVC and MVVM programming patterns.
The code was organized in the MVVM pattern as well. I created three folders and named them Model, View, and View Model and the code was split up accordingly.
Programming in WPF and using the MVVM programming pattern lends itself well to separating the UI design from the business logic and allow groups of specialists to build the different parts of complex applications. Graphic designers can use tools like Expression Blend or even Visual Studio to build a visually rich UI without having to understand how to write a single line of C# or VB code. The developer can then concentrate on writing the business logic and data layers without having to think about how the data will be presented. Obviously, there should be an agreement as to what kind of data is required between the two teams.
I feel that one of the keys to understanding and mastering WPF is understanding Data Binding. WPF Data Binding allows UI elements, like text boxes, to be bound to a property in the VM layer. And if done right, when the property changes, the UI will automatically change as well. Here is a link to Microsoft's website that explains data binding as well as examples. Sure, one can address the controls directly in the code-behind like we did in the good old days of WinForms. But that goes counter to what WPF is all about and requires more code behind than data binding.
Using the Code
The programs were written using VS 2013 and the 4.5 .NET framework/Silverlight 5. They are code complete. I have included both the WPF and the Silverlight projects above. You should be able to download and compile both projects separately.
Normally, when one shares code between two or more projects, one would copy the file from one project to the other or add the file to the second project from the first project. This makes maintaining the code much harder since there are multiple copies of the same file.
Starting with VS 2010, one can add a link to the original code file by clicking the down arrow on the "Add Existing Item" dialog box.
However, for these two projects, I did not use this feature. So each ZIP file contains all the necessary files and one can download whichever project that interests you without having to download the other.
Converting from VB.NET to C#
Syntactical differences aside, porting the code from VB.NET to C# was pretty straightforward since both run on top of the same CLR.
In addition to porting the code from VB.NET to C#, I also made some changes to the way arrays were addressed. In my VB code, arrays were addressed using the indices from 1 through 9. I did that because the valid answers for the game are the numbers from 1 through 9. However, in the C# code, since C is a zero based language, I decided to conform and changed everything to be zero based. Valid answers are still 1 through 9 though. That took a little while, but it was not hard to do.
Converting the UI from WinForms to WPF
After porting the code over to C#, the next step was to convert the UI from WinForms to WPF. That took a little more effort. The first decision to make was what kind of Panel
object to use as the base. In WinForms, there is just a blank form and one starts putting controls on the form to build the UI. But in WPF, there are several different kinds of background or Panel
objects to build the UI with and each one has different characteristics. So choosing the right one for the project is essential to the overall success. Here is a link to a website that describes the differences. The default is the Grid
.
Since down the line, I might want to implement a fireworks display in the background, I decided to use the Canvas
as my main window's background. The Canvas
Panel allows me to draw directly on the control. I then added all the necessary controls to make it look like the original VB version.
Both WPF and WinForms have buttons, comboboxes, labels, and checkboxes. So that was easy to replicate.
The game grid on the other hand was a challenge since WPF does not have a similar TableLayoutPanel
control. On the other hand, WPF has two kinds of grids: a DataGrid
and a Grid
grid. I felt that the DataGrid
was more for displaying a scrolling list of data items in a tabular format. Since I am not interested in scrolling around and the data size is static, I opted to use a Grid
control instead.
I added a Grid
control to my form and then split it into 3 rows and 3 columns. Within each cell, I added another Grid
control and also split it into 3 rows and 3 columns. I then added borders and rectangles as well as changed the background color to build the game grid. Since there is no line object, I used rectangles with a height (or width) of one pixel to represent the lines on the game grid.
The next issue was the status bar at the bottom. WPF also has a StatusBar
control, but it works differently than the WinForms StatusBar
. Instead of using the Items Collection Editor to add items to the Status Bar, one adds items directly in the XAML code.
Here is the final rendition of the main window in WPF:
A couple of things that you will see that are different from the original VB version is that the Hint and Clear buttons are missing. I moved those buttons to the Input Pad dialog. The reason I did that was because Hint and Clear are more cell specific actions rather than game specific. And therefore, they really belong to the Input Pad window which contain cell specific actions. One could make the same argument for the "Enter Notes" checkbox as well. But there are times when one wants to enter notes for multiple cells and not just for a single cell. That is why I left it on the main window.
The next step was to wire up the event action for the buttons. In the XAML section for the button, I just specified the Click
event as an XML property:
<Button Content="Close"
Style="{StaticResource ButtonBaseStyle}"
Margin="430,411,0,0"
Click="btnClose_Click"/>
And the code-behind looks like this, which looks similar to a WinForms
event:
private void btnClose_Click(object sender, RoutedEventArgs e)
{
}
I left the details of the code-behind blank for now. Once I am ready to work on the View Model, I will add code to wire them both together.
WPF and Data Binding
After wiring up the action or event code for each of the buttons, the next step was to wire up the UI to the data. Notice, I did not mention checkboxes at all. This is because we can use WPF data binding to set the checkboxes and do other really cool things. I will explain more later on.
I could have easily followed the WinForms conventions and just assigned the data to the control in the form's code-behind like so:
this.ElapsedTime.Content = "00:00:00";
Since WPF has powerful tools to connect the UI to the data, I will use that instead.
It starts with the ViewModel
class and adding the INotifyPropertyChanged
interface, which is part of the System.ComponentModel
namespace.
using System.ComponentModel;
internal class ViewModelClass : INotifyPropertyChanged
The other related declarations are as follows. I declare the event and then write code to raise the event.
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
And this is how it is used:
public string GameCountVeryEasy
{
get
{
return GetGameCount(DifficultyLevels.VeryEasy);
}
private set
{
OnPropertyChanged();
}
}
The set
accessor has only one line of code since we do not need to save the actual value in the ViewModel
class. When we need it, the get
accessor makes a call to GetGameCount
where it is stored.
Then in the XAML, here is how it is called:
<Label Content="{Binding Path=GameCountVeryEasy, Mode=OneWay}"/>
Easy, right? Actually, I am missing a few more things. First, in the XAML header declaration, I need to specify the namespace where the property GameCountVeryEasy
is located. To do this, I need to add the following line to the <Window
tag up top:
xmlns:srcVM="clr-namespace:SudokuWPF.ViewModel"
Then, in the form's code-behind, I need to add the following line:
this.DataContext = _viewModel;
Normally, it is added when the View Model class is initialized, either in the constructor of the form, right after InitializeComponent()
, or somewhere close to that. This tells the UI which instance of the View Model class has the actual data. Interestingly enough, I did not have to specify in the XAML code, the actual name of the class where this property is found. That is what the DataContext
is for. It tells WPF where to find the properties that are bound to the controls on the UI.
Let me explain in more details some of the more interesting points in the code above. First, the [CallerMemberName]
attribute in the OnPropertyChanged
method's parameter.
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
This attribute allows the OnPropertyChanged
method to obtain the method or property name of the caller to the method. This is new for .NET 4.5 and is part of the System.Runtime.CompilerServices
namespace. Here is a link to Microsoft's website that describes this attribute in more details.
Another concept new to C# is the ability to create optional arguments. This is something that the VB world has had for years. So for those who are already familiar with VB's optional arguments, the syntax above is already familiar. The only difference is that there is no Optional
qualifier. Simply put, when we add = ""
to the end of a parameter declaration, it turns that parameter into an optional parameter. The blank string simply means that is the default value if the parameter were omitted from the call. Any valid string
can be used as the default. For more information about optional parameters, here is the link to Microsoft's website.
Normally, when one calls OnPropertyChanged
, one has to pass the name of the property so that the corresponding WPF element that is bound to that property will know that it changed. For example:
OnPropertyChanged("GameCountVeryEasy");
By using the [CallerMemberName]
attribute and optional parameters, if the call came from the actual property GameCountVeryEasy
, we can omit the propertyName
parameter from the call since [CallerMemberName]
will fill it in for us automatically.
In C#, namespaces are declared with the using
keyword at the beginning of the code page. XAML uses xmlns:
. In the following XAML namespace declaration:
xmlns:srcVM="clr-namespace:SudokuWPF.ViewModel"
Here is what it all means. xmlns:
is just the XML attribute that allows us to specify a namespace. srcVM
is a shortcut name that can be used in the XAML code if we need to refer to a class, property, or method in that namespace. I could have said viewModel, vm,
or any valid name for that matter. And finally, the content of the string
is the name of the actual namespace. The XAML editor automatically adds "clr-namespace:"
when the final double quotation is entered. When you enter the first quotation, intellisense populates a dropdown list with all the valid namespaces in the current project.
Here is another huge difference between WinForms and WPF. In WPF, it is not necessary to name all the elements on a form. So our label control just has a Content
property. The binding code is what ties this element to the data source and that is how we will update that element from code.
<Label Content="{Binding Path=GameCountVeryEasy, Mode=OneWay}"/>
Generally speaking, if there is no need to address the control from code, then there is no need to give it a name. The data binding is sufficient. Binding
is a key word that tells the XAML interpreter that binding code follows. Path=
specifies the property to use specified by the DataContext
. Mode=OneWay
specifies that the binding goes from the View Model to the View, or in other words, to a read-only property. If it is not specified, then the binding defaults to TwoWay
. Here is a link to Microsoft's website that goes over data binding in WPF.
So, with all the wiring above, when we want to update the label with a new value for GameCountVeryEasy
, all we need to do is make the following call in the ViewModel
class.
GameCountVeryEasy = "4";
What happens next is, in the property's set
accessor, the OnPropertyChanged
method is called and the PropertyChanged
event is raised. The WPF engine, which is listening to all PropertyChanged
events, is triggered. It then looks for the corresponding data binding. Once it is found, WPF then calls the property's get
accessor and assigns the label the string
value of 4
.
The property to bind to can be located in pretty much any class in the project. It does not have to be limited to ViewModel
class. In fact, I bind the game cells to properties that are found in the Model
class as well. All you need is to add the INotifyPropertyChanged
interface and the related code. Then just bind to it in your XAML.
It may not seem impressive since the following is the WinForms equivalent. In a WinForm's code-behind, we would write the following code:
private delegate void SetGameCountCallback(Int32 value);
internal void SetLabel(Int32 value)
{
if (label1.InvokeRequired)
{
SetGameCountCallback callback = new SetGameCountCallback(SetLabel);
this.Invoke(callback, new object[] { value });
}
else
label1.Text = value.ToString();
}
And the code in the set
accessor of the GameCountVeryEasy
property would simply be:
Form1.SetLabel(value);
And we would not need a get
accessor since we already assigned the value to the label on the form.
On the surface, WinForms looks much simpler than the WPF equivalent, but WPF data binding can do so much more than simply setting the contents of a label. Also, we would have to replicate this code for each and every control that we want to update. Naturally, we can write a generic version of this method where we pass in the control that needs to be updated.
In WinForms, we need to call the InvokeRequired
method of the label to make sure that we are making a thread-safe call. In WPF, we do not need to check if the call is thread safe or not. WPF takes care of all that for us. So all we need to do is just raise the PropertyChanged
event and WPF takes care of the rest. I should also mention that the OnPropertyChanged
method can be called from anywhere in the class. So long as the correct property name is passed to the method, it will get updated. I used this technique in my project and I will talk about it later on.
Data Binding and Checkboxes
Here is the real magic of WPF data binding. For the check boxes, we also create properties in the ViewModel
class to store the checkbox state. For example, for the EnterNotes
checkbox, we have the following property definition in the ViewModel
class:
public bool IsEnterNotes
{
get
{
return _isEnterNotes;
}
set
{
_isEnterNotes = value;
OnPropertyChanged();
}
}
The property is pretty self explanatory. We either return or save the state of the check box. When we save the state, we also call the OnPropertyChanged
method to raise the PropertyChanged
event.
The check box XAML declaration looks like this:
<CheckBox Style="{StaticResource CheckboxBaseStyle}"
IsChecked="{Binding Path=IsEnterNotes}"
Content="Enter Notes"
Margin="23,420,0,0" />
The IsChecked
property is bound to the IsEnterNotes
property of the ViewModel
. And since we want to know when the user clicks the check box, we want the binding to go both ways. So the binding mode uses the default TwoWay
. Because the IsChecked
property of the check box is bound, we do not need to wire up the Click
event in the code-behind. That is one less thing to worry about. However, any Click
event code-behind we used to have in the WinForms version will go into the set
accessor of the IsEnterNotes
property instead.
WPF Styles
One of the powerful features in WPF is the ability to create styles for a group of controls like buttons. So we can create a Style
like this:
<Style x:Key="ButtonBaseStyle"
TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Width" Value="140" />
<Setter Property="Height" Value="30" />
</Style>
And apply it to all the buttons so that they all look the same:
<Button Content="New Game"
Style="{StaticResource ButtonBaseStyle}"
Margin="430,70,0,0"
Click="btnNew_Click"/>
<Button Content="About Sudoku WPF"
Style="{StaticResource ButtonBaseStyle}"
Margin="430,376,0,0"
Click="btnAbout_Click"/>
<Button Content="Close"
Style="{StaticResource ButtonBaseStyle}"
Margin="430,411,0,0"
Click="btnClose_Click"/>
The <Style></Style>
declaration can be put in several places in the project. It can be located in a separate resource file so that the user can select different themes. It can be put in the container's resource section, or in my case, in the resource section just above the container. So, right above my <Canvas></Canvas>
tags, I added the <Window.Resources></Windows.Resources>
tags and inserted my styles there. If the style is put in the <Application.Resources></Application.Resources>
section or a separate resources file, the scope expands to the entire application. But since I put it in the <Window.Resources></Windows.Resources> section of the Window that I am using it, the scope is limited to that window.
Notice again that none of these buttons are named.
The nice thing about being able to create styles is that I can use the same style for all the buttons on the window. If I need to change something, I can just change the style and all the buttons will reflect the changes. In WinForms, if I need to change something, I need to select all the buttons and then change the property.
If you notice in my Style XAML, I added an x:key
property to the Style
. If I omitted that property, this style will apply to all buttons and I can remove the following line from the button declaration.
Style="{StaticResource ButtonBaseStyle}"
However, since I need to apply a different style to both the Reset and the Start buttons, I added the x:Key
property. The reason is, both these buttons have some special states depending on where in the game play the user is in. For example, the Start button, if the user just loaded a new game, the button will say "Start Game". And when the game is in progress, it will change to "Pause", etc. Same with the Reset button. If a new game is loaded or when the game is paused, the Reset button will be disabled, and so on.
To code these states, we can add code to the form's code-behind. But then, that would bypass another great feature of WPF.
WPF Style Triggers
In the previous section, I mentioned that the Start and Reset buttons change state depending on where in the game play the user is in, and that we can manage the state of the buttons using WPF. To do this, we use Style Triggers
. Here is an example:
<Style x:Key="EnableGameButtonStyle"
TargetType="Button"
BasedOn="{StaticResource ButtonBaseStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnableGameControls, Mode=OneWay}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsEnableGameControls, Mode=OneWay}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
I added the following boolean property to the View Model class:
public bool IsEnableGameControls
{
get
{
return _isEnableGameControls;
}
private set
{
_isEnableGameControls = value;
OnPropertyChanged();
}
}
Notice in the set
accessor, I call OnPropertyChanged
.
Then, I create a new Style which is based on the ButtonBaseStyle
from above, since I want these buttons to maintain the same look. I then added two triggers based on possible values of this property: True
and False
. Basically, if the property is true
, then the button is enabled. If it is false
, then disable the button.
Let us go through this style line by line. I added an x:Key
property to the style so that I can reference it from the Reset button. Since this style will be used for buttons, I added the TargetType
property. I then added the BasedOn
property because I want the Reset button to look like all the other buttons on the form. I then added the Triggers
. The syntax is pretty straight forward. I need to bind the trigger to a property in the View Model class and then set the value for the trigger to look for. And within that trigger tag, I set the property, or properties of the button that I want to change when the value of the property is either True
or False
. In the case of the Reset button, all I want to do is either enable or disable the button.
In the binding declaration, I added the Mode=OneWay
property because the update only goes from the View Model to the UI. The UI never updates that particular property. And so the property is declared as read-only in the View Model class.
And to use the Style
, the Reset button declaration looks like this:
<Button Content="Reset Game"
Style="{StaticResource EnableGameButtonStyle}"
Margin="430,190,0,0"
Click="btnReset_Click"/>
The CheckBoxes
below the game grid also uses the IsEnableGameControls
property and the style and trigger syntax is silimar. Similarly, I added another property called IsShowGameGrid
that controls whether the game grid is displayed or not.
To enable or disable the game controls, I just call the IsEnableGameControls
property in the ViewModel
:
IsEnableGameControls = true;
And all controls that are bound to that property will be affected. To implement the same thing in WinForms, I would have to code something like the following. I would also have to wrap it with an InvokeRequired
to make sure the call is threadsafe.
private void EnableGameButtons(bool bEnable)
{
this.EnterNotesCheckbox.IsEnabled = bEnable;
this.ShowNotesCheckbox.IsEnabled = bEnable;
this.ShowSolutionCheckbox.IsEnabled = bEnable;
this.ResetButton.IsEnabled = bEnable;
}
Since I am just changing the IsEnabled
property of the affected controls and both the IsEnabled
and IsEnableGameControls
are boolean properties, I can just bind the IsEnabled
property of each control directly to the IsEnableGameControls
property of the ViewModel
and it will work the same way as using the Style Triggers
. The declaration would look like this:
IsEnabled="{Binding Path=IsEnableGameControls, Mode=OneWay}"
The Style
declaration for the Start button is more complicated since there are multiple states. But the syntax is similar.
<Style x:Key="StartButtonStyle"
TargetType="Button"
BasedOn="{StaticResource ButtonBaseStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=StartButtonState, Mode=OneWay}" Value="Start">
<Setter Property="Content" Value="Start Game"/>
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=StartButtonState, Mode=OneWay}" Value="Pause">
<Setter Property="Content" Value="Pause Game"/>
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=StartButtonState, Mode=OneWay}" Value="Resume">
<Setter Property="Content" Value="Resume Game"/>
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=StartButtonState, Mode=OneWay}" Value="Disable">
<Setter Property="Content" Value="Start Game"/>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
Each trigger is bound to the StartButtonState
property in the View Model class. Since the StartButtonState
property has more states than a simple boolean, we include a trigger for each state. And within each trigger tag, we set the properties that we want to change for each state. I did not have to include the following...
<Setter Property="IsEnabled" Value="True"/>
...in the first three trigger tags since the default value of this property is already True
and I am not changing anything. But I added them for readability.
Likewise, we can create properties in the ViewModel
that return a content string
and the enabled state for each StartButtonState
and bind the Content
and the IsEnabled
properties of the Start button to those properties. We can then bypass the use of Style Triggers.
Here is the modified StartButtonState
property in the ViewModel
as well as the StartButtonContent
and the IsEnableStartButton
properties:
private StartButtonStateEnum StartButtonState
{
get
{
return _startButtonState;
}
set
{
_startButtonState = value;
OnPropertyChanged("StartButtonContent");
OnPropertyChanged("IsEnableStartButton");
}
}
public string StartButtonContent
{
get
{
switch (StartButtonState)
{
case StartButtonStateEnum.Pause:
return "Pause Game";
case StartButtonStateEnum.Resume:
return "Resume Game";
default:
return "Start Game";
}
}
}
public bool IsEnableStartButton
{
get
{
return (StartButtonState != StartButtonStateEnum.Disable);
}
}
And the Start Button XAML code looks like this:
<Button Style="{StaticResource ButtonBaseStyle}"
Content="{Binding Path=StartButtonContent, Mode=OneWay}"
IsEnabled="{Binding Path=IsEnableStartButton, Mode=OneWay}"
Margin="430,105,0,0"
Click="btnStart_Click"/>
We change the Style
to point to the ButtonBaseStyle
and add the Content
and the IsEnabled
properties and bind them to the corresponding properties in the ViewModel
class.
The big thing here is in the set
accessor of the StartButtonState
property, we call the OnPropertyChanged
method on both the StartButtonContent
and the IsEnableStartButton
properties.
Personally, both methods work and I do not think that one is better than the other. There is less code when using Style Triggers though. However, that puts some of the business logic in the UI rather than in the ViewModel
. So, if we were to strictly adhere to the MVVM programming pattern, then the second method would probably be the preferred method to use. That way, the UI layer does not contain any business logic.
WPF and the Combo Box
The next order of business is how to populate the ComboBox
with the contents of an Enum
. If one searches in Google, there are many examples on how to do this. Let us use this example from Microsoft's own website. First, we need to declare the namespace where the enum
resides. In this project, the Enum
resides in the following namespace:
xmlns:srcME="clr-namespace:SudokuWPF.Model.Enums"
Then, in the Windows Resource section, we declare the following:
<ObjectDataProvider x:Key="GameLevels"
ObjectType="{x:Type sys:Enum}"
MethodName="GetValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="srcME:DifficultyLevels" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
This XAML object is actually creating a wrapper around the GetValues
method of the Enum
class. And in the ComboBox
declaration, we point the ItemsSource
property to this object like so:
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="140"
ItemsSource="{Binding Source={StaticResource GameLevels}}"
SelectedItem="{Binding GameLevel}"
IsEditable="False"
IsReadOnly="True"
Canvas.Left="430"
Canvas.Top="32"/>
Pretty straightforward, right? Well, not exactly. One of our enum
s is "VeryEasy
" and that is exactly how it will appear in the dropdown instead of "Very Easy
", with a space between the two words. To get around this, there are many examples on-line on how to fix this as well as how to create tooltips and how to localize the enum
values. But that is a bit much for this project so let us just stick to this method since it is easy to read and the missing space is not really noticeable.
The SelectedItem
property is bound to another property in the ViewModel
class. In this case, we use the default TwoWay
mode since we want the changes to go both ways. Meaning, if the user changes the selection, we want the View Model to be aware of it and act on it. And when the game first loads, we want to be able to set the level to the last selected level.
public DifficultyLevels GameLevel
{
get
{
return _gameLevel;
}
set
{
bool bLoadNewGame = (_gameLevel != value);
_gameLevel = value;
Properties.Settings.Default.Level = _gameLevel.GetHashCode();
if (bLoadNewGame)
LoadNewGame();
OnPropertyChanged();
}
}
Because we bound the SelectedItem
property, we do not need to add the SelectionChanged
property to the combo box and a corresponding event handler in the code behind. Instead, the usual SelectionChanged
event code is found in the set
accessor of this property.
One thing you will notice when you look at the code, all the bound properties are declared public
instead of my preferred access modifier, internal
. This is because WPF requires that level of access. If it was declared internal
, then the XAML data binding will fail. For this property, even the DifficultyLevels enum
has to be declared public
.
Game Cells
Now that we have more or less dealt with the periphery controls, how do we manage the state of the game cells? Do we use the Style triggers? Or is there a more powerful WPF tool that we can use? As it turns out, there is. WPF has a DataTemplate
class. The reason we use this instead of the simpler Style Triggers is because each game cell can display two primary kinds of data. A single value that denotes the answer or the user's answer. And another state where it displays the notes. However, we could still probably use Style Triggers to achieve the same effects.
First, each game cell is declared as a ContentControl
control. A ContentControl
is the base class for pretty much all the controls in WPF. It represents a control with a single piece of content of any type. With the ContentControl
, we can build our own custom control to represent the game cell.
In the Windows.Resources
section, we create the DataTemplate
for the game cell. The base for the game cell is a 3 x 3 Grid
so that we can display the notes for the cell. And in each grid, we put in a TextBlock
element to display the actual note. If we are displaying anything other than notes, we use another TextBlock
that spans the entire grid area.
Highlighting Cells
Each cell of the grid has an IsMouseDirectlyOver
property that we can use to add a style trigger to highlight it whenever the user mouses over a cell. The following is the style trigger I used. I excluded the rest of the style since it is not all that interesting.
<Style.Triggers>
<Trigger Property="IsMouseDirectlyOver" Value="True">
<Setter Property="Background" Value="LightBlue" />
</Trigger>
</Style.Triggers>
With this trigger, whenever the user moves the mouse over a game cell, the background
will change to LightBlue
. This works whether the background
is AliceBlue
or White
.
WPF and DataTemplates
So, how to do we deal with the different game cell states? In the original VB version, I used two properties to control the cell state:
While there is such a thing as a MultiDataTrigger
, I decided that it was easier to combine these two properties and modified the CellStateEnum
accordingly. In addition, I needed to change the way the Notes worked. I added another class to represent the state of the Notes for each cell so that each note cell can be bound to a property that displays notes.
Look up the CellDataTemplate
in the MainWindow
XAML code to see what I did.
I also needed to add the INotifyPropertyChanged
interface to the CellClass
and the NoteState
since both classes have properties that are bound to controls in the CellDataTemplate
.
In the NoteState
class, I have the following property declaration:
public bool State
{
get
{
return _state;
}
set
{
_state = value;
OnPropertyChanged("Value");
}
}
What this code does is, in the set
accessor, when the state of the note is changed, we raise a PropertyChanged
event for the Value
property. This is because the Value
property is bound to a control on the MainWindow
and not the State
property. This is an example of raising the PropertyChanged
event on a related property. Also in the ViewModel
class, we have the following function:
private void UpdateAllCells()
{
if (IsValidGame())
foreach (CellClass item in _model.CellList)
OnPropertyChanged(item.CellName);
}
We call this method whenever we need to update all the cells in the game grid. This is another example where we raise the PropertyChanged
event outside of the actual property that is bound to a WPF control.
Input Pad
For the Input Pad, which is used by the user to enter values into blank cells, the WPF version is pretty straight forward. This is how it looks in the IDE designer window:
As I mentioned earlier, I moved the Hint and Clear buttons from the main form onto the Input Pad form. The base Panel
I used for this form is the StackPanel
. I then added a 3 x 3 grid for the number pad and then two buttons: one for Hint and another for Clear.
To create spaces between the three elements, we add the Margin
property to the number grid and the bottom Clear button..
When the user clicks a cell on the game grid, we want to position this window right next to where the user just clicked. To do this, we use the following lines of code in the cell's click event.
System.Drawing.Point point = System.Windows.Forms.Control.MousePosition;
inputPad.Left = point.X + 20;
inputPad.Top = point.Y - (inputPad.Height / 2);
Basically, we query the system for the absolute location of the mouse. Then, we adjust the Left
and Top
properties of the Input window and position it 20 pixels to the right of the mouse click. The complete code to create and display the window follows:
inputPad = new InputPad();
inputPad.Owner = this;
System.Drawing.Point point = System.Windows.Forms.Control.MousePosition;
inputPad.Left = point.X + 20;
inputPad.Top = point.Y - (inputPad.Height / 2);
inputPad.ShowDialog();
Like the WinForms equivalent, we need set several properties on the Window:
ResizeMode="NoResize"
ShowInTaskbar="False"
SizeToContent="Height"
WindowStartupLocation="Manual"
WindowStyle="ToolWindow"
In order to position this window where we want it, like the WinForms version, we need to set the WindowStartupLocation
property to Manual
. Then, we can control the Top
and Left
properties of the Window. Otherwise, Windows will ignore our attempts to reposition the window and just put it wherever it wants to.
The code behind for this window is pretty straightforward as well. Once the user clicks something, we save the button clicked as a state and then close the window.
The About and Game Complete windows are similar except that we do not need to save any state. We just open the child window as a modal dialog and then when the user clicks "OK", just close it and return to the main window.
Wrapping Up WPF
At the beginning, I left all the click events in the code behind for the MainWindow
blank. But now that the ViewModelClass
is more or less complete, the next step is to wire them up together. Basically, the click
event in the MainWindow
just calls a function in the ViewModelClass
, similar to how the WinForms version did. So, the code-behind for "New Game" button looks like this:
private void btnNew_Click(object sender, RoutedEventArgs e)
{
if (ViewModel != null)
ViewModel.NewClicked();
}
To wrap up the WPF version, I cleaned up the game play code in the ViewModelClass
and then set the Application startup code. The Startup
object in a WPF application is the App.XAML object. When you open it, there is a StartupUri
property in the <Application/>
tag. By default, it points to the MainWindow.XAML.
But since we want to instantiate the ViewModelClass
outside of the View
, we repoint it to the ApplicationStartup
method of the Application
class instead. In addition, we rename it from StartupURI
to Startup
. Then we open the code-behind and add the following method to the code-behind of the App.xaml class.
public void ApplicationStartup(object sender, StartupEventArgs args)
{
MainWindow mainWindow = new MainWindow();
mainWindow.ViewModel = ViewModelClass.GetInstance(mainWindow);
mainWindow.Show();
}
So basically, we instantiate the main window, set the ViewModel
property for the main window, and then display the window.
If you look at the code-behind for the MainWindow
, it is pretty sparse compared to the code-behind for a WinForms application.
Here is how the WPF application looks like when it is running:
If you have played with the VB version of this game, you will notice that in this version, I have implemented the mirror image where the missing cells are mirrored on the vertical axis. In the next version of the game, I will add another dimension where the game generator randomly picks the mirror to be either vertical, horizontal, or diagonal.
WPF and Silverlight
Now that we have completed the port from VB.NET/WinForms to C#/WPF, we shall now attempt to port the code to Silverlight. I created a new C# Silverlight Application project, added all the subfolders (View, Model, and ViewModel), then copied all the files, except for the WPF UI files, from one project to the other.
I thought it would be a pretty straight port, but there were several bumps along the way. The first difference is that a Silverlight Application has two projects instead of one. These are:
SilverlightApplication1
SilverlightApplication1.Web
The second project is just a wrapper for the main project so that we can run it in a browser for testing/debugging. So we do not have to concern ourselves with the second project at all.
The next difference is that instead of a <Windows/>
tag in the MainWindow
, we have a <UserControl/>
in a MainPage
. The following sections detail the other bumps that I encountered as I tried to port the code from WPF to Silverlight.
Application Settings
The first bump was the application settings does not port over to Silverlight. In WPF, the application settings are found in a tab in the Application Property window.
Addressing the properties from code is similar to VB.NET Windows application.
Properties.Settings.Default.Level = (int)GameLevel;
Properties.Settings.Default.Save();
But in Silverlight, the Properties
namespace does not exist. There is however, the IsolatedStorageSettings
class. To use it, one needs to get an instance of the class before using it like so:
private IsolatedStorageSettings _appSettings = IsolatedStorageSettings.ApplicationSettings;
Before using any of the settings, we first check if the setting already exists or not:
if (!_appSettings.Contains("Level"))
_appSettings.Add("Level", "");
So, if it does not exist, we add it. Then to retrieve data, we use the following code:
_level = (Int32)_appSettings["Level"];
To save the settings, we use the following code:
_appSettings["Level"] = _level.ToString();
_appSettings.Save();
Since all the settings are saved as a string
, we need to convert the integer value to and from a string
when we use it. Also, to commit the data to disk, we need to call the Save()
method.
Since one needs an instance of this class, as well as a lot of support code, I thought it best to consolidate all the settings into a single class and wrap it as a Singleton
object.
Timer Class
The next bump was the Timer
class. Apparently, System.Timers
is not part of the Silverlight application namespace. Fortunately, there is another Timer
in the System.Threading
namespace. There are several differences between the two classes and I had to rewrite parts of the Timer
class to accommodate those differences.
The first difference is how one instantiates the Timer
class. The next difference is, there is no Enabled
property to start or stop the timer once it is instantiated. There is, however, a Change
method that one can use to pause/restart/stop the timer. Also, once the timer is instantiated and running, there is no way to know if it is running or not, so I created my own TimerEnabled
property to keep track of the state of the timer.
The UI XAML Code
After fixing the above issues, I copied the WPF XAML code from one project to the other. Instead of copying the actual files, I opened the original WPF code, copied the XAML code inside the <Window></Window>
tag for the MainWindow
and pasted it to the MainPage.XAML
window. I did this for the other three child windows as well.
When I pasted the code into the MainPage.XAML
window, there are several squiggly lines indicating that there are errors. Among the errors, are:
DataTriggers
StatusBar
Label
ObjectDataProvider
These are pretty serious issues since I used the DataTrigger
class to help control and manage the game controls and the game grid. So I had to look for work-arounds.
The StatusBar And Labels
This was an easy enough fix. Since Silverlight did not have a StatusBar
and the Label
controls, I just used the <Textblock/>
control instead. The issue was that the Labels
were put inside the StatusBar
control.
Since Silverlight projects are UserControl
as opposed to a Window, I switched the background panel object from a Canvas
to a Grid
. Part of the reason was since this UserControl
runs inside a browser window and does not have any defined border, it was easier to position all the controls a grid. So I added some rows and columns and then put each control into a specific grid column and row and aligned all the controls that way rather than using absolute coordinates. Here is how the UI looks in the designer window.
Here is another view with the actual grid rows and columns showing.
As you can see, I created a grid with four columns and ten rows. I then assigned each control to a particular row and column and lined them up that way. The game grid itself spans three columns and eight rows. I then centered the game grid within that. The status bar TextBlock
is in row ten and spans the first three columns. I added a gray border object to simulate a status bar at the bottom.
For cells that have more than one control, I used a StackPanel
object as the base and then aligned the controls within that StackPanel
. For example, here is the code for the ComboBox
and the label above it:
<StackPanel Grid.Column="3"
Grid.Row="0">
<TextBlock Text="Difficulty Level:"
Height="20"
Margin="0,12,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom" />
<ComboBox Name="GameLevel"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding}"
SelectedIndex="0"
Width="140"
SelectionChanged="DifficultyLevel_SelectionChanged"/>
</StackPanel>
I define a StackPanel
object and assign it to column three and row zero. I then define a TextBlock
object and then the ComboBox
object inside that StackPanel
.
In the lower right corner of the MainPage
, I also used a StackPanel
where the elements are oriented horizontally as opposed to vertically, which is the default. Here is an example where I define a style that is local to that StackPanel
object.
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="12" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="5" />
</Style>
</StackPanel.Resources>
Since all I have in that StackPanel
are TextBlock
objects, I do not bother with naming the style since I want this Style
to apply to all the TextBlock
objects within that StackPanel
. In other words, the scope of this Style
is limited to the StackPanel
object where it was defined.
The ComboBox ItemsSource
If you notice the XAML for the ComboBox
above, the ItemsSource
property is bound to something different than the WPF version. This is because the ObjectDataProvider
class is not available in Silverlight. As a work-around, I had to create an ObservableCollection
of string
s in the code-behind and then bind it to the ItemsSource
property of the ComboBox
. Here is the code:
private ObservableCollection<string> _levels;
private void InitComboBox()
{
_levels = new ObservableCollection<string>();
foreach (string item in Enum.GetNames(typeof(DifficultyLevels)))
_levels.Add(item);
this.GameLevel.DataContext = _levels;
}
First, I create a module level variable, then in the InitComboBox
method, which is called from the MainPage_Loaded
event, I populate the collection with the names from the Enum
. Finally, since the DataContext
for this combo box is different from the MainPage
, I bind the DataContext
of the combo box directly to the collection. This is one of the cases where I needed to give the ComboBox
control a name. That way, I could access the control from the code-behind.
Since I bind the DataContext
of the combo box directly to the ObservableCollection
, the ItemsSource
binding definition looks like this:
ItemsSource="{Binding}"
We do not have to specify a path or property in the binding declaration.
I could have also created an ObservableCollection
property in the ViewModel
class and bound the combo box's ItemsSource
property to that property rather than in the code behind.
Another difference that you might notice is that the ComboBox
's SelectedIndex
property is not bound to a property in the ViewModel
class and that I also defined a SelectionChanged
click event. There is a reason for this and I will explain later on when I continue with the differences between WPF and Silverlight.
Silverlight and DataTriggers
I was kind of shocked that Silverlight does not have DataTrigger
s. So how was I going to use all the fancy triggers that I wrote for the WPF project? Well, Silverlight has something called DataTemplates
. For highlighting the cells as the user mouses over them, I used the VisualStateManager
class.
I had to essentially redo the model and created an abstract
BaseCell
class. I then added an AnswerCell
, BlankCell
, HintCell
, etc. that all inherit from the BaseCell
class for each state in the CellStateEnum
. Since the BaseCell
is complete, the derived cells do not need to have any extra properties or methods.
Here is the BaseCell abstract
class which has a single public
property based on the CellClass
:
public abstract class BaseCell
{
public CellClass Cell { get; set; }
}
Then each of the other state classes derive from this abstract
class. They all look the same so I will just include the definition for the AnswerCell
class.
public class AnswerCell : BaseCell
{ }
As you can see, each class inherits from the abstract
class and the class definition is blank.
Then, for each of these state classes, I created a data template. So, for the Answer
class, the corresponding DataTemplate
looks like this. But first, I needed to create the base cell style.
<Style x:Key="CellOutputStyle"
TargetType="TextBlock">
<Setter Property="Height" Value="39"/>
<Setter Property="Width" Value="34" />
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="24" />
</Style>
Then, the DataTemplate
for the AnswerCell
looks like this:
<DataTemplate DataType="model:AnswerCell">
<Grid>
<TextBlock Style="{StaticResource CellOutputStyle}"
Text="{Binding Path=Cell.Answer}"/>
</Grid>
</DataTemplate>
The Text
property of TextBlock
is bound to underlying cell's Answer
property.
I then create a DataTemplate
for each and every cell state data type. Basically, each DataTemplate
starts out with a base Grid
and inside the grid, I add a TextBlock
control to display data. I also set the Foreground
color property to depending on whether it is displaying the answer (black, default), a hint (purple), or the user's correct (green) or incorrect answer (red).
I also need to add the Model.Structures
namespace to the top of the XAML code so that it knows where these classes reside.
xmlns:model="clr-namespace:Sudoku_Silverlight.Model.Structures"
Then the Content
property of each cell in the game grid is bound to a Cell
property in the ViewModel
that returns the BaseCell
class. So, in this example, the Content
property of the first cell (row zero, column zero) is bound to the Cell00
property of the ViewModel
class.
<Button Style="{StaticResource CellStyleBlue}"
Content="{Binding Path=Cell00, Mode=OneWay}"
Grid.Column="0"
Grid.Row="0"
Click="A0_CellClick" />
And the definition for Cell00
in the ViewModel
class looks like this:
public BaseCell Cell00
{
get
{
return GetCell(0, 0);
}
}
I then add an extra data layer between the model and the ViewModel
which is basically a list of BaseCell
objects.
private List<BaseCell> Cells { get; set; }
Then when I need to display the different states, I simply replace the cell object at a particular index in the list with the corresponding class. For example, if I want to display the answer in the first cell, I simply assign the first element in the list to an AnswerCell
class like so.
Cells[0] = new AnswerCell() { Cell = item };
where item
is the actual CellClass
object from the Model
for the first cell. Then I raise the PropertyChanged
event for that cell so the UI will update the cell contents with the DataTemplate
for the AnswerCell
.
OnPropertyChanged(item.CellName);
If you notice, in the XAML code for the Cell, there is no pointer to the DataTemplate
that we created earlier. But because the cell is bound to the abstract BaseCell
and that in turns points to an AnswerCell
class, Silverlight uses the AnswerCell DataTemplate
to format that cell. Pretty cool, right?
If I were to do this properly, I would have modified the Model
layer to output this list instead of creating it in the ViewModel
layer. Also, I would use indexers so that I do not have to define a property for each cell. But this works so let us leave it the way it is.
Highlighting Cells in Silverlight
The final trigger I needed to translate was highlighting the cell as the user mouses over each cell. We do this by using the VisualStateManager
class. Here is the XAML code to do this:
<Style x:Key="CellStyleBlue"
TargetType="ContentControl"
BasedOn="{StaticResource BaseCellStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Grid HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="3">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition To="MouseOver"
GeneratedDuration="0:0:0.5"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBrush"
Storyboard.TargetProperty="Color"
To="CadetBlue" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.Background>
<SolidColorBrush x:Name="ButtonBrush" Color="AliceBlue" />
</Grid.Background>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This looks pretty complicated, but it is actually pretty simple. We create a Style
and within that Style
, we add a Template
property to define the visual states that we care about. Basically, we care about the Normal
and MouseOver
states. When the user mouses over a cell, we change the Background
property of the Grid
object from AliceBlue
to CadetBlue
. In addition, take 0.5 seconds to make the transition.
Even though we are not doing anything in the Normal
state, if we remove that line, the cell will remain highlighted even after the mouse leaves the cell.
The interesting thing here is that even though the Styles all target the ContentControl
, I actually apply the style to a Button
control. When I change it to a ContentControl
, the highlighting does not work, but everything else does. I am not sure why the highlighting does not work properly. Maybe after writing this article, I will spend some time to try and figure it out.
One of the differences between the highlighting in the WPF application versus the Silverlight application is that the highlight appears for all the cells in the Silverlight version regardless of the underlying data. In the WPF version, if the underlying data is the Answer, the highlighting is disabled. This is because in Silverlight, the IsEnabled
property is missing for the TextBlock
control.
Invalid Cross-thread Access Error
After making the necessary changes to get the Silverlight project compiled, I tried to run it. It did not get far before it spit an "Invalid Cross-thread access error" in one of the OnPropertyChanged
events. Apparently, Silverlight does not handle multi-threaded applications as well as WPF does. I searched Google and the solution was to use the Dispatcher.CheckAccess()
method to check for cross-thread access. Here is how it is used:
private void SetTextBlockContent(TextBlock target, string value)
{
if (target.Dispatcher.CheckAccess())
target.Text = value;
else
target.Dispatcher.BeginInvoke(() => { target.Text = value; });
}
This is the same problem with WinForms applications where background threads are not allowed to access the UI controls directly. Instead, one needs to check InvokeRequired
. Here is the WinForms equivalent:
delegate void SetTextCallback(string text);
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
this.textBox1.Text = text;
}
I could have spent more time trying to figure out which ones need the Dispatcher
and which ones do not. Instead, I just decided to use the Dispatcher
pretty much every thing. Since using the Dispatcher
is just like using InvokeRequired
in WinForms, I decided to use custom events to get the ViewModel
to talk to the View
and vice versa. While I still raise the PropertyChanged
event for some things and I still set the DataContext
property of the MainPage
to the ViewModel
, I fire off a custom event for pretty much any kind of communication between the two just to get things working properly.
Earlier, I mentioned that the combo box SelectedIndex
property does not point to a property in the ViewModel. And that I also defined an event handler for the SelectionChanged
event. The reason I did this was because of this Dispatcher issue. So, when the user changes the selection in the combo box, it fires an event with the new selection and the ViewModel
class is listening for the change.
After making all these changes, the code-behind of the Silverlight project looks like a cross between WPF and WinForms.
Invalid XML
Another issue came up when I was entering XAML code for the MainPage
. The XAML editor window would display a squiggly line under the new code I was entering. When I mouse over the squiggly line, the editor would tell me that the underlying code was "Invalid XML." It would complain for something as simple as the following:
<DataTemplate DataType="model:AnswerCell">
</DataTemplate>
After searching Google, the answer had nothing to do with the XAML code I was entering, but rather, it was the name of the application. When I created the project, I named it "Sudoku Silverlight
" with a space in between. Apparently, this causes problems all over the place and this is one of the symptoms. After going to the project properties page and removing the space, the "Invalid XML" error went away. That was a really weird error.
So bottom line, be careful when naming your project and do not use spaces in the name.
Child Windows
Because Silverlight works inside a browser, opening a child window is different from a WPF or Windows application in general. For one thing, it does not have access to the mouse position. Second, it does not have any control over where the window is placed. Silverlight will always center it within the browser window. Another thing that is missing in Silverlight is the ShowDialog
method. It only has Show
. Since Show
is non-modal, it will return after opening the child window and does not wait around for the child window to close.
With those differences in mind, I had to tackle the child windows, especially the Input Pad window, differently than I would in a WinForms or WPF application. Fortunately, there is a Closed
event that one can wire up an event handler to.
Here is the code that opens the Input Pad window:
private void UIShowNumberPad(CellIndex callingCell)
{
InputPad inputPad = new InputPad(callingCell);
inputPad.Closed += NumberPadClosed;
inputPad.Show();
}
I create an instance of the child window, wire up the event handler, then open it. Here is the corresponding code that handles the Closed
event.
private void NumberPadClosed(object sender, EventArgs e)
{
InputPad inputPad = (InputPad)sender;
if (inputPad.InputPadState != InputPadStateEnum.InvalidState)
RaiseEvent(inputPad.CallingCell, inputPad.InputPadState);
}
When the user closes the Input Pad window by either clicking the "X
" or a button on the window, I raise an event with some data so that the ViewModel
knows which cell was last clicked as well as which button on the Input Pad the user clicked.
Message Box In Silverlight
Unlike a MessageBox
in a WinForms application, the MessageBox
in Silverlight has several limitations, I decided not to use it because I could not control the text in the buttons. By default, it has an "OK" button. There is a parameter in the method call where I can also add a "Cancel" button as well. But I cannot change the button text to say "Yes" and "No", so I decided to create my own Message Box.
It was not all that difficult. I used a Grid
as the base and added three columns and two rows. For the second row, where the buttons are located, the Height
was set to "Auto
".
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
Instead of splitting the height evenly between the two rows, Auto
sets the row height to whatever the height is of the controls on that row. And the rest of it is given to the first row. As for how this window works, it is similar to the way the Input Pad window works. I wire up a Closed
event handler and it returns information whether the user clicked Yes or No.
Wrapping Up The Silverlight Project
In the end, the code-behind for the MainPage
looks like a cross between WPF and WinForms. After publishing this article, I might spend some time to see if I can reduce the number of Dispatcher
calls as well as the number of custom events that are raised between the View
and the ViewModel
.
After working through the differences between WPF and Silverlight and cleaning up the game play code, the final step was to wrap up the application start/stop code. Since the project runs in a browser, we cannot close the application from within. So there is no need for a close button. We do, however, know when the application closes because there is an Exit
event in the Application
class.
I open the App.xaml.cs file and wire up the Application_Exit
handler in such a way that when the user closes the tab or browser where this application is running, it will call a method to stop all the background threads as well as save the current settings to the IsolatedStorageSettings
.
Here is how the Silverlight application looks like when running:
It looks pretty close to the WPF version. The main difference is the lack of a title bar and line separators between the numbers in the lower right corner. I also added a border
element around the user control so that when it runs in a browser, the user knows where the bounds of the game window is.
Conclusion
This was a fun project to try to convert a VB.NET WinForms application to C#/WPF and Silverlight. I learned a lot about both about WPF and Silverlight. Even though Silverlight is a subset of WPF, there are enough differences between the two that sharing XAML code would be a difficult task. The biggest difference for me is the lack of triggers in Silverlight.
However, if a project really needed to share the same code base between the two, with careful planning and lots of compiler directives, it can be done. There are several articles available on-line that talk about code re-use and code sharing between WPF, Silverlight, and Windows Phone/Store App. Basically, take the lowest common denominator and write code to use that subset. If done right, the code will be portable across all three platforms.
For this project, had I started out with Silverlight, it might have been much easier to port the code to WPF than the other way around. Maybe I will tackle that question at a later date. That is, try to port the Silverlight project to WPF and see just how much is actually re-usable.
I hope that by detailing all the issues I went through, it will help someone else out with their project.
One of the things that you will notice is that I created a property for each cell in the game grid as well as a matching click event handler. That comes out to 81 properties and 81 click events. I am sure there is a way to simplify that by passing parameters from the WPF XAML with the cell's row and column. It will be another thing that I will look into.
Feel free to contact me with questions or issues about anything in this article or if you would like me to go into more details about a particular issue or topic that I covered above or failed to cover.
History
- 2015-01-01: First release of the code/article