Data binding chain
I am trying to do DataBinding
Property -> DependencyProperty -> Property
But I'm in trouble. For example, We have a simple class with two properties, INotifyPropertyChanged:
public class MyClass : INotifyPropertyChanged
{
private string _num1;
public string Num1
{
get { return _num1; }
set
{
_num1 = value;
OnPropertyChanged("Num1");
}
}
private string _num2;
public string Num2
{
get { return _num2; }
set
{
_num2 = value;
OnPropertyChanged("Num2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(e));
}
}
And the TextBlock is declared in xaml:
<TextBlock Name="tb" FontSize="20" Foreground="Red" Text="qwerqwerwqer" />
Now try binding Num1 to tb.Text:
private MyClass _myClass = new MyClass();
public MainWindow()
{
InitializeComponent();
Binding binding1 = new Binding("Num1")
{
Source = _myClass,
Mode = BindingMode.OneWay
};
Binding binding2 = new Binding("Num2")
{
Source = _myClass,
Mode = BindingMode.TwoWay
};
tb.SetBinding(TextBlock.TextProperty, binding1);
//tb.SetBinding(TextBlock.TextProperty, binding2);
var timer = new Timer(500) {Enabled = true,};
timer.Elapsed += (sender, args) => _myClass.Num1 += "a";
timer.Start();
}
It works well. But if we uncomment this line
tb.SetBinding(TextBlock.TextProperty, binding2);
then the TextBlock will display nothing. DataBinding doesn't work! How can I do what I want?
a source to share
The problem is that the call SetBinding
clears up any previous bindings. So when you set the binding to Num2
, you clear the binding to Num1
. This is because a dependency property binding cannot have multiple sources - how would it know which one to use? (Of course this ignores usage MultiBinding
, but it won't help you in this scenario.)
You can do this to make dependency properties MyClass
a DependencyObject
and Num1
and Num2
. Then you can bind Num2
to a property Text
TextBox
, and it Num2
will update whenever the text gets updated from Num1
.
A picture is worth a thousand words - what you are trying to do is shown on the left. What you need to do is shown on the right:
alt text http://img339.imageshack.us/img339/448/twosources.png
Decided to try this so my logic is sound and it does work, but there are some tricks. First, here's the new MyClass
code:
public class MyClass : FrameworkElement
{
public static readonly DependencyProperty Num1Property =
DependencyProperty.Register("Num1", typeof(string), typeof(MyClass));
public static readonly DependencyProperty Num2Property =
DependencyProperty.Register("Num2", typeof(string), typeof(MyClass));
public string Num1
{
get { return (string)GetValue(Num1Property); }
set { SetValue(Num1Property, value); }
}
public string Num2
{
get { return (string)GetValue(Num2Property); }
set { SetValue(Num2Property, value); }
}
}
It's okay here, just replaced INotifyPropertyChanged
with DependencyProperty
. Now let's check the window code:
public partial class DataBindingChain : Window
{
public MyClass MyClass
{
get;
set;
}
public DataBindingChain()
{
MyClass = new MyClass();
InitializeComponent();
Binding binding1 = new Binding("Num1")
{
Source = MyClass,
Mode = BindingMode.OneWay
};
Binding binding2 = new Binding("Text")
{
Source = tb,
Mode = BindingMode.OneWay
};
tb.SetBinding(TextBlock.TextProperty, binding1);
MyClass.SetBinding(MyClass.Num2Property, binding2);
var timer = new Timer(500) { Enabled = true, };
timer.Elapsed += (sender, args) => Dispatcher.Invoke(UpdateAction, MyClass);
timer.Start();
}
Action<MyClass> UpdateAction = (myClass) => { myClass.Num1 += "a"; };
}
This is where the magic happens: we're setting up two bindings. The first binds TextBlock.Text
to Num1
, the second binds Num2
to TextBlock.Text
. We now have a scenario similar to the one on the right side of the image I showed you - a data binding chain. Another magic is that we cannot update a property Num1
on a different thread from the one it was created on, which will create a cross-thread exception. To work around this, we simply call the update on the UI thread with Dispatcher
.
Finally, the XAML is used to demonstrate:
<Window x:Class="TestWpfApplication.DataBindingChain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBindingChain" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Name="tb" Grid.Row="0" FontSize="20" Foreground="Red"/>
<TextBlock Name="tb2" Grid.Row="1" FontSize="20" Foreground="Blue" Text="{Binding MyClass.Num2}"/>
</Grid>
And voila! Finished product:
alt text http://img163.imageshack.us/img163/6114/victorynf.png
a source to share