MVVM模式违反:MediaElement.Play()(MVVM pattern violatio

2019-06-24 13:05发布

我明白,视图模型不应该查看的任何知识,但我怎么能叫MediaElement.Play()方法从视图模型,除了具有视图模型的引用视图(或者直接到MediaElement的)?
其他(链接)问题:我怎么能管理View的控件从视图模型知名度不违反MVVM模式?

Answer 1:

1)不要调用Play()从视图模型。 提高在视图模型的事件,而不是(例如PlayRequested ),并听取这一事件的看法:

视图模型:

public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
    this.PlayRequested(this, EventArgs.Empty);
}

视图:

ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.PlayRequested += (sender, e) =>
{
    this.myMediaElement.Play();
};

2)您可以在视图模型公开公共布尔属性,以及绑定Visibility您的控件的属性,这个属性。 由于Visibility是类型的Visibility ,而不是bool ,你必须使用转换器。

你可以找到这样的转换器的基本实现在这里 。 此相关的问题可能会帮助你。



Answer 2:

对于所有的后来者,

有许多方法来达到同样的效果,它真的取决于你想如何实现你的,只要你的代码是不是很难维持,我相信它的确定打破在某些情况下MVVM模式。

不过话说回来,我也相信总会有办法的格局内做到这一点,下面是其中的一个以防万一,如果有人想知道还有什么其他替代品可供选择。

任务:

  1. 我们不希望有从视图模型到任何UI元素直接引用,即中的MediaElement和视图本身。
  2. 我们要使用命令在这里做魔术

解决方案:

总之,我们要介绍的视图和视图模型之间的界面,打破的关系是不,和视图将实现该接口并同时使视图模型谈论了这个接口负责MediaElement的直接控制,这如果需要的话,和来这里的长版可以与其他实施互换用于测试目的:

  1. 介绍如下称为IMediaService的界面:

     public interface IMediaService { void Play(); void Pause(); void Stop(); void Rewind(); void FastForward(); } 
  2. 实现在查看IMediaService:

     public partial class DemoView : UserControl, IMediaService { public DemoView() { InitializeComponent(); } void IMediaService.FastForward() { this.MediaPlayer.Position += TimeSpan.FromSeconds(10); } void IMediaService.Pause() { this.MediaPlayer.Pause(); } void IMediaService.Play() { this.MediaPlayer.Play(); } void IMediaService.Rewind() { this.MediaPlayer.Position -= TimeSpan.FromSeconds(10); } void IMediaService.Stop() { this.MediaPlayer.Stop(); } } 
  3. 我们再不要在DemoView.XAML几件事情:

    • 给一个MediaElement的名称,以便后面的代码可以像上面的访问:
      <MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/> 
    • 给视图的名称,所以我们可以把它作为一个参数,
    • 导入以备后用(一些默认的命名空间是出于简单的原因省略)的交互命名空间:
      <UserControl x:Class="Test.DemoView" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity" x:Name="MediaService"> 
    • 通过联播触发Loaded事件通过命令视图本身传递给视图模型
      <ia:Interaction.Triggers> <ia:EventTrigger EventName="Loaded"> <ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction> </ia:EventTrigger> </ia:Interaction.Triggers> 
    • 最后但并非最不重要的,我们需要通过命令来装媒体控制:
      <Button Command="{Binding PlayCommand}" Content="Play"></Button> <Button Command="{Binding PauseCommand}" Content="Pause"></Button> <Button Command="{Binding StopCommand}" Content="Stop"></Button> <Button Command="{Binding RewindCommand}" Content="Rewind"></Button> <Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button> 
  4. 现在,我们可以赶上在视图模型的一切(我在这里使用棱镜的DelegateCommand):

     public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest { public IMediaService {get; private set;} private DelegateCommand<IMediaService> loadedCommand; public DelegateCommand<IMediaService> LoadedCommand { get { if (this.loadedCommand == null) { this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) => { this.MediaService = mediaService; }); } return loadedCommand; } } private DelegateCommand playCommand; public DelegateCommand PlayCommand { get { if (this.playCommand == null) { this.playCommand = new DelegateCommand(() => { this.MediaService.Play(); }); } return playCommand; } } . . // other commands are not listed, but you get the idea . } 

附注:我用的棱镜的自动布线功能连接起来,查看和视图模型。 因此,在查看的隐藏文件的代码是没有的DataContext分配代码,我宁愿保持这种方式,因此我选择使用纯粹的命令来实现这一结果。



Answer 3:

我使用的媒体元素,每当一个事件在应用程序发生在UI播放声音。 处理这个视图模型,用类型的来源属性URI创建(含通知属性更改,但你已经知道你需要通知UI)。

所有你需要做的,每当源的变化(这是由你),是设置源属性设置为null(这就是为什么来源属性应该是开放的,而不是字符串的MediaElement自然会抛出异常,引发NotSupportedException我认为),那么它设置为任何你想要的URI。

也许,这种尖端的最重要的方面是,你要设置的MediaElement的属性LoadedBehaviour在视图的XAML播放。 但愿是需要你想达到什么没有后面的代码。

诀窍是非常简单的,所以我将不会发布一个完整的例子。 视图模型的播放功能应该是这样的:

    private void PlaySomething(string fileUri)
    {
        if (string.IsNullOrWhiteSpace(fileUri))
            return;
        // HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI. 
        this.Source = null;
        this.Source = new Uri(fileUri);
    }

这里是源属性,没什么特别的地方:

    #region Source property

    /// <summary>
    /// Stores Source value.
    /// </summary>
    private Uri _Source = null;

    /// <summary>
    /// Gets or sets file URI to play.
    /// </summary>
    public Uri Source
    {
        get { return this._Source; }
        private set
        {
            if (this._Source != value)
            {
                this._Source = value;
                this.RaisePropertyChanged("Source");
            }
        }
    }

    #endregion Source property

至于知名度,这样的东西,你可以使用转换器(例如,从布尔能见度,你可以找到在CodePlex上的WPF,SL,WP7,8)和控件的属性绑定到该视图模型(如ISVISIBLE)的。 通过这种方式,您控制部件视图的方面。 或者,你可以有Visibility属性类型System.Windows.Visibility您的视图模型(我在这里看不到任何图案违约)。 真的,它不是少见。

祝好运,

安德烈

PS我不得不提的是.NET 4.5是我测试过的版本,但我认为它应该在其他版本上正常工作。



文章来源: MVVM pattern violation: MediaElement.Play()