A couple of days ago I had the necessity of using data binding to keep track of the selected value of a WPF TreeView . At first it seemed to be an easy task so I wrote:
<TreeView SelectedValue="{Binding selected, Mode=TwoWay}" ... />
Running this code results on the following error:
SystemError: 'Provide value on 'System.Windows.Data.Binding' threw an exception.' Line number '12' and line position '7'.
The problem is that the SelectedValue (and SelectedItem) property is read-only.
There are several ways to deal with this problem. One alternative is to use a technique similar to the one described in the "Forwarding the Result of WPF Validation in MVVM" post. We're going to define an attached property which works as an "output only" property that could be used with data binding.
Attached property definition
I couldn't find a way to wrote the definition of the attached property in IronPython because it needed to be instanciated by XamlReader. So the definition was written using C#:
using System.Windows.Markup;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Collections.ObjectModel;
namespace Langexplr
{
public static class TreeViewSelectedBehavior
{
public static readonly DependencyProperty MySelectedProperty =
System.Windows.DependencyProperty.RegisterAttached(
"MySelected",
typeof(object),
typeof(TreeViewSelectedBehavior)
);
public static object GetMySelected(TreeView t)
{
return t.GetValue(MySelectedProperty);
}
public static void SetMySelected(TreeView t, object theValue)
{
t.SetValue(MySelectedProperty, theValue);
}
public static readonly DependencyProperty SelectedHelperProperty =
System.Windows.DependencyProperty.RegisterAttached(
"SelectedHelper",
typeof(TreeViewSelectedHelper),
typeof(TreeViewSelectedBehavior),
new UIPropertyMetadata(null,OnSelectedHelperChanged)
);
public static TreeViewSelectedHelper GetSelectedHelper(TreeView t)
{
return (TreeViewSelectedHelper)t.GetValue(SelectedHelperProperty);
}
public static void SetSelectedHelper(TreeView t,
TreeViewSelectedHelper theValue)
{
t.SetValue(SelectedHelperProperty, theValue);
}
static void OnSelectedHelperChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
((TreeViewSelectedHelper)e.NewValue).Register((TreeView)depObj);
}
}
This class define two properties:
MySelected
: the output property that is used to set the selected element in the view modelSelectedHelper
: which is used to as an object that modifies the value ofMySelected
when the selected item changes(see below).
The following helper class is used to subscribe the SelectedItemChanged event and change "
MySelected
" .public class TreeViewSelectedHelper
{
public TreeViewSelectedHelper() { }
void SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> args)
{
(sender as TreeView).SetValue(
TreeViewSelectedBehavior.MySelectedProperty,
((sender as TreeView)).SelectedItem);
}
public void Register(TreeView t)
{
t.SelectedItemChanged += SelectedItemChanged;
}
}
}
We can now compile this class:
set NETFX4=c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\
set WPFPATH=%NETFX4%\WPF
csc /debug /r:%NETFX4%System.Xaml.dll /r:%WPFPATH%\WindowsBase.dll /r:%WPFPATH%\PresentationCore.dll /r:%WPFPATH%\PresentationFramework.dll /target:library utils.cs
The example
Having defined this attached property and helper class we can now write the following example.
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utils="clr-namespace:Langexplr;assembly=utils"
Title="TreeView selection test" Width="300" Height="300">
<Window.Resources>
<utils:TreeViewSelectedHelper x:Key="selHelper" />
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding selected.label}"/>
<TreeView ItemsSource="{Binding roots}"
utils:TreeViewSelectedBehavior.SelectedHelper="{StaticResource selHelper}">
<utils:TreeViewSelectedBehavior.MySelected>
<Binding Path="selected" Mode="OneWayToSource"/>
</utils:TreeViewSelectedBehavior.MySelected>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding children}">
<TextBlock Text="{Binding label}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Window>
With this XAML definition we can write the following IronPython code:
import clr
clr.AddReference("PresentationCore")
clr.AddReference("PresentationFramework")
clr.AddReference("WindowsBase")
clr.AddReference('GalaSoft.MvvmLight.WPF4.dll')
from System.Windows.Markup import XamlReader
from System.Windows import Application
from System.IO import File
from System.Windows.Controls import TreeView
import System
import clrtype
from GalaSoft.MvvmLight import ViewModelBase
from System.Collections.ObjectModel import ObservableCollection
class TestModel(ViewModelBase):
__metaclass__ = clrtype.ClrClass
def __init__(self):
self.name = 'algo'
self.root = NodeModel('x1',[NodeModel('y2',[NodeModel('z2',[])]),
NodeModel('y3',[])])
self.sselected = NodeModel('',[])
@property
@clrtype.accepts()
@clrtype.returns(System.Object)
def selected(self):
result = self.sselected
return result
@selected.setter
@clrtype.accepts(System.Object)
@clrtype.returns()
def selected(self, value):
self.sselected = value
self.RaisePropertyChanged('selected')
@property
@clrtype.accepts()
@clrtype.returns(System.Object)
def roots(self):
return [self.root]
@property
@clrtype.accepts()
@clrtype.returns(System.String)
def label(self):
return self.name
@label.setter
@clrtype.accepts(System.String)
@clrtype.returns()
def label(self, value):
self.name = value
self.RaisePropertyChanged('label')
class NodeModel:
__metaclass__ = clrtype.ClrClass
def __init__(self,label, initchildren):
self.children_collection = ObservableCollection[System.Object](initchildren)
self.name = label
@property
@clrtype.accepts()
@clrtype.returns(System.Object)
def label(self):
return self.name
@property
@clrtype.accepts()
@clrtype.returns(System.Object)
def children(self):
return self.children_collection
xamlFile = File.OpenRead('test.xaml')
window = XamlReader.Load(xamlFile)
window.DataContext = TestModel()
xamlFile.Close()
Application().Run(window)
By running this example we can see how the label of the selected element of the TreeView is reflected in the TextBlock defined above.