[Back]
Title:       Simplified MVVM: Silverlight Video Player
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 Video Player that is not just 'skinable' but fully 'designable'
Section      ASP.NET
SubSection   Silverlight
License:     MIT

 

Introduction

This project demonstrates an implementation of the MVVM (Model-View-ViewModel) pattern to create a fully "Designable" Silverlight Video Player. This is not to be confused with a "Skinable" Video Player. A Skinable Video Player allows you to change the look and feel of the buttons that control the player. A Designable player allows a Designer to use ANY set of controls to implement the Video Player.

The MVVM 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 Video Player, but, it actually works and hopefully demonstrates a non-trivial example of a MVVM Silverlight project. Certain functions such as the pause button, full screen support, and skip ahead were left out to simplify the example code as much as possible.

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

"Simplified" MVVM

 image

Much has been written about the MVVM pattern, and there are various interpretations. In this example, we will implement the pattern with as little code as possible. We will try to present it in the easiest possible way. The Model and the View of MVVM are very simple. The Model contains web services, and the View is the UI (that is created in Expression Blend with no coding). The only complex part is the ViewModel. In the ViewModel, all functionality will be implemented using:

For the commands, we will only need to use the InvokeCommandAction behavior.

That's it. This is "Simplified" MVVM.

A Silverlight MVVM Video Player

Let's first start with the experience a Designer would have in using the MVVM pattern to completely redesign the Video player.

When you download the attached code, you will need to place some .wmv videos in the Video folder.

When you run the project, the web service in the web project, will detect what videos are in the folder, and pass them to the Silverlight application.

When you open the project in Expression Blend, and open the MainView.xaml file, then click on the Data tab...

You will see that there is a Data Context section. The Data Context is set to the ViewModel and displays all the Properties, Collections, and Commands that the UI can interact with. The Designer only needs to interact with these elements to implement their own Video Player.

The image above shows what is bound to what. However, note that while buttons are bound to, and raise, ICommands, practically anything can raise an ICommand, it does not have to be a button. In the tutorial TreeView SelectedItemChanged using MVVM, a TreeView control is used to raise the ICommand.

Redesign The Video Player

We can delete the existing MainPage.xaml file...

 ... and create a new one.

If we click on the LayoutRoot in the Objects and Timeline window...

... and click on the New button next to DataContext...

... and select MainViewModel and click OK

When we click on the Data tab...

... we will see the Data Context has been set for the page.

Let me repeat this important point:

WE WILL NOT NEED TO CREATE A SINGLE LINE OF CODE TO IMPLEMENT THE VIDEO PLAYER

Create the UI

The one control that is required, is the MediaElement. In Expression Blend:

  1. Click the Asset button
  2. Locate the MediaElement
  3. Drag it to the design surface

Make sure you set the AutoPlay to false by un-checking the box in the Properties for the MediaElement.

I went to the Alan Beasley article: http://www.codeproject.com/KB/expression/ArcadeButton.aspx and stole a button (he really does good work. When you click on the button it actually animates and behaves like a real button).

I then created the "Masterpiece" you see above. Of course anyone could do better. I just wanted to show how radically different the UI could be, yet still work without any code changes.

Wire-up The MediaElement - Learn To "Think MVVM"

The UI is created, all we need to do is bind the UI elements to the ViewModel and the application is complete.

To understand how we bind the UI elements to the ViewModel, we need to learn a few things to "Think Like MVVM". Basically we:

In this application, we need to wire-up the MediaElement to the ViewModel. To do this, we have to:

  1. Bind the MediaElement to the SelectedVideoProperty (Uri). This will cause the MediElement to always play the video that the ViewModel sets to that property.
  2. Use an InvokeCommand behavior to raise the MediaOpenedCommand (ICommand). This behavior is configured to fire when the MediOpened event is raised on the MediaElement (this happens automatically when the MediaElement is ready to play the video). The behavior also passes a reference to the MediaElement as a parameter to the ViewModel (the ViewModel will use this reference to the MediaElement when the other commands such as the Start and Stop are called).

Let's walk through these steps:

In the Objects and Timeline window, select the MediaElement.

In the properties for the MediaElement, select the Advanced options for Source.

Select Data Binding...

Set it to the SelectedVideoProperty and click OK

You will know an element is bound because it will have a gold box around it.

In the Assets, click and drag an InvokeCommand behavior...

... and drop it under the MediaElement in the Objects and Timeline window.

In the properties for the behavior, set MediaOpened for the EventName and click the Data bind button next to Command.

Select MediaOpenedCommand and click OK.

Select the Advanced options for CommandParameter

Select Data Binding...

Click the Element Property tab, and select MediaElement and click OK.

That was the hard part. The rest is just binding the remaining Properties, Collections, and Commands.

Binding Properties And Collections 

These are the remaining Properties and Collections that need to be bound.

For example, the ProgressBar control has it's Value and Maximum properties set to the CurrentPositionProperty and the TotalDurationProperty respectively.

The ListBox control is naturally bound to the SilverlightVideoList, but the SelectedIndex for the ListBox, is also bound to the SelectedVideoInListProperty. The reason for this, is that you cannot set the Selectedindex until AFTER the list has been loaded.

The ViewModel fills the SilverlightVideoList, and only after it has filled that collection does it check to see if there are any items, and if there are, sets the Selectedindex value.

Binding Commands

These are the remaining Commands that need to be bound. To bind them, simply drop a InvokeCommand behavior on the control and set the properties to the respective commands.

For example, the ListBox uses an InvokeCommand behavior to call the SetVideoCommand when it's SelectionChanged event is raised.

It's CommandParameter is bound to the SelectedItem of the ListBox.

That's about it. If you're a Designer you can skip the next section.

The Code

The website part of the code is simply a website with a web service that returns a list of any videos that are in the Videos folder.

The Silverlight project consists of only a few files. The files that are important at this point are:

The Model

The Model for this application consists of a single web method in the SilverlightVideos.cs file. The "twist" is that we are using RX Extensions that call the web service and return an IObservable.

public static IObservable<IEvent<GetVideosCompletedEventArgs>> GetVideos()
{
  // Uses http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx
  // Also see: http://programmerpayback.com/2010/03/11/use-silverlight-reactive-extensions-rx-to-build-responsive-uis/
  // Also see: http://www.silverlightshow.net/items/Using-Reactive-Extensions-in-Silverlight-part-2-Web-Services.aspx 

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

  // Get the base address of the website that launched the Silverlight Application
  EndpointAddress MyEndpointAddress = new
    EndpointAddress(GetBaseAddress());

  // Set that address
  objWebServiceSoapClient.Endpoint.Address = MyEndpointAddress;

  // Set up a Rx Observable that can be consumed by the ViewModel
  IObservable<IEvent<GetVideosCompletedEventArgs>> observable = 
    Observable.FromEvent<GetVideosCompletedEventArgs>(objWebServiceSoapClient, "GetVideosCompleted");
  objWebServiceSoapClient.GetVideosAsync();

  return observable;
}

The ViewModel

image

The ViewModel is where all the Properties, Collections, and Commands are created.

This is the constructor of the ViewModel:

public MainViewModel()
{
    // Set the command property
    MediaOpenedCommand = new DelegateCommand(MediaOpened, CanMediaOpened);
    PlayVideoCommand = new DelegateCommand(PlayVideo, CanPlayVideo);
    StopVideoCommand = new DelegateCommand(StopVideo, CanStopVideo);
    SetVideoCommand = new DelegateCommand(SetVideo, CanSetVideo);
	
    // Call the Model to get the collection of Videos
    GetListOfVideos();
}

It sets up the Commands by using the DeleagateCommand method (from the DelegateCommand.cs file) to create ICommands. Next, it calls the GetListOfVideos() method.

private void GetListOfVideos()
{
    // Call the Model to get the collection of Videos
    SilverlightVideos.GetVideos().Subscribe(p =>
    {
        if (p.EventArgs.Error == null)
        {
            // loop thru each item
            foreach (string Video in p.EventArgs.Result)
            {
                // Add to the SilverlightVideoList collection
                SilverlightVideoList.Add(Video);
            }

            // if we have any videos, set the selected item value to the first one
            if (SilverlightVideoList.Count > 0)
            {
                SelectedVideoInListProperty = 0;
            }
        }
    });
}

This method calls the GetVideos() method in the Model that fills the SilverlightVideoList collection. It also sets the SelectedVideoInListProperty property if there are videos in the collection.

The SilverlightVideoList collection is a simple ObservableCollection:

private ObservableCollection _SilverlightVideoList = 
	new ObservableCollection();
public ObservableCollection SilverlightVideoList
{
    get { return _SilverlightVideoList; }
    private set
    {
        if (SilverlightVideoList == value)
        {
            return;
        }

        _SilverlightVideoList = value;
        this.NotifyPropertyChanged("SilverlightVideoList");
    }
}

When the SelectedVideoInListProperty property is set, the UI control that is bound to it (the ListBox or the combo drop down box) should raise the SetVideoCommand command that sets the SelectedVideoProperty:

public ICommand SetVideoCommand { get; set; }
public void SetVideo(object param)
{
    // Set Video
    string tmpSelectedVideo = string.Format(@"{0}/{1}", GetBaseAddress(), (String)param);
    SelectedVideoProperty = new Uri(tmpSelectedVideo, UriKind.RelativeOrAbsolute);

    // Stop Progress Timer
    progressTimer.Stop();
}

private bool CanSetVideo(object param)
{
    // only set video if the parameter is not null
    return (param != null);
}

The MediaElement is bound to the SelectedVideoProperty and will automatically start loading the Video. When it has opened the Video, it will call the MediaOpenedCommand command that will:

public ICommand MediaOpenedCommand { get; set; }
public void MediaOpened(object param)
{
    // Play Video
    MediaElement parmMediaElement = (MediaElement)param;
    MyMediaElement = parmMediaElement;

    this.progressTimer = new DispatcherTimer();
    this.progressTimer.Interval = TimeSpan.FromSeconds(1);
    this.progressTimer.Tick += new EventHandler(this.ProgressTimer_Tick);

    SetCurrentPosition();
}

private bool CanMediaOpened(object param)
{
    return true;
}

The code for the Start and Stop Commands is:

#region PlayVideoCommand
public ICommand PlayVideoCommand { get; set; }
public void PlayVideo(object param)
{
    // Play Video
    MyMediaElement.Play();
    progressTimer.Start();
}

private bool CanPlayVideo(object param)
{
    bool CanPlay = false;
    // only allow Video to Play if it is not already Playing
    if (MyMediaElement != null)
    {
        if (MyMediaElement.CurrentState != MediaElementState.Playing)
        {
            CanPlay = true;
        }
    }
    return CanPlay;
}  
#endregion

#region StopVideoCommand
public ICommand StopVideoCommand { get; set; }
public void StopVideo(object param)
{
    // Stop Video
    MyMediaElement.Stop();
    progressTimer.Stop();
}

private bool CanStopVideo(object param)
{
    bool CanStop = false;
    // only allow Video to Stop if it is Playing
    if (MyMediaElement != null)
    {
        if (MyMediaElement.CurrentState == MediaElementState.Playing)
        {
            CanStop = true;
        }
    }
    return CanStop;
}  
#endregion

The SetCurrentPosition() method sets the CurrentProgressProperty, CurrentPositionProperty and TotalDurationProperty:

private void SetCurrentPosition()
{
    // Update the time text e.g. 01:50 / 03:30
    CurrentProgressProperty = string.Format(
        "{0}:{1} / {2}:{3}",
        Math.Floor(MyMediaElement.Position.TotalMinutes).ToString("00"),
        MyMediaElement.Position.Seconds.ToString("00"),
        Math.Floor(MyMediaElement.NaturalDuration.TimeSpan.TotalMinutes).ToString("00"),
        MyMediaElement.NaturalDuration.TimeSpan.Seconds.ToString("00"));

    CurrentPositionProperty = MyMediaElement.Position.TotalSeconds;
    TotalDurationProperty = MyMediaElement.NaturalDuration.TimeSpan.TotalSeconds;
}

Wrap Your Head Around MVVM

It takes a bit of practice to wrap your head around MVVM. However, it is not hard, and you will find you will use less code than you would normally use. The main thing you want to get used to is binding everything. If you find yourself needing to raise an event from the UI and you're stuck, you're usually not binding enough things. Let a change in a value in the ViewModel raise the event for you through bindings.  

How Can You Learn More About Expression Blend ?

To learn how to use Expression Blend, all you have to do is go to:

http://www.microsoft.com/design/toolbox/

This site will give you the free training you need to master Expression Blend. It also will cover design principals to make you a better designer.