I have created reusable components let's say a label and a textbox:
HeaderAndTextBox.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock
Margin="10,0,0,0"
FontSize="16"
FontWeight="DemiBold"
Foreground="White"
Text="{Binding Header, ElementName=root}" />
<TextBox
Grid.Row="1"
MaxWidth="300"
Margin="10"
mah:TextBoxHelper.ClearTextButton="True"
mah:TextBoxHelper.IsWaitingForData="True"
FontSize="16"
Text="{Binding TextBoxContent, ElementName=root}" />
</Grid>
Now as you can see I created dependency properties for the Text properties. Here is the code behind:
HeaderAndTextBox.xaml.cs
public partial class HeaderAndTextBox : UserControl
{
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(HeaderAndTextBox), new PropertyMetadata(string.Empty));
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty TextBoxContentProperty =
DependencyProperty.Register("TextBoxContent", typeof(string), typeof(HeaderAndTextBox), new PropertyMetadata(string.Empty));
public string TextBoxContent
{
get { return (string)GetValue(TextBoxContentProperty); }
set { SetValue(TextBoxContentProperty, value); }
}
public HeaderAndTextBox()
{
InitializeComponent();
}
}
In my view I use this reusable component like this:
MyView.xaml
<controls:HeaderAndTextBox
Grid.Row="1"
Margin="10,10,0,0"
Header="Last Name"
TextBoxContent="{Binding Path=LastName, UpdateSourceTrigger=PropertyChanged}" />
And my view model:
MyViewModel.cs
private string? _lastName;
public string? LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
OnPropertyChanged(nameof(LastName));
}
}
Question is, how can I bind this dependency property to my view model's property? As my approach doesn't work. I have more than one property so I must find a solution for the binding to be dynamic. Could it be that for this kind of problem, I should use a completely different approach?
CodePudding user response:
The internal elements must bind to the control's properties either by Binding.ElementName
, where the the named UserControl
is the binding source or by using Binding.RelativeSource
.
HeaderAndTextBox.xaml
<UserControl>
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TextBoxContent, UpdateSourceTrigger=PropertyChanged}" />
</UserControl>
Next, make sure the DataContext
of the parent element that hosts HeaderAndTextBox
is correct:
MainWindow.xaml
<Window>
<Window.DataContext>
<MyViewModel />
</Window.DataContext>
<StackPanel>
<!-- The HeaderAndTextBox inherits the parent's DataContext,
which is MyViewModel, automatically. -->
<HeaderAndTextBox TextBoxContent="{Binding SomeMyViewModelTextProperty}" />
<Grid DataContext="{Binding GridViewModel}">
<!-- Same control, different instance,
binds to a different view model class (GridViewModel). -->
<HeaderAndTextBox TextBoxContent="{Binding SomeGridViewModelTextProperty}" />
</Grid>
</StackPanel>
</Window>
To make the HeaderAndTextBox.TextBoxContent
property send data back to the view model automatically (when typing into the TextBox
), you should configure the dependency property accordingly by using a FrameworkPropertyMetadata
object instead of a PropertyMetadata
and set the FrameworkPropertyMetadata.BindsTwoWayByDefault
property:
HeaderAndTextBox.xaml.cs
partial class HeaderAndTextBox : UserControl
{
public static readonly DependencyProperty TextBoxContentProperty = DependencyProperty.Register(
"TextBoxContent",
typeof(string),
typeof(HeaderAndTextBox),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string TextBoxContent
{
get => (string)GetValue(TextBoxContentProperty);
set => SetValue(TextBoxContentProperty, value);
}
}