在 MVVM 模式中控制 MediaElement 及其源
Controling MediaElement and its source in MVVM pattern
我有带有视频剪辑列表的 DataGrid 和应该播放在该 DataGrid 中选择的剪辑的 MediaElement。我通过这样做设法实现了这一目标:
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
没有更新播放视频时。我该怎么办?
简短的回答是,无论如何您都必须使用 hack,因为 MediaElement
默认情况下对 MVVM 不友好。
尽管如此,附加属性在这种情况下可能非常有用,您可以做您想做的事而不会真正破坏模式(或者至少,不会以会让任何人不高兴的方式)。您可以为 MediaElement
创建一个名为 IsPlaying
的附加 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
}
然后在你的视图模型中创建一个IsPlaying
属性,并将其绑定到MediaElement
上附加的属性 MediaElementAttached.IsPlaying
。
请记住绑定只是单向的:您将能够控制 MediaElement,但您将无法使用您的视图模型来了解 IsPlaying
的值如果用户使用传输控件更改播放状态。但是无论如何你都不能用 MediaElement
做到这一点,所以你没有放弃任何东西。
与大多数事情一样,有多种方法可以给这只猫蒙皮,但这是我个人的喜好,让您最接近我认为的模式。这样你至少可以从你的视图模型代码中得到对 MediaElement
的丑陋引用。
此外,就视频不随选择而改变而言,按照您现在的方式,MediaElement
的 Source
属性 不受任何限制,因为您在代码中创建它,所以它不会因为 SelectedVideo
改变而改变。我会回到你以前的方式,并尝试我附加的 属性 解决方案。
编辑 -
就 Position
问题而言,它与我在 IsPlaying
中提到的相同,即您只能将这些附加属性用作单向设置器;您不能使用它们来获取 MediaElement
上任何内容的值。但是当涉及到 Position
时,您还有另一个问题,即它不是 DependencyProperty
,因此无法获得关于它何时实际更改的通知,因此无法更新您的附加 属性 一个新值(可能是因为更新如此之快和频繁,以至于会使系统陷入困境)。我能想到的唯一解决方案是每隔一段时间轮询 MediaElement
(大约 100 毫秒应该不会太糟糕),然后在每次轮询时设置附加的 属性。我会在您的视图的代码隐藏中执行此操作。确保使用线程锁和某种 "suspend update" 标志,这样您就可以确保 MediaElement
的 Position
在轮询事件期间不会更新。希望这会有所帮助。
我有带有视频剪辑列表的 DataGrid 和应该播放在该 DataGrid 中选择的剪辑的 MediaElement。我通过这样做设法实现了这一目标:
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
没有更新播放视频时。我该怎么办?
简短的回答是,无论如何您都必须使用 hack,因为 MediaElement
默认情况下对 MVVM 不友好。
尽管如此,附加属性在这种情况下可能非常有用,您可以做您想做的事而不会真正破坏模式(或者至少,不会以会让任何人不高兴的方式)。您可以为 MediaElement
创建一个名为 IsPlaying
的附加 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
}
然后在你的视图模型中创建一个IsPlaying
属性,并将其绑定到MediaElement
上附加的属性 MediaElementAttached.IsPlaying
。
请记住绑定只是单向的:您将能够控制 MediaElement,但您将无法使用您的视图模型来了解 IsPlaying
的值如果用户使用传输控件更改播放状态。但是无论如何你都不能用 MediaElement
做到这一点,所以你没有放弃任何东西。
与大多数事情一样,有多种方法可以给这只猫蒙皮,但这是我个人的喜好,让您最接近我认为的模式。这样你至少可以从你的视图模型代码中得到对 MediaElement
的丑陋引用。
此外,就视频不随选择而改变而言,按照您现在的方式,MediaElement
的 Source
属性 不受任何限制,因为您在代码中创建它,所以它不会因为 SelectedVideo
改变而改变。我会回到你以前的方式,并尝试我附加的 属性 解决方案。
编辑 -
就 Position
问题而言,它与我在 IsPlaying
中提到的相同,即您只能将这些附加属性用作单向设置器;您不能使用它们来获取 MediaElement
上任何内容的值。但是当涉及到 Position
时,您还有另一个问题,即它不是 DependencyProperty
,因此无法获得关于它何时实际更改的通知,因此无法更新您的附加 属性 一个新值(可能是因为更新如此之快和频繁,以至于会使系统陷入困境)。我能想到的唯一解决方案是每隔一段时间轮询 MediaElement
(大约 100 毫秒应该不会太糟糕),然后在每次轮询时设置附加的 属性。我会在您的视图的代码隐藏中执行此操作。确保使用线程锁和某种 "suspend update" 标志,这样您就可以确保 MediaElement
的 Position
在轮询事件期间不会更新。希望这会有所帮助。