1

I am developing a WPF application with architecture based upon Model-View-ViewModel pattern principles, using MVVM Light toolkit framework.

The following XAML code is example of my View-ViewModel relation:

<... .Resources>
   <DataTemplate DataType="{x:Type viewm:MediaElementViewModel}">
       <view:MediaElement/>
   </DataTemplate>
</... .Resources>

I know it is possible to invoke View methods from ViewModel using View-First approach, by assigning MediaElement instance's DataContext property when constructing the concrete MediaElement, unfortunately this is not a solution for me.

View methods, for example, are MediaElement, such as Play(), Pause(), Focuse() or any other "pure" UI methods.

Thanks a lot.

Community
  • 1
  • 1
Eido95
  • 1,313
  • 1
  • 15
  • 29
  • 2
    If you absolutely have to do this I'd recommend using the MVVM Light Messenger class to send a message from your VM, receive it in your View codebehind and take the appropriate action. *The circumstances in which this is necessary are very rare.* If you're just trying to switch between Views then this approach is *much* better: https://rachel53461.wordpress.com/2011/05/28/switching-between-viewsusercontrols-using-mvvm . – goobering Jun 03 '15 at 19:13
  • 1
    you wanna invoke the .close() method in your view from your Viewmodel? if yes why not just create an event in your VM and subscribe to this event in your view – blindmeis Jun 04 '15 at 08:49
  • @goobering Thank you for your answer, MVVM Light Messenger indeed a recommended tool, Unfortunately this tool _sending messages in static manner_, which means that If I have many instances of the same ViewModel, _which one of the reasons I use ViewModel-First_, messaging will cause duplication, cross instances and false results. Accidentally, [I already asked about switching views](http://stackoverflow.com/questions/30232697/how-to-cache-dynamically-switched-views-by-viewmodel-first-approach-using-datate), and found it answer as a better solution for me than the link you provided. – Eido95 Jun 08 '15 at 09:55
  • @blindmeis The View doesn't aware to its ViewModel because of the ViewModel-First approach, means that the View doesn't have an instance for its ViewModel, so there is no event to subscribe for. – Eido95 Jun 08 '15 at 10:00

2 Answers2

1

I found a solution which works, I'll keep it an answer until a better solution will come up.

The solution bases are upon custom Behavior Generic Class.

It is important to note that the question and the answer are in global scope, rather than MediaElement scope. Accordingly, this solution is completely relevant for every FrameworkElement derived control which supports ViewModel-First approach.

I didn't remove any code for clarity on purpose.

ViewTemplates.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApplication2"
                    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <DataTemplate DataType="{x:Type local:MediaElementViewModel}">
        <MediaElement Source="{Binding Source}" Volume="{Binding Volume}"
                        LoadedBehavior="Manual" UnloadedBehavior="Manual">
            <i:Interaction.Behaviors>
                <local:MediaElementBehavior/>
            </i:Interaction.Behaviors>
        </MediaElement>
    </DataTemplate>
</ResourceDictionary>

MediaElementViewModel.cs

public MediaElementViewModel()
{
    Volume = 0.5;
}

private Uri _source;
public Uri Source
{
    get { return _source; }
    set
    {
        _source = value;
        RaisePropertyChanged("Source");
    }
}

private double _volume;
public double Volume
{
    get { return _volume; }
    set
    {
        _volume = value;
        RaisePropertyChanged("Volume");
    }
}

public Action Play { get; set; }
public Action Stop { get; set; }
public Func<bool> Focus { get; set; }

MediaElementBehavior.cs

public MediaElementBehavior()
{
}

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

    MediaElement player = (MediaElement)this.AssociatedObject;
    MediaElementViewModel viewModel = (MediaElementViewModel)this.AssociatedObject.DataContext;

    player.Dispatcher.Invoke(() =>
    {
        // backing up the player methods inside its view-model.
        if (viewModel.Play == null)
            viewModel.Play = player.Play;

        if (viewModel.Stop == null)
            viewModel.Stop = player.Stop;

        if (viewModel.Focus == null)
            viewModel.Focus = player.Focus;
    });
}

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

The following is an example of the above solution usage:

App.xaml

<Application x:Class="WpfApplication2.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
                 <ResourceDictionary Source="ViewTemplates.xaml"/>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.xaml

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication2"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ContentControl Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding CurrentMediaElement}"/>
        <Button Grid.Row="1" Grid.Column="0" Content="Set Source" Command="{Binding SetSourceCommand}"/>
        <WrapPanel Grid.Row="1" Grid.Column="2">
            <Button Grid.Row="1" Grid.Column="2" Content="Stop" Command="{Binding StopCommand}"/>
            <Button Content="Focus" Command="{Binding FocusCommand}"/>
        </WrapPanel>

        <Button Grid.Row="1" Grid.Column="1" Content="Play" Command="{Binding PlayCommand}"/>
        <TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding CurrentMediaElement.Source}" TextWrapping="Wrap"/>
        <Label Grid.Row="2" Grid.Column="1" Content="{Binding ElementName=SliderVolume, Path=Value}"/>
        <Slider x:Name="SliderVolume" Value="{Binding CurrentMediaElement.Volume}" Grid.Row="2" Grid.Column="2" Minimum="0" Maximum="1" Orientation="Horizontal"/>
    </Grid>
</Window>

MainViewModel.cs

public MainViewModel()
{
    CurrentMediaElement = new MediaElementViewModel();
}

private MediaElementViewModel _currentMediaElement;
public MediaElementViewModel CurrentMediaElement
{
    get { return _currentMediaElement; }
    set
    {
        _currentMediaElement = value;
        RaisePropertyChanged("CurrentMediaElement");
    }
}

private RelayCommand _setSourceCommand;
public ICommand SetSourceCommand
{
    get
    {
        return _setSourceCommand ??
                (_setSourceCommand = new RelayCommand(SetSourceExecute));
    }
}
private RelayCommand _playCommand;
public ICommand PlayCommand
{
    get
    {
        return _playCommand ??
                (_playCommand = new RelayCommand(PlayExecute));
    }
}
private RelayCommand _stopCommand;
public ICommand StopCommand
{
    get
    {
        return _stopCommand ??
                (_stopCommand = new RelayCommand(StopExecute));
    }
}
private RelayCommand _focusCommand;
public ICommand FocusCommand
{
    get
    {
        return _focusCommand ??
            (_focusCommand = new RelayCommand(FocusExecute));
    }
}

/// <summary>
/// Invoked whenever focusing media element;
/// </summary>
private void FocusExecute()
{
    bool isFocused = this.CurrentMediaElement.Focus();
}

/// <summary>
/// Invoked whenever setting a media source.
/// </summary>
private void SetSourceExecute()
{
    // Assume the media file location is Debug/bin/Resources/
    this.CurrentMediaElement.Source = new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\media.mp3");
}
/// <summary>
/// Invoked whenever playing media.
/// </summary>
private void PlayExecute()
{
    this.CurrentMediaElement.Play();
}
/// <summary>
/// Invoked whenerver stopping media.
/// </summary>
private void StopExecute()
{
    this.CurrentMediaElement.Stop();
}

As you can see (at MainViewModel.cs), I invoked "pure" View methods from ViewModel using ViewModel-First approach. (PlayExecute, StopExecute and FocusExecute).

Eido95
  • 1,313
  • 1
  • 15
  • 29
0

Your view model is referenced by the view but has no intrinsic knowledge or access of it.

Your suggested methods being show and close, it would be simple to handle this via a view model binding to the visibility property.

Other concepts that may be of use are data triggers which allow you to update the view through animated behaviours, set properties or trigger from many conditions.

kidshaw
  • 3,423
  • 2
  • 16
  • 28
  • 1
    Per my comment above, it is possible to use something like the Messenger class to communicate with the View codebehind from the VM while still maintaining separation between them. There are other approaches, such as this: http://stackoverflow.com/questions/10631748/mvvm-pattern-violation-mediaelement-play . Per that example, this can be necessary to do things like play MediaElements. – goobering Jun 03 '15 at 19:39
  • But that isn't mvvm. Message framework has its place and I have used this in the past - I would warn that it is very tricky to unit test and debug. It also adds overhead that can very quickly leak memory and performance. As long as you go in knowing the restrictions then it is an option - however general practise is to offer that as an answer to be critiqued. I'd also question why the mark down if that from yourself. – kidshaw Jun 03 '15 at 21:40
  • There's nothing about sending messages that violates MVVM - the VM remains completely View-agnostic, and will compile quite happily without the presence of the View. Even without using the Messenger class, the second approach I linked to provides an alternate method by which the View codebehind can receive state changes from the VM, without breaking MVVM. Being tricky to debug, adding overhead and leaking memory aren't in themselves violations of MVVM. The opening sentence of your answer is 'This isn't possible without breaking mvvm pattern', which is *wrong*, and so I downvoted it. – goobering Jun 03 '15 at 21:56
  • My comment was that the suggestion of view methods was a violation of mvvm, not that it couldn't be done. Issuing messages from code behind is not strictly breaking the pattern, on that you are correct, but the main benefit is separation. I respect your right to down vote, I will edit the answer to not be misleading. – kidshaw Jun 04 '15 at 17:14
  • @kidshaw Thank you for your answer, I edited the question because of false code, please re-read it. – Eido95 Jun 08 '15 at 10:02