Thursday 13 August 2009

Silverlight TreeView – Binding ItemContainerStyle properties to DataContext

If you are doing work with TreeView in Silverlight you most likely already know that you cannot natively do Binding for ItemContainerStyle properties like IsSelected or Visibility. Recent question in silverlight.net forum, at first confused me – this is no problem in WPF, and then made me spend couple of hours finding a “workaround”. So here is one way to do this in Silverlight (in WPF this is not needed as native Bidning is supported)

The workaround is based around the ContainerFromItem method of the ItemContainerGenerator for the TreeView (or any other ItemsControl).  What the example below does is to create a test data Structure that consists of DataItem objects and apply it to the DataContext of the TreeView trough a object called DataContainer.

The “Binding” is fired trough the Setter method of the Binded Property (IsVisible or IsExpanded) in the data structure. What the setter needs to know is a reference to the ItemsControl (the TreeView in this case) in order to use the ContainerFromItem to get reference to the actual container for the Item. This is information can be passed to the DataItem in multiple ways, but in the example this is done trough a Method of the DataContainer that updates all DataItems with the necessary reference to the TreeView.

The handling of the LayoutUpdated event handler of the TreeView is used for Initial setting of the properties as passed.

The rest is pretty simple. Hese is fully working example- the Xaml:

<navigation:Page x:Class="silverlight.net.TreeViewBinding" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:Windows="clr-namespace:System.Windows;assembly=System.Windows.Controls"
d:DesignWidth="640" d:DesignHeight="480"
Title="TreeViewBinding Page">
<
UserControl.Resources>
<
Windows:HierarchicalDataTemplate x:Key="ItemDataTemplate" ItemsSource="{Binding Children}">
<
TextBlock Text="{Binding Name}"></TextBlock>
</
Windows:HierarchicalDataTemplate>
</
UserControl.Resources>
<
Grid x:Name="LayoutRoot">
<
StackPanel>
<
Button Content="Togle Item 2 Visibility" Click="Button_Toggle_Visibility" HorizontalAlignment="Left" VerticalAlignment="Top"></Button>
<
Button Content="Togle Item 2 IsExpanded" Click="Button_Toggle_IsExpanded" HorizontalAlignment="Left" VerticalAlignment="Top"></Button>
<
Controls:TreeView x:Name="TestTreeView" ItemTemplate="{StaticResource ItemDataTemplate}" ItemsSource="{Binding Children}">
</
Controls:TreeView>
</
StackPanel>
</
Grid>
</
navigation:Page>


And here is the Code behind:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;

namespace silverlight.net
{
public partial class TreeViewBinding : Page
{
DataContainer dataContainer = new DataContainer();

public TreeViewBinding()
{
InitializeComponent();
// Create some test Data
DataItem item1 = new DataItem() { Name = "Test1" };
DataItem item2 = new DataItem() { Name = "Test2" };
DataItem item2_1 = new DataItem() { Name = "Test2.1" };
DataItem item2_2 = new DataItem() { Name = "Test2.2" };
item2.Children.Add(item2_1);
item2.Children.Add(item2_2);

DataItem item3 = new DataItem() { Name = "Test3" };

dataContainer.Children.Add(item1);
dataContainer.Children.Add(item2);
dataContainer.Children.Add(item3);

dataContainer.ItemsControl = TestTreeView;

TestTreeView.DataContext = dataContainer;
TestTreeView.LayoutUpdated += new EventHandler(treeMatrix_LayoutUpdated);
}

void treeMatrix_LayoutUpdated(object sender, EventArgs e)
{
dataContainer.UpdateAdditionalBindings();
}


private void Button_Toggle_Visibility(object sender, RoutedEventArgs e)
{
if (dataContainer.Children[1].IsVisible)
dataContainer.Children[1].IsVisible = false;
else
dataContainer.Children[1].IsVisible = true;
}


private void Button_Toggle_IsExpanded(object sender, RoutedEventArgs e)
{
if (dataContainer.Children[1].IsExpanded)
dataContainer.Children[1].IsExpanded = false;
else
dataContainer.Children[1].IsExpanded = true;
}
}
public class DataContainer
{
public System.Boolean UpdateLayout = true;
public List<DataItem> Children {get; set;}

// Reference to the Items control
private ItemsControl _ItemsControl;
public ItemsControl ItemsControl
{
get { return _ItemsControl; }
set
{
_ItemsControl = value;

// Propagade the Items control to the children
foreach (DataItem data_item in Children)
{
data_item.ItemsControl = value;
}
}
}

public void UpdateAdditionalBindings()
{
foreach (DataItem item in Children)
item.UpdateAdditionalBindings();
}

public DataContainer()
{
Children = new List<DataItem>();
}
}

public class DataItem
{
// Reference to the Items control
private ItemsControl _ItemsControl;
public ItemsControl ItemsControl
{
get { return _ItemsControl; }
set
{
_ItemsControl = value;

// Propagade the Items control to the children
foreach (DataItem data_item in Children)
{
data_item.ItemsControl = value;
}
}
}

public List<DataItem> Children {get; set;}

public override string ToString()
{
return Name;
}

public System.String Name { get; set; }

private System.Boolean _IsVisible = true;
public System.Boolean IsVisible
{
get { return _IsVisible; }
set
{
if (_IsVisible != value)
UpdateVisibility(value);

_IsVisible = value;
}
}

private System.Boolean _IsExpanded = true;
public System.Boolean IsExpanded
{
get { return _IsExpanded; }
set
{
if (_IsExpanded != value)
UpdateIsExpanded(value);

_IsExpanded = value;
}
}

public void UpdateAdditionalBindings()
{
// Here update all properties
this.UpdateVisibility(_IsVisible);
this.UpdateIsExpanded(_IsExpanded);

foreach (DataItem dataItem in Children)
dataItem.UpdateAdditionalBindings();
}

private void UpdateVisibility(System.Boolean isVisible)
{
if (ItemsControl == null) return;

UIElement container = (UIElement)ItemsControl.ItemContainerGenerator.ContainerFromItem(this);
if (container != null)
{
if (isVisible)
container.SetValue(UIElement.VisibilityProperty, Visibility.Visible);
else
container.SetValue(UIElement.VisibilityProperty, Visibility.Collapsed);
}
}
private void UpdateIsExpanded(System.Boolean isExpanded)
{
if (ItemsControl == null) return;

TreeViewItem container = (TreeViewItem)ItemsControl.ItemContainerGenerator.ContainerFromItem(this);
if (container != null)
{
container.SetValue(TreeViewItem.IsExpandedProperty, isExpanded);
}
}

public DataItem()
{
Children = new List<DataItem>();
}
}
}




I hope this helps you !



Tzanimir

No comments: