2015-06-03 110 views
1

我正在使用MVVM Light toolkit框架開發基於Model-View-ViewModel模式原則的體系結構的WPF應用程序。如何使用ViewModel-First方法從ViewModel調用View方法

下面的XAML代碼例如我視圖查看模型關係:

<... .Resources> 
    <DataTemplate DataType="{x:Type viewm:MediaElementViewModel}"> 
     <view:MediaElement/> 
    </DataTemplate> 
</... .Resources> 

我知道這是可能使用View-First approach從視圖模型視圖的方法,通過構建具體MediaElement當分配MediaElement實例的DataContext屬性來調用,不幸的是,這不是我的解決方案。

查看方法,例如,是MediaElement,如Play(),Pause(),Focuse()或任何其他「純」的UI方法。

非常感謝。

+2

如果你絕對需要這樣做,我建議使用MVVM Light Messenger類從虛擬機發送消息,在View代碼隱藏中接收消息並採取適當的操作。 *需要的情況非常少見*如果您只是想在視圖之間切換,那麼這種方法* *更好:https://rachel53461.wordpress.com/2011/05/28/switching- between-viewsusercontrols-using-mvvm。 – goobering

+1

你想從你的Viewmodel調用視圖中的.close()方法嗎?如果是的話爲什麼不在你的虛擬機中創建一個事件並在你的視圖中訂閱這個事件 – blindmeis

+0

@goobering感謝你的回答,MVVM Light Messenger確實是一個推薦的工具,不幸的是,這個工具以靜態方式發送消息_,這意味着如果我具有相同ViewModel的許多實例,這是我使用ViewModel-First_的原因之一,消息傳遞將導致重複,跨實例和錯誤結果。不小心,[我已經問過關於切換視圖](http://stackoverflow.com/questions/30232697/how-to-cache-dynamically-switched-views-by-viewmodel-first-approach-using-datate),並找到它爲我提供了比您提供的鏈接更好的解決方案。 – Eido95

回答

0

我發現了一個可行的解決方案,我會保留一個答案,直到有更好的解決方案出現。

解決方案的基礎是定製的Behavior Generic Class

重要的是要注意,問題和答案在全球範圍內,而不是MediaElement的範圍。因此,該解決方案與支持ViewModel優先方法的每個FrameworkElement衍生控件完全相關。

我沒有刪除任何代碼,目的明確。

ViewTemplates.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:WpfApplication2" 
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"> 
    <DataTemplate DataType="{x:Type local:MediaElementViewModel}"> 
     <MediaElement Source="{Binding Source}" Volume="{Binding Volume}" 
         LoadedBehavior="Manual" UnloadedBehavior="Manual"> 
      <i:Interaction.Behaviors> 
       <local:MediaElementBehavior/> 
      </i:Interaction.Behaviors> 
     </MediaElement> 
    </DataTemplate> 
</ResourceDictionary> 

MediaElementViewModel.cs

public MediaElementViewModel() 
{ 
    Volume = 0.5; 
} 

private Uri _source; 
public Uri Source 
{ 
    get { return _source; } 
    set 
    { 
     _source = value; 
     RaisePropertyChanged("Source"); 
    } 
} 

private double _volume; 
public double Volume 
{ 
    get { return _volume; } 
    set 
    { 
     _volume = value; 
     RaisePropertyChanged("Volume"); 
    } 
} 

public Action Play { get; set; } 
public Action Stop { get; set; } 
public Func<bool> Focus { get; set; } 

MediaElementBehavior.cs

public MediaElementBehavior() 
{ 
} 

protected override void OnAttached() 
{ 
    base.OnAttached(); 

    MediaElement player = (MediaElement)this.AssociatedObject; 
    MediaElementViewModel viewModel = (MediaElementViewModel)this.AssociatedObject.DataContext; 

    player.Dispatcher.Invoke(() => 
    { 
     // backing up the player methods inside its view-model. 
     if (viewModel.Play == null) 
      viewModel.Play = player.Play; 

     if (viewModel.Stop == null) 
      viewModel.Stop = player.Stop; 

     if (viewModel.Focus == null) 
      viewModel.Focus = player.Focus; 
    }); 
} 

protected override void OnDetaching() 
{ 
    base.OnDetaching(); 
} 

以下是上述溶液的使用的一個示例:

的App.xaml

<Application x:Class="WpfApplication2.App" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      StartupUri="MainWindow.xaml"> 
    <Application.Resources> 
     <ResourceDictionary> 
      <ResourceDictionary.MergedDictionaries> 
       <ResourceDictionary Source="ViewTemplates.xaml"/> 
      </ResourceDictionary.MergedDictionaries> 
     </ResourceDictionary> 
    </Application.Resources> 
</Application> 

主窗口。XAML

<Window x:Class="WpfApplication2.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:local="clr-namespace:WpfApplication2" 
     Title="MainWindow" Height="350" Width="525"> 

    <Window.DataContext> 
     <local:MainViewModel/> 
    </Window.DataContext> 

    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition/> 
      <RowDefinition/> 
      <RowDefinition/> 
     </Grid.RowDefinitions> 

     <Grid.ColumnDefinitions> 
      <ColumnDefinition/> 
      <ColumnDefinition/> 
      <ColumnDefinition/> 
     </Grid.ColumnDefinitions> 
     <ContentControl Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding CurrentMediaElement}"/> 
     <Button Grid.Row="1" Grid.Column="0" Content="Set Source" Command="{Binding SetSourceCommand}"/> 
     <WrapPanel Grid.Row="1" Grid.Column="2"> 
      <Button Grid.Row="1" Grid.Column="2" Content="Stop" Command="{Binding StopCommand}"/> 
      <Button Content="Focus" Command="{Binding FocusCommand}"/> 
     </WrapPanel> 

     <Button Grid.Row="1" Grid.Column="1" Content="Play" Command="{Binding PlayCommand}"/> 
     <TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding CurrentMediaElement.Source}" TextWrapping="Wrap"/> 
     <Label Grid.Row="2" Grid.Column="1" Content="{Binding ElementName=SliderVolume, Path=Value}"/> 
     <Slider x:Name="SliderVolume" Value="{Binding CurrentMediaElement.Volume}" Grid.Row="2" Grid.Column="2" Minimum="0" Maximum="1" Orientation="Horizontal"/> 
    </Grid> 
</Window> 

MainViewModel.cs

public MainViewModel() 
{ 
    CurrentMediaElement = new MediaElementViewModel(); 
} 

private MediaElementViewModel _currentMediaElement; 
public MediaElementViewModel CurrentMediaElement 
{ 
    get { return _currentMediaElement; } 
    set 
    { 
     _currentMediaElement = value; 
     RaisePropertyChanged("CurrentMediaElement"); 
    } 
} 

private RelayCommand _setSourceCommand; 
public ICommand SetSourceCommand 
{ 
    get 
    { 
     return _setSourceCommand ?? 
       (_setSourceCommand = new RelayCommand(SetSourceExecute)); 
    } 
} 
private RelayCommand _playCommand; 
public ICommand PlayCommand 
{ 
    get 
    { 
     return _playCommand ?? 
       (_playCommand = new RelayCommand(PlayExecute)); 
    } 
} 
private RelayCommand _stopCommand; 
public ICommand StopCommand 
{ 
    get 
    { 
     return _stopCommand ?? 
       (_stopCommand = new RelayCommand(StopExecute)); 
    } 
} 
private RelayCommand _focusCommand; 
public ICommand FocusCommand 
{ 
    get 
    { 
     return _focusCommand ?? 
      (_focusCommand = new RelayCommand(FocusExecute)); 
    } 
} 

/// <summary> 
/// Invoked whenever focusing media element; 
/// </summary> 
private void FocusExecute() 
{ 
    bool isFocused = this.CurrentMediaElement.Focus(); 
} 

/// <summary> 
/// Invoked whenever setting a media source. 
/// </summary> 
private void SetSourceExecute() 
{ 
    // Assume the media file location is Debug/bin/Resources/ 
    this.CurrentMediaElement.Source = new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\media.mp3"); 
} 
/// <summary> 
/// Invoked whenever playing media. 
/// </summary> 
private void PlayExecute() 
{ 
    this.CurrentMediaElement.Play(); 
} 
/// <summary> 
/// Invoked whenerver stopping media. 
/// </summary> 
private void StopExecute() 
{ 
    this.CurrentMediaElement.Stop(); 
} 

正如你可以看到(在MainViewModel.cs),我從調用視圖模型視圖模型使用優先方法 「純」 查看方式。 (PlayExecute,StopExecuteFocusExecute)。

0

您的視圖模型被視圖引用,但沒有內在知識或對其的訪問。

您的建議方法正在顯示和關閉,通過綁定到可見性屬性的視圖模型來處理此問題將非常簡單。

其他可能使用的概念是數據觸發器,它允許您通過動畫行爲更新視圖,設置屬性或觸發許多條件。

+1

根據我上面的評論,可以使用類似Messenger的類來與VM中的View代碼隱藏進行通信,同時仍保持它們之間的分離。還有其他方法,例如:http://stackoverflow.com/questions/10631748/mvvm-pattern-violation-mediaelement-play。按照這個例子,這可能對於像MediaElements這樣的事情來說是必要的。 – goobering

+0

但這不是mvvm。消息框架有其自己的位置,我過去曾經使用這個框架 - 我會警告說單元測試和調試非常棘手。它還增加了開銷,可能會非常快地泄漏內存和性能。只要你瞭解限制條件,那麼它就是一種選擇 - 但是一般的做法是把它作爲被批評的答案。我也會質疑爲什麼要記下你自己。 – kidshaw

+0

發送違反MVVM的消息沒有任何內容 - 虛擬機保持完全不依賴於View,並且在沒有View的情況下編譯會非常愉快。即使不使用Messenger類,我鏈接的第二種方法也提供了一種替代方法,通過該方法View代碼隱藏可以從VM接收狀態更改,而不會中斷MVVM。調試棘手,添加開銷和內存泄漏本身並不違反MVVM。你的答案的開頭句子是「這是不可能的,如果不打破mvvm模式,這是錯誤的*,所以我低估了它。 – goobering

相關問題