WPF-MVVM Binding ViewModel-Property to nested UserControl - wpf

As the title says, I want to bind a property from my ViewModel to a nested UserControl in the corresponding view.
I cant get it work the way I need.
The nested UserControl is nothing more than a DatePicker and a DropDown for the hours. How can I tell the DatePicker to choose the date propagated by the ViewModel as its selected date?
I tried nearly everything and now I'm not far away from jumping outside the window.
As you can see any help is appreciated ;)
Now to the code so far: DateTimePicker.xaml.cs (CodeBehind)
public partial class DateTimePicker
{
public static DependencyProperty SelectedDateValueProperty = DependencyProperty.Register("SelectedDateValue", typeof (DateTime), typeof (DateTimePicker), new FrameworkPropertyMetadata(default(DateTime), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPropertyChangedCallback));
private static void OnPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
Debug.WriteLine("Wohoo. I'm here and still debugging...");
}
public DateTimePicker()
{
InitializeComponent();
DataContext = this;
var times = GetTimes();
Times.ItemsSource = times;
Times.SelectedItem = times.First();
}
public DateTime SelectedDateValue
{
get { return (DateTime) GetValue(SelectedDateValueProperty); }
set { SetValue(SelectedDateValueProperty, value); }
}
}
The nested UserControl (DateTimePicker.xaml):
<UserControl x:Class="Framework.Controls.DateTimePicker"
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="30" d:DesignWidth="200"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<DatePicker HorizontalAlignment="Left" Name="DatePickerCalendar" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center" SelectedDate="{Binding SelectedDateValue}" />
<ComboBox Grid.Column="1" VerticalAlignment="Center" Name="Times" DisplayMemberPath="Name" />
</Grid>
And, last but not least: The View which has the nested UserControl (View.xaml)
<CustomControls:DateTimePicker SelectedDateValue="{Binding LocalRegistrationStartDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Hope the problem is clear and anybody can help me or get the point at what i am doing wrong here.

Using:
"{Binding SelectedDateValue}"
tells WPF "Hey check my DataContext for a property called SelectedDateValue".
What you want is, to get the Property from your user control.
The easiest way is to give your user control a name like:
<UserControl x:Name="myControl"/>
and then modify your binding to :
"{Binding ElementName=myControl, Path=SelectedDateValue}"

The usual way WPF controls are implemented is to use a template rather than defining the control as direct content, like you're doing here. By using a Template, you have access to TemplateBinding, allowing you to easily bind your control properties. See the Control Customization MSDN page.
<ControlTemplate TargetType="{x:Type local:DateTimePicker>
...
<DatePicker SelectedDate="{TemplateBinding SelectedDateValue}" />
...
</ControlTemplate>

Related

How to correctly bind to a dependency property of a usercontrol in a MVVM framework

I have been unable to find a clean, simple, example of how to correctly implement a usercontrol with WPF that has a DependencyProperty within the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.
I am trying to:
Set the DependencyProperty from the calling ItemsControl , and
Make the value of that DependencyProperty available to the ViewModel of the called usercontrol.
I still have a lot to learn and sincerely appreciate any help.
This is the ItemsControl in the topmost usercontrol that is making the call to the InkStringView usercontrol with the DependencyProperty TextInControl (example from another question).
<ItemsControl ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the InkStringView usercontrol with the DependencyProperty.
XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
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"
x:Name="mainInkStringView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
<TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
</UserControl>
Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT
} --ELSE TO DO?????
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
}
That is one of the many reasons you should never set the DataContext directly from the UserControl itself.
When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded to an instance that only the UserControl has access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.
There are two main ways of using UserControls in WPF
A standalone UserControl that can be used anywhere without a specific DataContext being required.
This type of UserControl normally exposes DependencyProperties for any values it needs, and would be used like this:
<v:InkStringView TextInControl="{Binding SomeValue}" />
Typical examples I can think of would be anything generic such as a Calendar control or Popup control.
A UserControl that is meant to be used with a specific Model or ViewModel only.
These UserControls are far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:
<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
Or more frequently, it would be used with an implicit DataTemplate. An implicit DataTemplate is a DataTemplate with a DataType and no Key, and WPF will automatically use this template anytime it wants to render an object of the specified type.
<Window.Resources>
<DataTemplate DataType="{x:Type m:InkStringViewModel}">
<v:InkStringView />
</DataTemplate>
<Window.Resources>
<!-- Binding to a single ViewModel -->
<ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
<!-- Binding to a collection of ViewModels -->
<ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
No ContentPresenter.ItemTemplate or ItemsControl.ItemTemplate is needed when using this method.
Don't mix these two methods up, it doesn't go well :)
But anyways, to explain your specific problem in a bit more detail
When you create your UserControl like this
<v:InkStringView TextInControl="{Binding text}" />
you are basically saying
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContext is not specified anywhere in the XAML, so it gets inherited from the parent item, which results in
vw.DataContext = Strings[x];
so your binding that sets TextInControl = vw.DataContext.text is valid and resolves just fine at runtime.
However when you run this in your UserControl constructor
this.DataContext = new InkStringViewModel();
the DataContext is set to a value, so no longer gets automatically inherited from the parent.
So now the code that gets run looks like this:
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
and naturally, InkStringViewModel does not have a property called text, so the binding fails at runtime.
You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.
UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.
Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.
If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."
Seems like you are mixing the model of the parent view with the model of the UC.
Here is a sample that matches your code:
The MainViewModel:
using System.Collections.Generic;
namespace UCItemsControl
{
public class MyString
{
public string text { get; set; }
}
public class MainViewModel
{
public ObservableCollection<MyString> Strings { get; set; }
public MainViewModel()
{
Strings = new ObservableCollection<MyString>
{
new MyString{ text = "First" },
new MyString{ text = "Second" },
new MyString{ text = "Third" }
};
}
}
}
The MainWindow that uses it:
<Window x:Class="UCItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:UCItemsControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<v:MainViewModel></v:MainViewModel>
</Window.DataContext>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Your UC (no set of DataContext):
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
}
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
(Your XAML is OK)
With that I can obtain what I guess is the expected result, a list of values:
First
I am row 1
Second
I am row 1
Third
I am row 1
You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).
1) Remove this.DataContext = new InkStringViewModel(); from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.
2) Change
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
to
<v:InkStringView TextInControl="{Binding }" />
The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.
<v:InkStringView TextInControl="{Binding SomeProperty}" />

Unable to bind dependency properties of a custom user control

I'm trying to create a legend control that is a databound set of stack panels and am having significant issues with getting data binding to work. After many searches I was able to get binding to work on a standard control defined in my datatemplate. However, when I use exactly the same binding to set the value on my custom control, my dependency property doesn't get set. Here is the relevant XAML
EDIT I changed my complex custom item with a simple user control that just has a button - same effect - thanks for the help
<StackPanel x:Name="LayoutRoot" Background="Transparent">
<TextBlock Text="Legend:" />
<ItemsControl x:Name="tStack" ItemsSource="{Binding LegendItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="{Binding ItemLabel}" />
<pxsc:TestItem ItemLabel="{Binding ItemLabel}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- <pxsc:PXLegendItem ItemColor="Green" ItemLabel="TextLabel"/> -->
</StackPanel>
// TestItem
<UserControl x:Class="SilverlightControls.TestItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
DataContext="{Binding RelativeSource={RelativeSource Self}}" >
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="{Binding ItemLabel}" />
</Grid>
</UserControl>
TestItem code behind
public partial class TestItem : UserControl
{
public TestItem()
{
InitializeComponent();
}
#region ItemLabel
public static readonly DependencyProperty ItemLabelProperty =
DependencyProperty.Register("ItemLabel", typeof(string), typeof(TestItem),
new PropertyMetadata(new PropertyChangedCallback(OnItemLabelPropertyChanged)));
public string ItemLabel
{
get { return (string)GetValue(ItemLabelProperty); }
set { SetValue(ItemLabelProperty, value); }
}
private static void OnItemLabelPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
}
public static void SetItemLabel(DependencyObject obj, string val)
{
obj.SetValue(ItemLabelProperty, val);
}
public static double GetItemLabel(DependencyObject obj)
{
return (double)obj.GetValue(ItemLabelProperty);
}
#endregion
}
In my sample code, I populate LegendItems with 3 objects. The ItemsControl creates three buttons that are correctly labeled and three of my legenditem controls without any label set.
If I uncomment the last line I do see that the additional control correctly accepts the TextLabel value.
I thought this was a fairly "by-the-book" implementation so I'm surprised that it's not working and any assitance is gretly appreciated!
As a stab in the dark, you haven't implemented the ItemLabel as a dependency property or if you have its not be implemented properly. If the latter then edit your question with the relevent code.
Edit
Since your control assigns itself to the DataContext any existing DataContext from its ancestors will be ignored. Remove your assignment to the DataContext. Change your inner xaml to:-
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="{Binding Parent.ItemLabel, ElementName=LayoutRoot}" />
</Grid>
That should get it working. BTW do you really need GetItemLabel and SetItemLabel static methods? Those are usually included for Attached properties but not standard dependency properties.

WPF databinding: Why doesn't my copy display on the screen?

What simple thing am I missing here? Why doesn't my copy display on the screen?
<Window x:Class="DeleteThis.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" >
<Grid>
<StackPanel>
<TextBlock Text="{Binding Path=SomeCopy}" Height="35" Width="100" Margin="10"/>
</StackPanel>
</Grid>
and my code-behind.
public partial class MainWindow : Window
{
private string _someCopy;
public string SomeCopy
{
get
{
return _someCopy;
}
set
{
_someCopy = value;
}
}
public MainWindow()
{
InitializeComponent();
SomeCopy = "why doesn't this display";
}
}
You never set the DataContext of the Window. Change your XAML to this...
<TextBlock Text="{Binding}" Height="35" Width="100" Margin="10"/>
...and change your code behind to add the DataContext line...
public MainWindow()
{
InitializeComponent();
SomeCopy = "why doesn't this display";
this.DataContext = SomeCopy;
}
Your current issue has nothing to do with needing a DependencyProperty as mentioned in the other answers.
WPF never finds out that the property changed.
To fix it, you can turn the property into a dependency property.
EDIT: You also need to bind to the property on the Window itself, like this
<TextBlock Text="{Binding Path=SomeCopy, RelativeSource={RelativeSource Self}}" ... />
SLaks's answer is the correct one. But making dependency properties manually is annoying, so I link you to my favorite solution: A custom PostSharp NotifyPropertyChangedAttribute that, when used in conjunction with PostSharp, makes all the properties of any given class into dependency properties.

wpf trouble using dependency properties in a UserControl

I made a UserControl that is meant to be updated once every few seconds with data from a serial port. This UserControl should be very simple, consisting of a Label for a field name, and another Label containing the field value. I say that it should be simple, but it doesn't work. It does not update at all, and doesn't even display the field name.
Below is the code:
public partial class LabeledField : UserControl {
public LabeledField() {
InitializeComponent();
}
public string fieldName {
get { return fieldNameLabel.Content.ToString(); }
set { fieldNameLabel.Content = value; }
}
public string fieldValue {
get { return (string)GetValue(fieldValueProperty); }
set { SetValue(fieldValueProperty, value); }
}
public static readonly DependencyProperty fieldValueProperty =
DependencyProperty.Register(
"fieldValue",
typeof(string),
typeof(LabeledField),
new FrameworkPropertyMetadata(
"No Data"
)
)
;
}
Here is the XAML:
<UserControl x:Class="DAS1.LabeledField" Name="LF"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal">
<Label Width="100" Height="30" Background="Gray" Name="fieldNameLabel" />
<Label Width="100" Height="30" Background="Silver" Name="fieldValueLabel" Content="{Binding fieldValue}" />
</StackPanel>
And here is the XAML for the Window which references the UserControl. First the header:
<Window x:Class="DAS1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:me="clr-namespace:DAS1"
Title="Window1" Height="580" Width="780">
Then the UserControl itself:
<me:LabeledField fieldName="Test" Width="200" Height="30" fieldValue="{Binding businessObjectField}"/>
If I knew of a more specific question to ask, I would--but can anyone tell me why this doesn't work?
Turns out that in the XAML for the user control, the binding was incorrectly specified.
Originally it was:
<Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding fieldValue}" />
But I had not specified the element to which fieldValue belongs. It should be (assuming my user control is named "LF":
<Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding ElementName=LF, Path=fieldValue}" />
If you want to bind to properties of the control, you should specify so in the binding. Bindings are evaluated relative to DataContext if their source isn't explicitly specified, so your binding doesn't bind to your control, but to the inherited context (which is likely missing the property you're binding to). What you need is:
<Label Width="100" Height="30" Name="fieldValueLabel"
Content="{Binding Path=fieldValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DAS1.LabeledField}}}" />
You really don't need a dependency property on your user control, in fact you should strive to keep user controls without anything special in the code-behind, custom Controls should be used for that.
You should define your UserControl like this (without any code behind):
<UserControl x:Class="DAS1.LabeledField"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal">
<Label Width="100" Height="30" Name="fieldNameLabel" Content="{Binding fieldName}" />
<Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding field}" />
</StackPanel>
Then, make sure your business object implements INotifyPropertyChanged, because you cannot update from your business object efficiently without modifying it at least this much. The fieldName is just an example how you could bind the display name on the label automatically to a property on your business object.
Then, simply make sure the DataContext of your UserControl is your business object.
How will this work? The Label.Content property is a DependencyProperty and will support binding itself. Your business object implements INotifyPropertyChanged and thus supports updates to binding - without it, the binding system does not get notified when your field's value changes, regardless if you bound it to a DependencyProperty on one end.
And if you want to reuse this user control elsewhere, simply place a desired instance of your business object to the DataContext of the desired LabeledField control. The binding will hook up itself from the DataContext.

WPF binding user control with data in C# code

I've create user control like this:
public partial class View
{
public View()
{
InitializeComponent();
}
public static DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(TeaserView) );
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
XAML:
<UserControl x:Class="Controls.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="200" Width="164">
<Grid VerticalAlignment="Stretch"
x:Name="Preview">
<Label Height="28" Content="{Binding ElementName=Preview, Path=Name}" Background="LightYellow" x:Name="name" VerticalAlignment="Top" ></Label>
</Grid>
</UserControl>
and use it in Window1 simply in XAML:
<controls:View Height="200" Name="View1" Width="164" />
and I try set the Content in C# (Name property in this sample) but it does'n work, label's content is still empty. (All refereces, etc. are good) What's wrong?
Your code is wrong. You bind to Grid.Name property, which is "Preview", not to View.Name.
I really encourage you to go read from A to Z "DataBinding Overview" on MSDN. It worth your time, trust me :). In fact whole "Windows Presentation Foundation" section would be worth your attention.
As for your code, the following will work:
<UserControl x:Class="WpfApplication5.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300"
Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Label Height="28"
Content="{Binding Path=Name}"
Background="LightYellow"
VerticalAlignment="Top"/>
</Grid>
</UserControl>
But are you sure you want to hide "Name" property from parents?
Have you set the datacontext on the user control? Try setting it to point to its own codebehind:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I've put the Name property just as sample. I'm trying to set Label Content in Window1.xaml.cs like:
View1.Name = "Casablanca";
Try the following binding, it should work:
<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:View}}, Path=Name}" />
You should also define a xmlns:local="whatever_path_you_have" on the top of the file.
I also suggest renaming "Name" DP to something else to avoid name collusion.
Copied your exact code and it works fine.
However, it's not doing what you're probably expecting it to do. You're setting the source of the binding to the Grid instance. Therefore, the Name property will yield "Preview". The Name property you've defined in your UserControl is ignored because there's already a Name property on UserControl.

Resources