Farsen
Farsen

Reputation: 1687

WPF custom UserControl - twoway databinding issue

I am quite new to databinding in WPF, and I have a problem with a custom Textbox/Label and TwoWay databinding. The Text property can be set through databinding, but when the user changes the Text, the binding doesent update the source property.

First, I have my object which contains the source property:


public class DataObject {

   public string MyString { get; set; }

}

Then I have my custom UserControl. The purpose of this UserControl is bascially to show a textbox whenever it is clicked, so the user can change the value.

....
<DockPanel >
    <Label Name="Label" MouseLeftButtonDown="EditText" Style="{StaticResource InputLabel}" Content="{Binding Path=Text, RelativeSource={RelativeSource FindAncestor, AncestorType=MyNamespace:MyCustomLabel, AncestorLevel=1},Mode=TwoWay}" ></Label>
    <TextBox Name="TextBox" Style="{StaticResource TextBox}" Visibility="Collapsed" HorizontalAlignment="Left"  HorizontalContentAlignment="Left" />
  </DockPanel>
  ....

MyCustomLabel.Xaml.cs:

public partial class MyCustomLabel : UserControl {

    private static readonly DependencyProperty TextProperty=DependencyProperty.Register("Text",typeof(string),typeof(MyCustomLabel));

    public MyCustomLabel() {
        InitializeComponent();
    }

    public string Text {
      get {
        if (TextBox.Visibility==Visibility.Visible) {
          return (string)GetValue(MyCustomLabel.TextProperty);
        } else {
          return Label.Content.ToString();
        }
      } set {
        base.SetValue(MyCustomLabel.TextProperty,value);
      }
    }

    private void EditText(object sender,MouseButtonEventArgs e) {
          TextBox.Visibility=Visibility.Visible;
          Label.Visibility=Visibility.Collapsed;
          Dispatcher.BeginInvoke((ThreadStart)delegate {
            TextBox.Focus();
            TextBox.SelectionStart=TextBox.Text.Length;
          });
    }
}

So this would be the current flow:
1. Dataobject is initialized, and the MyString property is set
2. The usercontrol which holds the MyCustomLabel is initialized
3. The binding works, and the MyString property shows in the Label
4. The user clicks the Label, the TextBox now shows, and the user changes the value
5. The user clicks a save button, and the content is supposed to be saved, but the MyString property is not updated with the new value

If I debug and access the MyCustomLabel.Text, the property is correctly changed.
I tried implementing the INotifyPropertyChanged in every class, but that did not have any effect.

Hope you can help :)

------- EDIT: ------

Thanks alot for your answers. It pointed me in the right direction.
@Sheridan:
The reason I dont have a single textbox with a readonly state, is I need the functionality and layout of a Label, until the label is clicked and the textbox gets visible. I could probably style my way out of it though.

Anyways, I solved it by binding the Label to the Textbox.Text, and then bind the Textbox.Text to the controls Text property like this:

<DockPanel >
    <Label Name="Label" MouseLeftButtonDown="EditText" Style="{StaticResource InputLabel}" Content="{Binding ElementName=TextBox, Path=Text}" ></Label>
    <TextBox Name="TextBox" Style="{StaticResource TextBox}" Visibility="Collapsed" HorizontalAlignment="Left" HorizontalContentAlignment="Left" Text="{Binding Text, RelativeSource={RelativeSource AncestorType=MyNamespace:MyCustomLabel},Mode=TwoWay}" />
  </DockPanel>

Upvotes: 2

Views: 214

Answers (2)

Sheridan
Sheridan

Reputation: 69979

You clearly have some problems there. First, you can't use a Label with a TwoWay Binding as it provides no method of data input, like a TextBox. Therefore, this is incorrect:

<Label Name="Label" MouseLeftButtonDown="EditText" Style="{StaticResource InputLabel}"
    Content="{Binding Path=Text, RelativeSource={RelativeSource FindAncestor, 
    AncestorType=MyNamespace:MyCustomLabel, AncestorLevel=1},Mode=TwoWay}" ></Label>

Secondly, you have set no Binding on the TextBox.Text property:

<TextBox Name="TextBox" Style="{StaticResource TextBox}" Visibility="Collapsed" 
    HorizontalAlignment="Left"  HorizontalContentAlignment="Left" />

Generally, it is preferable to use one control with read only and editable states, rather than using two controls like you have. A TextBox is perfect for this, as it has a IsReadOnly Property. Therefore, you could just display the one TextBox and toggle the IsReadOnly property instead of the Visibility of the other controls:

<TextBox Name="TextBox" Style="{StaticResource TextBox}" Visibility="Collapsed" 
    HorizontalAlignment="Left" HorizontalContentAlignment="Left" Text="{Binding Text, 
    RelativeSource={RelativeSource AncestorType={MyNamespace:MyCustomLabel}}}" 
    IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType={
    MyNamespace:MyCustomLabel}}}" />

Of course, you'd need to add a new IsReadOnly DependencyProperty to your UserControl and update that in your EditText method instead:

private void EditText(object sender,MouseButtonEventArgs e) {
    IsReadOnly = false;
}

Note that there is no need to set a TwoWay Binding on the TextBox.Text property because it has the FrameworkPropertyMetadata.BindsTwoWayByDefault Property set. From the TextBox.Text Property page on MSDN:

enter image description here

Hopefully you can manage to complete your control now.

Upvotes: 1

user3455395
user3455395

Reputation: 161

Basically in order the "second" way (i.e from UI to Non-UI side of binding) to work you need to implement INotifyPropertyChanged and manually fire an event from within you property setter. An alternative is to use DependencyProperty but it's considered to be an overkill by many (incl. myself).

Here's some prototype code:

  1. Implement INotifyPropertyChanged for the UI object reporting about its property changes (UserControl in your case)
  2. Fire up PropertyChanged(this, new PropertyChangedEventArgs("Text")).

If all you need is to bind one UI element to another you might get away with using ElementName='...' and Path in the relevant bindings (you're going to go codeless in this case, which is extra cool).

Upvotes: 3

Related Questions