In need of guidance

Jan 9, 2015 at 5:28 PM
Edited Jan 9, 2015 at 5:28 PM
I am new to Prism for WinRT and have been working through the AdventureWorks sample, but need some further guidance. I have a MediaElement on a page and want to be able to Play, Pause, and Stop the MediaElement from the ViewModel. I have a button and can of course bind to a PlayMedia command on the ViewModel, but once in the ViewModel how to I actually get the MediaElement to Play, Pause, and Stop on the UI?

Thanks,

Rob
Jan 9, 2015 at 7:19 PM
From what I understand this should be able to be accomplished with PubSubEvents, but I cannot seem to figure out how to wire up the View and ViewModel. I assume I need to publish the event in the ViewModel and subscribe to the event in the View.

The sequence as I understand is from the PlayButton in the view, which is bound to the ViewModel command which will then raise the PlayAudioEvent subscribed in to in the View and published in the ViewModel. Is this correct, or is there a better way to do this?
Jan 11, 2015 at 1:56 PM
Hi Rob,
Anytime you find yourself saying "I'm doing MVVM and I need something to be controlled by code in my ViewModel but the only way to get it done is with code in my code behind working directly against the UI element APIs", you have two main paths:
  1. Just write the code you need to write against the control API as code behind and then bridge to your ViewModel for the parts it can control by setting up a communications path from that code behind code to the ViewModel by casting the DataContext of the view to the ViewModel type (or an interface it exposes) and calling methods there or hook up to events raised by your view model.
  2. Use a behavior. Either use one of the built-in behaviors in the Behaviors SDK or write a custom one if the built-in ones are not straightforward to use for this scenario or if you will be implementing the same pattern in multiple views.
I almost always favor #2 unless I can really convince myself this is a one time thing I am going to do that is particular to the API of a control in that one view and I won't have to do it over and over again.

Code behind is to be avoided as much as possible in MVVM, but the only way it can be avoided entirely is to encapsulate all the little chunks of code that need to be code behind (because they need to directly access the members of a UI element) into behaviors.

If you want some more background in both Prism for Windows Runtime and in using Behaviors and writing your own, check out my courses on Pluralsight:
Building Windows Store Business Apps with Prism: http://www.pluralsight.com/courses/building-windows-store-business-applications-prism
Extending XAML Applications with Behaviors: http://www.pluralsight.com/courses/extending-xaml-applications-behaviors

Hope that helps.
Brian
Jan 11, 2015 at 3:36 PM
Edited Jan 11, 2015 at 3:41 PM
I usually choose to hide the view specific object behind an interface that exposes the functionality I need for the view model. In this scenario, I'd create an interface to hide the MediaElement behind.
    public interface IMedia
    {
        void Play();

        void Stop();

        void Pause();
    }
The implementation would require a MediaElement to be given, then it will wrap the MediaElement's methods.
    public class MediaItem : IMedia
    {
        private MediaElement element;

        public MediaItem(MediaElement media)
        {
            this.element = media;
        }

        public void Pause()
        {
            this.element.Pause();
        }

        public void Play()
        {
            this.element.Play();
        }

        public void Stop()
        {
            this.element.Stop();
        }
    }
This allows for the least amount of code behind, as the code-behind just needs to expose a property for the MediaItem
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.CurrentMedia = new MediaItem(this.mediaElement);
        }

        public IMedia CurrentMedia { get; set; }
    }
Next, in your view model, you can wire up commands that invoke the methods exposed by our IMedia interface. I use the generic DelegateCommand for this.
    public class MainPageViewModel
    {
        public MainPageViewModel()
        {
            PlayCommand = new DelegateCommand<IMedia>(media => media.Play());
            StopCommand = new DelegateCommand<IMedia>(media => media.Stop());
            PauseCommand = new DelegateCommand<IMedia>(media => media.Pause());
        }

        public DelegateCommand<IMedia> PlayCommand { get; private set; }

        public DelegateCommand<IMedia> StopCommand { get; private set; }

        public DelegateCommand<IMedia> PauseCommand { get; private set; }
    }
Lastly, the view just needs to have the buttons wired up with the view model commands. We will bind the command the the commands in our view model, and provide the command parameter with our code-behind CurrentMedia property.
<Page
    x:Class="MediaElementApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MediaElementApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    x:Name="MainPageElement"
    d:DataContext="{d:DesignInstance local:MainPageViewModel, IsDesignTimeCreatable=True}">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <MediaElement x:Name="mediaElement" Source="MyMovie.wmv" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3" />
        <Button Command="{Binding Path=PlayCommand}" 
                CommandParameter="{Binding ElementName=MainPageElement, Path=CurrentMedia}" 
                Grid.Column="0" 
                Grid.Row="1" 
                Content="Play" />
        <Button Command="{Binding Path=StopCommand}" 
                CommandParameter="{Binding ElementName=MainPageElement, Path=CurrentMedia}"
                Grid.Column="1" 
                Grid.Row="1" 
                Content="Stop" />
        <Button Command="{Binding Path=PauseCommand}" 
                CommandParameter="{Binding ElementName=MainPageElement, Path=CurrentMedia}"
                Grid.Column="2" 
                Grid.Row="1" 
                Content="Pause" />
    </Grid>
</Page>
You now have the ability to control the MediaElement from wthin your view model, without tightly coupling it to the view's control.

I usually keep my view models in a separate project in order to help enforce code-separation of the view and my view models. In this case, the IMedia would be in my View Model project and the MediaItem would be in the View's project. The View Model only ever knows about the interface.
Jan 11, 2015 at 6:57 PM
Hi guys,

What great replies! I really appreciate your help and taking the time to answer my question. Scionwest, what an elegant solution. Brain, I look forward to setting some time aside to take your Pluralsight courses on WinRT custom behaviors. Implementing both approaches will be part of my learning experience.

Rob