views:

39

answers:

1

Hello

I am trying to do follow DataBinding

Property -> DependencyProperty -> Property

But i have trouble. For example, We have simple class with two properties implements 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 TextBlock declared in xaml:

<TextBlock Name="tb" FontSize="20" Foreground="Red" Text="qwerqwerwqer" />

Now lets trying to bind 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 string

tb.SetBinding(TextBlock.TextProperty, binding2);

then TextBlock display nothing. DataBinding doesn't work! How can i to do what i want?

+2  A: 

The problem is that the SetBinding call clears out any previous bindings. So when you set a binding to Num2, you are clearing out the binding to Num1. This happens because a dependency property binding cannot have multiple sources- how would it know which one to use? (Of course, this ignores the usage of a MultiBinding, but that's not going to help you in this scenario).

The way you can do this is to make MyClass a DependencyObject and Num1 and Num2 dependency properties. Then you can bind Num2 to the Text property of the TextBox, and Num2 will be updated whenever the text receives an update from Num1.

A picture is worth a thousand words- what you're trying to do is shown on the left. What you need to do is shown on the right:

alt text

Decided to try this out to ensure my logic was sound, and indeed it works, but there are some tricks. For starters, here is 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); }
    }
}

Nothing scary here, just replaced your INotifyPropertyChanged with DependencyProperty. Now let's check out the window code-behind:

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 set up two bindings. The first binds the TextBlock.Text to Num1, the second binds Num2 to the TextBlock.Text. Now we have a scenario like the right side of the picture I showed you- a data-binding chain. The other magic is that we cannot update the Num1 property on a different thread from the one it was created on- that would create a cross-thread exception. To bypass this, we simply invoke an update onto the UI thread using the Dispatcher.

Finally, the XAML used for demonstration:

<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! The finished product:

alt text

Charlie
WOW! Dude i really love your answer! But i forgot to mention that actually one of my properties located in class which inherited from WinForms control that's why i cann't to use dependency property :(Your solution is great for asked question. unfortunately i cann't applied this to my real task.
Neir0
This is a bad approach to take in general and indicates that you probably need to rethink what you're really trying to accomplish. What this solution does is essentially use the UI to control a relationship between what were two non-UI object properties. How did this end up in the UI in the first place rather than in code?
John Bowen
Well you can use the binding mode "OneWayToSource" for that scenario. But you will still need to set the binding on two different objects- how you do that is up to you. But remember that setting the second binding will clear any bindings already set. So somehow you need to relocate some of your properties.
Charlie