相較于上一篇的數(shù)據(jù)綁定問題稼稿,這次也提及相關(guān)的問題苔咪,只不過是在View層面追溯到ViewModel的綁定問題漓概。這次主要是關(guān)于RelayCommand乏屯,以及數(shù)據(jù)綁定的問題根时。
在實際運(yùn)用之中,我們時常需要把一個程序的一個空間包裝成一個用戶控件來單獨(dú)編寫辰晕,再添加到主窗體之中蛤迎,不少這種用戶控件內(nèi)部都需要有指令(Command)來觸發(fā)一系列動作,但是主Model和主ViewModel都在MainWindow的工程之中含友,這就涉及到用戶控件與主窗體在MVVM模式中事件的觸發(fā)與綁定替裆。
首先假設(shè)我們有一個Menu的UserControl,這個用戶控件里面只有一個Menu窘问,然后把這個工程引用到主窗體之中辆童,并且把空間命名為uc,那么它在主窗體的引用如下:
<uc:MenuControl Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3"
DataContext="{Binding Menu}">
<!-- Others -->
</uc:MenuControl>
十分簡單明了的引用惠赫,其中DataContext綁定的MenuControl在主窗體之中的ViewModel把鉴,這個ViewModel負(fù)責(zé)讀取總體的Model以及所有MenuControl的邏輯。
現(xiàn)在架設(shè)一個情況汉形,我們在點擊Menu里的一個MenuItem之后需要出發(fā)一個指令纸镊,改變一個值,并且通知主窗體來完成一些邏輯概疆。在之前的文章中我們已經(jīng)說過逗威,值在用戶控件與主窗體之間的綁定依靠的是暴露成依賴屬性來綁定到主窗體的ViewModel上,通過設(shè)定綁定的Mode來達(dá)到各種效果岔冀,在此就不貼代碼了凯旭。那么問題就來了,UserControl本身就是一個黑盒使套,他自身內(nèi)部的ViewModel實現(xiàn)的邏輯并不能與主窗體進(jìn)行交互罐呼。這個時候采取一個比較巧妙的辦法,我們在UserControl的后臺文件(MenuControl.xaml.cs)后臺定義一個事件侦高,在View中觸發(fā)這個指令的時候我們讓這個指令執(zhí)行這個事件嫉柴,自定義路由事件(RoutedEvent)是與依賴屬性一樣暴露在控件之外的,可以與ViewModel中的屬性綁定奉呛,再通過MVVM模式中EventToCommand標(biāo)簽來把事件轉(zhuǎn)化為指令计螺,再將這個指令與總窗體的ViewModel里的RelayCommand所綁定,就達(dá)到的傳出的目的:
<uc:MenuControl Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3"
DataContext="{Binding Menu}">
<i:EventTrigger EventName="EventInMenuControl">
<command:EventToCommand Command="{Binding RelayCommandInMainWindow}"></command:EventToCommand>
</i:EventTrigger>
</uc:MenuControl>
其中在后臺代碼中定義路由事件:
public event EventHandler EventInMenuControl
{
add { AddHandler(EventInMenuControlEvent, value); }
remove { RemoveHandler(EventInMenuControlEvent, value); }
}
public static readonly RoutedEvent EventInMenuControlEvent = EventManager.RegisterRoutedEvent(
"EventInMenuControl", RoutingStrategy.Bubble, typeof(EventHandler), typeof(MapEditorControl));
而在綁定到主窗體的RelayCommand的時候有一個MVVM小bug瞧壮,一開始我遇到也是百思不得其解登馒,后來Revert之后逐步復(fù)位才找出。
假設(shè)在主窗體MenuViewModel.cs文件中定義的RelayCommandInMainWindow如下:
public class MenuViewModel : ViewModelBase
{
public RelayCommand RelayCommandInMainWindow { get; set; }
private XmlDocument File;
public MenuViewModel()
{
RelayCommandInMainWindow = new RelayCommand(() =>
{
File = new XmlDocument();
File.Load("D:\\File.xml");
});
}
}
在這種情況下觸發(fā)這個RelayCommand的時候這個指令并不會被執(zhí)行咆槽,這個是MVVM不支持弱引用的一個BUG陈轿,不能閉包引用外圍變量,需要在內(nèi)部定義秦忿,這個bug在5.4.0中得到了解決麦射,但是5.4.0一直是alpha版本,詳情見MVVM ToolKit灯谣。
===========================================================
接下來繼續(xù)討論MenuControl內(nèi)部的問題法褥,我們在上一篇說過動態(tài)綁定數(shù)據(jù)并顯示的方法,但是運(yùn)用到目錄中就會遇到問題酬屉,先看代碼:
<Menu Margin="0,0,0,0" SnapsToDevicePixels="True" DataContext="{Binding Source={StaticResource Locator}, Path=MenuControl}">
<MenuItem Header="Test " ItemsSource="{Binding MenuItem}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding MenuItemText}" Command="{Binding MenuCommand}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
按照我們上次說的半等,這個代碼已經(jīng)完美的完成了綁定,的確他可以很好的顯示動態(tài)數(shù)據(jù)的內(nèi)容呐萨,但是你會發(fā)現(xiàn)指令并不會被執(zhí)行杀饵。因為在MenuItem處已經(jīng)指定了ItemsSource的內(nèi)容,下面所有的綁定內(nèi)容都會從ItemsSource綁定的集合中尋找谬擦,所以Header能夠直接綁定元素名稱切距,但是內(nèi)部并沒有MenuCommand這個東西。在這里需要用到RelativeSource的內(nèi)容惨远,先貼代碼:
<MenuItem Header="Test" ItemsSource="{Binding MenuItem}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command"
Value="{Binding DataContext.MenuCommand,
RelativeSource={RelativeSource AncestorType={x:Type MenuItem},
Mode=FindAncestor, AncestorLevel=1}}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
用RelativeSource尋找Item的祖先谜悟,把對象定義為Menu中的一個MenuItem话肖,然后綁定到這個資源的DataContext.MenuCommand就行了,其實是很簡單的問題葡幸,主要看書最筒。
引以為戒。
歡迎指正