I have DataGrid with list of video clips and MediaElement which should play a clip selected in that DataGrid. I've managed to achieve this by doing this:
MainWindow.xaml
<DataGrid x:Name="dataGrid_Video"
...
SelectedItem="{Binding fosaModel.SelectedVideo}"
SelectionUnit="FullRow"
SelectionMode="Single">
...
<MediaElement x:Name="PlayerWindow" Grid.Column="1" HorizontalAlignment="Left" Height="236" Grid.Row="1" VerticalAlignment="Top" Width="431" Margin="202,0,0,0" Source="{Binding fosaModel.SelectedVideo.fPath}"/>
But then I have no control over playback. So I searched a little bit, and I found this solution, and implemented Play button:
MainWindow.xaml
...
<ContentControl Content="{Binding Player}" Grid.Column="1" HorizontalAlignment="Left" Height="236" Grid.Row="1" VerticalAlignment="Top" Width="431" Margin="202,0,0,0"/>
...
<Button x:Name="playButton" Content="Odtwórz" Grid.Column="1" HorizontalAlignment="Left" Margin="202,273,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" IsDefault="True" Command="{Binding PlayVideoCommand}"/>
...
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
...
Player = new MediaElement()
{
LoadedBehavior = MediaState.Manual,
};
PlayVideoCommand = new RelayCommand(() => { _playVideoCommand(); });
...
public MediaElement Player{ get; set; }
private void _playVideoCommand()
{
Player.Source = new System.Uri(fosaModel.SelectedVideo.fPath);
Player.Play();
}
fosaModel.cs
public class fosaModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<fosaVideo> ListOfVideos { get; set; } = new ObservableCollection<fosaVideo>();
public ObservableCollection<fosaAudio> ListOfAudios { get; set; } = new ObservableCollection<fosaAudio>();
public fosaVideo SelectedVideo { get; set; }
}
So now selected video is played when I press play button. Changing selection doesn't change the source of MediaElement, like it did earlier. What I want is to have control over playback of video, but also to have selecting other video in DataGrid change the source of MediaElement. Is there anyway to do so without breaking the MVVM pattern? I'm new to WPF, and I'm feeling like I'm missing something basic. I use MVVM Light and Fody.PropertyChanged.
EDIT:
I went with Peter Moore's answer and Play/Stop feature works like a charm. But now I would like to have control over position of playback of a video. I've made another attached property:
public class MediaElementAttached : DependencyObject
{
...
#region VideoProgress Property
public static DependencyProperty VideoProgressProperty =
DependencyProperty.RegisterAttached(
"VideoProgress", typeof(TimeSpan), typeof(MediaElementAttached),
new PropertyMetadata(new TimeSpan(0,0,0), OnVideoProgressChanged));
public static TimeSpan GetVideoProgress(DependencyObject d)
{
return (TimeSpan)d.GetValue(VideoProgressProperty);
}
public static void SetVideoProgress(DependencyObject d, TimeSpan value)
{
d.SetValue(VideoProgressProperty, value);
}
private static void OnVideoProgressChanged(
DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
MediaElement me = obj as MediaElement;
me.LoadedBehavior = MediaState.Manual;
if (me == null)
return;
TimeSpan videoProgress = (TimeSpan)args.NewValue;
me.Position = videoProgress;
}
#endregion
}
MainWindow.xaml
<MediaElement
...
local:MediaElementAttached.VideoProgress="{Binding fosaModel.videoProgress, Mode=TwoWay}" x:Name="playerWindow" Grid.Column="1" HorizontalAlignment="Left" Height="236" Grid.Row="1" VerticalAlignment="Top" Width="431" Margin="202,0,0,0" Source="{Binding fosaModel.SelectedVideo.fPath}" LoadedBehavior="Manual" />
I can set value of it, when I change videoProgress
property, but I can't get the position of video playback, that is, videoProgress
isn't updating as video is played. What should I do?
The short answer is that you're going to have to employ a hack no matter what, because
MediaElement
is not MVVM friendly by default.Attached properties can be very useful in situations like this though and you can do what you want to do without really breaking the pattern (or at least, not in a way that would upset anyone). You could create an attached
DependencyProperty
forMediaElement
calledIsPlaying
. In the property change handler, you could either Play or Stop the MediaElement depending on the new value of the property. Something like this might work:Then make an
IsPlaying
property in your view model, and bind it to the attached propertyMediaElementAttached.IsPlaying
on theMediaElement
.Just keep in mind the binding will only be one-way: you'll be able to control the MediaElement, but you won't be able to use your view model to learn the value of
IsPlaying
if the user changes the playback state with the transport controls. But you can't really do that withMediaElement
anyway, so you're not giving up anything.As with most things there are multiple ways to skin this cat, but this is my personal preference and keeps you closest to the pattern I think. This way you can at least get that ugly reference to
MediaElement
out of your view model code.Also as far as the video not changing with the selection, the way you have it now, the
MediaElement
'sSource
property isn't bound to anything because you're creating it in code, so it's not going to change just becauseSelectedVideo
changes. I would go back to the way you had it before, and try my attached property solution.Edit - As far as the
Position
issue goes, it's the same as what I mentioned withIsPlaying
, i.e., you can only use these attached properties as one-way setters; you can't use them to obtain the value of anything on theMediaElement
. But you have another problem too when it comes toPosition
which is that it isn't aDependencyProperty
, so there's no way to get a notification as to when it actually changes and thus no way to update your attached property with a new value (probably because the updates are so fast and frequent they would bog down the system quite a bit). The only solution I can think of is to poll theMediaElement
at some interval (something like 100ms shouldn't be too bad) and then set your attached property with every poll. I would do that in your View's code-behind. Make sure to employ a thread lock and some kind of "suspend update" flag too so you can make sure theMediaElement
'sPosition
is not updated during a poll event. Hope this helps.