Here is an implementation of the View Model Style pattern to create a simple Silverlight File Manager. This Silverlight project is not a full featured file manager, but, it actually works and hopefully demonstrates a non-trivial example of a View Model Style Silverlight project.
Introduction
This project demonstrates an implementation of the View Model Style pattern to create a simple Silverlight File Manager. This pattern allows a programmer to create an application that has absolutely no UI. The programmer only creates a ViewModel
and a Model
. A designer with no programming ability at all, is then able to start with a blank page and completely create the View
(UI) in Microsoft Expression Blend 4 (or higher).
The Power of the View Model Style
This Silverlight project is not a full featured file manager, but, it actually works and hopefully demonstrates a non-trivial example of a View Model Style Silverlight project.
The point however, is about the View Model Style pattern. We will highlight these important points:
- The
Programmer
creates code that consist of collections, properties, and Commands (that implement ICommand
). - The
Designer
uses Expression Blend, and creates the complete UI, without writing any code.
The Starter Solution
We will start with the solution contained in the MVMFileManager_BaseStartProject.zip file. This consists of a starter Silverlight project, and a standard ASP.NET website that shows files and folders that are contained in the Files folder. Note, this website could be implemented in another language such as PHP and the Silverlight application would still work.
Two simple classes, SilverlightFolder
and SilverlightFile
, are used to hold the collection of files and folders that the web service in the ASP.NET website will return.
namespace MVMFileManagerSite
{
[Serializable]
public class SilverlightFolder
{
private ObservableCollection<silverlightfolder> _SubFolders;
public ObservableCollection<silverlightfolder> SubFolders
{
get
{
if (_SubFolders == null)
{
_SubFolders = new ObservableCollection<silverlightfolder>();
}
return _SubFolders;
}
set
{
_SubFolders = value;
}
}
public string FolderName { get; set; }
public string FullPath { get; set; }
}
[Serializable]
public class SilverlightFile
{
public string FileName { get; set; }
public string FilePath { get; set; }
}
}
If we right-click on the Webservice.asmx file, and select View in Browser, we can see the web methods.
The GetLocalFolders
method returns a collection of folders in the Files directory.
The GetLocalFiles
method requires a folder name to be passed as a parameter, and it returns a collection of file names, as well as a link to download the file (by passing the folder and file name to the DownloadFile.aspx file).
The Programmer - The Model and the View Model
We will now open the solution in Expression Blend 4 (or higher) and create the Model
and the ViewModel
. However, first, we will add a simple class to support Commanding. Commanding will allow the Designer to raise an event in the ViewModel
.
We will use code created by John Papa that he posted in his blog "5 Simple Steps to Commanding in Silverlight".
Right-click on the Classes folder in the MVMFileManager
Silverlight project (that is part of the MVMFileManager
starter solution), and select Add New Item…
Add a class called DelegateCommand.cs and click OK.
Replace all the code with the following code:
using System.Windows.Input;
using System;
public class DelegateCommand : ICommand
{
Func< object, bool > canExecute;
Action< object > executeAction;
bool canExecuteCache;
public DelegateCommand(Action< object > executeAction, Func< object, bool > canExecute)
{
this.executeAction = executeAction;
this.canExecute = canExecute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
bool temp = canExecute(parameter);
if (canExecuteCache != temp)
{
canExecuteCache = temp;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
}
}
return canExecuteCache;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeAction(parameter);
}
#endregion
}
In the Models folder, add a class called SilverlightFolders.cs and replace the code with the following code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ServiceModel;
using MVMFileManager.FileManager;
using MVMFileManager;
namespace MVVMFileManager
{
public class SilverlightFolderAndFiles
{
MainViewModel _MainViewModel;
#region GetWebserviceAddress
private string GetWebserviceAddress()
{
string strXapFile = @"/ClientBin/MVMFileManager.xap";
string strBaseWebAddress =
App.Current.Host.Source.AbsoluteUri.Replace(strXapFile, "");
return string.Format
(@"{0}/{1}", strBaseWebAddress, @"WebService/WebService.asmx");
}
#endregion
#region GetFolders
public void GetFolders(MainViewModel objMainViewModel)
{
_MainViewModel = objMainViewModel;
WebServiceSoapClient objWebServiceSoapClient =
new WebServiceSoapClient();
EndpointAddress MyEndpointAddress = new
EndpointAddress(GetWebserviceAddress());
objWebServiceSoapClient.Endpoint.Address = MyEndpointAddress;
objWebServiceSoapClient.GetLocalFoldersCompleted +=
new EventHandler< GetLocalFoldersCompletedEventArgs >
(objWebServiceSoapClient_GetLocalFoldersCompleted);
objWebServiceSoapClient.GetLocalFoldersAsync();
}
void objWebServiceSoapClient_GetLocalFoldersCompleted
(object sender, GetLocalFoldersCompletedEventArgs e)
{
foreach (var item in e.Result)
{
_MainViewModel.SilverlightFolders.Add(item);
}
if (e.Result != null)
{
GetFiles(_MainViewModel, e.Result[0]);
}
}
#endregion
#region GetFiles
public void GetFiles(MainViewModel objMainViewModel,
SilverlightFolder objSilverlightFolder)
{
_MainViewModel = objMainViewModel;
WebServiceSoapClient objWebServiceSoapClient =
new WebServiceSoapClient();
EndpointAddress MyEndpointAddress = new
EndpointAddress(GetWebserviceAddress());
objWebServiceSoapClient.Endpoint.Address = MyEndpointAddress;
objWebServiceSoapClient.GetLocalFilesCompleted +=
new EventHandler<GetLocalFilesCompletedEventArgs>
(<getlocalfilescompletedeventargs>
objWebServiceSoapClient_GetLocalFilesCompleted);
objWebServiceSoapClient.GetLocalFilesAsync(objSilverlightFolder.FolderName);
}
void objWebServiceSoapClient_GetLocalFilesCompleted
(object sender, GetLocalFilesCompletedEventArgs e)
{
foreach (var item in e.Result)
{
_MainViewModel.SilverlightFiles.Add(item);
}
}
#endregion
}
}
This is a class that calls the GetFolders
and GetFiles
web service methods. A few things to note:
- The methods accept an instance of the
ViewModel
(that will be created in the next step) as one of their parameters. - The instance of the
ViewModel
is stored in the _MainViewModel
private variable to be later used by the web service call back method. - When the asynchronous method returns, it hydrates the appropriate collection on the
ViewModel
.
In the ViewModels folder, add a class called MainViewModel.cs and replace the code with the following code:
using System;
using System.ComponentModel;
using System.Windows.Input;
using System.Collections.ObjectModel;
using MVMFileManager.FileManager;
namespace MVVMFileManager
{
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
SetFilesCommand = new DelegateCommand(SetFiles, CanSetFiles);
SilverlightFolders = new ObservableCollection< SilverlightFolder >();
SilverlightFiles = new ObservableCollection< SilverlightFile >();
SilverlightFolderAndFiles objSilverlightFolderAndFiles =
new SilverlightFolderAndFiles();
objSilverlightFolderAndFiles.GetFolders(this);
}
#region Commanding
public ICommand SetFilesCommand { get; set; }
public void SetFiles(object param)
{
SilverlightFolder objSilverlightFolder = (SilverlightFolder)param;
SilverlightFiles = new ObservableCollection< SilverlightFile >();
SilverlightFolderAndFiles objSilverlightFolderAndFiles =
new SilverlightFolderAndFiles();
objSilverlightFolderAndFiles.GetFiles(this, objSilverlightFolder);
}
private bool CanSetFiles(object param)
{
return true;
}
#endregion
#region Folders
private ObservableCollection< SilverlightFolder > _SilverlightFolders;
public ObservableCollection< SilverlightFolder > SilverlightFolders
{
get { return _SilverlightFolders; }
private set
{
if (SilverlightFolders == value)
{
return;
}
_SilverlightFolders = value;
this.NotifyPropertyChanged("SilverlightFolders");
}
}
#endregion
#region Files
private ObservableCollection< SilverlightFile > _SilverlightFiles;
public ObservableCollection< SilverlightFile > SilverlightFiles
{
get { return _SilverlightFiles; }
private set
{
if (SilverlightFiles == value)
{
return;
}
_SilverlightFiles = value;
this.NotifyPropertyChanged("SilverlightFiles");
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
}
This is the class that will be consumed by the designer. The designer will not actually need to see this code, it will show up in Blend, in the Data
section, when the designer sets this class as the DataContext
for the UI page.
Note that this class implements ObservableCollection
, so that changes to values stored in the class will cause a notification to any UI element that is bound to it, so it can automatically update. The class also implements INotifyPropertyChanged
so that properties will also provide automatic change notification to any UI elements that are bound to them.
The SetFilesCommand
property implements an ICommand
. A UI element such as a Button
, or in this example, a selected node on a TreeView
, can bind to this property and call the SetFiles
method (that was registered using the DelegateCommand
in the class constructor).
The Designer - The View
THE PROGRAMMER HAS LEFT THE BUILDING!
We have finally arrived at the real point of this article. Allow me a moment to drive home the point that the View Model Style pattern in Silverlight allows us to create an application, yet not specify a UI at all. The UI can be created entirely in Blend without writing a single line of code.
Why is that important? Because the programmers need no longer hold the designers back. The programmers don't intend to hold anyone back (after all, I am a programmer), but normally nothing can go to production without the programmers implementing it.
Designers can now take charge of a project that has implemented the View Model Style pattern, and create new innovative user interfaces that do not have to go through a programmer. We can expect a lot of advances in user interfaces in the coming years, now that we have taken the "shackles" off of the designers.
Now, imagine that a programmer has created the previous files. This project can now be turned over to a designer to actually create the UI. Just in case you missed it, let me say this again, the programmer has left the building! and as the designer, you can deliver this project to production without writing another line of code.
Just to make sure you're ready to go, from the toolbar, select Project, then Build Project. The project should build without errors.
Double-click on the MainPage.xaml file in the Projects window to open it.
This will open the file. It is currently a blank UI.
Click on LayoutRoot in the Objects and Timeline window. Next, in the Properties window, click the New button next to DataContext.
Select MainViewModel and click OK. The DataContext
is now set. All child objects of the LayoutRoot
will have access to the data and commands provided by the DataContext
.
If you click on the Data tab...
... and expand MainViewModel (under the Data Context section). You will see the collections and commands exposed by the MainViewModel
class. The designer is able to interact with the ViewModel
by simply dragging items from this window onto controls on the design surface.
The File Manager UI
Click on the Assets icon on the Tools bar.
Type "Grid" in the Search box. Click on the Grid
and drag it to the design surface.
Position the Grid
on the design surface and expand its size so that it fills most of the page. Hover the mouse over the center of the Grid
(on the blue bar), a positioning line will appear.
Click the mouse to create a split in the Grid
creating two cells.
Click the Assets button and search for the ScrollViewer
and drag it to one of the cells in the Grid
.
Perform the action again, and drag a ScrollViewer
into the other cell of the Grid
.
In the properties of each ScrollViewer
, click the Advanced Options box...
And Reset
the values.
This will cause each ScrollViewer
to completely fill its cell in the Grid
.
Click on each of the ScrollViewers
in the Objects and Timeline window and rename them to Folders
and Files
respectively.
Search for the TreeView
control.
Drag and drop it into the Folders ScrollViewer
in the Objects and Timeline window.
In the Properties
for the TreeView
, set the Width
and Height
to Auto
by clicking the Set to Auto button for each setting.
Click on LayoutRoot in the Objects and Timeline window to select it.
In the Data tab, click and drag the SilverlightFolders
collection to the Objects and Timeline window.
Drop the collection on the TreeView
.
In the Projects window, right-click on the MVMFileManagerSite project and set it as the Startup Project.
Also, right-click on the Default.aspx page and set it as the Startup.
Hit the F5
key to run the project. You may see a warning message, just click Yes.
The project will run and the folders will show, however, they are not formatted correctly.
Note, you will see an error in the Errors window. The reason for this is that the Blend designer is trying to show sample data in the designer but it cannot run the web service that supplies the data. However, as you can see, it does work at run-time. You can use the command (in your code) DesignerProperties.IsInDesignTool
to determine if the code is being called from Blend and provide sample data that the designer can display. I did not implement that in this example because I wanted to focus on the View Model Style code.
In Blend, in the Objects and Timeline window, right-click on the TreeView control and select Edit Additional Templates > Edit Generated Items (Item Template) > Edit Current.
This will take you to the Template editing mode. Select the lower TextBlock
(that the file Path is bound to) and right-click on it and select Delete, to delete it.
In the Projects window, open the Images folder in the MVMFileManager
project and click on the Folder.png file.
Drag and drop it anywhere on the design surface.
It will also appear in the Objects and Timeline window, click on it and drag it so that it is on top of the TextBlock.
Click on the StackPanel in the Objects and Timeline window, and in the Properties window, set its Orientation to Horizontal.
Click on the TextBlock in the Objects and Timeline window, and in the Properties window, set its left Margin
to 5
.
Click the Return Scope icon, in the Objects and Timeline window, to return to normal design mode.
Hit the F5 key on your keyboard to run the project. The folders are looking much better. Now on to the files.
Click on LayoutRoot in the Objects and Timeline window to select it.
In the Data tab, click and drag the SilverlightFiles
collection to the design surface.
In the Objects and Timeline window, it will appear below the Files ScrollViewer
, it needs to be inside the ScrollViewer
.
In the Objects and Timeline window, drag it so that it is inside the Files ScrollViewer
.
In the Properties for the ListBox, set the Width and Height to Auto by clicking the Set to Auto button for each setting.
Hit the F5 key on your keyboard to run the project. The files show, but they are not formatted correctly. We should see the file name, and when we click on it, we want to download the file. We can do this by using a HyperlinkButton
control.
In Blend, in the Objects and Timeline window, right-click on the ListBox
control and select Edit Additional Templates > Edit Generated Items (Item Template) > Edit Current.
This will take you to the Template editing mode. Select the both TextBlock
s and right-click on them and select Delete, to delete them.
In the Assets
, search for the HyperlinkButton
.
Drag and drop it on the design surface.
In the Properties
for the HyperlinkButton
, click on the Advanced options box for the Content
property.
Select Data Binding...
- Select the Data Context tab
- Select FileName (under
SilverlightFiles
) - Click OK
In the Properties for the HyperlinkButton
, click on the Advanced options box for the NavigateUri
property.
Select Data Binding...
- Select the Data Context tab
- Select FilePath (under SilverlightFiles)
- Click OK
Hit the F5 key on your keyboard to run the project. The files for the first folder will display, and when you click on a file, it will download. However, if you change folders, the files will not change.
Now, how to show the files for the selected folder?
In Blend, Click the Return Scope icon, in the Objects and Timeline window, to return to normal design mode.
Click on the Assets button on the Tools window.
Type “InvokeCommand
” in the search box and the InvokeCommandAction
behavior will show. (if you don’t see this, install the Silverlight 4 SDK). Drag and drop it on the TreeView
control (either in the Objects and Timeline window or on the design canvas).
The Behavior will show under the TreeView
control in the Objects and Timeline window.
Click on the Behavior in the Objects and Timeline window, and in the Properties window, set the EventName
to SelectedItemChanged
(so that the Behavior will fire when the selected item is changed in the TreeView
).
Click the Data bind icon next to Command
(under Common Properties).
- Select the Data Context tab
- Select SetFilesCommand (under MainViewModel)
- Click OK
This instructs the Behavior to call the SetFilesCommand
in the ViewModel
.
Click the Advanced options box next to CommandParameter
(under Common Properties).
Select Data Binding…
- Select the Element Property tab
- Select [TreeView] (in the Scene elements window)
- Select SelectedItem (in the Properties window)
- Click OK
This instructs the Behavior to pass the SilverlightFolder
object, that the currently selected TreeView
item is bound to, to the SetFilesCommand
in the ViewModel
.
Hit the F5 key on your keyboard to compile and run the project. The TreeView
control will show the Folder structure, and when you click on a Folder, it will show the files for the selected folder.
Is There More to the View Model Style ?
Yes there is, you will find the need to use Value Converters and different Behaviors as well as the Visual State Manager in order to have the full set of tools you need to complete any Silverlight project. To learn how to use these tools, all you have to do is go to http://www.microsoft.com/design/toolbox/.
This site will give to the free training you need to master Expression Blend. It also will cover design principals to make you a better designer. Really, it cannot get any easier. The time to join the "revolution" is now.
History
- 27th March, 2010: Initial version