How ist the DataContext of a Unsercontrol related to its DependencyProperties? - wpf

while working with UserControls having DependencyProperties i realized that it is curcial to consider where to set the DataContext. To picture it ive created a sample application. There are two UserControls, both equal except on where the DataContext is set:
Working UserControl:
<UserControl x:Class="DpropTest.OkUserControl"
...>
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dpropTest:OkUserControl }}">
<TextBlock Text="{Binding Path=MyDepProp}"></TextBlock>
</Grid>
</UserControl>
Not working user control:
<UserControl x:Class="DpropTest.NotOkUserControl"
...
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dpropTest:NotOkUserControl}}"
>
<Grid >
<TextBlock Text="{Binding Path=MyDepProp}"></TextBlock>
</Grid>
Both UserControls have a DependencyProperty called MyDepProp,
#region Dependency Property Declaration
public static readonly DependencyProperty MyDepPropProperty = DependencyProperty.Register(
"MyDepProp", typeof(string), typeof(NotOkUserControl), new PropertyMetadata(default(string)));
public string MyDepProp
{
get { return (string)GetValue(MyDepPropProperty); }
set { SetValue(MyDepPropProperty, value); }
}
#endregion Dependency Property Declaration
This is how i integrated the UserControls to the mainWindow:
<Grid x:Name="ParentGrid">
<StackPanel>
<dpropTest:OkUserControl MyDepProp="{Binding Path=ActualWidth, ElementName=ParentGrid}"/>
<dpropTest:NotOkUserControl MyDepProp="{Binding Path=ActualWidth, ElementName=ParentGrid}"/>
</StackPanel>
</Grid>
The running application shows the actualWith for the first UserControlonly only, the second UserControl remains unset as the DP doesnt bind.
There is no error in the output window regarding the second UserControl...
Maybe there is an WPF Pro out there with an brief explanation?
Thank you!
Uli

I don't think FindAncestor will start with the element itself, but apart from that: you can either set this on the UserControl:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
or set this in the constructor of the user control, before the InitializeComponent:
DataContext = this;
As a sidenote: it's often not necessary to bind with the ActualWidth of some ancestor; in this case the width of the stackpanel is the same as the width of its parent grid and the width of the usercontrols is the same as the width of ths stackpanel. So in effect MyDepProp is equal to the ActualWidth of the usercontrol.

<UserControl x:Class="DpropTest.NotOkUserControl"
...
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dpropTest:OkUserControl }}"
Seems to me you are binding to wrong parent! You are inside NotOkUserControl but you are asking for unreachable AncestorType...

Related

WPF: relativesource data binding on a custom dependencyproperty

I'm trying to create a custom multi value combobox. So basically a combobox with some checkboxes as items. The idea is, to keep the whole control fully bindable, so that I can be reused any time.
Here's the XAML
<ComboBox x:Class="WpfExtensions.Controls.MultiSelectComboBox"
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"
xmlns:local="clr-namespace:WpfExtensions.Controls"
mc:Ignorable="d" d:DesignHeight="23" d:DesignWidth="150">
<ComboBox.Resources>
<local:CheckBoxConverter x:Key="CheckBoxConverter" />
</ComboBox.Resources>
<ComboBox.ItemTemplateSelector>
<local:MultiSelectBoxTemplateSelector>
<local:MultiSelectBoxTemplateSelector.SelectedItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MultiSelectComboBox}}, Path=SelectedItems, Converter={StaticResource CheckBoxConverter}}" />
</DataTemplate>
</local:MultiSelectBoxTemplateSelector.SelectedItemsTemplate>
<local:MultiSelectBoxTemplateSelector.MultiSelectItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" HorizontalAlignment="Stretch"
Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" Indeterminate="CheckBox_Checked" Click="CheckBox_Checked" />
</DataTemplate>
</local:MultiSelectBoxTemplateSelector.MultiSelectItemTemplate>
</local:MultiSelectBoxTemplateSelector>
</ComboBox.ItemTemplateSelector>
</ComboBox>
And the code behind for the custom property "SelectedItems"
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectComboBox));
[Bindable(true)]
public IList SelectedItems
{
get
{
return (IList)GetValue(SelectedItemsProperty);
}
private set
{
SetValue(SelectedItemsProperty, value);
}
}
Now when I test the project, the RelativeSource is resolved correctly towards the control itself, however the Binding on the path "SelectedItems" fails with the debugger stating, that there is no such Path on the RelativeSource object.
Did I mess up the binding or did I make a complete logical error?
You are setting a RelativeSource as the Source, instead set the RelativeSource propperty like so:
<TextBlock Text="{Binding Path=SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type local:MultiSelectComboBox}}, Converter={StaticResource CheckBoxConverter}}" />

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}" />

How to bind a control's property to a property of the control's element?

I need a digit control to my windows phone app.
I try to create a custom control but I can't bind a property of the control to the control's element.
I had added a dependency property to the control
public static readonly DependencyProperty LineThicknessProperty =
DependencyProperty.Register("LineThickness", typeof (double), typeof (DigitControl), new PropertyMetadata(default(double)));
[DefaultValue(10D)]
public double LineThickness
{
get { return (double) GetValue(LineThicknessProperty); }
set { SetValue(LineThicknessProperty, value); }
}
And have tried to bind it to the control's element
<UserControl x:Class="Library.DigitControl"
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"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480">
<Grid x:Name="LayoutRoot">
<Rectangle Margin="0" StrokeThickness="0" Width="{Binding LineThickness, RelativeSource={RelativeSource Self}}" Fill="#FFFF5454" RadiusX="5" RadiusY="5"/>
</Grid>
</UserControl>
But it doesn't work. Is where a way to bind that property to the element's property?
Do it in the code behind.
Set a name:
<Rectangle x:Name="theRect" Margin="0" StrokeThickness="0" Fill="#FFFF5454" RadiusX="5" RadiusY="5"/>
Then in code behind:
theRect.SetBinding(Rectangle.WidthProperty, new Binding("LineThickness"){Source = this});
Not at PC with Visual Studio, so applogies if it's not 100% compileable! But gives you the general idea.
What you have done wont work because RelativeSource={RelativeSource Self} sets the source to the target object, which is the Rectangle in your case.
And since rectangle doesn't have a LineThickness property, the binding fails.
To get the right binding you can do several things.
The preferable approach would probably be to set this.DataContext = this; in your UserControl contructor, and then simply set the binding as Width="{Binding LineThickness}" in your XAML.
Or you could target the closest element of type UserControl and find the property on that one, if you don't feel like setting the Datacontext:
Width="{Binding LineThickness, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
Update
You can also simply give the UserControl a name, and reference it with the ElementName property in the binding:
<UserControl x:Name="uc1" ... </UserControl>
Width="{Binding LineThickness, ElementName=uc1}"

Accessing codebehind object in XAML

Another post describes how to access a codebehind variable in XAML.
However, I'd like to access a variable in codebehind object from XAML. The codebehind object, called FeedData, is declared as a dependency property of type FeedEntry. This class is just a container class with string and datetime properties.
Codebehind's property definitition is this:
public FeedEntry FeedData
{
get { return (FeedEntry)GetValue(FeedDataProperty); }
set { SetValue(FeedDataProperty, value); }
}
public static readonly DependencyProperty FeedDataProperty =
DependencyProperty.Register("FeedData", typeof(FeedReaderDll.FeedEntry), typeof(FeedItemUserControl),
new FrameworkPropertyMetadata(new FeedEntry(){ Title="Hi!", Published=DateTime.Now }));
In XAML I'm doing this, which doesn't work:
<UserControl x:Class="FeedPhysics.UserControls.FeedItemUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="40" Width="200"
Background="Blue"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
x:Name="xRoot">
<StackPanel>
<TextBlock Text="{Binding Title}" Foreground="White"/>
<TextBlock Text="{Binding Published}" Foreground="White"/>
</StackPanel>
</UserControl>
But if I override Window's datacontext setting in codebehind's contructor, it will work! Like this:
xRoot.DataContext = FeedData;
I understand why it works when datacontext is set in codebehing. But I'd like to find out a way to grab variables within an object that is declared in codebehind. Because, everything should be doable from XAML, right?
Thanks for answers in advance.
Try setting the StackPanel's DataContext to the FeedData object:
<StackPanel DataContext="{Binding FeedData}">
...
This will force the StackPanel to look at the DependencyProperty, and all elements in it will be referenced as properties of FeedData.
As long as you define the DataContext as "FeedData" somewhere in the logical tree above the visual elements you are binding to properties of it, it will work.

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