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;
#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();
DropUIElementOpacity = AssociatedObject.Opacity;
AssociatedObject.AllowDrop = true;
AssociatedObject.DragOver += new DragEventHandler(DropUIElement_DragOver);
AssociatedObject.DragLeave += new DragEventHandler(DropUIElement_DragLeave);
AssociatedObject.Drop += new DragEventHandler(DropUIElement_Drop);
}
protected override void OnDetaching()
{
base.OnDetaching();
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)
{
AssociatedObject.Opacity = (double)0.5;
}
#endregion
#region DropUIElement_DragLeave
void DropUIElement_DragLeave(object sender, DragEventArgs e)
{
AssociatedObject.Opacity = DropUIElementOpacity;
}
#endregion
#region DropUIElement_Drop
void DropUIElement_Drop(object sender, DragEventArgs e)
{
AssociatedObject.Opacity = DropUIElementOpacity;
if (e.Data != null)
{
FileInfo[] Dropfiles = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
FileDialogDialogResult = Dropfiles[0];
if (!CheckFileExtension(FileDialogDialogResult.Extension))
{
MessageBox.Show("Only .gif, .jpg, .jpeg, .doc,
.docx, .xls, .xlsx, .pdf, .png, .txt files may be used.");
FileDialogDialogResult = null;
}
else
{
SelectedFileName = ShortenFileName(FileDialogDialogResult.Name);
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)
{
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>
{
#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
#region Invoke
protected override void Invoke(object parameter)
{
FileDialogDialogResult = null;
SelectedFileName = "File Name";
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.