在WPF中,如何以编程方式取消选择TreeView中的所有项?

3

我正在尝试创建一个递归方法来取消选择WPF TreeView中的所有项目。复杂的事情是每个TreeViewItem不是一个小型TreeView,这导致需要反复进行大量转换。因此,这是我所尝试的:

TreeViewDeselectAll(myTreeView.Items);      

// Must send in ItemCollection to allow the recursive call
private void TreeViewDeselectAll(ItemCollection myTreeViewItems)
{
    // The only way to get to the IsSelected property is to turn it back into a TreeViewItem
    foreach (TreeViewItem currentItem in myTreeViewItems)
    {
        currentItem.IsSelected = false;
        if (currentItem.HasItems)
        {
             // Recursify!
             TreeViewDeselectAll(currentItem.Items);
        }
    }
}

有没有人成功地取消选择TreeView中的所有项?您是否能够以递归方式遍历TreeView?

Winforms TreeView具有Nodes集合,这实际上是一个微型TreeView。这允许递归很好地工作。但是WPF TreeView没有Nodes。

在 .Net 4.0中工作。


1
我还想指出的是,除非你做了一些特殊的处理,否则TreeView只允许选择一个项目。 - Joel Lucsy
@JoelLucsy 没错。我尝试过只使用ItemContainerStyle来玩耍,但发现在TreeView中不可能实现。然而,OP肯定需要多选功能,这可以通过将选择状态移动到Model/ViewModel来实现。请参见我的答案。 - Federico Berasategui
2个回答

6

好的。删除你所有的代码,重新开始。

如果你正在使用WPF,你需要摆脱所有过时的做法,理解并接受WPF思维

在WPF中,你不会通过编程方式“选择一个TreeViewItem”,因为UI不是数据

UI不负责跟踪你的数据项(显示在TreeView或任何其他UI元素中)的选中状态。

相反,你需要创建一个合适的DataModel和一个ViewModel来分别保存数据和应用程序逻辑。

Josh Smith有一篇非常有趣的文章,解释了如何以正确的方式处理WPF中的TreeView。

基本上像这样:

<Window x:Class="MiscSamples.MVVMTreeViewSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MVVMTreeViewSample" Height="300" Width="300">
    <DockPanel>
        <Button Content="Select All" Click="SelectAll" DockPanel.Dock="Top"/>
        <Button Content="Select None" Click="SelectNone" DockPanel.Dock="Top"/>

        <TreeView ItemsSource="{Binding}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <CheckBox IsChecked="{Binding IsSelected}" Content="{Binding DisplayName}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </DockPanel>
</Window>

后台代码:

public partial class MVVMTreeViewSample : Window
{
    private ObservableCollection<HierarchicalData> Data; 

    public MVVMTreeViewSample()
    {
        InitializeComponent();
        DataContext = Data = CreateData();
    }

    private void Select(IEnumerable<HierarchicalData> items, bool isselected)
    {
        while (items.Any())
        {
            items.ToList().ForEach(x => x.IsSelected = isselected);
            items = items.SelectMany(x => x.Children);
        }  
    }

    private void SelectAll(object sender, RoutedEventArgs e)
    {
        Select(Data, true);
    }

    private void SelectNone(object sender, RoutedEventArgs e)
    {
        Select(Data, false);
    }

    private ObservableCollection<HierarchicalData> CreateData()
    {
        return new ObservableCollection<HierarchicalData>
        {
            //... Dummy Data here
        }
    }
}

数据项:
public class HierarchicalData : System.ComponentModel.INotifyPropertyChanged
{

    public string DisplayName { get; set; }

    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            _isSelected = value;
            OnPropertyChanged("IsSelected");
        }
    }

    public ObservableCollection<HierarchicalData> Children { get; private set; }

    public HierarchicalData()
    {
        Children = new ObservableCollection<HierarchicalData>();
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

结果:

enter image description here


请看,没有任何代码操纵任何UI元素。一切都通过DataBinding简单的属性和INotifyPropertyChanged来完成。这就是你在WPF中编程的方法。不需要复杂的VisualTreeHelper.Whatever(),也不需要复杂的操作,因为它们会导致各种问题,如UI虚拟化等。 在SelectAll()SelectNone()方法中,我只是遍历Data Items而不是UI元素,并相应地设置它们的值。然后,WPF通过DataBinding引擎更新UI。 请看,没有必要将任何东西转换为任何东西,以及如何使用自己的简单类而不必处理复杂、晦涩的WPF对象模型。 忘掉winforms吧。它是个没用的恐龙,什么都不支持。 WPF非常棒。只需将我的代码复制并粘贴到文件 -> 新建项目 -> WPF应用程序中,亲自查看结果。请注意,您需要在CreateData()方法中添加项目到集合中。 如果您需要进一步的帮助,请告诉我。

不错的文章。我喜欢WPF,尽管我认为许多程序员滥用数据绑定。许多人编写的代码假定数据库始终处于完美的稳健状态,并且网络始终是可靠的。 - Clay Acord
@ClayAcord 我无法理解你的评论。WPF的数据绑定与数据库或网络连接完全没有任何关系。 - Federico Berasategui
我也非常担心微软现在所采取的方向,或者说缺乏方向。.Net和WPF会被Windows 9淘汰吗?谁真正知道呢?<讽刺>我很高兴微软建议转向ASP.NET中的MVC,以便消除巨大的VIEWSTATE。我需要那个空间来塞3或4个我需要用到的JavaScript库。</讽刺> - Clay Acord
如果 .Net / WPF 被淘汰,我会直接将所有的 XAML + MVVM 迁移到 WinRT XAML。甚至可以使用非 XAML 的框架,因为我的 ViewModels 实际上是与 View 无关的。相比之下,非 MVVM 代码要难得多才能迁移。 - Federico Berasategui
@ClayAcord 为了明确起见,如果从数据库加载数据后需要执行数据一致性检查,则应由“数据访问层”负责,而不是UI层。同样适用于从网络获取的数据,其一致性应由“服务层”或“通信层”保证,而不是“UI层”。WPF是一个UI框架,它负责UI方面的问题,而不是其他所有方面。 - Federico Berasategui

1
每个TreeViewItem都是一个迷你树形视图。你可能有错的印象或者你读错了关于TreeViewItems的某些内容。
你做错的是传递了Items集合,这些集合不包含TreeViewItem类型的子元素,而是数据项。
传递子元素集合,就应该没问题了。
像这样:
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

    }

    private bool @switch = false;

    private void TreeViewDeselectAll(IEnumerable myTreeViewItems, bool value)
    {
        if (myTreeViewItems != null)
        {
            foreach (var currentItem in myTreeViewItems)
            {
                if (currentItem is TreeViewItem)
                {
                    TreeViewItem item = (TreeViewItem)currentItem;
                    item.IsSelected = value;
                    if (item.HasItems)
                    {
                        TreeViewDeselectAll(LogicalTreeHelper.GetChildren(item), value);
                    }
                }
            }
        }
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.TreeViewDeselectAll(LogicalTreeHelper.GetChildren(this.treeView), this.@switch);
    }
}

例如,XAML代码看起来会像这样:

<StackPanel>
    <TreeView Name="treeView">
        <TreeViewItem Header="First Level">
            <TreeViewItem Header="Second Level"/>
        </TreeViewItem>
    </TreeView>
    <Button Click="Button_Click">select/unselect all</Button>
</StackPanel>

我稍微修改了你的TreeViewDeselectAll方法。根据开关,您可以选择或取消选择所有内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接