[Back]
Title:       Silverlight MVVMN File Manager
Author:      Michael Washington 
Email:       
Member ID:   
Language:    C# 
Platform:    Windows, .NET 4.0
Technology:  ASP.NET, Silverlight
Level:       Beginner, Intermediate, Advanced
Description: An example of a Silverlight MVVM File Manager
Section      ASP.NET
SubSection   Silverlight
License:     MIT

Introduction

This project demonstrates an implementation of the MVVM (Model-View-View Model) 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 MVVM

This Silverlight project is not a full featured file manager, but, it actually works and hopefully demonstrates a non-trivial example of a MVVM Silverlight project.

The point however, is about the MVVM pattern. Much has been written about it and there are various interpretations. In this example we will implement the pattern with as little code as possible. We will highlight these important points:

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 _SubFolders;

        public ObservableCollection SubFolders
        {
            get
            {
                if (_SubFolders == null)
                {
                    _SubFolders = new ObservableCollection();
                }
                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;

// From http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/
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)
        {
            // Set MainViewModel
            _MainViewModel = objMainViewModel;

            // Set up web service call
            WebServiceSoapClient objWebServiceSoapClient =
                new WebServiceSoapClient();

            EndpointAddress MyEndpointAddress = new
                EndpointAddress(GetWebserviceAddress());

            objWebServiceSoapClient.Endpoint.Address = MyEndpointAddress;

            // Call web method
            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);
            }

            // Get the files for the first Folder
            if (e.Result != null)
            {
                GetFiles(_MainViewModel, e.Result[0]);
            }
        } 
        #endregion

        #region GetFiles
        public void GetFiles(MainViewModel objMainViewModel, SilverlightFolder objSilverlightFolder)
        {
            // Set MainViewModel
            _MainViewModel = objMainViewModel;

            // Set up web service call
            WebServiceSoapClient objWebServiceSoapClient =
                new WebServiceSoapClient();

            EndpointAddress MyEndpointAddress = new
                EndpointAddress(GetWebserviceAddress());

            objWebServiceSoapClient.Endpoint.Address = MyEndpointAddress;

            // Call web method
            objWebServiceSoapClient.GetLocalFilesCompleted += 
                new EventHandler(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:

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()
        {
            // Set the command property
            SetFilesCommand = new DelegateCommand(SetFiles, CanSetFiles);

            // Set default values
            SilverlightFolders = new ObservableCollection< SilverlightFolder >();
            SilverlightFiles = new ObservableCollection< SilverlightFile >();
            
            // Pass a reference of this class to the method to get the Folders
            SilverlightFolderAndFiles objSilverlightFolderAndFiles = new SilverlightFolderAndFiles();
            objSilverlightFolderAndFiles.GetFolders(this);
        }

        #region Commanding
        public ICommand SetFilesCommand { get; set; }

        public void SetFiles(object param)
        {
            // Get the Folder selected
            SilverlightFolder objSilverlightFolder = (SilverlightFolder)param;

            // Clear the file list
            SilverlightFiles = new ObservableCollection< SilverlightFile >();

            // Pass a reference of this class to the method to get the Files
            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 designerwill 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 MVVM 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 MVVM 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 it's 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 it's 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 MVVM 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 it's Orientation to Horizontal.

Click on the TextBlock in the Objects and Timeline window, and in the Properties window, set it's 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 TextBlocks 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...

In the Properties for the HyperlinkButton, click on the Advanced options box for the NavigateUri property.

Select Data Binding...

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).

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…

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 MVVM ?

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.