2012-05-17 62 views
14

我明白,ViewModel不應該有任何關於視圖的知識,但我怎麼能從ViewModel調用MediaElement.Play()方法,除了有一個引用視圖(或直接MediaElement)在ViewModel中?
其他(鏈接)的問題:如何管理視圖的ViewModel控件可見性而不違反MVVM模式?MVVM模式違規:MediaElement.Play()

+0

鏈接的問題是不存在的.. :( – rydev

回答

22

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屬性綁定到此屬性。由於VisibilityVisibility類型,而不是bool,因此您必須使用轉換器。

你可以找到這種轉換器的基本實現here。 這related question也可能會幫助你。

+0

非常感謝!PS:無需轉換,如果我公開可見性屬性,而不是一個bool一個 – italianogrosso

+2

更好地利用布爾與轉換器的 – Zabavsky

+2

@italianogrosso不客氣。 :)但是你不應該暴露一個'Visibility'類型的屬性。這個枚舉位於'System.Windows'命名空間中,正如命名空間所說 - 它意味着它完全與應用程序的視圖方面相關。真的,即使它需要更多的代碼,最好公開一個與視圖無關的布爾值。 – ken2k

4

當應用程序中發生事件時,我使用媒體元素在UI中播放聲音。處理這個的視圖模型是使用Uri類型的Source屬性創建的(通知屬性已更改,但您已經知道需要通知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 

至於知名度,這樣的東西,你可以使用轉換器(例如從bool到可見性,您可以在CodePlex上找到WPF,SL,WP7,8),並將控件的屬性與視圖模型的屬性(例如IsVisible)綁定。這樣,你可以控制部分視圖的方面。或者你可以在視圖模型上使用Visibility屬性類型System.Windows.Visibility(我在這裏沒有看到任何模式違規)。真的,這並不罕見。

祝你好運,

安德烈

附:我必須提到的是,.NET 4.5是我測試過的版本,但我認爲它也適用於其他版本。

8

對於所有的後來者,

有很多方法來達到同樣的效果,它真的取決於你想如何實現你的,只要你的代碼是不是難以維持,怎麼辦相信在某些情況下可以打破MVVM模式。

但說了這麼多,我也相信在模式中總有辦法做到這一點,以下是其中之一,以防萬一任何人想知道其他替代品是否可用。

的任務:

  1. 我們不希望有從視圖模型到任何UI元素,即中的MediaElement和視圖本身直接引用。
  2. 我們要使用命令在這裏做

解決方案的魔力:

總之,我們要介紹的視圖和視圖模型之間的界面,打破的關係是不,View將實現接口並負責直接控制MediaElement,同時讓ViewModel只與接口交流,如果需要的話可以與其他實現交換以用於測試目的,下面是長版本:

  1. 介紹叫IMediaService的界面如下:

    public interface IMediaService 
    { 
        void Play(); 
        void Pause(); 
        void Stop(); 
        void Rewind(); 
        void FastForward(); 
    } 
    
  2. 在View落實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> 
    
    • 最後但並非通過視圖本身向視圖模型至少,我們需要通過命令okup媒體控件:
    <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 
        . 
    } 
    

方注意:我使用Prism的自動佈線功能來連接View和ViewModel。所以在View的代碼隱藏文件中沒有DataContext賦值代碼,我更喜歡這樣保留,因此我選擇使用純粹的Commands來實現這個結果。