MVVM pattern violation: MediaElement.Play()

前端 未结 3 553
南旧
南旧 2020-12-02 23:55

I understand that ViewModel shouldn\'t have any knowledge of View, but how can I call MediaElement.Play() method from ViewModel, other than having a reference to View (or di

3条回答
  •  清歌不尽
    2020-12-03 00:21

    For all the late-comers,

    There are many ways to achieve the same result and it really depends on how you would like to implement yours, as long as your code is not difficult to maintain, I do believe it's ok to break the MVVM pattern under certain cases.

    But having said that, I also believe there is always way to do this within the pattern, and the following is one of them just in case if anyone would like to know what other alternatives are available.

    The Tasks:

    1. we don't want to have direct reference from the ViewModel to any UI elements, i.e. the the MediaElement and the View itself.
    2. we want to use Command to do the magic here

    The Solution:

    In short, we are going to introduce an interface between the View and the ViewModel to break the dependecy, and the View will be implementing the interface and be responsible for the direct controlling of the MediaElement while leaving the ViewModel talking only to the interface, which can be swapped with other implementation for testing purposes if needed, and here comes the long version:

    1. Introduce an interface called IMediaService as below:

      public interface IMediaService
      {
          void Play();
          void Pause();
          void Stop();
          void Rewind();
          void FastForward();
      }
      
    2. Implement the IMediaService in the View:

      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. we then do few things in the DemoView.XAML:

      • Give the MediaElement a name so the code behind can access it like above:
         
      
      • Give the view a name so we can pass it as a parameter, and
      • import the interactivity namespace for later use (some default namespaces are omitted for simplicity reason):
         
      
      • Hookup the Loaded event through Trigger to pass the view itself to the view model through a Command
         
               
                   
               
           
      
      • last but not least, we need to hookup the media controls through Commands:
          
          
          
          
          
      
    4. We now can catch everything in the ViewModel (I'm using prism's DelegateCommand here):

      public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest
      {
          public IMediaService {get; private set;}
      
          private DelegateCommand loadedCommand;
          public DelegateCommand LoadedCommand
          {
              get
              {
                  if (this.loadedCommand == null)
                  {
                      this.loadedCommand = new DelegateCommand((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
          .
      }
      

    Side note: I use Prism's Auto Wiring feature to link up the View and ViewModel. So at the View's code behind file there is no DataContext assignment code, and I prefer to keep it that way, and hence I chose to use purely Commands to achieve this result.

提交回复
热议问题