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.
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.
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.
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.
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.
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.
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.
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.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.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.
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.
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
Fig. 10 shows the overall operation of the application.
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.
7 comments:
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.
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 :-)
The link to the code file no longer works.
Great article - I'm looking for just this. Do you have an updated link to the source code?
Fixed the download link!
Can you fix the donlaod link again.
I really like this project and want to tryout this for me.
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
Post a Comment