Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF UserControl with Binding Mode=OneWay

I am trying to make a sample WPF user control (maybe it would be better to say “developer control”) with bindable properties. My code consists of these files:

----- MainWindow.xaml -----
<Window x:Class="Test_Binding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:testBinding="clr-namespace:Test_Binding"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <testBinding:MyLabelledTextBox x:Name="MLTB" LabelText="My custom control: MyLabelledTextBox" Text="{Binding StringData, Mode=OneWay}" />
    </StackPanel>
</Window>

----- MainWindow.xaml.cs -----
using System.Windows;

namespace Test_Binding
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.DataContext = new MyDataObject();
            this.InitializeComponent();
        }
    }
}

----- MyDataObject.cs -----
using System.Runtime.CompilerServices; // CallerMemberName
using System.ComponentModel; // INotifyPropertyChanged

namespace Test_Binding
{
    public class MyDataObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string stringData;
        public string StringData
        {
            get { return this.stringData; }
            set
            {
                if (value != this.stringData)
                {
                    this.stringData = value;
                    this.OnPropertyChanged();
                }
            }
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public MyDataObject()
        {
            System.Timers.Timer t = new System.Timers.Timer();
            t.Interval = 10000;
            t.Elapsed += t_Elapsed;
            t.Start();
        }

        private void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.StringData = ((this.StringData ?? string.Empty).Length >= 4 ? string.Empty : this.StringData + "*");
        }

    }
}

----- MyLabelledTextBox.xaml -----
<UserControl x:Class="Test_Binding.MyLabelledTextBox"
             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" 
             d:DesignHeight="300" d:DesignWidth="300">
  <StackPanel Background="Yellow">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*" />
            <ColumnDefinition Width="0.5*" />
        </Grid.ColumnDefinitions>   
        <Label x:Name="MLTBLabel" Grid.Row="0" Grid.Column="0" />
        <TextBox x:Name="MLTBTextBox" Grid.Row="0" Grid.Column="1" Background="Yellow" Text="{Binding Text, Mode=TwoWay}" />
    </Grid>
  </StackPanel>
</UserControl>

----- MyLabelledTextBox.xaml.cs -----
using System.Windows;
using System.Windows.Controls;

namespace Test_Binding
{
    /// <summary>
    /// Interaction logic for MyLabelledTextBox.xaml
    /// </summary>
    public partial class MyLabelledTextBox : UserControl
    {
        public static readonly DependencyProperty LabelTextProperty =
            DependencyProperty.Register("LabelText", typeof(string), typeof(MyLabelledTextBox),
            new PropertyMetadata(string.Empty, MyLabelledTextBox.LabelTextPropertyChanged));
        public string LabelText
        {
            get { return (string)this.GetValue(MyLabelledTextBox.LabelTextProperty); }
            set { this.SetValue(MyLabelledTextBox.LabelTextProperty, value); }
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(MyLabelledTextBox),
            new PropertyMetadata(string.Empty, MyLabelledTextBox.TextPropertyChanged));
        public string Text
        {
            get { return (string)this.GetValue(MyLabelledTextBox.TextProperty); }
            set { this.SetValue(MyLabelledTextBox.TextProperty, value); }
        }

        public MyLabelledTextBox()
        {
            this.InitializeComponent();

            this.MLTBLabel.DataContext = this;
            this.MLTBTextBox.DataContext = this;
            this.MLTBTextBox.TextChanged += new TextChangedEventHandler(this.MLTBTextBox_TextChanged);
        }

        private void MLTBTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            this.Text = this.MLTBTextBox.Text; // transfer changes from TextBox to bindable property (bindable property change notification will be fired)
        }

        private static void LabelTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MyLabelledTextBox)d).MLTBLabel.Content = (string)e.NewValue; // transfer changes from bindable property to Label
        }

        private static void TextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MyLabelledTextBox)d).MLTBTextBox.Text = (string)e.NewValue; // transfer changes from bindable property to TextBox
        }
    }
}

There is an instance of the “MyDataObject” class with the property “StringData”, which is modified periodically using a timer. My user control is bound to its property “StringData”. If the binding in the “MainWindow.xaml” file is set as “TwoWay”, the user control keeps being updated, but if I use the “OneWay” binding, then the user control is updated a single time and then the “PropertyChanged” event of the instance of the “MyDataObject” class does not fire again, because suddenly it has no subscriber.

Why does the “OneWay” binding stop working after being invoked once ? What code change would allow both the “TwoWay” and “OneWay” bindings to keep working ?

like image 329
Aspro Avatar asked May 31 '15 17:05

Aspro


1 Answers

Firstly.

this.MLTBLabel.DataContext = this;
this.MLTBTextBox.DataContext = this;

Noooooooooooooooo!

Never. Ever. Ever. Set your DataContext from code-behind. As soon as you do this, you lose the magical beauty of binding to your user control's dependency properties from your parent control. In other words, just don't do it.

Here's what you should do:

Give your UserControl an x:Name.

<UserControl ...
    x:Name="usr">

Bind your UserControl's Dependency Properties to your elements, like this:

<TextBlock Text="{Binding MyDependencyProperty, ElementName=usr}" ... />

Bind your UserControl's DataContext properties to your elements, like this:

<TextBlock Text="{Binding MyDataContextProperty}"/>

Using this method will allow you to set the DataContext of your UserControl in the MainWindow, but still be able to bind to the UserControl's dependency properties within the UserControl. If you set the DataContext of your UserControl in the code-behind, you will not be able to bind to your Dependency Properties.

Now, onto your actual problem.

All of this:

private void MLTBTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        this.Text = this.MLTBTextBox.Text; // transfer changes from TextBox to bindable property (bindable property change notification will be fired)
    }

    private static void LabelTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MyLabelledTextBox)d).MLTBLabel.Content = (string)e.NewValue; // transfer changes from bindable property to Label
    }

    private static void TextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MyLabelledTextBox)d).MLTBTextBox.Text = (string)e.NewValue; // transfer changes from bindable property to TextBox
    }

Forget about it. Looks like you're trying to get round the wrongdoings that I spoke about earlier.

You should be binding to your dependency properties instead:

<Label Grid.Row="0" Grid.Column="0" Text="{Binding Text, ElementName=usr}"/>

Another issue that you have is that in your MainWindow, you are using a binding on your UserControl.

Text="{Binding StringData, Mode=OneWay}"

Now, as you have already set your DataContext in code-behind. What this is effectively saying is:

Bind to StringData from the DataContext of the current control.

Which in your case, is a totally different binding from your MainWindow DataContext. (As you have explicitly set the DataContext in your UserControl).

Run through what I mentioned earlier. There's a lot to learn, but it's a start.

like image 76
Mike Eason Avatar answered Sep 30 '22 12:09

Mike Eason