以MVVM模式控制MediaElement及其源

时间:2017-01-04 16:23:17

标签: c# wpf mvvm mediaelement

我有DataGrid,其中包含视频剪辑和MediaElement列表,它们应播放在该DataGrid中选择的剪辑。我通过这样做成功实现了这一目标:

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}"/>

但是我无法控制播放。所以我搜索了一下,我发现了这个解决方案,并实现了播放按钮:

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; }
}

因此,当按下播放按钮时,播放现在选择的视频。更改选择不会像之前那样更改MediaElement的来源。我想要的是控制视频的播放,还要在DataGrid中选择其他视频来改变MediaElement的来源。反正有没有打破MVVM模式呢? 我是WPF的新手,我觉得我错过了一些基本的东西。我使用MVVM Light和Fody.PropertyChanged。

修改

我选择了Peter Moore的回答,Play / Stop功能就像一个魅力。但现在我想控制视频播放的位置。我做了另一个附属物:

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" />

我可以设置它的值,当我更改videoProgress属性时,但我无法获得视频播放的位置,也就是说,videoProgress在播放视频时没有更新。我该怎么办?

1 个答案:

答案 0 :(得分:3)

简短的回答是,无论如何你都必须使用hack,因为默认情况下MediaElement不是MVVM友好的。

附加属性在这种情况下非常有用,但你可以在不破坏模式的情况下做你想做的事情(或者至少不会让任何人感到不安)。您可以为名为DependencyProperty的{​​{1}}创建附加的MediaElement。在属性更改处理程序中,您可以播放或停止MediaElement,具体取决于属性的新值。这样的事情可能有用:

IsPlaying

然后在视图模型中创建一个 public class MediaElementAttached : DependencyObject { #region IsPlaying Property public static DependencyProperty IsPlayingProperty = DependencyProperty.RegisterAttached( "IsPlaying", typeof(bool), typeof(MediaElementAttached ), new PropertyMetadata(false, OnIsPlayingChanged)); public static bool GetIsPlaying(DependencyObject d) { return (bool)d.GetValue(IsPlayingProperty); } public static void SetIsPlaying(DependencyObject d, bool value) { d.SetValue(IsPlayingProperty, value); } private static void OnIsPlayingChanged( DependencyObject obj, DependencyPropertyChangedEventArgs args) { MediaElement me = obj as MediaElement; if (me == null) return; bool isPlaying = (bool)args.NewValue; if (isPlaying) me.Play(); else me.Stop(); } #endregion } 属性,并将其绑定到IsPlaying上的附加属性MediaElementAttached.IsPlaying

请记住,绑定只能是单向的:您可以控制MediaElement,但是您无法使用视图模型来学习{{1如果用户使用传输控件更改播放状态。但是你无论如何都不能用MediaElement做到这一点,所以你不会放弃任何东西。

与大多数事情一样,有多种方法可以让这只猫受到伤害,但这是我个人的偏好,让你最接近我认为的模式。这样,您至少可以从视图模型代码中获得对IsPlaying的丑陋引用。

此外,视频不会随着选择而改变,现在的方式,MediaElement&#39; MediaElement属性不会绑定到任何内容,因为您&# 39;在代码中重新创建它,所以它不会因为MediaElement发生变化而改变。我会回到你之前的方式,并尝试我附属的财产解决方案。

修改 - 就Source问题而言,它与我在SelectedVideo中提到的相同,即您只能将这些附加属性用作单向设置器;您无法使用它们来获取Position上任何内容的值。但是在IsPlaying时你也遇到了另一个问题,那就是它不是MediaElement,因此无法获得关于它何时实际发生变化的通知无法使用新值更新附加属性(可能因为更新速度非常快且频繁,因此会使系统陷入困境)。我能想到的唯一解决方案是在某个时间间隔内轮询Position(类似于100毫秒不应该太糟糕),然后在每次轮询时设置附加属性。我会在你的View代码隐藏中做到这一点。确保使用线程锁和某种&#34;暂停更新&#34;标记也是如此,以确保在投票活动期间DependencyProperty MediaElement未更新。希望这会有所帮助。