John Kraft
John Kraft

Reputation: 6840

TreeView Not Storing Expected State

I am encountering the following behavior which I do not understand. I have data that is correctly displayed in the TreeView as follows.

Sport
  > BaseBall
    > Apparel
    > Equiptment
        Glove
        Bat
  > Football
    > Helmet
  > Soccer

However, when I click on any node, the underlying node data is that of it's first child.

    Node Clicked            Actual Data
-------------------------------------------
    Sport                       Baseball
    Baseball                    Apparel
    Football                    Helmet
    Bat                         null

I have looked at many examples on the web and in books, but I cannot spot the issue; and I'm sure it's very simple.

Edit I have visually inspected each node in the debugger, as well as using a handy little code snippet from Mathew MacDonald's Pro WPF in C# 2010 that actually displays the types and value of every control in the TreeView.

Edit I have replaced initial code with an actual full application that reproduces the issue. It is the simplest example I could come up with.

The code(irrelevant sections removed):

<Application x:Class="WpfApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:this="clr-namespace:WpfApplication1"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <this:MainWindowViewModel x:Key="ViewModel:MainWindow"></this:MainWindowViewModel>
    </Application.Resources>
</Application>

-

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{StaticResource ViewModel:MainWindow}">
    <TreeView x:Name="theTree" ItemsSource="{Binding Path=OrgChart}"
              MouseUp="theTree_MouseUp">
        <TreeView.Resources>
            <HierarchicalDataTemplate ItemsSource="{Binding Path=Subordinates}" DataType="{x:Type this:OrgChartNodeViewModel}">
                <TextBlock Text="{Binding Path=Position.Title}"></TextBlock>
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>
</Window>

-

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void theTree_MouseUp(object sender, MouseButtonEventArgs e)
    {
        Stack<DependencyObject> stack = new Stack<DependencyObject>();
        var it = e.OriginalSource as DependencyObject;
        while (it != null)
        {
            it = VisualTreeHelper.GetParent(it);
            stack.Push(it);
        }

        int level = 0;
        while (stack.Any())
        {
            Debug.Write("".PadLeft(level++));
            it = stack.Pop();
            if (it is TreeViewItem)
            {
                var item = it as TreeViewItem;
                OrgChartNodeViewModel vm = ((OrgChartNodeViewModel)((TreeViewItem)it).Items.CurrentItem);
                if (vm != null)
                    Debug.WriteLine(vm.Position.Title);
            }
            else
            {
                Debug.WriteLine(it);
            }
        }
    }
}

-

public class MainWindowViewModel
{
    public List<OrgChartNodeViewModel> OrgChart { get; set; }

    public MainWindowViewModel()
    {
        Position ceo = new Position { Title = "CEO" };
        Position vp = new Position { Title = "VP" };
        Position boss = new Position { Title = "Boss" };
        Position worker = new Position { Title = "Worker" };

        OrgChartNodeViewModel root;
        OrgChartNodeViewModel node = new OrgChartNodeViewModel { Position = ceo };
        OrgChartNodeViewModel child = new OrgChartNodeViewModel { Position = vp };
        root = node;
        node.Subordinates.Add(child);
        node = child;
        child = new OrgChartNodeViewModel { Position = boss };
        node.Subordinates.Add(child);
        node = child;
        child = new OrgChartNodeViewModel { Position = worker };
        node.Subordinates.Add(child);

        OrgChart = new List<OrgChartNodeViewModel> { root };
    }
}

-

public class OrgChartNodeViewModel
{
    public Position Position { get; set; }
    public List<OrgChartNodeViewModel> Subordinates { get; set; }

    public OrgChartNodeViewModel()
    {
        Subordinates = new List<OrgChartNodeViewModel>();
    }
}

-

public class Position
{
    public string Title { get; set; }
}

Here is the output on my machine...

enter image description here

Upvotes: 3

Views: 98

Answers (1)

Viv
Viv

Reputation: 17380

Try switching

OrgChartNodeViewModel vm = ((OrgChartNodeViewModel)((TreeViewItem)it).Items.CurrentItem);

to

OrgChartNodeViewModel vm = ((OrgChartNodeViewModel)viewItem.DataContext);

In your while loop, you want the TreeViewItem.DataContext rather than Items.CurrentItem which points to it's children(and only one exists until expanded firstly). Hence why you're seeing VP instead of CEO.

I'd prolly switch out the entire function to something like:

private void theTree_MouseUp(object sender, MouseButtonEventArgs e) {
  var clickedItem = e.OriginalSource as FrameworkElement;
  if (clickedItem == null)
    return;
  var chartNode = clickedItem.DataContext as OrgChartNodeViewModel;
  if (chartNode != null)
    Debug.WriteLine(chartNode.Position.Title);
}

than iterate through the Visual-Tree to get a TreeViewItem. DataContext gets inherited in your xaml setup, so might as well use it to save doing redundant work.

Upvotes: 3

Related Questions