c# - 在 MVVM 模式中控制 MediaElement 及其源

标签 c# wpf mvvm mediaelement

我有带有视频剪辑列表的 DataGrid 和应该播放在该 DataGrid 中选择的剪辑的 MediaElement。我已经通过这样做设法实现了这一目标:

主窗口.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 的回答,播放/停止功能非常有用。但现在我想控制视频播放的位置。我做了另一个附加属性:

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
}

主窗口.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 没有更新播放视频时。我该怎么办?

最佳答案

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

虽然在这种情况下,附加属性可能非常有用,您可以做您想做的事而不会真正破坏模式(或者至少,不会以一种会让任何人不安的方式)。您可以为名为 IsPlayingMediaElement 创建附加的 DependencyProperty。在属性更改处理程序中,您可以根据属性的新值播放或停止 MediaElement。这样的事情可能会起作用:

    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
    }

然后在您的 View 模型中创建一个 IsPlaying 属性,并将其绑定(bind)到 MediaElement 上的附加属性 MediaElementAttached.IsPlaying

请记住绑定(bind)只是单向的:您将能够控制 MediaElement,但您将无法使用您的 View 模型来了解 IsPlaying 如果用户使用传输控件更改播放状态。但是无论如何,您都无法使用 MediaElement 真正做到这一点,因此您不会放弃任何东西。

与大多数事情一样,有多种方法可以给这只猫蒙皮,但这是我个人的偏好,让您最接近我认为的模式。通过这种方式,您至少可以从 View 模型代码中获取对 MediaElement 的丑陋引用。

此外,就视频不随选择而改变而言,按照您现在的方式,MediaElementSource 属性未绑定(bind)任何内容,因为您在代码中创建它,因此它不会因为 SelectedVideo 更改而更改。我会回到您之前的方式,并尝试我的附加属性解决方案。

编辑 - 就 Position 问题而言,它与我在 IsPlaying 中提到的相同,即,您只能将这些附加属性用作单向 setter ;您不能使用它们获取 MediaElement 上任何内容的值。但是当涉及到 Position 时,您还有另一个问题,即它不是 DependencyProperty,因此无法获得关于它何时实际更改的通知,并且因此无法用新值更新您的附加属性(可能是因为更新如此之快和频繁,它们会使系统陷入困境)。我能想到的唯一解决方案是在某个时间间隔轮询 MediaElement(大约 100 毫秒应该不会太糟糕),然后在每次轮询时设置附加属性。我会在您的 View 的代码隐藏中执行此操作。确保使用线程锁和某种“暂停更新”标志,这样您就可以确保 MediaElementPosition 在轮询事件期间不会更新。希望这会有所帮助。

关于c# - 在 MVVM 模式中控制 MediaElement 及其源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41468631/

相关文章:

c# - WPF - 使用鼠标事件在 Canvas 上绘图

c# - 在另一个类中运行循环时更新View?

c# - 不是单例 ViewModel 并且仍在同步

c# - 在 winrt c# 中加密字符串并在 c# .net 中解密

c# - 要静态还是不要静态

c# - 如何从另一个解决方案打开自定义 DLL 文件/类?

c# - 弹出式文本框双向绑定(bind)

c# - 如何将快门速度值转换为可用数据?

c# - 访问 AttachedProperty 中的 DataGrid.RowStyle

c# - 如何检测哪个线程正在阻止应用程序在 .NET 中关闭