So I am attempting to make a navigation (back/forwards) buttons for an application that utilises a treeview as the main navigation control, however on occassion when I am navigating backwards and forwards the selection change event triggers for multiple items at once even though I am only setting is selected once.
Below is an example application that illustrates the problem, when you select an item in the treeview it is displayed in the listview and the current position in the history is identified by which item is selected in the listview. If you select Item 1, Item 5, Item 9 and Item 16, then press the back button until you at item one, this works correctly. However when pressing forward, when going from Item 5 to Item 9 it also triggers events in Item 4 and Item 1 which causes the navigation logic to fail.
<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="500" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.ColumnSpan="2" HorizontalAlignment="Center">
<Button x:Name="buttonBack" Content="Back" Width="150" Margin="0,0,20,0" Click="buttonBack_Click"/>
<Button x:Name="buttonForwards" Content="Forwards" Width="150" Margin="20,0,0,0" Click="buttonForwards_Click"/>
</StackPanel>
<TreeView x:Name="tvTest" Margin="2" Grid.Column="0" Grid.Row="1" SelectedItemChanged="tvTest_SelectedItemChanged">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type this:TreeViewObject}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Content}" Margin="3,0,0,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="true"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<ListView x:Name="lvTest" Grid.Column="1" Grid.Row="1" Margin="2"/>
</Grid>
</Window>
And the main code for the application:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace WpfApplication1
{
public class TreeViewObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private int _id = -1;
public int ID
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("ID");
}
}
private string _content = "";
public string Content
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged("Content");
}
}
private ObservableCollection<TreeViewObject> _children = new ObservableCollection<TreeViewObject>();
public ObservableCollection<TreeViewObject> Children
{
get { return _children; }
set
{
_children = value;
OnPropertyChanged("Children");
}
}
}
public class TreeViewObjectManager
{
private static TreeViewObjectManager _instance = null;
public static TreeViewObjectManager Instance
{
get { return _instance ?? (_instance = new TreeViewObjectManager()); }
private set { _instance = value; }
}
private ObservableCollection<TreeViewObject> _items = new ObservableCollection<TreeViewObject>();
public ObservableCollection<TreeViewObject> Items
{
get { return _items; }
private set { _items = value; }
}
private List<TreeViewObject> _raw = new List<TreeViewObject>();
private TreeViewObjectManager()
{
TreeViewObject obj1 = new TreeViewObject();
obj1.ID = 1;
obj1.Content = "Item 1";
TreeViewObject obj2 = new TreeViewObject();
obj2.ID = 2;
obj2.Content = "Item 2";
TreeViewObject obj3 = new TreeViewObject();
obj3.ID = 3;
obj3.Content = "Item 3";
TreeViewObject obj4 = new TreeViewObject();
obj4.ID = 4;
obj4.Content = "Item 4";
TreeViewObject obj5 = new TreeViewObject();
obj5.ID = 5;
obj5.Content = "Item 5";
TreeViewObject obj6 = new TreeViewObject();
obj6.ID = 6;
obj6.Content = "Item 6";
TreeViewObject obj7 = new TreeViewObject();
obj7.ID = 7;
obj7.Content = "Item 7";
TreeViewObject obj8 = new TreeViewObject();
obj8.ID = 8;
obj8.Content = "Item 8";
TreeViewObject obj9 = new TreeViewObject();
obj9.ID = 9;
obj9.Content = "Item 9";
TreeViewObject obj10 = new TreeViewObject();
obj10.ID = 10;
obj10.Content = "Item 10";
TreeViewObject obj11 = new TreeViewObject();
obj11.ID = 11;
obj11.Content = "Item 11";
TreeViewObject obj12 = new TreeViewObject();
obj12.ID = 12;
obj12.Content = "Item 12";
TreeViewObject obj13 = new TreeViewObject();
obj13.ID = 13;
obj13.Content = "Item 13";
TreeViewObject obj14 = new TreeViewObject();
obj14.ID = 14;
obj14.Content = "Item 14";
TreeViewObject obj15 = new TreeViewObject();
obj15.ID = 15;
obj15.Content = "Item 15";
TreeViewObject obj16 = new TreeViewObject();
obj16.ID = 16;
obj16.Content = "Item 16";
obj1.Children.Add(obj2);
obj1.Children.Add(obj3);
obj1.Children.Add(obj4);
obj1.Children.Add(obj8);
obj4.Children.Add(obj5);
obj4.Children.Add(obj6);
obj4.Children.Add(obj7);
obj9.Children.Add(obj10);
obj9.Children.Add(obj11);
obj9.Children.Add(obj16);
obj11.Children.Add(obj12);
obj12.Children.Add(obj13);
obj12.Children.Add(obj14);
obj12.Children.Add(obj15);
Items.Add(obj1);
Items.Add(obj9);
_raw.Add(obj1);
_raw.Add(obj2);
_raw.Add(obj3);
_raw.Add(obj4);
_raw.Add(obj5);
_raw.Add(obj6);
_raw.Add(obj7);
_raw.Add(obj8);
_raw.Add(obj9);
_raw.Add(obj10);
_raw.Add(obj11);
_raw.Add(obj12);
_raw.Add(obj13);
_raw.Add(obj14);
_raw.Add(obj15);
_raw.Add(obj16);
}
public TreeViewObject GetObj(int id)
{
foreach(TreeViewObject obj in _raw)
{
if (obj.ID == id)
return obj;
}
return null;
}
}
public partial class MainWindow : Window
{
private ObservableCollection<int> _history = new ObservableCollection<int>();
public ObservableCollection<int> History
{
get { return _history; }
set { _history = value; }
}
private int _currentHistory = 0;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
tvTest.ItemsSource = TreeViewObjectManager.Instance.Items;
lvTest.ItemsSource = History;
}
private void tvTest_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var selected = (sender as TreeView).SelectedItem as TreeViewObject;
if(selected != null)
{
if(_history.Count == 0)
{
_history.Add(selected.ID);
_currentHistory = _history.Count - 1;
}
else
{
if(_history.Count - 1 == _currentHistory)
{
if (_history[_currentHistory] != selected.ID)
{
_history.Add(selected.ID);
_currentHistory = _history.Count - 1;
}
}
else
{
if (_history[_currentHistory] != selected.ID)
{
for (int i = _history.Count - 1; i > _currentHistory; --i)
_history.RemoveAt(i);
_history.Add(selected.ID);
_currentHistory = _history.Count - 1;
}
}
}
}
lvTest.SelectedIndex = _currentHistory;
e.Handled = true;
}
private void buttonBack_Click(object sender, RoutedEventArgs e)
{
if(_currentHistory > 0)
{
int tempID = _history[_currentHistory - 1];
var obj = TreeViewObjectManager.Instance.GetObj(tempID);
if(obj != null)
{
_currentHistory--;
Action uiAction = () =>
{
var container = GetTreeViewItem(tvTest, obj);
if (container != null)
container.IsSelected = true;
};
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, uiAction);
}
}
}
private void buttonForwards_Click(object sender, RoutedEventArgs e)
{
if (_history.Count - 1 > _currentHistory)
{
int tempID = _history[_currentHistory + 1];
var obj = TreeViewObjectManager.Instance.GetObj(tempID);
if (obj != null)
{
_currentHistory++;
Action uiAction = () =>
{
var container = GetTreeViewItem(tvTest, obj);
if (container != null)
container.IsSelected = true;
};
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, uiAction);
}
}
}
public TreeViewItem GetTreeViewItem(ItemsControl cont, object item)
{
if (cont != null)
{
if (cont.DataContext == item)
return cont as TreeViewItem;
if (cont is TreeViewItem && !((TreeViewItem)cont).IsExpanded)
cont.SetValue(TreeViewItem.IsExpandedProperty, true);
cont.ApplyTemplate();
ItemsPresenter itemsPres = (ItemsPresenter)cont.Template.FindName("ItemsHost", cont);
if (itemsPres != null)
itemsPres.ApplyTemplate();
else
{
itemsPres = FindVisualChild<ItemsPresenter>(cont);
if (itemsPres == null)
{
cont.UpdateLayout();
itemsPres = FindVisualChild<ItemsPresenter>(cont);
}
}
System.Windows.Controls.Panel itemsHostPanel = (System.Windows.Controls.Panel)VisualTreeHelper.GetChild(itemsPres, 0);
UIElementCollection children = itemsHostPanel.Children;
for (int i = 0, count = cont.Items.Count; i < count; ++i)
{
TreeViewItem subCont;
subCont = (TreeViewItem)cont.ItemContainerGenerator.ContainerFromIndex(i);
subCont.BringIntoView();
if (subCont != null)
{
TreeViewItem result = GetTreeViewItem(subCont, item);
if (result != null)
return result;
else
subCont.IsExpanded = false;
}
}
}
return null;
}
private T FindVisualChild<T>(Visual visual) where T : Visual
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
if (child != null)
{
T correctlyTyped = child as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
T descendent = FindVisualChild<T>(child);
if (descendent != null)
{
return descendent;
}
}
}
return null;
}
}
}
Any suggestions on how to get around this issue would be greatly appreciated.
I found the answer, by removing the code to collapse and expand the treeview items it is still possible to get the item (note this will probably only work if you are not using a virtualising in your treeview). Therefore the structure of the treeview does not change and I still have access to the item. Then in the selected item changed event I just added a call to BringIntoView on the selected treeview item therefore expanding the treeview to show the selected object where required.
Below is the adjusted GetTreeViewItem method:
public TreeViewItem GetTreeViewItem(ItemsControl cont, object item)
{
if(cont != null)
{
if (cont.DataContext == item)
return cont as TreeViewItem;
cont.ApplyTemplate();
ItemsPresenter itemsPres = (ItemsPresenter)cont.Template.FindName("ItemsHost", cont);
if (itemsPres != null)
itemsPres.ApplyTemplate();
else
{
itemsPres = FindVisualChild<ItemsPresenter>(cont);
if(itemsPres == null)
{
cont.UpdateLayout();
itemsPres = FindVisualChild<ItemsPresenter>(cont);
}
}
System.Windows.Controls.Panel itemsHostPanel = (System.Windows.Controls.Panel)VisualTreeHelper.GetChild(itemsPres, 0);
UIElementCollection children = itemsHostPanel.Children;
for(int i = 0, count = cont.Items.Count; i < count; ++i)
{
TreeViewItem subCont;
subCont = (TreeViewItem)cont.ItemContainerGenerator.ContainerFromIndex(i);
if(subCont != null)
{
TreeViewItem result = GetTreeViewItem(subCont, item);
if (result != null)
return result;
}
}
}
return null;
}