MVVM模式违规:MediaElement.Play()

时间:2012-05-17 07:46:11

标签: wpf mvvm visibility mediaelement

我理解ViewModel不应该具有View的任何知识,但是我如何从ViewModel调用MediaElement.Play()方法,而不是在ViewModel中引用View(或直接到MediaElement)? 其他(链接)问题:如何在不违反MVVM模式的情况下从ViewModel管理View的控件可见性?

3 个答案:

答案 0 :(得分:25)

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,因此您必须使用转换器。

您可以找到此类转换器here的基本实现。 这个related question也可能对您有帮助。

答案 1 :(得分:13)

对于所有后来者,

有很多方法可以实现相同的结果,这实际上取决于你希望如何实现你的结果,只要你的代码不难维护,我相信打破MVVM模式是可以的在某些情况下。

但话虽如此,我也相信在这种模式中总有办法做到这一点,以下是其中之一,以防万一有人想知道其他替代方案可用。

任务:

  
      
  1. 我们不希望从ViewModel直接引用任何UI元素,即MediaElement和View本身。
  2.   
  3. 我们希望使用Command来实现这里的魔力
  4.   

解决方案:

  

简而言之,我们将在View和ViewModel之间引入一个接口以打破依赖性,View将实现接口并负责直接控制MediaElement,同时让ViewModel只与接口,如果需要,可以与其他实现交换以进行测试,这里有长版本:

  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">
    
    • 通过Trigger连接Loaded事件,通过Command
    • 将视图本身传递给视图模型
       <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. 我们现在可以捕获ViewModel中的所有内容(我在这里使用prism&#39的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
        .
    }
    
  5. 附注:我使用Prism的自动连线功能来链接View和ViewModel。因此,在View的代码隐藏文件中没有DataContext分配代码,我更喜欢保持这种方式,因此我选择使用纯命令来实现此结果。

答案 2 :(得分:4)

每当应用程序中发生事件时,我都会使用media元素在UI中播放声音。处理此问题的视图模型是使用类型为Uri的Source属性创建的(更改了通知属性,但您已经知道需要通知UI)。

每当源更改(这取决于你)时你所要做的就是将source属性设置为null(这就是为什么Source属性应该是Uri而不是string,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);
    }

这是Source属性,没什么特别的:

    #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

对于可见性和类似的东西,你可以使用转换器(例如从bool到可见性,你可以在CodePlex for WPF,SL,WP7,8上找到)并将你的控件的属性绑定到视图模型的属性(例如IsVisible)。这样,您可以控制视图的各个方面。或者您可以在视图模型上使用Visibility属性键入System.Windows.Visibility(我没有看到任何模式违规)。真的,这并不罕见。

祝你好运,

安德烈

P.S。我不得不提到.NET 4.5是我测试过的版本,但我认为它也应该适用于其他版本。