MVVM模式违规:MediaElement.Play()

ita*_*sso 18 wpf visibility mediaelement mvvm

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

ken*_*n2k 25

1)不要Play()从视图模型中调用.改为在视图模型中引发事件(例如PlayRequested)并在视图中监听此事件:

查看模型:

public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
    this.PlayRequested(this, EventArgs.Empty);
}
Run Code Online (Sandbox Code Playgroud)

视图:

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

2)您可以在视图模型中公开一个公共布尔属性,并将Visibility控件的属性绑定到此属性.由于Visibility类型Visibility而不是bool,您将不得不使用转换器.

您可以在此处找到此类转换器的基本实现.这个相关问题也可能对您有所帮助.

  • 更好地使用bool与转换器. (2认同)
  • @italianogrosso欢迎你:)但是你不应该公开一个类型为`Visibility`的属性.此枚举位于`System.Windows`命名空间中,如命名空间所示 - 表示它与应用程序的视图侧完全相关.实际上,即使它需要更多代码,最好还是暴露一个与视图无关的布尔值. (2认同)

小智 12

对于所有后来者来说,

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

但话虽如此,我也相信在这种模式中始终有这样做的方式,以下是其中之一,以防万一有人想知道其他替代方案是否可用.

任务:

  1. 我们不希望从ViewModel直接引用任何UI元素,即MediaElement和View本身.
  2. 我们想在这里使用Command来做魔术

解决方案:

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

  1. 引入一个名为IMediaService的接口,如下所示:

    public interface IMediaService
    {
        void Play();
        void Pause();
        void Stop();
        void Rewind();
        void FastForward();
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 然后我们在DemoView.XAML中做一些事情:

    • 为MediaElement命名,以便后面的代码可以像上面一样访问它:
       <MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
    
    Run Code Online (Sandbox Code Playgroud)
    • 为视图命名,以便我们可以将其作为参数传递,并且
    • 导入交互命名空间以供以后使用(出于简单原因,省略了一些默认命名空间):
       <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">
    
    Run Code Online (Sandbox Code Playgroud)
    • 通过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>
    
    Run Code Online (Sandbox Code Playgroud)
    • 最后但并非最不重要的是,我们需要通过命令连接媒体控件:
       <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> 
    
    Run Code Online (Sandbox Code Playgroud)
  4. 我们现在可以捕获ViewModel中的所有内容(我在这里使用prism的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
        .
    }
    
    Run Code Online (Sandbox Code Playgroud)

附注:我使用Prism的自动连线功能来链接View和ViewModel.所以在View的代码隐藏文件中没有DataContext赋值代码,我更喜欢保持这种方式,因此我选择使用纯命令来实现这个结果.