3

I'm trying to create a recursive method to deselect all items in a WPF TreeView. The thing that complicates things is that each TreeViewItem is not a mini-TreeView. This causes you to have to do a lot of casting back and forth. So, here's what I have tried:

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);
        }
    }
}

Has anyone successfully deselected all items in a TreeView? Have you even been able to traverse a TreeView in a recursive manner?

The Winforms TreeView has a Nodes collection, which is really a mini-TreeView. This allows recursion just fine. But the WPF TreeView does not have Nodes.

Working in .Net 4.0.

Clay Acord
  • 305
  • 1
  • 4
  • 13
  • 1
    I'd also like to point out that, unless you've done something special, the TreeView only allows one item to be selected. – Joel Lucsy Dec 20 '13 at 17:23
  • @JoelLucsy That's right. I've tried to play around with `ItemContainerStyle` only to find out it's not possible with the `TreeView`. The OP surely needs multiple selection, though, which can be achieved by moving the selection state to the Model/ViewModel. See my answer. – Federico Berasategui Dec 20 '13 at 17:27

2 Answers2

6

Ok. Delete all your code and start all over.

If you're working with WPF, you really need to leave behind all the archaic practices from dinosaur technologies (such as winforms) and understand and embrace The WPF Mentality.

In WPF, You don't "select a TreeViewItem" programatically, simply because UI is Not Data.

The UI is not responsible for keeping track of the selection state of your Data items, which are displayed in a TreeView, or any other UI element.

Instead, you create a proper DataModel and a ViewModel to hold the data and the application logic, respectively.

There is a very interesting article by Josh Smith explaining how to deal with a TreeView in WPF, the right way.

Basically something like this:

<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>

Code Behind:

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
        }
    }
}

Data Item:

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));
    }
}

Result:

enter image description here

  • See how there is NOT a single line of code that manipulates ANY UI element. Everything is done via DataBinding to Simple, Simple Properties and INotifyPropertyChanged. That's how you program in WPF. No need for complicated VisualTreeHelper.Whatever() stuff, no need for complicated manipulations which incur in all sorts of issues due to UI virtualization or the like.
  • In the SelectAll() and SelectNone() methods, I'm just iterating thru Data Items rather than UI elements, and setting their values accordingly. WPF then updates the UI via the DataBinding engine.
  • See how there is no need to cast anything to anything, and how I'm working with my own simple classes rather than having to deal with the complex, arcane WPF Object Model.
  • forget winforms. it'a useless dinosaur that doesn't support anything.
  • WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself. Notice that you will need to add items to the collection in the CreateData() method.
  • Let me know if you need further help.
Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • Nice post. I do enjoy WPF, although I think databinding is misused by many programmers. Many folks write code that assumes the database is going to be perfectly robust at all times, and the network is always going to be reliable. – Clay Acord Dec 20 '13 at 20:47
  • @ClayAcord I cannot understand your comment. WPF's DataBinding has absolutely NOTHING to do with databases or network connections. – Federico Berasategui Dec 20 '13 at 20:51
  • I'm also very worried about the direction, or rather, lack of direction that Microsoft is taking now. Is .Net and WPF going to be obsoleted in Windows 9? Who really knows? I'm glad MS is recommending moving toward MVC in ASP.NET, so that it takes out the massive VIEWSTATE. I need that room to cram 3 or 4 JavaScript libraries I'm gonna need. – Clay Acord Dec 20 '13 at 20:52
  • @ClayAcord if .Net / WPF is obsoleted, I will port all my XAML + MVVM straight to WinRT XAML. Or even non-XAML-based frameworks, since my ViewModels are actually View-agnostic. whereas non-MVVM code is much harder to port. – Federico Berasategui Dec 20 '13 at 20:53
  • @ClayAcord to make it clear, if there are Data consistency checks to be performed after loading data from a DB, it is the responsibility of the `Data Access Layer`, NOT the UI layer. The same applies to data obtained from the network, whose consistency should be assured by the `Service Layer` or `Communications Layer`, NOT the `UI Layer`. WPF is a UI framework, and it takes responsibility for UI concerns, not everything else. – Federico Berasategui Dec 20 '13 at 21:22
1

Each TreeViewItem is a Mini-TreeView. You got the wrong impression or you read something wrong about TreeViewItems.

What you doing wrong is passing the Items collection which doesnt contain children of type TreeViewItem but instead those are the data items.

Pass the children collection and you should do fine.

Like this:

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 would look like this for example:

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

I changed your TreeViewDeselectAll method a little bit. Based on switch you can select or unselect all.

dev hedgehog
  • 8,698
  • 3
  • 28
  • 55