Overview

This documentation does not describe how to use this application, but rather it describes how the application source code is structured.  In addition to Developer Studio, you will need to first install the Silverlight Toolkit before this project will build.

Source Data

The data displayed in this application is loaded from two text files, results.txt and tournaments.txt.  This application does not provide a mechanism to edit data.  Instead, the user should maintain these two files on their local PC, using spreadsheet software for editing.  The user can then upload the files to their website as needed.  While this may sound a bit archaic, it is actually a fairly easy-to-use process, and was a compromise that allowed me to write this application in one week.

Results.txt

Contains all scores in table form, with these fields:

Place ( 1, 2, etc.), Group (like “Boys 7-8”), FirstName, LastName, Suffix, Hometown, GraduationYear, Round1 (score from first day), Round2 (score from second day, Total (score), Points, Date (of first day of play).

Tournaments.txt

Contains one line per tournament in table form, with these fields:

Name, Course, Location, From (date of first day of play), To (date of second day of play), Website (of golf course), Season (for ranking purposes), Weather (freeform text).

Model->ViewModel->View

In summary, the model contains the data, the view-model is the user interface that acts upon the data and responds to user input, and the View is nothing more than the look (or skin) of the application.

The entry point to this program is in App.xaml.cs.  Here you should find the startup code in ApplicationStartup().  We first create the main model (MainModel), then load data into the main model, then create the main viewmodel (MainViewModel), then create the main view (MainView).  Notice that the MainView has access to the MainViewModel and that the MainViewModel has access to the MainModel.  Notice also that the MainModel does not have any knowledge of the MainViewModel and the MainViewModel does not have any knowledge of the MainView.

Models

Introduction

Models contain data and references to other models.  However, models do not reference views or viewmodels, models do not contain action button handlers, models do not provide formatted data, models do not provide for current selection tracking, and models do not track what mode the user interface is in.

MainModel

When the MainModel is created and its Load function is called, it creates “sub” models and then calls their respective Load functions.  Because the Load function is asynchronous, the sub models raise an event once they are loaded.  These events then trigger the next step in the initialization process of the MainModel.

TODO: Reworking this class to use the Async support would be a nice way to clean up the asynchronous event sequencing taking place here.

ResultsModel & TournamentsModel

The two primary models that correspond to the source data are ResultsModel (from results.txt) TournamentsModel (from tournaments.txt).  You can examine these two classes for an example of downloading text files from a Url using the WebClient interface.

One thing to notice is the use of a conditional attribute to use a local server for debugging purposes.

TODO: Reworking these classes to use the Async support would be a nice way to clean up all the callbacks.

The ResultModel and TournamentModel are simple classes to hold one result and one tournament respectively.

TODO: Computing ResultModel.Total instead of loading it from results.txt would bring a slight improvement to the system.

Once created, each tournament contains a list of results and each result is linked back to a specific tournament.  This is done in the AssignResults function in the TournamentsModel.

PlayersModel

Once the ResultsModel is loaded, the PlayersModel is built by locating all the unique player names from the ResultsModel.  See the Load function in PlayersModel for an example of using Linq to sort results by last name, first name and date.

Once created, a cross reference is built, where each player contains a list of results for that player, and each result is linked back to a specific player.  This is done in the Load function in the PlayersModel.

Discussion

I think it would have been just as elegant a solution not to create a PlayersModel and instead leave the work of extracting unique players for the viewmodels.  This would have reduced the number of classes in the solution, which can be a good thing.  This is the approach I took for the GroupsViewModel, SeasonsViewModel, and RankingsViewModel.  In the same way, I suppose I could have implemented models for groups, seasons, and rankings to reduce the complexity of those viewmodels, which can be a good thing.

If the web host supported .net data access, I could have used some form of SQL to load only the data needed.  This approach is certainly needed when the size of the dataset makes it impractical to load all at once.  As of this writing, I’m not sure how to implement this using Yahoo!-based web hosting.

ViewModels

Introduction

Viewmodels implement concepts like current selection, user interface mode, and commands.  Viewmodels make it easy to create a view by offering formatted forms of data, statistics, and other calculated data.  Viewmodels access models for data as needed.  Viewmodels implement INotifyPropertyChanged for all properties that are available to views for binding.

Viewmodels should not reference views.

Viewmodels should not derive from DependencyObject or implement DependencyProperties.  This is a mistake I made in the past.  The downside of doing this is the amount of work required to create all the DependencyProperties and fact that DependencyObjects makes eventual multi-threading features more difficult.

MainViewModel

Silverlight (and WPF) provides a mechanism called binding that connects properties in a viewmodel to user interface elements in views.  For example, there is a string property in the MainViewModel called SelectedSeason.  Any view can display the value of SelectedSeason by binding to it.  The details of setting up a binding will be covered in the section called views.

For binding to work, Silverlight (and WPF) need to know when the value of a property changes.  This is done through the INotifyPropertyChanged interface.  In this project, this interface is implemented in the base class of all viewmodels, the ViewModelBase.  Whenever the value of SelectedSeason changes, the MainViewModel calls ViewModelBase’s Notify(“SelectedSeason”).  Failing to do this will cause views to continue displaying the old value of SelectedSeason.

There is another type of binding in Silverlight (and WPF) for commands.  When a button’s command is bound to a command handler in a viewmodel, the code associated with the command handler in the viewmodel will execute when the button is clicked.  For example, MainViewModel has a command handler called ShowPlayers.  When a button in a view that is bound to ShowPlayers is clicked, the code associated with ShowPlayers in the MainViewModel is executed.

In addition to properties and command handlers, the MainViewModel contains other “sub” viewmodels.  These other viewmodels exist to support the display of players, tournaments, rankings, groups, and seasons.  It also contains a viewmodel of the selected player and a viewmodel of the selected tournament.

PlayerViewModel

The PlayerViewModel contains all information needed to display a specific player in a view.  The properties that a view can bind to include LastName, FirstName, Gender, GraduationYear, and HomeTown.

The PlayerViewModel also contains one ResultViewModel for each tournament the player participated in.  The ResultViewModels are stored in an ObservableCollection called Results.  The PlayerViewModel also contains one PlayerSeasonViewModel for each season the player participated in.  The PlayerSeasonsViewModels are also stored in an ObservableCollection called Seasons.

An ObservableCollection is a list that implements the INotifyCollectionChanged interface.  Implementation of the INotifyCollectionchanged interface is required to support dynamic bindings so that insertions or deletions in the collection update the user interface automatically.  An ObservableCollection can be bound to a ListBox or to an ItemsControl in views.

The PlayerViewModel also contains one command handler called PlayerSelected.  When the view determines that the user selected the player represented by a given PlayerViewModel, the view should invoke the PlayerSelected command.  Typically, a button’s command handler will be bound to PlayerSelected.

In this application, we want the selection of the player to be handled by the MainViewModel.  Yet, to avoid writing intertwined code, we don’t want the PlayerViewModel to reference its containing MainViewModel.  So instead the PlayerViewModel’s constructor is passed a reference to an interface called INotifySelection.  INotifySelection contains a function called PlayerSelected that the PlayerViewModel can call when its PlayerSelected command handler is invoked.  The MainViewModel implements the INotifySelection interface.

The constructor of the PlayerViewModel populates its properties from the provided PlayerModel, including calculating the season statistics stored in each PlayerSeasonViewModel.

PlayersViewModel

The PlayersViewModel is constructed with a reference to PlayersModel (the model containing all players), which is saved in a private member called _playersModel.  PlayersViewModel has a public method called Query, which finds all the player models that match the given group and season to populate its ObservableCollection of PlayerViewModels called Players.  The user interface binds to Players, showing only the players that match the query criteria.

TournamentViewModel

The TournamentViewModel contains all information needed to display a specific tournament in a view.  The properties that a view can bind to include Name, Course, Location, Dates, Website, Season, and Weather.

The TournamentViewModel also contains one ResultViewModel for players that participated in the tournament.  The ResultViewModels are stored in an ObservableCollection called Results.  Instead of showing all the players in the tournament, Results is populated only when it public method called Query is invoked.  Query searches through all the results for players that match the given group.

The TournamentViewModel also contains one command handler called TournamentSelected.  When the view determines that the user selected the tournament represented by a given TournamentViewModel, the view should invoke the TournamentSelected command.  The command handler invokes the TournamentSelected method in the provided INotifySelection object passed in the constructor.

TournamentsViewModel

The TournamentsViewModel is constructed with a reference to TournamentsModel (the model containing all tournaments), which is saved in a private member called _tournamentsModel.  TournamentsViewModel has a public method called Query, which finds all the tournament models that match the given group and season to populate its ObservableCollection of TournamentViewModels called Tournaments.  The user interface binds to Tournaments, showing only the tournaments that match the query criteria.

ResultViewModel

The ResultViewModel contains all information needed to display a specific result (player and tournament) in a view.  When shown as part of a player, the views show the tournament information, and when shown as part of a tournament, the views show the player information.

The ResultViewModel also contains two command handlers.  When the view determines that the user selected the tournament represented by this ResultViewModel, the view should invoke the TournamentSelected command.  The command handler invokes the TournamentSelected method in the provided INotifySelection object passed in the constructor.  When the view determines that the user selected the player represented by this ResultViewModel, the view should invoke the PlayerSelected command.  The command handler invokes the PlayerSelected method in the provided INotifySelection object passed in the constructor.

Other ViewModels

The viewmodels GroupViewModel, GroupsViewModel, SeasonViewModel, SeasonsViewModel, RankingViewModel, RankingsViewModel, and PlayerSeasonViewModel are very straightforward and need no further explanation here.

Discussion

The strategy of using INotifySelection to keep sub viewmodels from knowing the specifics of their containing viewmodel, while allowing them to raise events to their containing viewmodel can is not the only solution to this type of problem.  One alternative is for the MainViewModel to pass a delegate to the sub viewmodels that should be invoked when the viewmodel is selected by the user.  Another (possibly superior) alternative is to use the EventAggregator found in Microsoft’s Prism framework.  The EventAggregator allows any viewmodel to publish a named event and any other viewmodel to subscribe to those named events.

Some of the Linq query are non-trivial to write, for example, the Linq query in RankingsviewModel.  There are many good web sites that describe how to write Linq queries such as 101 LINQ Samples, so there is no need to go into depth here.  Resharper by JetBrains automatically converts nested loops with conditional expressions into Linq queries and is definitely worth the time to evaluate.

I should point out that I use an adaptation of Josh Smith’s RelayCommand to make it easier to write the command handlers.  See RelayCommand in this project and this link for more information.

Views

Introduction

Views visualize or “skin” the content in viewmodels for presentation to the user.  In general, views consist entirely of XAML, and very little, if any C#.  The avoidance of C# code (called code-behind) in views forces the user interface logic to be implemented in the viewmodels, where it is easier to test.

Each view has a current DataContext, which is typically a view-model that a view is using for binding.  Binding connects DependencyProperties in the view to properties in the view-model.  It is assumed that the view-model implements the INotifyPropertyChanged interface, and enumerable properties also implement the INotifyCollectionChanged interface.

A DependencyProperty is a property in the View that uses GetValue/SetValue as the backing store.  DependencyProperties are only needed when writing custom controls.

The VisualStateManager is a handy way to change the look of a view.  As of this writing, setting the current visual state often needs a small amount of code-behind (C#).

SeasonsView

The SeasonsView consists of Grid with two rows.  Row 0 is the header containing a TextBlock and row 1 displays all the seasons in an ItemsControl.  The ItemsControl binds to Seasons in SeasonsViewModel.  Each SeasonViewModel is displayed as RadioButton.  The RadioButton Text is bound to a SeasonViewModel’s Season and the RadioButton command handler is bound to a SeasonViewModel’s SeasonSelected.  The RadioButtons are placed into a vertical StackPanel.

GroupsView

The GroupsView parallels the SeasonsView in design.

RankingsView

The RankingsView parallels the SeasonsView in design.

TournamentsView

The TournamentsView is very similar to the SeasonsView, with the exception that the TournamentsView wraps the display in a ScrollViewer, and the Button contains not just text, but a stack panel of an icon (a UserControl) and text.

PlayersView

The PlayersView is very similar to the TournamentsView, with the exception that the PlayersView places all the Players in a WrapPanel.  A WrapPanel is part of the Silverlight Toolkit.  Also, the icon for each player is bound to the player’s gender, allowing the icon to represent the gender as either pink or blue.

TournamentView

The TournamentView is very similar to the SeasonsView, but with a more complicated header, and an ItemsControl that binds to the Results in the TournamentViewModel.

PlayerView

The PlayerView is very similar to the TournamentView, but includes two ItemsControls; one for the Results and one for the Seasons.

MainView

The MainView contains a SeasonsView, a GroupsView, a PlayersView, a PlayerView, a TournamentsView, a TournamentView, and a RankingsView.  It assigns the appropriate sub viewmodel as the DataContext to each of these views.  The views then show whatever data is currently contained in its viewmodel.

The MainView also contains five buttons across the top, for showing Rankings, Players, Tournaments, the selected Player, and the selected Tournament.  The command handler for these buttons are bound to the command handlers in the MainViewModel.

As written, all of these views are created as collapsed (not visible).  To show views, the MainView defines five VisualStates, each of which shows a subset of views when that visual state is active.  The VisualState also defines which button is to be shown as selected (bold).

MainView.cs

Unfortunately, there is no simple way (yet?) in Silverlight (and WPF) to bind a visual state to a property in a viewmodel.  Therefore, code behind in the MainView is needed to “bind” the current VisualState to the property called Show in the MainViewModel.  First, we need to know when MainView is loaded.  This is done with the statement:

Loaded += MainPageLoaded;

Once the view is loaded, we can extract a reference to the MainViewModel by casting DataContext to type MainViewModel.  Once we have a reference to the MainViewModel, we can request notifications whenever a property in the MainViewModel changes.  This is done with the statement:

mainViewModel.PropertyChanged += MainViewModelPropertyChanged;

Once the MainView is notified that a property in the MainViewModel changed, we can check if that property was Show.  If it was, we can then use the VisualStateManager to set the current VisualState to the contents of Show.

Since the property notification only happens when Show changes, there needs to be a mechanism to initialize the current VisualState to the initial value of Show when the program begins.  This initialization is done by simulating a property changed notification with the statement:

MainViewModelPropertyChanged(mainViewModel, new PropertyChangedEventArgs("Show"));

Icons

The icons such as the logo, rankings icon, and tournament icon are simple XAML files with no code behind, and really do not need any additional explanation.

PlayerIcon

The PlayerIcon is slightly complicated in the sense that is contains a DependencyProperty called Gender.  Because Gender is a DependencyProperty it can be bound to a property in PlayerIcon’s DataContext.  When the value of Gender changes, the code behind changes the VisualState to the value stored in Gender.  The VisualState code in XAML then causes the icon to be rendered pink if Gender is F, and blue if Gender is M.

Conclusion

I hope that in studying this project, it will be easier for other developers to get a sense of the basics of the MVVM architecture, and how to write a maintainable Silverlight application.

Last edited Jan 2, 2011 at 8:42 PM by JohnMichaelHauck, version 2

Comments

No comments yet.