Monday, September 21, 2009

[2009.09.21] The Periodic Table in WPF using MVVM


When the idea of doing this project came to me, at first I thought I would do it the same way as I did my previous WPF Writing one. I am doing this to simply to put into practice some of the concepts I have learnt previously. But using the same methods would not really prove much - at least to me. Since then I have learned much more and as such I wanted to use newer/updated tools, technologies and concepts to build this application.

Download the Code!

Technologies and Concepts used in this project:
  • Visual Studio 2008 SP1 + .NET Framework 3.5 SP1
  • Microsoft Expression Blend 3 + Sketchflow
  • Model-View-ViewModel (MVVM)
  • Actions, Behaviors and Triggers
  • Visual State Manager
  • Data binding
  • User Controls
  • Styles
  • Templates

The Prototype

Before an application is written, it may be helpful to have a decent, working prototype to sort of pitch/sell the idea of the project. Expression Blend 3 SketchFlow is the tool for the job. It will let you do rapid prototyping and provide a means to provide feedback on the initial prototype. My design will not be very complicated as this particular application will not have a whole lot of navigational needs. Basically the main window will show each element, a mouse over will bring up a summary for the element and clicking the control representing the element will bring up a details pane. Again, not a ton of interaction.
First, let's take a look at what the main UI may look like with a little bit of interaction in Fig. 1. Basically each element will have an attached GoToStateAction which will vary the Opacity of the SummaryUserControl. The states will react to the MouseEnter and MouseLeave events of the control.

[2009.09.21].01.Sketchflow.main.ui
Fig. 1. SketchFlow of the Main UI

If the user wants detailed information on the particular element, then clicking the control that represents the element will navigate to a Details window as in Fig. 2.

[2009.09.21].02.Sketchflow.details.ui
Fig. 2. SketchFlow of the Details UI

Fig. 3 is the SketchFlow map of the overall project. Notice that the MainUI screen has the SummaryUserControl Component linked to it and the DetailsUI has the Details Component linked to itself. The MainUI screen is also linked to the DetailsUI and the DetailsUI is linked back to the MainUI. As such you can use the NavigateForwardAction and NavigateBackAction to move between the screens.

[2009.09.21].03.Sketchflow.map
Fig. 3. SketchFlow Map of the project

The Design and Implementation

Fig. 4 is the Class Diagram of the overall application. Based on that, I will talk about each subsection as necessary. Fig. 5 is the hierarchy of the project.

[2009.09.21].04.PeriodicTable.ClassDiagram.01
Fig. 4. Class Diagram


[2009.09.21].05.Project.hierarchy
Fig. 5. Project Hierarchy

Given that my prototype passed and I have all the feedback necessary, then I would want to design next. This is where I would want to put some patterns and industry practices into play. I will use the Model-View-ViewModel pattern here, which I feel is appropriate for an application of this scale. Also, I want to use it to better acquaint myself with the pattern.

Model

The raw data would be an XML file that I have made up using the properties for the particular element. Since I have not used a lot of LINQ or XML processing in .NET, instead of using attributes and nested nodes, I have opted to use one level of nesting for all the nodes. Fig. 6 shows this.

[2009.09.21].06.model.01.xml.file
Fig. 6. XML Data File

The MVVM pattern completely separates the data from the rest of the application, so in order to expose the XML data, I have created a public class, ElementModel.cs whose sole purpose is to expose the data (nodes) of the XML data file as a set of public properties which can then be manipulated and data bound to. All this class does is declare a set of properties which mirror the data from the XML file. It supplies a single constructor that takes all these parameters in to create an instance of the class. In addition, there is a helper class called ElementModelFileLoader.cs which provides a single method that returns a List of ElementModel by using LINQ to load, parse and create the list. The List<ElementModel> can then be referenced by the ViewModel sections to be interacted with.

View

The MVVM kit will automatically create a MainView.xaml for a MVVM application. I will keep that view and add user controls and additional views as necessary. So the MainView will contain several ElementUserControl which will make up the periodic table. In addition, I have a second window called DetailsView.xaml which is loaded and displayed if the user clicks on an ElementUserControl. This window holds a DetailsUserControl.xaml. Both of these user controls use Data Binding to populate the necessary TextBlock fields that make them up.
Since I am on the topic, I have used Actions and Behaviors, some new features of Blend 3, as well as the Visual State Manager, to quickly add interactivity to my user controls. An Action is just a piece of code that runs after being triggered. For instance, Blend has a GoToStateAction which as its name implies, transitions the element it is attached to go to a specified state as defined by the VSM. For example, I have defined some states that will make the Border surrounding the ElementUserControl changes its Opacity property by listening for MouseEnter and MouseLeave events. See Fig. 7.

[2009.09.21].07.actions.in.blend3
Fig. 7. Using Actions in Blend 3

I have an earlier blog post that has more information on Actions and Behaviors.
According to the MVVM pattern, a View will have a reference to its corresponding ViewModel, which it can set to be its data context ( myView.DataContext = myViewModelInstance ). In this way, bindings defined in the View, which references properties exposed by the ViewModel will be resolved. I will go into more detail when I discuss the ViewModels. Fig. 8 shows a representation of the MainView and Fig. 9 that of the DetailsView.

[2009.09.21].08.mainview
Fig. 8. The MainView view of the application


[2009.09.21].09.detailsview
Fig. 9. The DetailsView view of the application

ViewModel

Ideally, each of the Views listed above would have their own corresponding ViewModel file, which would be pure C# as shown in Fig. 5 above. Actually, I did not need a separate ViewModel for the DetailsView, but I added one one to bind to commands that is relevant to the DetailsView only. However, as of this writing, it is a redundant class as its functionality was not what I expected. I hope to fix it in the future.
Since most of the action happens in the MainViewModel, I will talk about that. Since this is what the View is bound to, it is the ViewModel that exposes properties and commands for the view to bind to. In this case, the MainViewModel wraps each List<ElementModel> into an ObservableCollection<ElementModelViewModel>. This may seem a bit confusion but the ElementModelViewModel is actually the ViewModel which represents the ElementModel. If you recall, the ElementModel was the class that exposes the data in the XML file. However, in the ElementModelViewModel you can choose exactly what properties you want to expose to your Views. For example, ElementModel could expose 10 properties, but out of those 10, the ElementModelViewModel class chooses only exposes 5 to its View. Since the View cannot "see" the model, then all it knows about is the 5 properties it is allowed to bind to.
In addition to custom object properties, the ViewModel also exposes Commands that the View can bind to. So in summary, the MainViewModel class exposes the properties of the Model as a collection and commands.
Since the code has lots of comments you can easily understand what is happening. A good deal of plumbing magic takes place in the OnStartup() event handler of the App code behind file - App.xaml.cs file. This is shown in Listing 1.

  1: using System.Collections.ObjectModel;
  2: using System.Windows;
  3: using PeriodicTable.ViewModels;
  4: using PeriodicTable.Views;
  5: 
  6: namespace PeriodicTable
  7: {
  8:     /// <summary>
  9:     /// Interaction logic for App.xaml
 10:     /// </summary>
 11:     public partial class App : Application
 12:     {
 13:         MainView view;
 14:         MainViewModel mainViewModel;
 15:         DetailsView detailsView;
 16:         DetailsViewViewModel detailsViewModel;
 17: 
 18:         public App()
 19:         {
 20:             view = new MainView();
 21:             mainViewModel = new MainViewModel();
 22: 
 23:             detailsView = new DetailsView();
 24:             detailsViewModel = new DetailsViewViewModel();
 25: 
 26:         }
 27:         private void OnStartup(object sender, StartupEventArgs e)
 28:         {
 29:             // Call the Load method in the MainViewModel call
 30:             // This finds the XML file, loads the data, create a CLR object
 31:             //  to represent the XML data, create a collection of these 
 32:             //  objects and create a public property to expose this collection
 33:             mainViewModel.LoadElementsFromFile(@"Models\periodicTable.xml");
 34: 
 35:             ObservableCollection<ElementModelViewModel> col = mainViewModel.ElementCollection;
 36: 
 37:             // Since my user controls for the elements are not in a container
 38:             //  that knows how to bind each element as an item in the container
 39:             //  I have to manually iterate and set each control's data context property
 40:             //  (ex: ListBox - if you set the ItemsSource to the property exposing
 41:             //       the ObservableCollection, then each ListBoxItem is bound to
 42:             //       one item in the collection)
 43:             for (int i = 0; i < col.Count; i++)
 44:             {
 45:                 // use data binding to populate each user control
 46:                 ElementUserControl euc = (ElementUserControl)view.elementsGrid.Children[i];
 47:                 if (euc.Name == col[i].ElementName)
 48:                 {
 49:                     euc.DataContext = col[i];
 50:                     euc.symbolText.Text = col[i].Symbol;
 51:                     euc.atomicNumberText.Text = col[i].AtomicNumber;
 52: 
 53:                     // respond to these events to do some bindings
 54:                     euc.MouseEnter += new System.Windows.Input.MouseEventHandler(euc_MouseEnter);
 55:                     euc.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(euc_MouseLeftButtonDown);
 56:                 }
 57:             }
 58: 
 59:             // set a higer level datacontext to get acccess to the Commands
 60:             // defined in the ViewModel
 61:             view.DataContext = mainViewModel;
 62: 
 63:             view.Show(); // show the main window
 64:         }
 65: 
 66:         // Handler for the MouseLeftButtonDown event which will show the DetailsView window
 67:         void euc_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
 68:         {
 69:             //detailsView.LayoutRoot.DataContext = (sender as ElementUserControl).DataContext;
 70:             detailsView.Owner = view;
 71:             if (detailsView == null)
 72:             {
 73:                 detailsView = new DetailsView();
 74:                 detailsView.Show();
 75:             }
 76:             else
 77:             {
 78:                 detailsView.Show();
 79:             }
 80:         }
 81: 
 82:         // Handler for the MouseEnter event which set the data context of each
 83:         //  both the Summary and Detail UserControls
 84:         void euc_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
 85:         {
 86:             view.summaryUC.DataContext = (sender as ElementUserControl).DataContext;
 87:             detailsView.LayoutRoot.DataContext = (sender as ElementUserControl).DataContext;
 88:         }
 89:     }
 90: }

Listing 1: App code behind
In the constructor of the App class, instances of both the MainView and the DetailsView views are created and instantiated. Within the OnStartup() handler, the XML file is loaded, the collection of data is created and each ElementUserControl in the MainView has some of its compositional control values bound to properties of the objects in the collection of data items (Lines 43-51). The code is pretty much self explanatory.
Fig. 10 shows the overall operation of the application.
[2009.09.21].10.overall.app
Fig. 10. The app in action
Current unresolved problems
The first problem I ran into was having a command execute when a certain event fires. I got around this problem using the CommandBehavior from Tor Langlo. The problem after was that I wanted a CloseCommand that the DetailsView can bind to, which will cause the details window to close. Even though the command works when I tested it out in the main window, it does not work here.
The second problem was even though the CloseCommand did not work, I resolved to use an event in the DetailsView code behind (yes, I know) to close the window. However, once the window was closed, I could not reopen it. I found a workaround by responding to the Closing event and injecting a Hide() method on the application dispatcher queue. Basically, instead of closing the details window, I merely hide it until it is requested to be shown again. I am not satisfied with this but it will do for now.
Finally, this project was done with the idea that it can be extended. The fact is, I do not have any desire to create a complete XML file to represent periodic table elements. Thus far all I have are two elements. The infrastructure is there is anyone wants to add to the XML file. An idea to remedy this would be to add a window that will allow a user to add to the XML file. Maybe in the future I will add this.

Download the Code!

7 comments:

Neal Ekengren said...

Sparky, this project is extremely useful to me. Very interesting and I must say a very professional and formal presentation you have here. I get to see how you use some newer features of .NET that I need to learn. I also get the periodic table GUI which I need to plug into a larger project I am working on that will drive an Arduino Microcontroller that drives LED displays. I'll post followup later.

Sparky Dasrath said...

I haven't had a chance to work on this blog for a while or even do stuff with .net 4.0 features, but soon hopefully. I am glad you found it useful and I will try to do more in the near future. Thanks for your kind words :-)

Anonymous said...

The link to the code file no longer works.

Anonymous said...

Great article - I'm looking for just this. Do you have an updated link to the source code?

Sparky Dasrath said...

Fixed the download link!

Jelle said...

Can you fix the donlaod link again.
I really like this project and want to tryout this for me.

Sparky Dasrath said...

I am in the process of moving all this to my own domain. Send me a mail: sdasrath@gmail.com and I will send you the code. Put "mvvm sample code" in the subject line