WPF - MVVM - TreeView - 基于所选项

时间:2015-09-22 20:11:26

标签: wpf mvvm treeview command

我发现很多页面都以这种或那种方式存在,但仍然没有发现如何实现它。这是我的XAML:

<TreeView ItemsSource="{Binding Document}" HorizontalAlignment="Stretch" BorderThickness="0" Background="#FFC2A2A2">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            <Setter Property="FontWeight" Value="Normal" />
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="FontWeight" Value="Bold" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding ImageFile}" Height="16" Width="16"/>
                <TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
    <TreeView.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Add" Command="{Binding AddCommand}"/>
            <MenuItem Header="Delete" Command="{Binding DeleteCommand}"/>
        </ContextMenu>
    </TreeView.ContextMenu>
</TreeView>

我大致基于this中的搜索按钮实现实现了AddCommand和DeleteCommand。

这两个命令都需要树中的SelectedItem,所以我在树MVVM中实现了它,向每个MVVM项添加了一个指向树MVVM的指针,并通过MVVM项中的IsSelected属性进行维护。

public bool IsSelected
{
    get { return mIsSelected; }
    set 
    {
        if (value != mIsSelected)
        {
            mIsSelected = value;
            this.OnPropertyChanged("IsSelected");
        }
        if (mIsSelected)
        {
            mDocViewModel.SelectedItem = this;
        }
    }
}

(我们将mAbc用于数据成员,而不是_abc。)

这一切都有效。但是,上下文菜单具有上下文。根据选择的内容,AddCommand可能无效,我希望在视图中表示为已禁用并启用。

我在每个命令的CanExecute方法中对此条件进行了测试。但是在运行时,似乎永远不会调用CanExecute,并且两个菜单项始终显示为禁用。

有没有办法完成这项工作?有一个简单的方法吗?

谢谢, 领域

后来:

编辑我的问题似乎是做出更长回复的方法。那么,这是一个Command类...关于之后提到的CanExecute。

#region DeleteCommand
public ICommand DeleteCommand
{
    get { return mDeleteCommand; }
}

void DeleteNode()
{
    if (mSelectedItem != null)
    {
        mSelectedItem.Remove();
        mSelectedItem = null;
    }
}

private class DeleteNodeCommand : RoutedCommand
{
    DocumentRulesViewModel mDocumentViewModel;

    public DeleteNodeCommand (DocumentRulesViewModel _docViewModel)
    {
        mDocumentViewModel = _docViewModel;
    }

    void SelectedItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    public bool CanExecute(object parameter)
    {
        DesignObjectViewModel current = mDocumentViewModel.SelectedItem;
        return (current != null);
    }

    event EventHandler CanExecuteChanged
    {
        // I intentionally left these empty because
        // this command never raises the event, and
        // not using the WeakEvent pattern here can
        // cause memory leaks.  WeakEvent pattern is
        // not simple to implement, so why bother.
        add { }
        remove { }
    }

    public void Execute(object parameter)
    {
        mDocumentViewModel.DeleteNode();
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
#endregion

我没有对底部的事件做任何事情,只是从示例中复制了它。并且,在该示例中,命令始终有效。所以也许问题就在那里。

但我为CanExecuteChange做了一些徘徊,并没有真正看到它如何处理。

吉姆,我想我能做的就是全部显示(当然,我必须省略应用程序/模型部分。

主要xaml:

<Window x:Class="xDesign.MainWindow" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:xDesign.View"
        Title="{StaticResource thisAppName}" Height="350" Width="525">
    <DockPanel>
        <Menu VerticalAlignment="Top" DockPanel.Dock="Top" BorderThickness="0">
            <MenuItem Header="{StaticResource fileMenu}" Name="FileMenu">
                <MenuItem Header="{StaticResource newFileMenu}" Click="NewDocumentMenuItem_Click" Name="FileMenuNewDoc"/>
                <MenuItem Header="{StaticResource openFileMenu}" Click="OpenDocumentMenuItem_Click" Name="FileMenuOpenDoc" />
                <MenuItem Header="{StaticResource closeFileMenu}" Click="CloseDocumentMenuItem_Click" IsEnabled="False" Name="FileMenuCloseDoc" />
                <Separator />
                <MenuItem Name="FileMenuCheckout" Header="{StaticResource checkoutFileMenu}" Click="FileMenuCheckout_Click"/>
                <MenuItem Name="FileMenuCheckin" Header="{StaticResource checkinFileMenu}" Click="FileMenuCheckin_Click" IsEnabled="False"/>
                <MenuItem Name="FileMenuDeleteFromServer" Header="{StaticResource deleteFromServerFileMenu}" Click="FileMenuDeleteFromServer_Click" IsEnabled="False"/>
                <MenuItem Name="FileMenuLogon" Header="{StaticResource logonFileMenu}" Click="FileMenuLogon_Click"/>
                <MenuItem Name="FileMenuLogoff" IsEnabled="False" Header="{StaticResource logoffFileMenu}" Click="FileMenuLogoff_Click"/>
            </MenuItem>
            <MenuItem Header="{StaticResource editMenu}" IsEnabled="False" Name="EditMenu">
                <MenuItem Header="{StaticResource findEditMenu}" Click="FindEditMenuItem_Click"/>
            </MenuItem>
            <MenuItem Header="{StaticResource viewMenu}" IsEnabled="False" Name="ViewMenu">
                <MenuItem Header="{StaticResource expandViewMenu}" Click="ExpandViewMenuItem_Click"/>
                <MenuItem Header="{StaticResource collapseViewMenu}" Click="CollapseViewMenuItem_Click"/>
            </MenuItem>
        </Menu>
        <Grid Name="DesignPanel" DockPanel.Dock="Top">
            <Grid.ColumnDefinitions >
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions >
            <local:DocumentTreeView x:Name="DocTreeView" Grid.Column="0"/>
            <GridSplitter Grid.Column="0" HorizontalAlignment="Right" VerticalContentAlignment="Stretch" Width="3" ResizeDirection="Columns"  />
            <WebBrowser x:Name="objectPreviewBrowser" Grid.Column="1" Margin="6,6,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" OpacityMask="#FF9B8E8E"/>
        </Grid>
    </DockPanel>
</Window>

控制xaml:

<UserControl x:Class="xDesign.View.DocumentTreeView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <TreeView ItemsSource="{Binding Document}" HorizontalAlignment="Stretch" BorderThickness="0" Background="#FFC2A2A2">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                <Setter Property="FontWeight" Value="Normal" />
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="FontWeight" Value="Bold" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                <StackPanel Orientation="Horizontal">
                    <Image Source="{Binding ImageFile}" Height="16" Width="16"/>
                    <TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
        <TreeView.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Add rule" Command="{Binding AddRuleCommand}"/>
                <MenuItem Header="Delete" Command="{Binding DeleteCommand}"/>
            </ContextMenu>
        </TreeView.ContextMenu>
    </TreeView>
</UserControl>

主视图模型:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using xDesign.Actions;
using xDesign.API.Model;

namespace xDesign.ViewModel
{
    public class DocumentRulesViewModel : INotifyPropertyChanged
    {
        #region data members
        DesignObjectViewModel mRootObject = null;
        ObservableCollection<DesignObjectViewModel> mDocument = null;
        DesignObjectViewModel mSelectedItem = null;
        ICommand mDeleteCommand = null;
        ICommand mAddRuleCommand = null;
        #endregion

        #region consructors
        public DocumentRulesViewModel(DocumentObject _rootObject)
        {
            mRootObject = new DesignObjectViewModel(_rootObject, this);
            mDocument = new ObservableCollection<DesignObjectViewModel>
            (new DesignObjectViewModel[] { mRootObject });
            mRootObject.IsExpanded = true; // We start with the top node expanded
            mDeleteCommand = new DeleteNodeCommand(this);
            mAddRuleCommand = new AddRuleCommandClass(this);
        }

        ~DocumentRulesViewModel()
        {
            Close();
        }

        public void Close()
        {
            Document = null;
        }
        #endregion

        #region properties
        public ObservableCollection<DesignObjectViewModel> Document
        { 
            get { return mDocument; }
            set
            {
                if (value != mDocument)
                {
                    mDocument = value;
                    this.OnPropertyChanged("Document");
                }
            }
        }

        public DesignObjectViewModel SelectedItem
        {
            get { return mSelectedItem; }
            set
            {
                if (value != mSelectedItem)
                {
                    mSelectedItem = value;
                    this.OnPropertyChanged("SelectedItem");
                }
            }
        }

        public IDesignObject CurrentDesignObject
        {
            get
            {
                if (mSelectedItem == null)
                {
                    return null;
                }
                else
                {
                    return mSelectedItem.DesignObject;
                }
            }
            set
            {
                DesignObjectViewModel dovm = SearchForNode(value);
                if (dovm != null)
                {
                    if (dovm.Parent != null && !dovm.Parent.IsExpanded)
                    {
                        dovm.Parent.IsExpanded = true;
                    }
                    dovm.IsSelected = true;
                }
            }
        }
        #endregion
        #region DeleteCommand
        public ICommand DeleteCommand
        {
            get { return mDeleteCommand; }
        }

        public void DeleteItem ()
        {
            DesignObjectViewModel node = this.SelectedItem;
            node.Remove();
        }

        private class DeleteNodeCommand : RoutedCommand
        {
            DocumentRulesViewModel mTree;

            public DeleteNodeCommand(DocumentRulesViewModel _tree)
            {
                mTree = _tree;
            }

            public bool CanExecute(object parameter)
            {
                DesignObjectViewModel node = mTree.SelectedItem;
                return (node != null);
            }

            public void Execute(object parameter)
            {
                mTree.DeleteItem();
            }

            // allows for constant updating if the event can execute or not.
            public event EventHandler CanExecuteChanged
            {
                add
                {
                    CommandManager.RequerySuggested += value;
                }
                remove
                {
                    CommandManager.RequerySuggested -= value;
                }
            }

            public void RaiseCanExecuteChanged()
            {
                // we should not have to reevaluate every can execute.  
                // but since there are too many places in product code to verify
                // we will settle for all or nothing.
                CommandManager.InvalidateRequerySuggested();
            }
        }
        #endregion

        #region AddRuleCommand
        public ICommand AddRuleCommand
        {
            get { return mAddRuleCommand; }
        }

        void AddRule()
        {
            int index = -1; // Where to insert; -1 = inside selected item
            if (mSelectedItem.Parent != null)
            {
                index = mSelectedItem.Parent.Children.IndexOf(mSelectedItem) + 1;  // Insert after selected item
            }

            // Call the application logic
            IDesignObject dobj = DocStructureManagement.AddRule(mSelectedItem.DesignObject, ref index);

            if (dobj != null)
            {
                DesignObjectViewModel newItemParent;
                if (index == -1)
                {
                    newItemParent = mSelectedItem;
                    index = 0;
                }
                else
                {
                    newItemParent = mSelectedItem.Parent;
                }
                DesignObjectViewModel newItem = new DesignObjectViewModel(dobj, this, newItemParent);
                newItemParent.InsertChild(newItem, index);
            }
        }

        private class AddRuleCommandClass : RoutedCommand
        {
            DocumentRulesViewModel mTree;

            public AddRuleCommandClass(DocumentRulesViewModel _tree)
            {
                mTree = _tree;
            }

            public bool CanExecute(object parameter)
            {
                DesignObjectViewModel node = mTree.SelectedItem;
                return (node != null && node.DesignObject.CanContainOrPrecede(eDesignNodeType.ContentRule));
            }

            public void Execute(object parameter)
            {
                mTree.AddRule();
            }

            // allows for constant updating if the event can execute or not.
            public event EventHandler CanExecuteChanged
            {
                add
                {
                    CommandManager.RequerySuggested += value;
                }
                remove
                {
                    CommandManager.RequerySuggested -= value;
                }
            }

            public void RaiseCanExecuteChanged()
            {
                // we should not have to reevaluate every can execute.  
                // but since there are too many places in product code to verify
                // we will settle for all or nothing.
                CommandManager.InvalidateRequerySuggested();
            }
        }
        #endregion


        #region Search
        private DesignObjectViewModel SearchForNode(IDesignObject _dobj)
        {
            return SearchNodeForNode(mRootObject, _dobj);
        }

        private DesignObjectViewModel SearchNodeForNode(DesignObjectViewModel _node, IDesignObject _dobj)
        {
            if (_node.DesignObject == _dobj)
            {
                return _node;
            }
            foreach (DesignObjectViewModel child in _node.Children)
            {
                DesignObjectViewModel childNode = SearchNodeForNode(child, _dobj);
                if (childNode != null)
                {
                    return childNode;
                }
            }
            return null;
        }
        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion // INotifyPropertyChanged Members
    }
}

TreeViewItem视图模型:

using System;
using System.Collections.ObjectModel;
using System.Linq;
using xDesign.API.Model;
using xDesign.Actions;
using System.ComponentModel;
using System.Windows.Input;

namespace xDesign.ViewModel
{
    public class DesignObjectViewModel : INotifyPropertyChanged
    {
        #region data
        DocumentRulesViewModel mDocViewModel = null;
        IDesignObject mDesignObject = null;
        DesignObjectViewModel mParent = null;
        ObservableCollection<DesignObjectViewModel> mChildren = null;
        bool mIsSelected = false;
        bool mIsExpanded = false;
        #endregion

        #region constructors
        public DesignObjectViewModel(IDesignObject _dobj, DocumentRulesViewModel _docViewModel)
            : this(_dobj, _docViewModel, null)
        {
        }

        public DesignObjectViewModel(IDesignObject _dobj, DocumentRulesViewModel _docViewModel, DesignObjectViewModel _parent)
        {
            mDesignObject = _dobj;
            mDocViewModel = _docViewModel;
            mParent = _parent;
            if (_dobj.Type != eDesignNodeType.ContentGroup)
            {
                mChildren = new ObservableCollection<DesignObjectViewModel>(
                        (from child in mDesignObject.Children
                         select new DesignObjectViewModel(child, mDocViewModel, this))
                         .ToList<DesignObjectViewModel>());
            }
            else
            {
                ContentHolder ch = (ContentHolder)_dobj;
                mChildren = new ObservableCollection<DesignObjectViewModel>(
                        (from child in ch.Contents
                         select new DesignObjectViewModel(child, mDocViewModel, this))
                         .ToList<DesignObjectViewModel>());
            }
        }
        #endregion

        #region properties
        public ObservableCollection<DesignObjectViewModel> Children
        {
            get { return mChildren; }
        }

        public DesignObjectViewModel Parent
        {
            get { return mParent; }
        }

        public String Name
        {
            get { return mDesignObject.Name; }
        }

        public IDesignObject DesignObject
        {
            get { return mDesignObject; }
        }

        public Type DataType
        {
            get { return mDesignObject.GetType(); }
        }

        // Can we use DataType for this, and task the View with finding a corresponding image?
        // And do we want to?  We could end up with file names that include Model type names.
        // Better?  Worse?  The same?
        public String ImageFile
        {
            get { return GetImageUri(mDesignObject); }
        }

        public bool IsExpanded
        {
            get { return mIsExpanded; }
            set
            {
                if (value != mIsExpanded)
                {
                    mIsExpanded = value;
                    this.OnPropertyChanged("IsExpanded");
                }
                // Expand all the way up to the root.
                if (mIsExpanded && mParent != null)
                    mParent.IsExpanded = true;
            }
        }

        public bool IsSelected
        {
            get { return mIsSelected; }
            set 
            {
                if (value != mIsSelected)
                {
                    mIsSelected = value;
                    this.OnPropertyChanged("IsSelected");
                    if (mIsSelected)
                    {
                        mDocViewModel.SelectedItem = this;
                    }
                    CommandManager.InvalidateRequerySuggested();
                }
            }
        }
        #endregion

        #region public methods
        public void Remove()
        {
            DocStructureManagement.DeleteNode(mDesignObject); // Remove from application

            if (mParent != null) // Remove from ViewModel
            {
                mParent.Children.Remove(this);
                mParent.OnPropertyChanged("Children");
            }
        }

        public void InsertChild(DesignObjectViewModel _newChild, int _insertIndex)
        {
            Children.Insert(_insertIndex, _newChild);
            this.OnPropertyChanged("Children");
        }
        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion // INotifyPropertyChanged Members

        internal static string GetImageUri(IDesignObject _dobj)
        {
            string name = null;
            switch (_dobj.Type)
            {
                case eDesignNodeType.Document:
                    name = "xDesign.ico";
                    break;
                case eDesignNodeType.ContentRule:
                    name = "Content Rule.png";
                    break;
                case eDesignNodeType.Section:
                    name = "section rule.png";
                    break;
                case eDesignNodeType.Table:
                    name = "Table Rule.bmp";
                    break;
                case eDesignNodeType.Read:
                    name = "Read Rule.bmp";
                    break;
                case eDesignNodeType.Goto:
                    name = "Goto Rule.bmp";
                    break;
                case eDesignNodeType.Label:
                    name = "Label Rule.bmp";
                    break;
                case eDesignNodeType.ContentGroup:
                    name = "ContentGroup.png";
                    break;
                case eDesignNodeType.Content:
                    name = "content.png";
                    break;
                case eDesignNodeType.Criteria:
                    name = "Criteria.bmp";
                    break;
            }

            if (name == null)
            {
                throw new Exception("No image found for " + _dobj.Name);
            }

            return string.Format(@"C:\DEVPROJECTS\XDMVVM\XDMVVM\Images\{0}", name);
        }
    }
}

最后,从主窗口代码后面的代码片段,我创建并连接主视图模型。

            mDocumentRulesViewModel = new DocumentRulesViewModel(mCurrentDocument);
            this.DocTreeView.DataContext = mDocumentRulesViewModel;

同样,我在两个命令类中的每一个的CanExecute方法中设置了断点,并且控制永远不会在那里停止。

4 个答案:

答案 0 :(得分:1)

我创建了一个小型示例项目,与您的类似,以解决此问题。我能够让上下文菜单CanExecute正常运行。如果您模仿这种风格,您将能够解决您的问题。

MainWindow.Xaml:

<Window x:Class="CommandChangesInTreeViewContextMenu.MainWindow"
        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"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Button Command="{Binding AddCommand}">Add Command </Button>
        <TreeView Grid.Row="1"
            ItemsSource="{Binding MasterList}" HorizontalAlignment="Stretch" BorderThickness="0" Background="#FFC2A2A2">
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                </Style>
            </TreeView.ItemContainerStyle>
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate >
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
            <TreeView.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Add" Command="{Binding AddCommand}"/>
                    <!--<MenuItem Header="Delete" Command="{Binding DeleteCommand}"/>-->
                </ContextMenu>
            </TreeView.ContextMenu>
        </TreeView>
        <Button Grid.Row="2" 
                Command="{Binding ClearSelectionsCommand}">Clear Selections </Button>
    </Grid>
</Window>

MainWindow.Xaml的DataContext ViewModel是TreeViewModel:

public class TreeViewModel : ObservableObject
    {
        private ObservableCollection<MasterItem> _masterList;
        private ICommand _addCommand;
        private ICommand _clearSelectionsCommand;

        public ObservableCollection<MasterItem> MasterList
        {
            get { return _masterList; }
            set
            {
                if (_masterList != value)
                {
                    _masterList = value; 
                    OnPropertyChanged("MasterList");
                }
            }
        }

        public ICommand AddCommand
        {
            get
            {
                if (_addCommand == null)
                {
                    _addCommand = new RelayCommand<object>(Add, CanExecuteAddCommand);
                }
                return _addCommand;
            }
        }

        public ICommand ClearSelectionsCommand
        {
            get
            {
                if (_clearSelectionsCommand == null)
                {
                    _clearSelectionsCommand = new RelayCommand<object>(ClearSelections);
                }
                return _clearSelectionsCommand;
            }
        }

        public TreeViewModel()
        {
            MasterList = new ObservableCollection<MasterItem>
            {
                new MasterItem("sup"), new MasterItem("hi"), new MasterItem("test"), new MasterItem("yo")
            };
        }

        private void Add(object o)
        {
            // does nothing 
        }

        private void ClearSelections(object o)
        {
            foreach (var mItem in MasterList)
            {
                mItem.IsSelected = false;
            }
        }

        private bool CanExecuteAddCommand(object o)
        {
            return MasterList.Any(mItem => mItem.IsSelected == true);
        }
    }

MasterItem类,它们是MasterList中的对象: MasterItem.cs:

public class MasterItem : ObservableObject
{
private string _name;
private bool _isSelected;

public string Name
{
    get { return _name; }
    set
    {
        if (_name != value)
        {
            _name = value;
            OnPropertyChanged("Name");
        }
    }
}

public bool IsSelected
{
    get { return _isSelected; }
    set
    {
        if (_isSelected != value)
        {
            _isSelected = value;
            OnPropertyChanged("IsSelected");
            CommandManager.InvalidateRequerySuggested();
        }
    }
}

public MasterItem(string name)
{
    Name = name;
    IsSelected = false;
}
}

**请注意,当设置IsSelected时,它将使InvalidateRequerySuggested()正常工作。 =)**

支持类,RelayCommand和ObservableObject

/// <summary>
/// RelayCommand
/// 
/// General purpose command implementation wrapper. This is an alternative 
/// to multiple command classes, it is a single class that encapsulates different
/// business logic using delegates accepted as constructor arguments.
/// </summary>
/// <typeparam name="T"></typeparam>
public class RelayCommand<T> : ICommand
{
    private static bool CanExecute(T paramz)
    {
        return true;
    }

private readonly Action<T> _execute;
private readonly Func<T, bool> _canExecute;

/// <summary>
/// Relay Command
///
/// Stores the Action to be executed in the instance field variable. Also Stores the
/// information about IF it canexecute in the instance field variable. These executing
/// commands can be sent from other methods in other classes. Hence the lambda expressions.
/// Tries to be as generic as possible T type as parameter.
/// </summary>
/// <param name="execute">Holds the method body about what it does when it executes</param>
/// <param name="canExecute">Holds the method body conditions about what needs to happen for the ACTION
/// Execute to execute. If it fails it cannot execute. </param>
public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
    if (execute == null)
        throw new ArgumentNullException("execute");
    _execute = execute;
    _canExecute = canExecute ?? CanExecute;

}

public bool CanExecute(object parameter)
{
    return _canExecute(TranslateParameter(parameter));
}

// allows for constant updating if the event can execute or not.
public event EventHandler CanExecuteChanged
{
    add
    {
        if (_canExecute != null)
            CommandManager.RequerySuggested += value;

    }
    remove
    {
        if (_canExecute != null)
            CommandManager.RequerySuggested -= value;
    }
}

public void Execute(object parameter)
{
    _execute(TranslateParameter(parameter));
}

private T TranslateParameter(object parameter)
{
    T value = default(T);
    if (parameter != null && typeof(T).IsEnum)
        value = (T)Enum.Parse(typeof(T), (string)parameter);
    else
        value = (T)parameter;
    return value;
}

public void RaiseCanExecuteChanged()
{
    // we should not have to reevaluate every can execute.  
    // but since there are too many places in product code to verify
    // we will settle for all or nothing.
    CommandManager.InvalidateRequerySuggested();
}
}


/// <summary>
/// Class is based on two delegates; one for executing the command and another for returning the validity of the command.
/// The non-generic version is just a special case for the first, in case the command has no parameter.
/// </summary>
public class RelayCommand : RelayCommand<object>
{
    public RelayCommand(Action execute, Func<bool> canExecute = null)
        : base(obj => execute(),
            (canExecute == null ?
            null : new Func<object, bool>(obj => canExecute())))
    {

    }
}

ObservableObject:

    public abstract class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetProperty<T>(ref T field, T value, Expression<Func<T>> expression)
    {
        // Allows a comparison for generics. Otherwise could just say x == y ?
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            var lambda = (LambdaExpression)expression;
            MemberExpression memberExpr;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpr = (UnaryExpression)lambda.Body;
                memberExpr = (MemberExpression)unaryExpr.Operand;
            }
            else
            {
                memberExpr = (MemberExpression)lambda.Body;
            }

            OnPropertyChanged(memberExpr.Member.Name);
            return true;
        }
        return false;
    }
}

请注意,ObservableObject和RelayCommand只是帮助程序,而不是生成解决方案所必需的。主要看看MainWindow.Xaml,TreeViewModel和MasterItem。我希望这有帮助!

Picture of the disabled context menu when IsSelected is set to false for all the MasterItems in MasterList

MasterList中所有MasterItems的IsSelected设置为false时禁用上下文菜单的图片

使用RelayCommand的示例: 在你的构造函数

        public PrimaryViewModel()
        {
        ICommand bob = new RelayCommand(CommandMethodThatDoesStuff,CanExecuteCommandMethod);
        }

        private void CommandMethodThatDoesStuff(object o)
        {
            // do your work
        }

        private bool CanExecuteCommandMethod(object o)
        {
            return IsSelected;
        }

答案 1 :(得分:0)

有两种方法可以做到这一点,我能想到。

1 - 将上下文菜单放在HierarchicalDataTemplate上。这意味着上下文菜单的DataContext将是树中的项目。这可能很好,因为例如,AddCommand接近需要添加的地方。在这种情况下,您无需跟踪所选项目。

2 - 将MenuItem中的IsEnabled绑定到&#34; IsEnabled&#34; VM中的属性,然后在选择更改时更新它。这不太好,但可以整洁。如果您只进行单一选择,并且您已经拥有VM中所选项目的属性(您可能已经拥有),那么您可以将其绑定到类似{Binding SelectedItem.IsEnabled}之类的东西。

答案 2 :(得分:0)

使用命令并实现CanExecute,您处于正确的轨道上。我遇到过类似的问题。命令不会立即更新其CanExecute。如果您希望更新所有命令,一个简单的强力解决方案是添加对CommandManager.InvalidateRequerySuggested()的调用。

public bool IsSelected
{
    get { return mIsSelected; }
    set 
    {
        if (value != mIsSelected)
        {
            mIsSelected = value;
            this.OnPropertyChanged("IsSelected");
        }
        if (mIsSelected)
        {
            mDocViewModel.SelectedItem = this;
            CommandManager.InvalidateRequerySuggested();
        }
    }
}

这会调用所有命令的无效排列并强制命令逻辑,CanExecute布尔方法在UI上刷新它们的状态。

答案 3 :(得分:0)

ContextMenus不是可见树的一部分,因此直接绑定到视图模型不起作用。解决方法是使用BindingProxy as explained on this page

<TreeView ItemsSource="{Binding Items}" >
    <TreeView.Resources>
        <local:BindingProxy x:Key="Proxy" Data="{Binding}"/>
    </TreeView.Resources>
    <TreeView.ContextMenu>
        <ContextMenu DataContext="{Binding Path=Data, Source={StaticResource Proxy}}">
            <MenuItem Header="Add" Command="{Binding AddCommand}"/>
        </ContextMenu>
    </TreeView.ContextMenu>
</TreeView>

或者,如果每个树项目都有自己的视图模型,那么您可以将命令处理程序添加到项目本身并相对于放置目标进行绑定:

<TreeView ItemsSource="{Binding Items}" >
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
                        <MenuItem Header="Add" Command="{Binding AddCommand}"/>
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>