65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight DataTrigger 是解决 ViewModel / MVVM 问题的答案

starIconstarIconstarIconstarIconstarIcon

5.00/5 (32投票s)

2010年11月5日

Ms-PL

4分钟阅读

viewsIcon

143859

downloadIcon

1463

使用 Silverlight DataTrigger 如何在 View Model (MVVM) 中运行一个进程然后触发另一个。

Silverlight Data Trigger 可能是解决您所有 ViewModel MVVM 问题的方法

实时示例: http://silverlight.adefwebserver.com/ViewModelTreeControl2

(注意:如果您是 ViewModel (MVVM) 新手,请参见:Silverlight ViewModel 风格:一个(过于)简化的解释)

我发现您要么喜欢 ViewModel (MVVM),要么憎恨它。如果您憎恨它,我知道原因……使用代码隐藏更简单。 :) 代码隐藏主要允许您执行以下操作:A) 做一件事,然后 B) 做另一件事。ViewModel 样式编程的问题在于,虽然绑定在大多数情况下都很棒,但许多人发现使用 ViewModel 实现某些功能非常令人沮丧,有时甚至看似不可能,而这些功能通常很容易使用代码隐藏来完成。

在本文中,我希望向您展示,应该能解决您大多数 ViewModel (MVVM) 问题的关键工具是 Silverlight DataTrigger

问题

在文章 通过 ViewModel (MVVM) 以编程方式展开 Silverlight Tree View 控件节点 中,我展示了如何创建一个 Behavior,该 Behavior 可以在不使用代码隐藏的情况下展开选定的 Tree Node。问题是,它仅在指示 Tree Node 已选定的属性在 Behavior 运行之前设置时才有效。

使用代码隐藏,这不成问题。当用户单击 Button 时,代码会选择 Categories,然后代码会展开 Tree NodesViewModel 的问题在于,即使 Button 被设置为触发设置 Categories触发 Behavior 选择 Tree Nodes,但选定的 Categories 可能在 Behavior 运行之前未设置,因此 Behavior 将不会展开 Tree Nodes真是令人抓狂

解决方案

解决方案是仅在 Categories 被选中之后执行 Behavior

这可以通过在 Categories 被选中之后ViewModel 中设置一个 Boolean 属性来实现。Silverlight Expression Blend DataTrigger(如果您没有 Expression Blend,可以 在此链接 下载 SDK)用于将 Behavior 绑定到 ViewModel 中的属性,并将触发 Behavior 并展开 Tree Nodes

此解决方案解决您“正常的代码隐藏方式”的关键之处在于,当 SetCategoryCommand Button 触发时,您可以“运行所有您想要的​​代码然后再设置 ExpandSelectedTreeNodes 属性。

在普通代码隐藏中,您只能做到这些;运行代码,然后设置(或不设置)UI 上的值(在本例中为 View)。通常让人们对 ViewModel (MVVM) 感到抓狂的是,他们觉得他们失去了

  1. 单击一个 Button
  2. 运行代码
  3. 更改 UI
  4. 结束

现在您可以

  1. 单击一个 Button
  2. 运行代码
  3. 更改 ViewModel 中的属性
  4. Behavior(绑定到 ViewModel 中的属性)更改 UI
  5. 结束

示例应用程序

在示例应用程序中,我们有两个 Behaviors。一个用于展开 Tree Control (TreeExpandBehavior),另一个用于折叠它 (TreeCollapseBehavior)。

下面是 Behaviors

TreeExpandBehavior

[System.ComponentModel.Description("Expands Tree Node")]
public class TreeExpandBehavior : TargetedTriggerAction<TreeView>, INotifyPropertyChanged
{
    private TreeView objTreeView;

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

        objTreeView = (TreeView)(this.AssociatedObject);
    }

    protected override void Invoke(object parameter)
    {
        SelectNode();
    }

    void objTreeView_Loaded(object sender, RoutedEventArgs e)
    {
        SelectNode();
    }

    #region SelectNode
    private void SelectNode()
    {
        // Refresh Tree Control
        objTreeView.UpdateLayout();
        // Get the DataContext of the Tree Control
        MainPageViewModel objMainPageViewModel = 
		(MainPageViewModel)objTreeView.DataContext;

        // Get collection of items bound to Tree Control
        ObservableCollection<Category> colCategories = 
            (ObservableCollection<Category>)objMainPageViewModel.colCategory;

        if (colCategories != null)
        {
            // Loop through the top levels items
            foreach (var Cat in colCategories)
            {
                // Find the Node
                var result = (from objCategory in Cat.AllChildren()
                                where objCategory.IsSelected == true
                                select objCategory).FirstOrDefault();

                if (result != null)
                {
                    // Get the Tree Control node container for the item
                    TreeViewItem objTreeViewItem = (TreeViewItem)
			objTreeView.ItemContainerGenerator.ContainerFromItem(Cat);
                    // Expand the Node
                    objTreeViewItem.IsExpanded = true;

                    // Refresh Tree Control
                    objTreeView.UpdateLayout();

                    ExpandChildNode(objTreeViewItem, Cat);
                }
            }
        }
    }

    // This method expands child nodes
    private void ExpandChildNode(TreeViewItem objTreeViewItem, Category Cat)
    {
        // Loop through all the sub Categories
        foreach (var item in Cat.Categories)
        {
            // Find the Node
            var result = (from objCategory in item.AllChildren()
                            where objCategory.IsSelected == true
                            select objCategory).FirstOrDefault();

            if (result != null)
            {
                // Get the Tree Control node container for the item
                TreeViewItem SubTreeViewItem = (TreeViewItem)
		objTreeViewItem.ItemContainerGenerator.ContainerFromItem(item);
                // Expand the Node
                SubTreeViewItem.IsExpanded = true;

                // Refresh Tree Control
                objTreeView.UpdateLayout();

                // Recursively call the function                
                ExpandChildNode(SubTreeViewItem, item);
            }
        }
    }
    #endregion

    // Utility

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
    #endregion
}

TreeCollapseBehavior

[System.ComponentModel.Description("Collapse Tree Nodes")]
public class TreeCollapseBehavior : 
	TargetedTriggerAction<TreeView>, INotifyPropertyChanged
{
    private TreeView objTreeView;

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

        objTreeView = (TreeView)(this.AssociatedObject);
    }

    protected override void Invoke(object parameter)
    {
        CollapseTree();
    }

    void objTreeView_Loaded(object sender, RoutedEventArgs e)
    {

    }

    #region CollapseTree
    private void CollapseTree()
    {
        // Refresh Tree Control
        objTreeView.UpdateLayout();
        // Get the DataContext of the Tree Control
        MainPageViewModel objMainPageViewModel = 
            (MainPageViewModel)objTreeView.DataContext;

        // Get collection of items bound to Tree Control
        ObservableCollection<Category> colCategories = 
            (ObservableCollection<Category>)objMainPageViewModel.colCategory;

        if (colCategories != null)
        {
            // Loop through the top levels items
            foreach (var Cat in colCategories)
            {
                // Get Parent Node
                var result = (from objCategory in Cat.AllChildren()
                                select objCategory).FirstOrDefault();

                // Get the Tree Control node container for the item
                TreeViewItem objTreeViewItem = (TreeViewItem)
		objTreeView.ItemContainerGenerator.ContainerFromItem(Cat);

                if (objTreeViewItem != null)
                {
                    // Collapse the Node
                    objTreeViewItem.IsExpanded = false;

                    // Refresh Tree Control
                    objTreeView.UpdateLayout();

                    CollapseChildNode(objTreeViewItem, Cat);
                }
            }
        }
    }

    // This method collapses child nodes
    private void CollapseChildNode(TreeViewItem objTreeViewItem, Category Cat)
    {
        // Loop through all the sub Categories
        foreach (var item in Cat.Categories)
        {
            // Find the Node
            var result = (from objCategory in item.AllChildren()
                            select objCategory).FirstOrDefault();

            // Get the Tree Control node container for the item
            TreeViewItem SubTreeViewItem = (TreeViewItem)
		objTreeViewItem.ItemContainerGenerator.ContainerFromItem(item);

            if (SubTreeViewItem != null)
            {
                // Collapse the Node
                SubTreeViewItem.IsExpanded = false;

                // Refresh Tree Control
                objTreeView.UpdateLayout();

                // Recursively call the function                
                CollapseChildNode(SubTreeViewItem, item);
            }
        }
    }
    #endregion

    // Utility

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
    #endregion
}

Behaviors 的属性中,我们单击 New ButtonTriggerType 更改为 DataTrigger,并将 Binding 设置为 ViewModel 中的 ExpandSelectedTreeNodes 属性。对于 TreeExpandBehavior,我们将 Comparison 设置为True,对于 TreeCollapseBehavior,我们将 Comparison 设置为False

注意:如果未弹出窗口允许您更改 TriggerType,您可以将一个 ChangePropertyAction Behavior 拖到页面上,然后将其删除。这似乎会将项目所需的引用添加到项目中,以便在单击 New Button 时,Expression Blend 会弹出 TriggerType 选择框。

代码

当单击 Set Nodes Button 时,它会调用 ViewModel 中的 SetCategory 方法

    public ICommand SetCategoryCommand { get; set; }
    public void SetCategory(object param)
    {
        // Set Nodes as Selected
        foreach (var Cat in colCategory)
        {
            // Find the Node
            var result = from objCategory in Cat.AllChildren()
                            where 
                            (objCategory.CategoryName == "Category Sub2-1" ||
                            objCategory.CategoryName == "Category Two Sub2")
                            select objCategory;

            foreach (var item in result)
            {
                // Set the IsSelected property so the checkbox
                // will be checked
                item.IsSelected = true;
            }

            // Change the value of ExpandSelectedTreeNodes to True
            ExpandSelectedTreeNodes = true;

            // This forces anything bound to is, to be notified
            // That the value has been updated so that
            // Any Behavior bound to it is fired
            this.NotifyPropertyChanged("ExpandSelectedTreeNodes");
        }
    }

ExpandSelectedTreeNodes 属性在方法设置 Categories之后才被设置。

是的,就是这样。:)

DataTrigger + Behaviors = 无代码隐藏

如果您被告知使用 ViewModel 和绑定的好处,您会期望能够将它们用于一切。如果绑定是“好的”,那么您应该能够 100% 的时间使用它们。

希望我们已经证明,Silverlight DataTrigger 允许您“引发一个事件”,该事件可以被 Behavior 在 UI(View)上“检测”到。ViewModel 仍然与 View 完全解耦(它甚至不知道是否有人在监听)。

Behaviors 易于编写,在许多情况下,您会发现代码比使用代码隐藏少。此外,Behaviors 易于重用,并且可以轻松地被非程序员(例如 Designer)使用。

Kunal Chowdhury 的文章

要真正理解 Triggers 并了解它们如何与 ViewModel 中的方法(不仅仅是 ICommands)配合使用,请参阅 Kunal Chowdhury 的文章 在 XAML 中为 MVVM 使用 EventTrigger – 无代码隐藏

延伸阅读

© . All rights reserved.