Bind view models' property to dependency property - wpf

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?

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);
}
}

Related

Binding with DependencyProperty

I've defined a class name TextColumns.cs, which has a DependencyProperty RichTextBlockContentProperty:
public static readonly DependencyProperty RichTextBlockContentProperty =
DependencyProperty.Register("RichTextBlockContent", typeof(string),
typeof(RichTextColumns), new PropertyMetadata(""));
public string RichTextBlockContent
{
get { return (string)GetValue(RichTextBlockContentProperty); }
set //Debug, but the SetValue won't fire
{
SetValue(RichTextBlockContentProperty, value);
}
}
In the XAML, I use it as
<FlipView x:Name="flipView"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}">
<FlipView.ItemTemplate>
<DataTemplate x:Name="myDataTemplate">
<UserControl Loaded="StartLayoutUpdates" Unloaded="StopLayoutUpdates">
<ScrollViewer x:Name="scrollViewer" Style="{StaticResource HorizontalScrollViewerStyle}" Grid.Row="1">
<!-- Content is allowed to flow across as many columns as needed -->
<common:RichTextColumns x:Name="richTextColumns" Margin="117,0,117,47"
RichTextBlockContent="{Binding title}">
<RichTextBlock x:Name="richTextBlock" Width="560" Style="{StaticResource ItemRichTextStyle}">
<Paragraph>
<Run x:Name="RunText" FontSize="26" FontWeight="SemiBold" Text="{Binding title}"/>
</Paragraph>
</RichTextBlock>
</common:RichTextColumns>
</UserControl>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
When the page loaded, it's supposed that the RichTextBlockContent will get the value of the Binding "title", while the Binding in the RichTextBlock worked.
Is there something I've missed?
The setter won't get called. If you need to do logic when the value gets set you need to supply a PropertyChanged callback in the PropertyMetadata Constructor
http://msdn.microsoft.com/en-us/library/ms557330.aspx

How to force datasource to be updated from TemplateControl

I broke my head by solving a simple problem. I have a custom control with filled Template property. The template is a simple Grid with a TextBox inside. This text box is bound to a singleton's proprty with setter and getter. How can I programmatically force the TextBox to read value from the singleton and put it back?
<Window x:Class="Spike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="305" Width="521" xmlns:my="clr-namespace:Spike" xmlns:Data="clr-namespace:Spike.Data">
<Grid>
<Grid.Resources>
<ControlTemplate x:Key="editingTemplate">
<Grid>
<TextBox Text="{Binding Source={x:Static Data:MyClass.Instance}, Path=Value2}"/>
</Grid>
</ControlTemplate>
</Grid.Resources>
<UserControl Template="{StaticResource editingTemplate}" HorizontalAlignment="Left" Margin="58,60,0,0" x:Name="myUserControl1" VerticalAlignment="Top" Height="75" Width="284" />
<Button Content="Update source" Height="23" HorizontalAlignment="Left" Margin="184,23,0,0" Name="button1" VerticalAlignment="Top" Width="111" Click="button1_UpdateSource" Focusable="False" />
<Button Content="Update control" Focusable="False" Height="23" HorizontalAlignment="Left" Margin="58,23,0,0" Name="button2" VerticalAlignment="Top" Width="111" Click="button2_UpdateControl" />
</Grid>
</Window>
namespace Spike.Data
{
public class MyClass
{
private static readonly MyClass MyClassInstance = new MyClass();
public MyClass()
{
Value1 = "value1";
Value2 = "value2";
}
public static MyClass Instance
{
get { return MyClassInstance; }
}
public string Value1 { get; set; }
public string Value2 { get; set; }
}
}
In other words what should be implemented in button2_UpdateControl and button1_UpdateSource methods?
Thank you in advance for any help
A Binding shouldn't be re-invoked manually. Instead, you should try and make your current Binding mechanism work properly.
Your TextBox should bind to DependancyProperty or the DataContext of the TextBox should implement INotifyPropertyChanged.
Define your Value2 property like this:
public static readonly DependencyProperty Value2Property =
DependencyProperty.Register("Value2", typeof(string), typeof(MyClass), new PropertyMetadata(string.Empty));
public string Value2
{
get { return (string)GetValue(Value2Property); }
set { SetValue(Value2Property, value); }
}
Also, MyClass should implement UIElement.

Repeated children in WPF TreeView

Solved it myself. It was the way I initialised the Settings collection. Specifying a default when registering it as a DependencyProperty causes all of the Settings to refer to the same collection object. Adding a constructor to Category and explicitly initialising Settings resolves the issue.
A class Category specifies a name and a collection of Settings objects.
using System.Collections.ObjectModel;
using System.Windows;
namespace CasEdit
{
public class Categories : ObservableCollection<Category> { }
public class Category : DependencyObject
{
public string Caption
{
get { return (string)GetValue(CategoryProperty); }
set { SetValue(CategoryProperty, value); }
}
public static readonly DependencyProperty CategoryProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(Category),
new UIPropertyMetadata("Category name not set"));
public Settings Settings
{
get { return (Settings)GetValue(SettingsProperty); }
}
public static readonly DependencyProperty SettingsProperty =
DependencyProperty.Register("Settings", typeof(Settings), typeof(Category),
new UIPropertyMetadata(new Settings()));
}
}
The following XAML defines templates, UI and some test data.
<Window x:Class="CasEdit.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CasEdit="clr-namespace:CasEdit"
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type CasEdit:Category}" ItemsSource="{Binding Settings}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Caption}" />
<Button Content="Gratuitous button" Margin="3" Click="Button_Click"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type CasEdit:Setting}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Caption}" Margin="3" />
<TextBlock Text="{Binding Template}" Margin="3" />
<TextBox Text="{Binding Editor}" Margin="3" />
<TextBlock Text="{Binding CasDevice}" Margin="3" />
</StackPanel>
</DataTemplate>
<CasEdit:Categories x:Key="cats">
<CasEdit:Category Caption="1st category">
<CasEdit:Category.Settings>
<CasEdit:Setting Caption="Setting 1-1" />
<CasEdit:Setting Caption="Setting 1-2" />
</CasEdit:Category.Settings>
</CasEdit:Category>
<CasEdit:Category Caption="2nd category" >
<CasEdit:Category.Settings>
<CasEdit:Setting Caption="Setting 2-1" />
</CasEdit:Category.Settings>
</CasEdit:Category>
<CasEdit:Category Caption="3rd category" >
<CasEdit:Category.Settings>
<CasEdit:Setting Caption="Setting 3-1" />
</CasEdit:Category.Settings>
</CasEdit:Category>
</CasEdit:Categories>
</Window.Resources>
<Grid>
<TreeView x:Name="tree" ItemsSource="{Binding Source={StaticResource cats}}" />
</Grid>
</Window>
You would expect a tree like this
1st category
Setting 1-1
Setting 1-2
2nd Category
Setting 2-1
3rd category
Setting 3-1
but what I get is this
which is very confusing. Where have I gone astray, that each category shows all of the settings?
The last parameter here is telling making it so that every instance of Category has it's Settings property initialized to point to the same object:
public static readonly DependencyProperty SettingsProperty =
DependencyProperty.Register("Settings", typeof(Settings), typeof(Category),
new UIPropertyMetadata(new Settings()));
Instead, do this:
public static readonly DependencyProperty SettingsProperty =
DependencyProperty.Register("Settings", typeof(Settings), typeof(Category),
new UIPropertyMetadata(null));
public Category()
{
Settings = new Settings();
}

Silverlight: How to bind to parent view's DataContext?

I have a ParentView that contains a childView
<UserControl ... x:Name="MyParentView">
<Grid>
<sdk:TabControl Name="ContactTabControl">
<sdk:TabItem Header="Contact" Name="CustomerTabItem">
<Grid>
<Views:CustomerView/>
</Grid>
</sdk:TabItem>
</sdk:TabControl>
</Grid>
</UserControl>
Within my CustomerView I would like to bind the Firstname textbox to Parent's DataContext. I have tried this inside the CustomerView:
<TextBox Text={Binding ElementName=MyParentView, Path=DataContext.Firstname} />
I have the feeling that CustomerView won't be able to see its parent at all, hence the ElementName "MyParentView" would never be found.
What is your advice on this?
I've done a similar thing but I just bound it directly to Path considering that if I don't give it explicit data context, it will lookup the hierarchy and find one that matches.
So this should get you what you want:
<TextBox Text={Binding Path=FirstName} />
if you need to specify explicit datacontext you can always do:
<Grid>
<Views:CustomerView DataContext={"CustomContextHere"}/>
</Grid>
An alternative solution to Maverik's is :
1 Define a dependency property in your customer view :
public partial class CustomerView : UserControl
{
public CustomerView()
{
InitializeComponent();
}
public static DependencyProperty FirstNameProperty =
DependencyProperty.Register("FirstName", typeof(string), typeof(CustomerView), new PropertyMetadata(string.Empty, CustomerView.FirstNameChanged));
public string FirstName
{
get { return (string)GetValue(FirstNameProperty); }
set { SetValue(FirstNameProperty, value); }
}
private static void FirstNameChanged(object sender, DependencyPropertyChangedEventArgs e)
{ }
}
2 Modify the customer view's textbox to bind to this dependency property (note the element binding "this")
<UserControl x:Class="SLApp.CustomerView"
x:Name="this"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<TextBox Text="{Binding Path=FirstName, ElementName=this, Mode=TwoWay}"/>
</Grid> </UserControl>
3 Modify the parent view and bind it's DataContext to the new dependency property
<sdk:TabControl Name="ContactTabControl">
<sdk:TabItem Header="Contact" Name="CustomerTabItem">
<Grid>
<local:CustomerView FirstName="{Binding ElementName=ContactTabControl, Path=DataContext}"/>
</Grid>
</sdk:TabItem>
</sdk:TabControl>
4 Set the parent's DataContext
public partial class MyParentView : UserControl
{
public MyParentView()
{
InitializeComponent();
ContactTabControl.DataContext = "A name";
}
}
Voila' it works. Not the most elegant solution but it gets the job done for your scenario

WPF: Need some help binding into UserControl

I'm trying to build a UserControl which includes 2 listBoxes. I then wan't to set the itemsSources for the listboxes where I use the UserControl.
As I understand that is what DependencyProperties are for. However I'm not successful in doing this. I do believe it mostly has to do with the timing of initialization. Any pointer on what I'm doing right, how I can make it better and such is welcome.
Here is my user control, I'm learning as I'm going so I guess I could do it better
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<ListBox Name="SET" ItemsSource="{Binding Path=SetCollection}" />
<UniformGrid Rows="4" Grid.Column="1">
<Grid />
<Button Margin="10" Click="selectionBtnClick">--></Button>
<Button Margin="10" Click="removeBtnClick">Remove</Button>
</UniformGrid>
<ListBox Name="SUBSET" ItemsSource="{Binding Path=SubsetCollection}" Grid.Column="2" />
</Grid>
CodeBehind
public partial class SubsetSelectionLists : UserControl
{
public static DependencyProperty SetCollectionProperty = DependencyProperty.Register("SetCollection", typeof(CollectionView), typeof(SubsetSelectionLists));
public static DependencyProperty SubsetCollectionProperty = DependencyProperty.Register("SubsetCollection", typeof(CollectionView), typeof(SubsetSelectionLists));
public CollectionView SetCollection
{
get
{
return (CollectionView) GetValue(SetCollectionProperty);
}
set
{
SetValue(SetCollectionProperty, value);
}
}
public CollectionView SubsetCollection
{
get
{
return (CollectionView)GetValue(SubsetCollectionProperty);
}
set
{
SetValue(SubsetCollectionProperty, value);
}
}
public SubsetSelectionLists()
{
InitializeComponent();
}
private void selectionBtnClick(object sender, RoutedEventArgs e)
{
SUBSET.Items.Add(SET.SelectedItem);
}
private void removeBtnClick(object sender, RoutedEventArgs e)
{
SUBSET.Items.Remove(SUBSET.SelectedItem);
}
}
And the code behind where I use it
public partial class SomeWindow : Window
{
ViewModel vm = new ViewModel();
public SomeWindow()
{
InitializeComponent();
NameOfUserControl.SetCollection = vm.InputView;
NameOfUserControl.SubsetCollection = vm.OutputView;
}
}
Here the SetCollection is set as inputView and then later tied to the DependencyProperty severing the original binding I think. Any idea where I'm going wrong?
Ps. subquestion - As I will be moving from one collection to the other shouldn't I ensure somehow that the collection hold objects off the same type? How could I do that?
First of all you should set the DataContext property in SomeWindow to vm. This will allow very simple binding expression in your SomeWindow.xaml.
public partial class SomeWindow : Window
{
ViewModel vm = new ViewModel();
public SomeWindow()
{
InitializeComponent();
this.DataContext = vm;
}
}
In SomeWindow.xaml:
<local:SubsetSelectionLists
SetCollection="{Binding Path=InputView}"
SubsetCollection ="{Binding Path=OutputView}" />
There are several ways to solve the binding problem in the user control:
Setting the data context
Add the following to the constructor of the user control.
this.DataContext = this;
Wpf binds against the current data context, unless a specific source (e.g. ElementName) has been set.
Use binding with ElementName
You could reference the user control in the binding expressions.
<UserControl x:Class="WpfApplication1.SubsetSelectionLists"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="root">
<Grid>
...
<ListBox Name="SET"
ItemsSource="{Binding Path=SetCollection, ElementName=root}" />
...
<ListBox Name="SUBSET" Grid.Column="2"
ItemsSource="{Binding Path=SubsetCollection, ElementName=root}" />
</Grid>
</UserControl>
Bind to the VM
Another method would be to set the data context of the user control in SomeWindow and bind to the Properties of the VM. You could then remove the dependency properties of the user control.
public partial class SomeWindow : Window
{
ViewModel vm = new ViewModel();
//with properties InputView and OutputView
public SomeWindow()
{
InitializeComponent();
NameOfUserControl.DataContext = vm;
}
}
In the user control:
<Grid>
...
<ListBox Name="SET"
ItemsSource="{Binding Path=InputView}" />
...
<ListBox Name="SUBSET" Grid.Column="2"
ItemsSource="{Binding Path=OutputView}" />
</Grid>

Resources