Messenger
Mvvm提倡View和ViewModel的分離,View只負責數據的顯示,業務邏輯都盡可能放到ViewModel中,
保持View.xaml.cs中的簡潔(沒有任何代碼,除了構造函數),但是某些場景下也不必一定要保持
View.xaml.cs中的簡潔,例如動畫。我們想要讓界面酷炫一點,就需要故事版,故事版中必然有與
控件相關的,動畫和界面耦合很緊,並且也沒有辦法分離(或許有呢),我們大可直接將動畫的邏輯
就放置到View的后台代碼中,動畫的觸發條件由ViewModel發出,這里我們就要借助Messenger來完成
消息的傳遞。不僅View和ViewModel可以通過消息傳遞,ViewModel和ViewModel也需要通過消息傳遞
來完成一些交互。
Messenger的使用首先要注冊消息,消息的標志是什么,消息接受的參數是什么,收到消息后執行什么
操作,然后是發送消息,向哪個消息發送信息,參數是什么。使用上和事件的訂閱,事件的觸發是一樣的。
ViewModel之間通信
這個例子中,我們打開兩個窗口(注意是同一個程序中,當初接觸的時候不了解其原理,以為是Windows通信
機制中的消息通信,還傻傻的打開兩個Application,讓他們通信),窗口2向窗口1發送消息,窗口1顯示接受到的消息。
我們先來看窗口1是怎么注冊消息的
AppView1.xaml
<Window x:Class="MessengerDemo.Views.AppView1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MessengerDemo.Views" DataContext="{Binding Source={StaticResource Locator},Path=View1}"
mc:Ignorable="d"
Title="AppView1" Height="300" Width="300">
<Grid>
<TextBlock Text="{Binding Msg}"></TextBlock>
</Grid>
</Window>
AppView1Model.cs
public class AppView1Model : ViewModelBase
{
private string _msg;
public string Msg
{
get
{
return _msg;
}
set
{
_msg = value;
RaisePropertyChanged(() => Msg);
}
}
public AppView1Model()
{
Messenger.Default.Register<string>(this, MessageToken.SendMessageToken, (msg) =>
{
Msg = msg;
});
}
}
注意:這里使用了一個靜態類MessageToken,它的作用是定義消息標志,也是通過它區分不同的消息
MessageToken.cs
public static class MessageToken
{
/// <summary>
/// 動畫信息標志
/// </summary>
public static readonly string AnimateMessageToken;
/// <summary>
/// 發送消息標志
/// </summary>
public static readonly string SendMessageToken;
static MessageToken()
{
AnimateMessageToken = nameof(AnimateMessageToken);
SendMessageToken = nameof(SendMessageToken);
}
}
這里定義了2個消息類型,一個用於動畫演示,一個用於發送消息。
我們再來看窗口2是怎么發送消息的
AppView2.xaml
<Window x:Class="MessengerDemo.Views.AppView2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MessengerDemo.Views" DataContext="{Binding Source={StaticResource Locator},Path=View2}"
mc:Ignorable="d"
Title="AppView2" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Msg}"></TextBox>
<Button Width="100" Height="30" Grid.Row="1" Content="Send" Command="{Binding SendCommand}"></Button>
</Grid>
</Window>
AppView2Model.cs
public class AppView2Model : ViewModelBase
{
private string _msg;
public string Msg
{
get
{
return _msg;
}
set
{
_msg = value;
RaisePropertyChanged(() => Msg);
}
}
public RelayCommand SendCommand
{
get; set;
}
public AppView2Model()
{
SendCommand = new RelayCommand(() =>
{
Messenger.Default.Send<string>(Msg, MessageToken.SendMessageToken);
});
}
}
這里使用的是同一個MessageToken,這樣調試的時候也方便查找。這里我們發送消息時,傳遞的參數是字符串,
這里可以傳遞任何類型。
View和ViewModel之間通信
在來看一個動畫的,動畫的邏輯都寫到了View的后台代碼中,ViewModel發送觸發動畫的消息
AppView1.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Msg}"></TextBlock>
<Button Width="100" Height="30" Grid.Row="1" Content="執行動畫" x:Name="btn" Command="{Binding AnimateCommand}">
<Button.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Button.RenderTransform>
</Button>
</Grid>
AppView1.xaml.cs
public partial class AppView1 : Window
{
private Storyboard _storyboard;
public AppView1()
{
InitializeComponent();
_storyboard = new Storyboard();
DoubleAnimation doubleAnimation = new DoubleAnimation(0, 180, new Duration(new TimeSpan(0, 0, 2)));
Storyboard.SetTarget(doubleAnimation, btn);
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"));
_storyboard.Children.Add(doubleAnimation);
Messenger.Default.Register<string>(this, MessageToken.AnimateMessageToken, (msg) =>
{
_storyboard.Begin();
});
}
}
AppView1Model.cs
public class AppView1Model : ViewModelBase
{
public RelayCommand AnimateCommand
{
get; set;
}
public AppView1Model()
{
AnimateCommand = new RelayCommand(() =>
{
Messenger.Default.Send<string>("", MessageToken.AnimateMessageToken);
});
}
}
當然,動畫還是使用Blend編寫要舒服一些。有了Messenger,使得ViewModel和ViewModel之間一定的
解耦,可以處理更復雜的情況。