Yesterday we saw all the plumbing involved to lay a groundwork for RadUpload being used to add files to applicants. Today we actually dive into the code for getting RadUpload working in the MVVM application.
The first step in this is deciding where to add the ability to upload files. Doing this within the main Applicants grid is nice (since we're already using the RowDetails presenter), but that'll clutter up the UI there as I want to switch that into a quick and easy overview of each applicant. So instead we're modifying the AddEditApplicantView to make it tab-based, providing one tab for the regular information and the other for handling the file uploads (and project rating, since we do have a RadRating control now!). I'll spare you the markup, but here is the new UI:
Do you see the RadUpload instances? Neither do I, that's because they have Visibility set to Collapsed and we're handling all actions through the RadUploadDropPanel and attached behaviors on RadUpload. Since we now utilize DropTarget support with RadUpload, we don't even need to see the control anymore to take advantage of the fast upload capabilities it gives us. To get into the code a little, here is the XAML markup for the first instance of RadUploadDropPanel, RadUpload, and the corresponding HyperlinkButton:
<
telerik:RadUploadDropPanel
x:Name
=
"xResumeDropPanel"
Grid.Row
=
"1"
Grid.Column
=
"1"
AllowDrop
=
"True"
DragOver
=
"xResumeDropPanel_DragOver"
DragLeave
=
"xResumeDropPanel_DragLeave"
Drop
=
"xResumeDropPanel_Drop"
RadUpload
=
"{Binding ElementName=xResumeRadUpload}"
>
<
Border
x:Name
=
"xResumeUploadBorder"
Margin
=
"5"
Background
=
"LightBlue"
Height
=
"30"
>
<
TextBlock
HorizontalAlignment
=
"Center"
VerticalAlignment
=
"Center"
Text
=
"Resume"
/>
</
Border
>
</
telerik:RadUploadDropPanel
>
<
telerik:RadUpload
x:Name
=
"xResumeRadUpload"
AllowDrop
=
"True"
IsAutomaticUpload
=
"True"
UploadServiceUrl
=
"/HRUpload.ashx"
OverwriteExistingFiles
=
"True"
TargetPhysicalFolder
=
"{Binding UploadTargetFolder}"
command:UploadStartedEventClass.Command
=
"{Binding UploadStartedCommand}"
command:UploadFinishedEventClass.Command
=
"{Binding UploadFinishedCommand}"
command:FileUploadedEventClass.Command
=
"{Binding FileUploadFinishedCommand}"
Grid.Row
=
"1"
Grid.Column
=
"3"
Visibility
=
"Collapsed"
d:DesignHeight
=
"10"
d:DesignWidth
=
"10"
/>
<
HyperlinkButton
x:Name
=
"xResumeUploadTextBlock"
HorizontalAlignment
=
"Center"
VerticalAlignment
=
"Center"
Content
=
"{Binding ResumeUploadStatus}"
Foreground
=
"{Binding ResumeUploadStatus, Converter={StaticResource StatusToColorConverter}}"
NavigateUri
=
"{Binding ActiveApplicant.Resume}"
TargetName
=
"_blank"
Grid.Row
=
"1"
Grid.Column
=
"2"
/>
And we also get a little bit of code-behind:
// I'm not a bad MVVM programmer, but creating behaviors in order to
// modify the drag-over color for the RadUploadPanel content is
// overkill - this is strictly a view-related issue and therefore
// it is okay to have strictly view-related code in the code-behind. :)
private
void
xResumeDropPanel_DragOver(
object
sender, DragEventArgs e)
{
((sender
as
RadUploadDropPanel).Content
as
Border).Background =
new
SolidColorBrush(Colors.Orange);
}
private
void
xResumeDropPanel_DragLeave(
object
sender, DragEventArgs e)
{
((sender
as
RadUploadDropPanel).Content
as
Border).Background =
new
SolidColorBrush(Color.FromArgb(255, 96, 194, 255));
}
private
void
xResumeDropPanel_Drop(
object
sender, DragEventArgs e)
{
((sender
as
RadUploadDropPanel).Content
as
Border).Background =
new
SolidColorBrush(Color.FromArgb(255, 96, 194, 255));
}
For those MVVM purists, yes, there is code in my code-behind, but I've got a very specific reason for that (explained in the comments, but I'll touch on it again). The DragOver, DragLeave, and Drop events are only effecting something in the view and have nothing to do with the overall data model for the application. So for each I could have gone the route of creating an attached behavior, setting the colors in the viewmodel to bind to the drop panels, and adding more code to my viewmodel to ensure this all works without a hitch, but why? This works, it only impacts the view, and is a more streamlined and less time-consuming approach than all that.
Back on track, in order of appearance we see our RadUploadDropPanel that is bound to the xResumeRadUpload (therefore any files dropped on the drop panel get added to RadUpload), with a simple border and textblock being used to provide a UI that updates based on whether the user is dragging a file over it or not.
Next is RadUpload, where all the magic is going to happen:
The commands cover the three things that we need to ensure uploads are going as planned and reporting status back to the UI. UploadStarted will let us know, if you haven't guessed, when the upload is starting. UploadFinished is doing the same when the upload finishes, except this lets us grab the instance of RadUpload so that when FileUploadFinished runs, we know which property of the entity to 'attach' the file to. I won't go into the EventClass code (it's the same for every command I add like this, albeit with names and control references switched up), but here are the three EventBehaviors:
public
class
UploadStartedEventBehavior : CommandBehaviorBase<RadUpload>
{
public
UploadStartedEventBehavior(RadUpload element)
:
base
(element)
{
element.UploadStarted +=
new
EventHandler<UploadStartedEventArgs>(element_UploadStarted);
}
void
element_UploadStarted(
object
sender, UploadStartedEventArgs e)
{
RadUpload ru = sender
as
RadUpload;
base
.CommandParameter = ru.Name;
base
.ExecuteCommand();
}
}
...
public
class
UploadFinishedEventBehavior : CommandBehaviorBase<RadUpload>
{
public
UploadFinishedEventBehavior(RadUpload element)
:
base
(element)
{
element.UploadFinished +=
new
EventHandler<EventArgs>(element_UploadFinished);
}
void
element_UploadFinished(
object
sender, EventArgs e)
{
RadUpload ru = sender
as
RadUpload;
base
.CommandParameter = ru.Name;
base
.ExecuteCommand();
}
}
...
public
class
FileUploadedEventBehavior : CommandBehaviorBase<RadUpload>
{
public
FileUploadedEventBehavior(RadUpload element)
:
base
(element)
{
element.FileUploaded +=
new
EventHandler<FileUploadedEventArgs>(element_FileUploaded);
}
void
element_FileUploaded(
object
sender, FileUploadedEventArgs e)
{
base
.CommandParameter = e;
base
.ExecuteCommand();
}
}
and their respective DelegateCommands on the viewmodel:
// When upload starts we update the UI
public
void
UploadStartCommand(
object
obj)
{
string
controlName = (
string
)obj;
if
(controlName.Contains(
"Resume"
))
{
ResumeUploadStatus =
"Started"
;
}
else
if
(controlName.Contains(
"CoverLetter"
))
{
CoverLetterUploadStatus =
"Started"
;
}
else
// only one left is Project
{
ProjectUploadStatus =
"Started"
;
}
}
// Same when upload finishes
public
void
UploadFinishCommand(
object
obj)
{
string
controlName = (
string
)obj;
// save this to use in FileUploadFinishCommand
_uploadName = controlName;
if
(controlName.Contains(
"Resume"
))
{
ResumeUploadStatus =
"Complete"
;
}
else
if
(controlName.Contains(
"CoverLetter"
))
{
CoverLetterUploadStatus =
"Complete"
;
}
else
// only one left is Project
{
ProjectUploadStatus =
"Complete"
;
}
}
// Use this to capture the file name on upload finishing :)
public
void
FileUploadFinishCommand(
object
obj)
{
FileUploadedEventArgs uploadEvents = obj
as
FileUploadedEventArgs;
string
uploadFileNameResult = uploadEvents.SelectedFile.Name;
if
(
string
.IsNullOrEmpty(uploadFileNameResult))
{
eventAggregator.GetEvent<NotifyUserEvent>().Publish(
"Problem uploading file!"
);
}
else
{
if
(_uploadName.Contains(
"Resume"
))
{
ActiveApplicant.Resume =
"/HR/applicants/"
+ ActiveApplicant.ApplicantGUID.ToString() +
"/"
+ uploadFileNameResult;
}
else
if
(_uploadName.Contains(
"CoverLetter"
))
{
ActiveApplicant.CoverLetter =
"/HR/applicants/"
+ ActiveApplicant.ApplicantGUID.ToString() +
"/"
+ uploadFileNameResult;
}
else
// only one left is Project
{
ActiveApplicant.OverviewProject =
"/HR/applicants/"
+ ActiveApplicant.ApplicantGUID.ToString() +
"/"
+ uploadFileNameResult;
}
_uploadName =
""
;
}
}
So the order of events is:
Lastly we have the HyperlinkButton, which utilizes *UploadStatus as a tracking variable on the viewmodel to determine where we are at with our upload (and if we have one when loading an Applicant in), the foreground gets converted to show successful (green), standard (black), or error (red) states, and the NavigateUri points to the file that is accessible on the server.
So what happens here? Let me give you a short pictorial to show you how this all goes down...
And we're good to go. :)
Next week we cover the (I hope) significantly easier approach of doing this all in the code-behind version.
Cheers!
Evan Hutnick works as a Developer Evangelist for Telerik specializing in Silverlight and WPF in addition to being a Microsoft MVP for Silverlight. After years as a development enthusiast in .Net technologies, he has been able to excel in XAML development helping to provide samples and expertise in these cutting edge technologies. You can find him on Twitter @EvanHutnick.