0
votes

I am using DataBinding on a DependencyProperty of an UserControl and on a DependencyProperty of a TextBlock. I am binding those to a Property TestValue. First I initialize TestValue with TestValue="TestValue set first time!"; That Works! Both, UserControl and TextBlock get updated.
Then, by a DispatcherTimer, I write a random value to TestValue every second and this Does not Work. The TextBlock gets updated, but the UserControl not.

Here ist my code of the MainWindow:

//MainWindow.cs
public partial class MainWindow : Window
{
    private MainWindowViewModel viewModel;

    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = this.viewModel = new MainWindowViewModel()
        {
            TestValue = "TestValue set first time!",
        };
    }
}

//MainWindow.xaml
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local ="clr-namespace:RhinoTouchUIwpf;assembly="
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="RhinoTouchUIwpf.MainWindow"
    SizeToContent="WidthAndHeight">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto"/>
    </Grid.ColumnDefinitions>
    <local:UserControl1 OperationName="{Binding TestValue, Mode=OneWay}" Grid.Row="0"/>
    <TextBlock Text="{Binding TestValue, Mode=OneWay}" FontSize="15" Grid.Row="1" />
</Grid>

And here is the code of the UserControl:

//UserControl1.cs
public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    public static DependencyProperty MyDependencyProperty =DependencyProperty.Register("OperationName", typeof(String), typeof(UserControl1), new UIPropertyMetadata(null, MyDependencyPropertyChangedHandler));

    private static void MyDependencyPropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        ((UserControl1)sender).OperationName = (String)e.NewValue;
        ((UserControl1)sender).NumberLabel.Text = (String)e.NewValue;
    }
    public String OperationName
    {
        get { return (string)(this.GetValue(MyDependencyProperty)); }
        set { this.SetValue(MyDependencyProperty, value); }
    }
}

//UserControl1.xaml
<UserControl
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:RhinoTouchUIwpf" x:Class="RhinoTouchUIwpf.UserControl1" 
         mc:Ignorable="d">

<Border BorderThickness="1" BorderBrush="Blue" x:Name="ControlBorder">
    <Grid Background="AliceBlue">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock x:Name="NumberLabel" FontSize="20"  Text="0" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Border>

This is the code of the MainWindowViewModel, whose property I change by the DispatcherTimer every second.

public class MainWindowViewModel : ViewModelBase
{
    DispatcherTimer dispatcherTimer; 

    public MainWindowViewModel()
    {
        dispatcherTimer = new DispatcherTimer();
        dispatcherTimer.Tick += Dispatcher_Tick;
        dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
        dispatcherTimer.IsEnabled = true;
    }

    void Dispatcher_Tick(object sender, EventArgs e)
    {
        this.TestValue = "" + DateTime.Now.Millisecond;
    }

    private String _TestValue;

    public String TestValue
    {
        get { return _TestValue; }
        set
        {
            _TestValue = value;
            OnPropertyChanged("TestValue");
        }
    }

    private static readonly MainWindowViewModel NullInstance = null;

}

public abstract class ViewModelBase : System.ComponentModel.INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this,
                new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

The text of the TextBlock changes every second, the text of the UserControl1 doesn't.
What am I doing wrong?

1

1 Answers

0
votes

The problem you are having is this line of code in your PropertyChangedCallback:

((UserControl1)sender).OperationName = (String)e.NewValue;

The problem here is that the event handler will notify you of a new change to that same property. When you enter this function, it is coming from:

set { this.SetValue(MyDependencyProperty, value); }

Effectively entering an infinite loop (that the dependency property system detect and stops further call to that function). Removing that line will solve your problem.

On the other hand, you can get rid of the PropertyChangedCallback altogether if you define this in your XAML instead:

// On your UserControl element, add a x:Name="uc"
<TextBlock x:Name="NumberLabel" FontSize="20"  
           Text="{Binding OperationName, ElementName=uc, FallbackValue=0}" 
           Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center" 
           VerticalAlignment="Center"/>

Here's the full UserControl1.xaml

<UserControl x:Class="WpfApplication1.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" x:Name="uc"
             d:DesignHeight="300" d:DesignWidth="300">
    <Border BorderThickness="1" BorderBrush="Blue" x:Name="ControlBorder">
        <Grid Background="AliceBlue">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <TextBlock x:Name="NumberLabel" FontSize="20"  
                        Text="{Binding OperationName, ElementName=uc, FallbackValue=0}" 
                        Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center" 
                        VerticalAlignment="Center"/>
        </Grid>
    </Border>
</UserControl>