如何在递归期间更新 TreeView c#
How to update TreeView during recursion c#
我正在使用递归函数填充树视图,如下所示。树视图得到正确填充,但我无法在递归期间在 UI 中更新它。我仍然是线程的新手,但我尝试使用下面给出的 "updateTreeView" 函数......在递归函数中,但无法正确实现它。我怎样才能实现这个功能??请分享一些代码...因为我不知道线程。
为了简单起见,我修改了代码。但是,递归函数非常复杂并且处理 COM 对象。
private void CreateMyTree(List<string> RootNodes, TreeViewItem ParentNode)
{
if(mycheck here....)
{
for (int i = 1; i <= RootNodes.Count; i++)
{
TreeViewItem NewTreeItem = new TreeViewItem() { Header = RootNodes[i], IsExpanded = false };
ParentNode.Items.Add(NewTreeItem);
}
}
else
{
///here some checks again and recursion again
CreateMyTree(RootNodes, ParentNode)
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
//Create RootNode in TreeView
TreeViewItem ParentNode = new TreeViewItem() { Header = "TopNode", IsExpanded = true };
//Recursively add items to TreeView
CreateMyTree(RootNode, ParentNode);
//update TreeView GUI
treeView1.Items.Add(ParentNode);
}
private void updateTreeView(TreeViewItem TreeItem)
{
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(delegate()
{
treeView1.Items.Add(TreeItem);
}));
}
我认为你应该采纳 HighCore 的建议并使用适当的 XAML 数据绑定,如果你坚持要 "old school" 你可以这样做:
private static Action EmptyDelegate = delegate() { };
private void CreateMyTree(List<string> RootNodes, TreeViewItem ParentNode)
{
if(mycheck here....)
{
for (int i = 1; i <= RootNodes.Count; i++)
{
TreeViewItem NewTreeItem = new TreeViewItem() { Header = RootNodes[i], IsExpanded = false };
ParentNode.Items.Add(NewTreeItem);
updateTreeView();
}
}
else
{
///here some checks again and recursion again
CreateMyTree(RootNodes, ParentNode)
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
//Create RootNode in TreeView
TreeViewItem ParentNode = new TreeViewItem() { Header = "TopNode", IsExpanded = true };
//update TreeView GUI
treeView1.Items.Add(ParentNode);
//Recursively add items to TreeView
CreateMyTree(RootNode, ParentNode);
}
private void updateTreeView()
{
treeView1.Dispatcher.Invoke(DispatcherPriority.Background, EmptyDelegate);
}
你应该跟着做。
这与线程无关。如果您想要多个级别,仍然需要使用 DataTemplateSelector 进行更好的实现。
以下是跨越 3 个级别的层次结构示例:
CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private Entity _entity;
public Entity Entity
{
get
{
if(_entity == null)
_entity = new Entity();
return _entity;
}
}
public List<Entity> Entities
{
get { return CreateMyTree(); }
}
private List<Entity> CreateMyTree()
{
var list = new List<Entity>();
var p1 = new Entity {Title = "Parent 1"};
p1.Children.Add(new Entity{ Title = "Child 1"});
p1.Children.Add(new Entity { Title = "Child 2" });
var p2 = new Entity { Title = "Parent 2" };
var c1 = new Entity { Title = "Child 1"};
var g1 = new Entity {Title = "GrandChild 1"};
c1.Children.Add(g1);
var c2 = new Entity { Title = "Child 2" };
p2.Children.Add(c1);
p2.Children.Add(c2);
list.Add(p1);
list.Add(p2);
return list;
}
}
public class Entity : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
}
}
private List<Entity> _children;
public List<Entity> Children
{
get
{
if(_children == null)
_children = new List<Entity>();
return _children;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
xaml :
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication7="clr-namespace:WpfApplication7"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="level3">
<TextBlock Text="{Binding Title}" Foreground="Green" />
</DataTemplate>
<HierarchicalDataTemplate x:Key="rootTemplate" ItemsSource="{Binding Children}" >
<TextBlock Text="{Binding Title}" Foreground="Red" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" ItemTemplate="{StaticResource level3}">
<TextBlock Text="{Binding Title}" Foreground="Blue" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Entities}" ItemTemplate="{StaticResource rootTemplate}"/>
</Grid>
</Window>
前面的帖子关于数据绑定的设计是完全正确的。此外,如果你想解决异步填充树的问题,你必须在另一个 thread/task 中执行此操作。使用 "ObservableCollection",您还可以在此任务 运行 和更改树时使您的树保持最新。但要小心 - ObservableCollections 不喜欢从其他线程然后 Ui 线程更改它们。此问题已讨论 here and here。
在我编写的以下示例中,我只是为 ObservableCollection 的每次修改调用 Dispatcher,但这不是最佳解决方案。
在示例中,您可以同步或异步调用树创建 - 因此您可以理解 Ui 的实现和行为的差异。
namespace TreeViewExample
{
public class MyNode
{
public ObservableCollection<MyNode> ChildNodes { get; set; }
public int Number { get; set; }
public MyNode()
{
ChildNodes = new ObservableCollection<MyNode>();
}
}
public class SimpleCommand : ICommand
{
private readonly Action _action;
public SimpleCommand(Action action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (parameter is string && (string) parameter == "async")
{
new TaskFactory().StartNew(_action);
}
else _action();
}
public event EventHandler CanExecuteChanged;
}
public class NodeViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyNode> _rootNodes;
public ObservableCollection<MyNode> RootNodes
{
set
{
_rootNodes = value;
OnPropertyChanged();
}
get { return _rootNodes; }
}
private readonly ICommand _populateCommand;
private readonly Random _random;
private Dispatcher _dispatcher;
public ICommand PopulateCommand { get { return _populateCommand; } }
public NodeViewModel()
{
_dispatcher = Dispatcher.CurrentDispatcher;
_random = new Random();
_populateCommand = new SimpleCommand(PopulateTree);
RootNodes = new ObservableCollection<MyNode>();
}
private void PopulateTree()
{
try
{
var node = new MyNode {Number = 0};
if (_dispatcher.CheckAccess())
RootNodes.Add(node);
else _dispatcher.Invoke(() => RootNodes.Add(node));
FillNodeRecursively(node, 1);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private void FillNodeRecursively(MyNode rootNode, int level)
{
int rand = _random.Next(0, 4);
for (var i = 0; i <= rand; i++)
{
var subNode = new MyNode {Number = rand + i};
Thread.Sleep(50); //simulating some workload
if (_dispatcher.CheckAccess())
rootNode.ChildNodes.Add(subNode);
else _dispatcher.Invoke(() => rootNode.ChildNodes.Add(subNode));
if (level < 4)
FillNodeRecursively(subNode, level + 1);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
这是 WPF window 的 xaml:
<Window x:Class="TreeViewExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<HierarchicalDataTemplate x:Key="NodesTemplate" ItemsSource="{Binding Path=ChildNodes}" >
<TextBlock Text="{Binding Path=Number}" />
</HierarchicalDataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Bottom" >
<Button Command="{Binding PopulateCommand}" CommandParameter="sync">Sync</Button>
<Button Command="{Binding PopulateCommand}" CommandParameter="async">Async</Button>
</StackPanel>
<TreeView DockPanel.Dock="Top" ItemsSource="{Binding RootNodes}" ItemTemplate="{StaticResource NodesTemplate}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
我正在使用递归函数填充树视图,如下所示。树视图得到正确填充,但我无法在递归期间在 UI 中更新它。我仍然是线程的新手,但我尝试使用下面给出的 "updateTreeView" 函数......在递归函数中,但无法正确实现它。我怎样才能实现这个功能??请分享一些代码...因为我不知道线程。
为了简单起见,我修改了代码。但是,递归函数非常复杂并且处理 COM 对象。
private void CreateMyTree(List<string> RootNodes, TreeViewItem ParentNode)
{
if(mycheck here....)
{
for (int i = 1; i <= RootNodes.Count; i++)
{
TreeViewItem NewTreeItem = new TreeViewItem() { Header = RootNodes[i], IsExpanded = false };
ParentNode.Items.Add(NewTreeItem);
}
}
else
{
///here some checks again and recursion again
CreateMyTree(RootNodes, ParentNode)
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
//Create RootNode in TreeView
TreeViewItem ParentNode = new TreeViewItem() { Header = "TopNode", IsExpanded = true };
//Recursively add items to TreeView
CreateMyTree(RootNode, ParentNode);
//update TreeView GUI
treeView1.Items.Add(ParentNode);
}
private void updateTreeView(TreeViewItem TreeItem)
{
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(delegate()
{
treeView1.Items.Add(TreeItem);
}));
}
我认为你应该采纳 HighCore 的建议并使用适当的 XAML 数据绑定,如果你坚持要 "old school" 你可以这样做:
private static Action EmptyDelegate = delegate() { };
private void CreateMyTree(List<string> RootNodes, TreeViewItem ParentNode)
{
if(mycheck here....)
{
for (int i = 1; i <= RootNodes.Count; i++)
{
TreeViewItem NewTreeItem = new TreeViewItem() { Header = RootNodes[i], IsExpanded = false };
ParentNode.Items.Add(NewTreeItem);
updateTreeView();
}
}
else
{
///here some checks again and recursion again
CreateMyTree(RootNodes, ParentNode)
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
//Create RootNode in TreeView
TreeViewItem ParentNode = new TreeViewItem() { Header = "TopNode", IsExpanded = true };
//update TreeView GUI
treeView1.Items.Add(ParentNode);
//Recursively add items to TreeView
CreateMyTree(RootNode, ParentNode);
}
private void updateTreeView()
{
treeView1.Dispatcher.Invoke(DispatcherPriority.Background, EmptyDelegate);
}
你应该跟着做。 这与线程无关。如果您想要多个级别,仍然需要使用 DataTemplateSelector 进行更好的实现。
以下是跨越 3 个级别的层次结构示例:
CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private Entity _entity;
public Entity Entity
{
get
{
if(_entity == null)
_entity = new Entity();
return _entity;
}
}
public List<Entity> Entities
{
get { return CreateMyTree(); }
}
private List<Entity> CreateMyTree()
{
var list = new List<Entity>();
var p1 = new Entity {Title = "Parent 1"};
p1.Children.Add(new Entity{ Title = "Child 1"});
p1.Children.Add(new Entity { Title = "Child 2" });
var p2 = new Entity { Title = "Parent 2" };
var c1 = new Entity { Title = "Child 1"};
var g1 = new Entity {Title = "GrandChild 1"};
c1.Children.Add(g1);
var c2 = new Entity { Title = "Child 2" };
p2.Children.Add(c1);
p2.Children.Add(c2);
list.Add(p1);
list.Add(p2);
return list;
}
}
public class Entity : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
}
}
private List<Entity> _children;
public List<Entity> Children
{
get
{
if(_children == null)
_children = new List<Entity>();
return _children;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
xaml :
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication7="clr-namespace:WpfApplication7"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="level3">
<TextBlock Text="{Binding Title}" Foreground="Green" />
</DataTemplate>
<HierarchicalDataTemplate x:Key="rootTemplate" ItemsSource="{Binding Children}" >
<TextBlock Text="{Binding Title}" Foreground="Red" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" ItemTemplate="{StaticResource level3}">
<TextBlock Text="{Binding Title}" Foreground="Blue" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Entities}" ItemTemplate="{StaticResource rootTemplate}"/>
</Grid>
</Window>
前面的帖子关于数据绑定的设计是完全正确的。此外,如果你想解决异步填充树的问题,你必须在另一个 thread/task 中执行此操作。使用 "ObservableCollection",您还可以在此任务 运行 和更改树时使您的树保持最新。但要小心 - ObservableCollections 不喜欢从其他线程然后 Ui 线程更改它们。此问题已讨论 here and here。
在我编写的以下示例中,我只是为 ObservableCollection 的每次修改调用 Dispatcher,但这不是最佳解决方案。
在示例中,您可以同步或异步调用树创建 - 因此您可以理解 Ui 的实现和行为的差异。
namespace TreeViewExample
{
public class MyNode
{
public ObservableCollection<MyNode> ChildNodes { get; set; }
public int Number { get; set; }
public MyNode()
{
ChildNodes = new ObservableCollection<MyNode>();
}
}
public class SimpleCommand : ICommand
{
private readonly Action _action;
public SimpleCommand(Action action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (parameter is string && (string) parameter == "async")
{
new TaskFactory().StartNew(_action);
}
else _action();
}
public event EventHandler CanExecuteChanged;
}
public class NodeViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyNode> _rootNodes;
public ObservableCollection<MyNode> RootNodes
{
set
{
_rootNodes = value;
OnPropertyChanged();
}
get { return _rootNodes; }
}
private readonly ICommand _populateCommand;
private readonly Random _random;
private Dispatcher _dispatcher;
public ICommand PopulateCommand { get { return _populateCommand; } }
public NodeViewModel()
{
_dispatcher = Dispatcher.CurrentDispatcher;
_random = new Random();
_populateCommand = new SimpleCommand(PopulateTree);
RootNodes = new ObservableCollection<MyNode>();
}
private void PopulateTree()
{
try
{
var node = new MyNode {Number = 0};
if (_dispatcher.CheckAccess())
RootNodes.Add(node);
else _dispatcher.Invoke(() => RootNodes.Add(node));
FillNodeRecursively(node, 1);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private void FillNodeRecursively(MyNode rootNode, int level)
{
int rand = _random.Next(0, 4);
for (var i = 0; i <= rand; i++)
{
var subNode = new MyNode {Number = rand + i};
Thread.Sleep(50); //simulating some workload
if (_dispatcher.CheckAccess())
rootNode.ChildNodes.Add(subNode);
else _dispatcher.Invoke(() => rootNode.ChildNodes.Add(subNode));
if (level < 4)
FillNodeRecursively(subNode, level + 1);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
这是 WPF window 的 xaml:
<Window x:Class="TreeViewExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<HierarchicalDataTemplate x:Key="NodesTemplate" ItemsSource="{Binding Path=ChildNodes}" >
<TextBlock Text="{Binding Path=Number}" />
</HierarchicalDataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Bottom" >
<Button Command="{Binding PopulateCommand}" CommandParameter="sync">Sync</Button>
<Button Command="{Binding PopulateCommand}" CommandParameter="async">Async</Button>
</StackPanel>
<TreeView DockPanel.Dock="Top" ItemsSource="{Binding RootNodes}" ItemTemplate="{StaticResource NodesTemplate}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>