我的 treeView 填充了一个奇怪的 children :WPF MVVM
My treeView populate a strange children : WPF MVVM
我在这个非常好的帮助下构建了一个treeView WPF MVVM article
然后我为某个节点创建了一个上下文菜单,允许我从选定的 parent.
添加 children
问题是,如果我点击 "Add" 而没有手动展开所选节点 (parent),除了预期生成的节点之外,还会自动创建一个奇怪的 child当点击 "Add".
我试图检测问题,所以我更改了下面的代码:
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
至:
<Setter Property="IsExpanded" Value="True" />
下面的图 1 显示了此测试的结果,或者图 2 显示了我的 treeView 必须显示的内容。
图片1
image2
Rq: 我使用了article 中的图像,我谈到了它。此外,我使用了文章中描述的相同方法(包括 class TreeViewItemViewModel.cs )
所有 ViewModel 的基础 class
public class TreeViewItemViewModel:INotifyPropertyChanged
{
#地区数据
static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();
readonly ObservableCollection<TreeViewItemViewModel> _children;
readonly TreeViewItemViewModel _parent;
bool _isExpanded;
bool _isSelected;
#endregion // Data
#region Constructors
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
// This is used to create the DummyChild instance.
private TreeViewItemViewModel()
{
}
#endregion // Constructors
#region Presentation Members
#region Children
/// <summary>
/// Returns the logical child items of this object.
/// </summary>
public ObservableCollection<TreeViewItemViewModel> Children
{
get { return _children; }
}
#endregion // Children
#region HasLoadedChildren
/// <summary>
/// Returns true if this object's Children have not yet been populated.
/// </summary>
public bool HasDummyChild
{
get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
}
#endregion // HasLoadedChildren
#region IsExpanded
/// <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");
}
// Expand all the way up to the root.
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
// Lazy load the child items, if necessary.
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
#endregion // IsExpanded
#region IsSelected
/// <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");
}
}
}
#endregion // IsSelected
#region LoadChildren
/// <summary>
/// Invoked when the child items need to be loaded on demand.
/// Subclasses can override this to populate the Children collection.
/// </summary>
protected virtual void LoadChildren()
{
}
#endregion // LoadChildren
#region Parent
public TreeViewItemViewModel Parent
{
get { return _parent; }
}
#endregion // Parent
#endregion // Presentation Members
#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
}
Myxml:
<TreeView ItemsSource="{Binding Regions}" IsEnabled="{Binding EnableTree}" >
<TreeView.ItemContainerStyle>
<!--
This Style binds a TreeViewItem to a TreeViewItemViewModel.
-->
<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.Resources>
<ContextMenu x:Key="AddCity" ItemsSource="{Binding AddCityItems}"/>
<HierarchicalDataTemplate
DataType="{x:Type local:StateViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal" ContextMenu="{StaticResource AddCity}">
<Image Width="16" Height="16" Margin="3,0" Source="Images\Region.png" />
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
- 区域视图模型:
`public class 状态视图模型:TreeViewItemViewModel
{
readonly State _state;
public ICommand AddCityCommand { get; private set; }
public List<MenuItem> AddCityItems { get; set; }
public StateViewModel(State state, RegionViewModel parentRegion)
: base(parentRegion, true)
{
_state = state;
AddCityItems = new List<MenuItem>();
AddCityCommand = new DelegateCommand<CancelEventArgs>(OnAddCityCommandExecute, OnAddCityCommandCanExecute);
AddCityItems.Add(new MenuItem() { Header = "Add City", Command = AddCityCommand });
}
public string StateName
{
get { return _state.StateName; }
}
protected override void LoadChildren()
{
foreach (City city in Database.GetCities(_state))
base.Children.Add(new CityViewModel(city, this));
}
bool OnAddCityCommandCanExecute(CancelEventArgs parameter)
{
return true;
}
public void OnAddCityCommandExecute(CancelEventArgs parameter)
{
var myNewCity = new city();
Children.Add(new CityViewModel(myNewCity, this));
}
}`
顺便说一句,如果我展开我的 parent 节点然后单击添加城市,我会得到预期的结果,但如果我不展开 parent 节点并单击上下文菜单我有除了我要创建的 child
之外,还创建了另一个 child
编辑
我将下面的语句添加到我的 add() 方法中,现在我没有任何问题:
public void OnAddCityCommandExecute(CancelEventArgs parameter)
{
var myNewCity = new city();
Children.Add(new CityViewModel(myNewCity, this));
//the modif
this.Children.Remove(DummyChild);
}
我可以看到你代码中的错误。
重现步骤如下:
- 在状态节点(永远不要先展开)
- 如果不预先扩展 Child,您的 StateViewModel 的 Children 包含一个 DummyChild。
- 在列表中添加了 1 个新城市,这导致 HasDummyChild 无法工作,因为 Children 的列表
中的计数现在是 2
- 然后当你尝试展开节点时查看结果。您的树列表将包含 DummyChild,这是一个基础 class,它搞砸了一切
所以,基本上这就是为什么 "Expand" first 是问题的关键,因为当时 HasDummyChild 仍在工作,因为它比较 .Count == 1。树不会删除如果您向列表中添加一个额外的 child 使 .Count == 2.
从您的 Children 列表中删除 DummyChild
请求的附加信息
只需将 HasDummyChild 更改为以下内容
public bool HasDummyChild
{
//get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
get { return Children.Any() && Children.Contains(DummyChild); }
}
我在这个非常好的帮助下构建了一个treeView WPF MVVM article 然后我为某个节点创建了一个上下文菜单,允许我从选定的 parent.
添加 children问题是,如果我点击 "Add" 而没有手动展开所选节点 (parent),除了预期生成的节点之外,还会自动创建一个奇怪的 child当点击 "Add".
我试图检测问题,所以我更改了下面的代码:
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
至:
<Setter Property="IsExpanded" Value="True" />
下面的图 1 显示了此测试的结果,或者图 2 显示了我的 treeView 必须显示的内容。
图片1
image2
Rq: 我使用了article 中的图像,我谈到了它。此外,我使用了文章中描述的相同方法(包括 class TreeViewItemViewModel.cs )
所有 ViewModel 的基础 class
public class TreeViewItemViewModel:INotifyPropertyChanged { #地区数据
static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel(); readonly ObservableCollection<TreeViewItemViewModel> _children; readonly TreeViewItemViewModel _parent; bool _isExpanded; bool _isSelected; #endregion // Data #region Constructors protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren) { _parent = parent; _children = new ObservableCollection<TreeViewItemViewModel>(); if (lazyLoadChildren) _children.Add(DummyChild); } // This is used to create the DummyChild instance. private TreeViewItemViewModel() { } #endregion // Constructors #region Presentation Members #region Children /// <summary> /// Returns the logical child items of this object. /// </summary> public ObservableCollection<TreeViewItemViewModel> Children { get { return _children; } } #endregion // Children #region HasLoadedChildren /// <summary> /// Returns true if this object's Children have not yet been populated. /// </summary> public bool HasDummyChild { get { return this.Children.Count == 1 && this.Children[0] == DummyChild; } } #endregion // HasLoadedChildren #region IsExpanded /// <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"); } // Expand all the way up to the root. if (_isExpanded && _parent != null) _parent.IsExpanded = true; // Lazy load the child items, if necessary. if (this.HasDummyChild) { this.Children.Remove(DummyChild); this.LoadChildren(); } } } #endregion // IsExpanded #region IsSelected /// <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"); } } } #endregion // IsSelected #region LoadChildren /// <summary> /// Invoked when the child items need to be loaded on demand. /// Subclasses can override this to populate the Children collection. /// </summary> protected virtual void LoadChildren() { } #endregion // LoadChildren #region Parent public TreeViewItemViewModel Parent { get { return _parent; } } #endregion // Parent #endregion // Presentation Members #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
}
Myxml:
<TreeView ItemsSource="{Binding Regions}" IsEnabled="{Binding EnableTree}" >
<TreeView.ItemContainerStyle>
<!--
This Style binds a TreeViewItem to a TreeViewItemViewModel.
-->
<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.Resources>
<ContextMenu x:Key="AddCity" ItemsSource="{Binding AddCityItems}"/>
<HierarchicalDataTemplate
DataType="{x:Type local:StateViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal" ContextMenu="{StaticResource AddCity}">
<Image Width="16" Height="16" Margin="3,0" Source="Images\Region.png" />
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
- 区域视图模型:
`public class 状态视图模型:TreeViewItemViewModel {
readonly State _state;
public ICommand AddCityCommand { get; private set; }
public List<MenuItem> AddCityItems { get; set; }
public StateViewModel(State state, RegionViewModel parentRegion)
: base(parentRegion, true)
{
_state = state;
AddCityItems = new List<MenuItem>();
AddCityCommand = new DelegateCommand<CancelEventArgs>(OnAddCityCommandExecute, OnAddCityCommandCanExecute);
AddCityItems.Add(new MenuItem() { Header = "Add City", Command = AddCityCommand });
}
public string StateName
{
get { return _state.StateName; }
}
protected override void LoadChildren()
{
foreach (City city in Database.GetCities(_state))
base.Children.Add(new CityViewModel(city, this));
}
bool OnAddCityCommandCanExecute(CancelEventArgs parameter)
{
return true;
}
public void OnAddCityCommandExecute(CancelEventArgs parameter)
{
var myNewCity = new city();
Children.Add(new CityViewModel(myNewCity, this));
}
}`
顺便说一句,如果我展开我的 parent 节点然后单击添加城市,我会得到预期的结果,但如果我不展开 parent 节点并单击上下文菜单我有除了我要创建的 child
之外,还创建了另一个 child编辑 我将下面的语句添加到我的 add() 方法中,现在我没有任何问题:
public void OnAddCityCommandExecute(CancelEventArgs parameter)
{
var myNewCity = new city();
Children.Add(new CityViewModel(myNewCity, this));
//the modif
this.Children.Remove(DummyChild);
}
我可以看到你代码中的错误。
重现步骤如下:
- 在状态节点(永远不要先展开)
- 如果不预先扩展 Child,您的 StateViewModel 的 Children 包含一个 DummyChild。
- 在列表中添加了 1 个新城市,这导致 HasDummyChild 无法工作,因为 Children 的列表 中的计数现在是 2
- 然后当你尝试展开节点时查看结果。您的树列表将包含 DummyChild,这是一个基础 class,它搞砸了一切
所以,基本上这就是为什么 "Expand" first 是问题的关键,因为当时 HasDummyChild 仍在工作,因为它比较 .Count == 1。树不会删除如果您向列表中添加一个额外的 child 使 .Count == 2.
从您的 Children 列表中删除 DummyChild请求的附加信息
只需将 HasDummyChild 更改为以下内容
public bool HasDummyChild
{
//get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
get { return Children.Any() && Children.Contains(DummyChild); }
}