MVVM模式--游戲地圖編輯器開發隨筆(3)

相較于上一篇的數據綁定問題,這次也提及相關的問題,只不過是在View層面追溯到ViewModel的綁定問題。這次主要是關于RelayCommand,以及數據綁定的問題。

在實際運用之中,我們時常需要把一個程序的一個空間包裝成一個用戶控件來單獨編寫,再添加到主窗體之中,不少這種用戶控件內部都需要有指令(Command)來觸發一系列動作,但是主Model和主ViewModel都在MainWindow的工程之中,這就涉及到用戶控件與主窗體在MVVM模式中事件的觸發與綁定。
首先假設我們有一個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負責讀取總體的Model以及所有MenuControl的邏輯。
現在架設一個情況,我們在點擊Menu里的一個MenuItem之后需要出發一個指令,改變一個值,并且通知主窗體來完成一些邏輯。在之前的文章中我們已經說過,值在用戶控件與主窗體之間的綁定依靠的是暴露成依賴屬性來綁定到主窗體的ViewModel上,通過設定綁定的Mode來達到各種效果,在此就不貼代碼了。那么問題就來了,UserControl本身就是一個黑盒,他自身內部的ViewModel實現的邏輯并不能與主窗體進行交互。這個時候采取一個比較巧妙的辦法,我們在UserControl的后臺文件(MenuControl.xaml.cs)后臺定義一個事件,在View中觸發這個指令的時候我們讓這個指令執行這個事件,自定義路由事件(RoutedEvent)是與依賴屬性一樣暴露在控件之外的,可以與ViewModel中的屬性綁定,再通過MVVM模式中EventToCommand標簽來把事件轉化為指令,再將這個指令與總窗體的ViewModel里的RelayCommand所綁定,就達到的傳出的目的:

        <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之后逐步復位才找出。
假設在主窗體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");
            });
         }
     }

在這種情況下觸發這個RelayCommand的時候這個指令并不會被執行,這個是MVVM不支持弱引用的一個BUG,不能閉包引用外圍變量,需要在內部定義,這個bug在5.4.0中得到了解決,但是5.4.0一直是alpha版本,詳情見MVVM ToolKit

===========================================================
接下來繼續討論MenuControl內部的問題,我們在上一篇說過動態綁定數據并顯示的方法,但是運用到目錄中就會遇到問題,先看代碼:

    <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>

按照我們上次說的,這個代碼已經完美的完成了綁定,的確他可以很好的顯示動態數據的內容,但是你會發現指令并不會被執行。因為在MenuItem處已經指定了ItemsSource的內容,下面所有的綁定內容都會從ItemsSource綁定的集合中尋找,所以Header能夠直接綁定元素名稱,但是內部并沒有MenuCommand這個東西。在這里需要用到RelativeSource的內容,先貼代碼:

            <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就行了,其實是很簡單的問題,主要看書。
引以為戒。

歡迎指正

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容