Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Silverlight File Manager

0.00/5 (No votes)
1 Aug 2010 5  
An implementation of the View Model Style pattern to create a simple Silverlight 4 File Manager
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.

Image 1

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

Image 2

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

Image 3

If we right-click on the Webservice.asmx file, and select View in Browser, we can see the web methods.

Image 4

The GetLocalFolders method returns a collection of folders in the Files directory.

Image 5

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

Image 6

Right-click on the Classes folder in the MVMFileManager Silverlight project (that is part of the MVMFileManager starter solution), and select Add New Item…

Image 7

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<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()
        {
            // 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 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.

Image 8

Just to make sure you're ready to go, from the toolbar, select Project, then Build Project. The project should build without errors.

Image 9

Double-click on the MainPage.xaml file in the Projects window to open it.

Image 10

This will open the file. It is currently a blank UI.

Image 11

Click on LayoutRoot in the Objects and Timeline window. Next, in the Properties window, click the New button next to DataContext.

Image 12

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.

Image 13

If you click on the Data tab...

Image 14

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

Image 15

Click on the Assets icon on the Tools bar.

Image 16

Type "Grid" in the Search box. Click on the Grid and drag it to the design surface.

Image 17

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.

Image 18

Click the Assets button and search for the ScrollViewer and drag it to one of the cells in the Grid.

Image 19

Perform the action again, and drag a ScrollViewer into the other cell of the Grid.

Image 20

In the properties of each ScrollViewer, click the Advanced Options box...

Image 21

And Reset the values.

Image 22

This will cause each ScrollViewer to completely fill its cell in the Grid.

Image 23

Click on each of the ScrollViewers in the Objects and Timeline window and rename them to Folders and Files respectively.

Image 24

Search for the TreeView control.

Image 25

Drag and drop it into the Folders ScrollViewer in the Objects and Timeline window.

Image 26

In the Properties for the TreeView, set the Width and Height to Auto by clicking the Set to Auto button for each setting.

Image 27

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.

Image 28

Drop the collection on the TreeView.

Image 29

In the Projects window, right-click on the MVMFileManagerSite project and set it as the Startup Project.

Image 30

Also, right-click on the Default.aspx page and set it as the Startup.

Image 31

Hit the F5 key to run the project. You may see a warning message, just click Yes.

Image 32

The project will run and the folders will show, however, they are not formatted correctly.

Image 33

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.

Image 34

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.

Image 35

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.

Image 36

In the Projects window, open the Images folder in the MVMFileManager project and click on the Folder.png file.

Image 37

Drag and drop it anywhere on the design surface.

Image 38

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.

Image 39

Click on the StackPanel in the Objects and Timeline window, and in the Properties window, set its Orientation to Horizontal.

Image 40

Click on the TextBlock in the Objects and Timeline window, and in the Properties window, set its left Margin to 5.

Image 41

Click the Return Scope icon, in the Objects and Timeline window, to return to normal design mode.

Image 42

Hit the F5 key on your keyboard to run the project. The folders are looking much better. Now on to the files.

Image 43

Click on LayoutRoot in the Objects and Timeline window to select it.

Image 44

In the Data tab, click and drag the SilverlightFiles collection to the design surface.

Image 45

In the Objects and Timeline window, it will appear below the Files ScrollViewer, it needs to be inside the ScrollViewer.

Image 46

In the Objects and Timeline window, drag it so that it is inside the Files ScrollViewer.

Image 47

In the Properties for the ListBox, set the Width and Height to Auto by clicking the Set to Auto button for each setting.

Image 48

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.

Image 49

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.

Image 50

This will take you to the Template editing mode. Select the both TextBlocks and right-click on them and select Delete, to delete them.

Image 51

In the Assets, search for the HyperlinkButton.

Image 52

Drag and drop it on the design surface.

Image 53

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

Image 54

Select Data Binding...

Image 55

  • Select the Data Context tab
  • Select FileName (under SilverlightFiles)
  • Click OK

Image 56

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

Image 57

Select Data Binding...

Image 58

  • Select the Data Context tab
  • Select FilePath (under SilverlightFiles)
  • Click OK

Image 59

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?

Image 60

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

Image 61

The Behavior will show under the TreeView control in the Objects and Timeline window.

Image 62

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

Image 63

Click the Data bind icon next to Command (under Common Properties).

Image 64

  • Select the Data Context tab
  • Select SetFilesCommand (under MainViewModel)
  • Click OK

This instructs the Behavior to call the SetFilesCommand in the ViewModel.

Image 65

Click the Advanced options box next to CommandParameter (under Common Properties).

Image 66

Select Data Binding…

Image 67

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

Image 68

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here