Возможно, я что-то здесь упускаю, но я бы передал SelectedIndex в качестве параметра команды привязке для метода CanExecute команды. Затем просто используйте это, чтобы решить, включена ли команда или нет.
Проблема может заключаться в том, что текст данных контекстного меню не изменяется после загрузки, поскольку контекстное меню отсутствует в визуальном дереве. Я обычно использую этот метод , чтобы раскрыть текст данных для элементов, не входящих в визуальное дерево, через статический ресурс. Я действительно написал ответ на вопрос по этому вопросу сегодня сегодня .
Я действительно думаю, что что-то упустил. Не могли бы вы объяснить, почему это не сработает?
Редактировать
Хорошо, я прочитал abit о TreeViews и до сих пор не понял, в чем проблема. Поэтому я пошел вперед и сделал пример и сумел заставить его работать.
Моим первым шагом было чтение Эта статья Джоша Смита о древовидных представлениях . В нем говорится о создании моделей представления для каждого типа элемента и предоставлении свойств, таких как IsSelected и IsExpanded, с которыми вы затем связываетесь в xaml. Это позволяет получить доступ к свойствам элемента дерева в моделях представления.
Прочитав это, я принялся за работу:
Сначала я сделал небольшую структуру данных, которая показывает какую-то иерархию, чтобы поместить ее в древовидное представление. Я выбрал фильмы.
#region Models
public class Person
{
public string FirstName { get; set; }
public string SurName { get; set; }
public int Age { get; set; }
}
public class Actor:Person
{
public decimal Salary { get; set; }
}
public class ActingRole :Person
{
public Actor Actor { get; set; }
}
public class Movie
{
public string Name { get; set; }
public List<ActingRole> Characters { get; set; }
public string PlotSummary { get; set; }
public Movie()
{
Characters = new List<ActingRole>();
}
}
#endregion
Следующим шагом является создание модели представления для TreeViewItems, которая содержит все свойства, относящиеся к управлению элементами представления дерева, т.е. IsExpanded, IsSelected и т. Д.
Важно отметить, что у них всех есть родитель и ребенок .
Именно так мы будем отслеживать, являемся ли мы первым или последним элементом в родительской коллекции.
interface ITreeViewItemViewModel
{
ObservableCollection<TreeViewItemViewModel> Children { get; }
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
TreeViewItemViewModel Parent { get; }
}
public class TreeViewItemViewModel : ITreeViewItemViewModel, INotifyPropertyChanged
{
private ObservableCollection<TreeViewItemViewModel> _children;
private TreeViewItemViewModel _parent;
private bool _isSelected;
private bool _isExpanded;
public TreeViewItemViewModel Parent
{
get
{
return _parent;
}
}
public TreeViewItemViewModel(TreeViewItemViewModel parent = null,ObservableCollection<TreeViewItemViewModel> children = null)
{
_parent = parent;
if (children != null)
_children = children;
else
_children = new ObservableCollection<TreeViewItemViewModel>();
}
public ObservableCollection<TreeViewItemViewModel> Children
{
get
{
return _children;
}
}
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
}
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
}
После этого мы создаем наши модели для каждой Модели. Все они наследуются от TreeViewItemModel, поскольку все они будут элементами дерева.
public class MovieViewModel : TreeViewItemViewModel
{
private Movie _movie;
public MovieViewModel(Movie movie)
{
_movie = movie;
foreach(ActingRole a in _movie.Characters)
Children.Add(new ActingRoleViewModel(a,this));
}
public string Name
{
get
{
return _movie.Name;
}
set
{
_movie.Name = value;
OnPropertyChanged("Name");
}
}
public List<ActingRole> Characters
{
get
{
return _movie.Characters;
}
set
{
_movie.Characters = value;
OnPropertyChanged("Characters");
}
}
public string PlotSummary
{
get
{
return _movie.PlotSummary;
}
set
{
_movie.PlotSummary = value;
OnPropertyChanged("PlotSummary");
}
}
}
public class ActingRoleViewModel : TreeViewItemViewModel
{
private ActingRole _role;
public ActingRoleViewModel(ActingRole role, MovieViewModel parent):base (parent)
{
_role = role;
Children.Add(new ActorViewModel(_role.Actor, this));
}
public string FirstName
{
get
{
return _role.FirstName;
}
set
{
_role.FirstName = value;
OnPropertyChanged("FirstName");
}
}
public string SurName
{
get
{
return _role.SurName;
}
set
{
_role.SurName = value;
OnPropertyChanged("Surname");
}
}
public int Age
{
get
{
return _role.Age;
}
set
{
_role.Age = value;
OnPropertyChanged("Age");
}
}
public Actor Actor
{
get
{
return _role.Actor;
}
set
{
_role.Actor = value;
OnPropertyChanged("Actor");
}
}
}
public class ActorViewModel:TreeViewItemViewModel
{
private Actor _actor;
private ActingRoleViewModel _parent;
public ActorViewModel(Actor actor, ActingRoleViewModel parent):base (parent)
{
_actor = actor;
}
public string FirstName
{
get
{
return _actor.FirstName;
}
set
{
_actor.FirstName = value;
OnPropertyChanged("FirstName");
}
}
public string SurName
{
get
{
return _actor.SurName;
}
set
{
_actor.SurName = value;
OnPropertyChanged("Surname");
}
}
public int Age
{
get
{
return _actor.Age;
}
set
{
_actor.Age = value;
OnPropertyChanged("Age");
}
}
public decimal Salary
{
get
{
return _actor.Salary;
}
set
{
_actor.Salary = value;
OnPropertyChanged("Salary");
}
}
}
Затем я создал MainWindowViewModel, который создаст коллекцию этих моделей представления (которая привязана к TreeView), а также реализует команды, используемые меню, и логику их включения.
Важно отметить, что у меня есть свойство SelectedItem. Я получил этот элемент, подписавшись на all событие изменения свойства viewmodel, а затем получив выбранное. Я использую этот предмет, чтобы проверить, является ли он первым из последнего предмета в его родительской коллекции Children.
Также обратите внимание, что в команде, включающей методы, я решаю, находится ли элемент в корне или нет. Это важно, потому что моя mainwindowviewmodel не является TreeViewItemViewModel и не реализует свойство Children. Очевидно, что для вашей программы вам потребуется другой способ сортировки рута. Возможно, вы захотите поместить в TreeViewItemViewModel логическую переменную с именем root, которую можно просто установить в true, если у элемента нет родителя.
public class MainWindowViewModel : INotifyPropertyChanged
{
private ObservableCollection<MovieViewModel> _movieViewModels;
public ObservableCollection<MovieViewModel> MovieViewModels
{
get
{
return _movieViewModels;
}
set
{
_movieViewModels = value;
OnPropertyChanged("MovieViewModels");
}
}
private TreeViewItemViewModel SelectedItem { get; set; }
public MainWindowViewModel()
{
InitializeMovies();
InitializeCommands();
InitializePropertyChangedHandler((from f in MovieViewModels select f as TreeViewItemViewModel).ToList());
}
public ICommand MoveItemUpCmd { get; protected set; }
public ICommand MoveItemDownCmd { get; protected set; }
private void InitializeCommands()
{
//Initializes the command
this.MoveItemUpCmd = new RelayCommand(
(param) =>
{
this.MoveItemUp();
},
(param) => { return this.CanMoveItemUp; }
);
this.MoveItemDownCmd = new RelayCommand(
(param) =>
{
this.MoveItemDown();
},
(param) => { return this.CanMoveItemDown; }
);
}
public void MoveItemUp()
{
}
private bool CanMoveItemUp
{
get
{
if (SelectedItem != null)
if (typeof(MovieViewModel) == SelectedItem.GetType())
{
return MovieViewModels.IndexOf((MovieViewModel)SelectedItem) > 0;
}
else
{
return SelectedItem.Parent.Children.IndexOf(SelectedItem) > 0;
}
else
return false;
}
}
public void MoveItemDown()
{
}
private bool CanMoveItemDown
{
get
{
if (SelectedItem != null)
if (typeof(MovieViewModel) == SelectedItem.GetType())
{
return MovieViewModels.IndexOf((MovieViewModel)SelectedItem) < (MovieViewModels.Count - 1);
}
else
{
var test = SelectedItem.Parent.Children.IndexOf(SelectedItem);
return SelectedItem.Parent.Children.IndexOf(SelectedItem) < (SelectedItem.Parent.Children.Count - 1);
}
else
return false;
}
}
private void InitializeMovies()
{
MovieViewModels = new ObservableCollection<MovieViewModel>();
//Please note all this data is pure speculation. Prolly have spelling mistakes aswell
var TheMatrix = new Movie();
TheMatrix.Name = "The Matrix";
TheMatrix.Characters.Add(new ActingRole(){FirstName = "Neo", SurName="", Age=28, Actor=new Actor(){FirstName="Keeanu", SurName="Reeves", Age=28, Salary=2000000}});
TheMatrix.Characters.Add(new ActingRole() { FirstName = "Morpheus", SurName = "", Age = 34, Actor = new Actor() { FirstName = "Lorance", SurName = "Fishburn", Age = 34, Salary = 800000 } });
TheMatrix.PlotSummary = "A programmer by day, and hacker by night searches for the answer to a question that has been haunting him: What is the matrix? The answer soon finds him and his world is turned around";
var FightClub = new Movie();
FightClub.Name = "Fight Club";
FightClub.Characters.Add(new ActingRole() { FirstName = "", SurName = "", Age = 28, Actor = new Actor() { FirstName = "Edward", SurName = "Norton", Age = 28, Salary = 1300000 } });
FightClub.Characters.Add(new ActingRole() { FirstName = "Tylar", SurName = "Durden", Age = 27, Actor = new Actor() { FirstName = "Brad", SurName = "Pit", Age = 27, Salary = 3500000 } });
FightClub.PlotSummary = "A man suffers from insomnia, and struggles to find a cure. In desperation he starts going to testicular cancer surviver meetings, and after some weeping finds he sleeps better. Meanwhile a new aquantance, named Tylar Durden is about so show him a much better way to deal with his problems.";
MovieViewModels.Add(new MovieViewModel(TheMatrix));
MovieViewModels.Add(new MovieViewModel(FightClub));
}
private void InitializePropertyChangedHandler(IList<TreeViewItemViewModel> treeViewItems)
{
foreach (TreeViewItemViewModel t in treeViewItems)
{
t.PropertyChanged += TreeViewItemviewModel_PropertyChanged;
InitializePropertyChangedHandler(t.Children);
}
}
private void TreeViewItemviewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected" && ((TreeViewItemViewModel)sender).IsSelected)
{
SelectedItem = ((TreeViewItemViewModel)sender);
}
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
}
Наконец, вот xaml MainWindow, где мы привязываемся к свойствам.
Обратите внимание на стиль внутри дерева для дерева. Здесь мы привязываем все свойства TreeViewItem к свойствам, созданным в TreeviewItemViewModel.
Свойство команды contextIne MenuItems связано с командами через DataContextBridge (аналогично ElementSpy, оба создания Джоша Смита). Это связано с тем, что контекстное меню находится вне визуального дерева и поэтому имеет проблемы с привязкой к модели представления.
Также обратите внимание, что у меня есть разные HierarchicalDataTemplate для каждого из типов представленных моделей. Это позволяет мне связывать разные свойства для разных типов, которые будут отображаться в древовидном представлении.
<TreeView Margin="5,5,5,5" HorizontalAlignment="Stretch" ItemsSource="{Binding Path=MovieViewModels,UpdateSourceTrigger=PropertyChanged}">
<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" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{StaticResource DataContextBridge}">
<MenuItem Header="Move _Up"
Command="{Binding DataContext.MoveItemUpCmd}" />
<MenuItem Header="Move _Down"
Command="{Binding DataContext.MoveItemDownCmd}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type classes:MovieViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type classes:ActingRoleViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5,0,0,0" Text="{Binding FirstName}"/>
<TextBlock Margin="5,0,5,0" Text="{Binding SurName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type classes:ActorViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5,0,0,0" Text="{Binding FirstName}"/>
<TextBlock Margin="5,0,5,0" Text="{Binding SurName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>