Reputation: 1687
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
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:
Hopefully you can manage to complete your control now.
Upvotes: 1
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:
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