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

Silverlight Simple Drag And Drop / Or Browse View Model / MVVM File Upload Control

0.00/5 (No votes)
25 Jun 2011 2  
An example of a Silverlight Drag And Drop / Or Browse File Upload Control using View Model / MVVM

A Simple Silverlight View Model / MVVM File Upload Control

Live example: http://silverlight.adefwebserver.com/SimpleMVVMFileUpload
Update
: I created a blog post that creates a dramatization of this code: Silverlight View Model (MVVM) - A Play In One Act.

See an example of this code in a Visual Studio LightSwitch application at: http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/35/Saving-Files-To-File-System-With-LightSwitch-Uploading-Files.aspx

I have covered uploading files using Silverlight in a previous article: Silverlight View Model Style File Manager Drag and Drop Upload. In that article, you drop a file and it is uploaded immediately. However, while working on a Silverlight client for my popular Open Source Help Desk software program ADefHelpDesk.com, I realized that I needed to create an interface that allowed a user to select a file for upload, enter information on a form, and have the information saved, and the file uploaded with the push of a single button.

In addition, Silverlight allows a user to drag and drop a file, or to click a button and browse for a file. Also, if a user selects a file to upload and changes her/his mind, she/he needs a way to remove that file.

Therefore, my goals for an upload control were:

  • Easy to integrate into my existing View Model / MVVM projects
  • Allows a designer to use Microsoft Expression Blend to fully redesign all visuals and animations

The Upload Experience

When you run the example, you will see a file upload control that allows you to either browse for a file, or to drop a file on the space marked Drop File Here.

After you select a file, the Upload button will be enabled and you can click it to upload the file.

A button with a X will appear, and you can click that button to remove the selected file. Hovering over the file name will display a popup that shows the full name of the selected file.

When you click the Upload button, the upload control will display an animation to indicate the file is being uploaded.

After the file is uploaded, the file will display in the list box below the control.

The Upload Control

All the Silverlight code for the upload control is contained in the ADefFileUploadControl folder. The website code is mostly contained in the Webservice folder.

The Visual State Manager is used to define 3 Visual States for the control.

One for NoFileSelected, one for FileSelectedState, and one for FileUploadingState.

Behaviors are used to provide the primary functionality. The diagram above shows how the DropFilesToUploadBehavior and OpenFileDialogBoxBehavior are bound.

The code for the OpenFileDialogBoxBehavior is covered in the article Silverlight Open File Dialog Behavior (MVVM).

Here is the code for the DropFilesToUploadBehavior:

[System.ComponentModel.Description("Allows files to be dropped for upload")]
public class DropFilesToUploadBehavior : Behavior<UIElement>
{
    private double DropUIElementOpacity;

    // Properties

    #region FileDialogDialogResultProperty

    public static readonly DependencyProperty FileDialogDialogResultProperty =
        DependencyProperty.Register("FileDialogDialogResultProperty",
        typeof(FileInfo), typeof(DropFilesToUploadBehavior), null);

    public FileInfo FileDialogDialogResult
    {
        get
        {
            return (FileInfo)base.GetValue(FileDialogDialogResultProperty);
        }
        set
        {
            base.SetValue(FileDialogDialogResultProperty, value);
        }
    }
    #endregion

    #region ParentControlProperty

    public static readonly DependencyProperty ParentControlProperty =
        DependencyProperty.Register("ParentControlProperty",
        typeof(object), typeof(DropFilesToUploadBehavior), null);

    public object ParentControl
    {
        get
        {
            return (object)base.GetValue(ParentControlProperty);
        }
        set
        {
            base.SetValue(ParentControlProperty, value);
        }
    }
    #endregion

    #region SelectedFileNameProperty

    public static readonly DependencyProperty SelectedFileNameProperty =
        DependencyProperty.Register("SelectedFileName", typeof(string),
        typeof(DropFilesToUploadBehavior), null);

    public string SelectedFileName
    {
        get
        {
            return (string)base.GetValue(SelectedFileNameProperty);
        }
        set
        {
            base.SetValue(SelectedFileNameProperty, value);
        }
    }

    #endregion

    protected override void OnAttached()
    {
        base.OnAttached();

        // Save the opacity of the element
        DropUIElementOpacity = AssociatedObject.Opacity;

        // Turn on allow drop
        AssociatedObject.AllowDrop = true;

        // Attach event handlers
        AssociatedObject.DragOver += new DragEventHandler(DropUIElement_DragOver);
        AssociatedObject.DragLeave += new DragEventHandler(DropUIElement_DragLeave);
        AssociatedObject.Drop += new DragEventHandler(DropUIElement_Drop);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        // Attach event handlers
        AssociatedObject.DragOver -= new DragEventHandler(DropUIElement_DragOver);
        AssociatedObject.DragLeave -= new DragEventHandler(DropUIElement_DragLeave);
        AssociatedObject.Drop -= new DragEventHandler(DropUIElement_Drop);
    }

    #region DropUIElement_DragOver
    void DropUIElement_DragOver(object sender, DragEventArgs e)
    {
        // If you hover over the drop point, change it's opacity so users will 
        // have some indication that they can drop
        AssociatedObject.Opacity = (double)0.5;
    }
    #endregion

    #region DropUIElement_DragLeave
    void DropUIElement_DragLeave(object sender, DragEventArgs e)
    {
        // Return opacity to normal
        AssociatedObject.Opacity = DropUIElementOpacity;
    }
    #endregion

    #region DropUIElement_Drop
    void DropUIElement_Drop(object sender, DragEventArgs e)
    {
        // Return opacity to normal
        AssociatedObject.Opacity = DropUIElementOpacity;

        // If there is something being dropped upload it
        if (e.Data != null)
        {
            FileInfo[] Dropfiles = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];

            // Set the file to the FileInfo property
            // this should be bound to the ViewModel by the designer
            FileDialogDialogResult = Dropfiles[0];

            // Check File Extension
            if (!CheckFileExtension(FileDialogDialogResult.Extension))
            {
                // Show error
                MessageBox.Show("Only .gif, .jpg, .jpeg, .doc, 
        .docx, .xls, .xlsx, .pdf, .png, .txt files may be used.");
                // Clear the selected file
                FileDialogDialogResult = null;
            }
            else
            {
                SelectedFileName = ShortenFileName(FileDialogDialogResult.Name);

                // Change to selected file Visual State
                VisualStateManager.GoToState((Control)ParentControl, 
                "FileSelectedState", true);
            }
        }
    }
    #endregion

    #region ShortenFileName
    private string ShortenFileName(string filename)
    {
        string strFilename = "...";

        if (filename.Length > 10)
        {
            strFilename = filename.Substring(0, 10) + " ...";
        }
        else
        {
            strFilename = filename;
        }

        return strFilename;
    }
    #endregion

    #region CheckFileExtension
    private bool CheckFileExtension(string strExtension)
    {
        if (
            string.Compare(Path.GetExtension(strExtension).ToLower(), ".gif") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".jpg") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".jpeg") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".doc") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".docx") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".xls") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".xlsx") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".pdf") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".txt") != 0
            & string.Compare(Path.GetExtension(strExtension).ToLower(), ".png") != 0
            )
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    #endregion
}

The UploadingStateBehavior and NoFileSelectedStateBehavior are simple Behaviors that simply change state when invoked. For example:

protected override void Invoke(object parameter)
{
    // Change to File Uploading Visual State
    VisualStateManager.GoToState
        ((Control)AssociatedObject, "NoFileSelectedState", true);
}

The Behaviors are triggered by a DataTrigger. See Silverlight DataTrigger is the Answer to View Model / MVVM issues for more information on DataTriggers.

Here is the code for RemoveSelectedFileBehavior:

[System.ComponentModel.Description("Removes Selected File on Event Trigger")]
public class RemoveSelectedFileBehavior : TargetedTriggerAction<Button> 

{
    // Properties

    #region FileDialogDialogResultProperty

    public static readonly DependencyProperty FileDialogDialogResultProperty =
        DependencyProperty.Register("FileDialogDialogResultProperty",
        typeof(FileInfo), typeof(RemoveSelectedFileBehavior), null);

    public FileInfo FileDialogDialogResult
    {
        get
        {
            return (FileInfo)base.GetValue(FileDialogDialogResultProperty);
        }
        set
        {
            base.SetValue(FileDialogDialogResultProperty, value);
        }
    }
    #endregion

    #region ParentControlProperty

    public static readonly DependencyProperty ParentControlProperty =
        DependencyProperty.Register("ParentControlProperty",
        typeof(object), typeof(RemoveSelectedFileBehavior), null);

    public object ParentControl
    {
        get
        {
            return (object)base.GetValue(ParentControlProperty);
        }
        set
        {
            base.SetValue(ParentControlProperty, value);
        }
    }
    #endregion

    #region SelectedFileNameProperty

    public static readonly DependencyProperty SelectedFileNameProperty =
        DependencyProperty.Register("SelectedFileName", typeof(string),
        typeof(RemoveSelectedFileBehavior), null);

    public string SelectedFileName
    {
        get
        {
            return (string)base.GetValue(SelectedFileNameProperty);
        }
        set
        {
            base.SetValue(SelectedFileNameProperty, value);
        }
    }

    #endregion

    // Operations

    #region Invoke

    //Shows OpenFileDialog on event trigger you set in designer

    protected override void Invoke(object parameter)
    {
        // Clear Selected File
        FileDialogDialogResult = null;
        SelectedFileName = "File Name";

        // Change to selected file Visual State
        VisualStateManager.GoToState((Control)ParentControl, "NoFileSelectedState", true);
    }

    #endregion
}

Consuming the Control

The main control communicates with the upload control using View Model Communication. See Expression Blendable Silverlight View Model Communication for more information.

The code to upload the files was taken from the article, Silverlight View Model Style File Manager Drag and Drop Upload (part 2).

View Model / MVVM For Maximum Flexibility

Using Behaviors allowed me to create a control using as much code as I would using code behind. What I gained however, is the ability to create a completely different view with different animations and UI yet still uses the same View Model.

If you are new to View Model Style, it is suggested that you read Silverlight View Model Style : An (Overly) Simplified Explanation for an introduction.

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