WPF Control's Nested property's data binding - c#

I'm trying to develop user control with some nested properties that allows to use databinding to set it. For example, I have something like this:
// Top level control
public class MyControl : Control
{
public string TopLevelTestProperty
{
get { return (string)GetValue(TopLevelTestPropertyProperty); }
set { SetValue(TopLevelTestPropertyProperty, value); }
}
public static readonly DependencyProperty TopLevelTestPropertyProperty =
DependencyProperty.Register("TopLevelTestProperty", typeof(string), typeof
(MyControl), new UIPropertyMetadata(""));
// This property contains nested object
public MyNestedType NestedObject
{
get { return (MyNestedType)GetValue(NestedObjectProperty); }
set { SetValue(NestedObjectProperty, value); }
}
public static readonly DependencyProperty NestedObjectProperty =
DependencyProperty.Register("NestedObject", typeof(MyNestedType), typeof
(MyControl), new UIPropertyMetadata(null));
}
// Nested object's type
public class MyNestedType : DependencyObject
{
public string NestedTestProperty
{
get { return (string)GetValue(NestedTestPropertyProperty); }
set { SetValue(NestedTestPropertyProperty, value); }
}
public static readonly DependencyProperty NestedTestPropertyProperty =
DependencyProperty.Register("NestedTestProperty", typeof(string), typeof
(MyNestedType), new UIPropertyMetadata(""));
}
// Sample data context
public class TestDataContext
{
public string Value
{
get
{
return "TEST VALUE!!!";
}
}
}
...
this.DataContext = new TestDataContext();
...
XAML:
<local:mycontrol x:name="myControl" topleveltestproperty="{Binding Value}" >
<local:mycontrol.nestedobject>
<local:mynestedtype x:name="myNestedControl" nestedtestproperty="{Binding Value}" />
</local:mycontrol.nestedobject>
</local:mycontrol>
It works well for property TopLevelTestProperty, but it doesn't work for NestedTestProperty.
It seems that nested bindings do not work. Can anybody help me please? Is there any way to make such binding?
I think that it happens because of my nested object has no any reference to the top level object, so it cannot be resolved using MyControl's DataContext.

H.B. right, nested control does not inherit DataContext from mycontrol.
Tyr out setting it explicitly:
<local:mycontrol x:name="myControl"
topleveltestproperty="{Binding Value}" >
<local:mycontrol.nestedobject>
<local:mynestedtype x:name="myNestedControl"
DataContext="{Binding ElementName=myControl,
Path=DataContext}"
nestedtestproperty="{Binding Value}" />
</local:mycontrol.nestedobject>
</local:mycontrol>

Related

Custom UserControl DependencyProperty Binding

I created a custom UserControl where among other controls I have the following
<TextBlock Text="{Binding NameUtility}" />
<TextBlock Text="{Binding TotalCost}" "/>
In the code both binding are declared as follows
public static readonly DependencyProperty SetNameUtilityProperty =
DependencyProperty.Register(
nameof(NameUtility),
typeof(string),
typeof(SummaryInfo));
public string NameUtility
{
get { return (string)GetValue(SetNameUtilityProperty); }
set { SetValue(SetNameUtilityProperty, value); }
}
public static readonly DependencyProperty SetTotalCostProperty =
DependencyProperty.Register(
nameof(TotalCost),
typeof(string),
typeof(SummaryInfo));
public string TotalCost
{
get { return (string)GetValue(SetTotalCostProperty); }
set { SetValue(SetTotalCostProperty, value); }
}
The above control is used in another control XAML as
<Utilities:SummaryInfo NameUtility="GAS" TotalCost="{Binding TotalGasEuro}"/>
The binded variable TotalGasEuro is correctly declare as follows
private string _totalGasEuro;
public string TotalGasEuro { get => _totalGasEuro; set { _totalGasEuro = value; NotifyPropertyChanged(); } }
When running the app, GAS shows up, while the binded value, which is updated on runtime, does not. (I removed from the code above graphical portions)
I found out my problem.
Looks like to have a binding as the one I was trying to achieve, you need to specify the relative source.
In my case when calling the custom control from XAML:
<Utilities:SummaryInfo NameUtility="GAS" TotalCost="{Binding TotalGasEuro, RelativeSource={RelativeSource AncestorType=UserControl}}"/>

XAML - Defining attached properties in custom UserControl

I have a custom UserControl, and I would like to attach custom properties to some contained UI elements.
I tried to achieve it like this, but VS does not accept my XAML code.
It says MyProp is not available, or accessible.
<UserControl
x:Class="mynamespace.MyDataSourceSelector"
xmlns:local="clr-namespace:mynamespace"
... >
<TabControl>
<TabItem Header="Tab1" local:MyDataSourceSelector.MyProp="something1"/>
<TabItem Header="Tab2" local:MyDataSourceSelector.MyProp="something2"/>
</TabControl>
<UserControl>
My custom UserControl class looks something like this:
public partial class MyDataSourceSelector: UserControl
{
...
public string MyProp
{
get { return (string)GetValue(MyPropProperty); }
set { SetValue(MyPropProperty, value); }
}
public static readonly DependencyProperty MyPropProperty
= DependencyProperty.Register(
"MyProp",
typeof(string),
typeof(MyDataSourceSelector),
new PropertyMetadata(null)
);
}
I would like to bind a value for every tab, then read out the active tab's MyProp value, when needed.
How can I do this?
You messed up a few things. In your case you should declaring the extension properties like
public static class TabItemExtensions
{
public static void SetMyProp(TabItem element, string value)
{
element.SetValue(MyPropProperty, value);
}
public static string GetMyProp(TabItem element)
{
return (string)element.GetValue(MyPropProperty);
}
public static readonly DependencyProperty MyPropProperty
= DependencyProperty.RegisterAttached(
"MyProp",
typeof(string),
typeof(TabItemExtensions),
new PropertyMetadata(null)
);
}
and use it like
<TabItem Header="Tab1" local:TabItemExtensions.MyProp="something1"/>

WPF custom composite user controls

First of all, I am a WPF beginner! My approach is potentially not the right way to do what I want so do not hesitate to tell me if that is the case. What I want to do is a composite user control in WPF, using MVVM.
Some classes will do a better presentation than I, here are my view models:
interface IParameter : INotifyPropertyChanged
{
string Name { get; set;}
string Value { get; set;}
}
class TextParameter : ViewModelBase, IParameter
{
private string _value;
public string Name { get; private set; }
public string Value
{
get { return _value; }
set
{
_value = value;
RaisePropertyChanged();
}
}
public TextParameter (string name)
{
this.Name = name;
}
}
class ParameterList : ViewModelBase, IParameter
{
private string _value;
public string Name { get; private set; }
public string Value
{
get { return _value; }
set
{
_value = value;
RaisePropertyChanged();
}
}
ObservableCollection<IParameter> Parameters { get; set; }
public ParameterList (string name, IEnumerable<IParameter> parameters = null)
{
this.Name = name;
this.Parameters = new ObservableCollection<IParameter>(parameters ?? new List<IParameter>());
}
}
I am using MVVM Light, so all the PropertyChanged stuff is managed into ViewModelBase. Also, this is not an exhaustive list of all the parameters, there is some others, more complex but the issue is about these ones.
Here are my custom user controls:
TextParameterControl.xaml:
<UserControl x:Class="Stuff.TextParameterControl" [..] x:Name="parent">
<StackPanel DataContext="{Binding ElementName=parent}" Orientation="Horizontal">
<TextBlock Text="{Binding Path=ParamName, StringFormat='{}{0}:'}" Width="100"></TextBlock>
<TextBox Text="{Binding Path=Value}" Width="100"></TextBox>
</StackPanel>
</UserControl>
TextParameterControl.xaml.cs :
public class TextParameterControl : UserControl
{
#region param name
public string ParamName
{
get { return (string)GetValue(ParamNameProperty); }
set { SetValue(ParamNameProperty, value); }
}
// Using a DependencyProperty as the backing store for ParamName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParamNameProperty =
DependencyProperty.Register("ParamName", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));
#endregion
#region value
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));
#endregion
public TextParameterControl()
{
InitializeComponent();
}
}
ParameterListControl.xaml:
<UserControl x:Class="Stuff.ParameterListControl" [..] x:Name="parent">
<UserControl.Resources>
<DataTemplate x:Key="TextParameterTemplate">
<c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterListTemplate">
<c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
</DataTemplate>
<s:ParameterTemplateSelector x:Key="ParameterSelector"
TextParameterTemplate="{StaticResource TextParameterTemplate}"
ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>
<Expander DataContext="{Binding ElementName=parent}" Header="{Binding Path=ParamName}" IsExpanded="True" ExpandDirection="Down">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Items}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
</StackPanel>
</Expander>
</UserControl>
ParameterListControl.xaml.cs:
public partial class ParameterListControl: UserControl
{
#region param name
public string ParamName
{
get { return (string)GetValue(ParamNameProperty); }
set { SetValue(ParamNameProperty, value); }
}
// Using a DependencyProperty as the backing store for ParamName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParamNameProperty =
DependencyProperty.Register("ParamName", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));
#endregion
#region value
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));
#endregion
#region items
public IList<string> Items
{
get { return (List<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IList<string>), typeof(ParameterListControl), new PropertyMetadata(new List<string>()));
#endregion
public ParameterListControl()
{
InitializeComponent();
}
}
Here is my custom template selector:
class ParameterTemplateSelector : DataTemplateSelector
{
public DataTemplate ParameterListTemplate { get; set; }
public DataTemplate TextParameterTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TextParameter)
{
return this.TextParameterTemplate;
}
else if (item is ParameterList)
{
return this.ParameterListTemplate;
}
throw new Exception(String.Format("This parameter ({0}) is not handled in the application", item.GetType().Name));
}
}
And here is the calling View and ViewModel:
ViewModel:
public class MainViewModel : ViewModelBase
{
public ObservableCollection<IParameter> Parameters { get; set; }
public MainViewModel()
{
this.Parameters = new ObservableCollection<IParameter>();
this.Parameters.Add(new TextParameter("Customer"));
// here I am building my complex composite parameter list
}
View:
<UserControl.Resources>
<DataTemplate x:Key="TextParameterTemplate">
<c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterListTemplate">
<c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
</DataTemplate>
<s:ParameterTemplateSelector x:Key="ParameterSelector"
TextParameterTemplate="{StaticResource TextParameterTemplate}"
ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Parameters}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
When I run the application, the TextParameter in the MainViewModel.Parameters are well loaded (VM.Name and VM.Value properties are well binded to UC.ParamName and UC.Value. Contrariwise, the ParameterList in MainViewModel.Parameters are partially loaded. UC.Name is well binded to the UC.ParamName but the VM.Parameters is not binded to the UC.Items (the UC.DataContext is the VM, the VM.Parameters is well defined, but the UC.Items is desperately null).
Do you have any idea of what I am missing ?
(I am not a native speaker, excuse me if my english hurts you)
I see you have a binding MainViewModel.Parameters -> ParameterListControl.Items but you might be missing the binding from ParameterListControl.Items -> ParameterList.Parameters. (That's assuming ParameterList is the ViewModel for the ParameterListControl - you provide the code for DataContext bindings.)
See the accepted answer on this question. (Ignore the comment on Caliburn.Micro - same solution worked for me in MVVM Light.)
Essentially, in the constructor of ParameterListControl you create an extra binding between the dependency property of the view and the viewmodel's property.
(Also, Dbl is right in the comments that when debugging binding problems, the "unimportant" "plumbing" code that you omitted is very important.)
I finally find out:
The Items dependency property of ParameterListControl was a IList<string>. It was a copy/paste mistake from another UC. I changed it to IEnumerable and everything works fine now:
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IEnumerable), typeof(ParameterListControl), new PropertyMetadata(new List<object>()));
I continued to work on the code and it is now finished and truly composite compared to the sample I posted earlier. If someone is interested in seeing/using this code, you can find it on github.

Create a proxy for a dependency property

I am trying to create a simple dependency property proxy. I made a custom control, it's a file picker, which is made off a textbox (name: "TextBox_FilePath") and a button showing the open file dialog.
As I am making a reusable control I'd like it to have a "SelectedFilePath" property. As the Text property seems to be perfect for my control to be the "SelectedFilePath" property, I'd just like to proxy these dependency property.
The first approach I made was:
public static readonly DependencyProperty SelectedFilePathProperty = TextBox.TextProperty;
public string SelectedFilePath
{
get { return (string) this.TextBox_FilePath.GetValue(SelectedFilePathProperty); }
set { this.TextBox_FilePath.SetValue(SelectedFilePathProperty, value); }
}
which worked, but throwed an exception when trying to bind to that property. Then I came off with:
public static readonly DependencyProperty SelectedFilePathProperty =
DependencyProperty.Register("SelectedFilePath", typeof (string), typeof (FilePicker), new PropertyMetadata(default(string)));
public string SelectedFilePath
{
get { return (string) this.TextBox_FilePath.GetValue(SelectedFilePathProperty); }
set { this.TextBox_FilePath.SetValue(SelectedFilePathProperty, value); }
}
which does work, but I've got no idea why?! Where did I specify I wanted the text property of the textbox?
What am I missing to simply proxy out that dependency property?
EDIT:
The solution with AddOwner doesn't work too, it throws an Excetion saying "binding can only be applied on a dependency property". Code:
public static readonly DependencyProperty SelectedFilePathProperty =
TextBox.TextProperty.AddOwner(typeof(FilePicker));
public string SelectedFilePath
{
get { return (string)this.TextBox_FilePath.GetValue(SelectedFilePathProperty); }
set { this.TextBox_FilePath.SetValue(SelectedFilePathProperty, value); }
}
What don't I understand?
EDIT2:
For everybody else having issues understanding the answer, I've made a little graphic
The first approach does not work because the property is registered only for the TextBox, adding a reference in another class does nothing.
The second one just creates a whole new string property.
If you really want to reuse the TextBox.TextProperty call AddOwner on it.
e.g.
public static readonly DependencyProperty SelectedFilePathProperty =
TextBox.TextProperty.AddOwner(typeof(FilePicker));
(Note that this property is registered as "Text", so you probably should just create a new property with the name you want as you did already. I would also recommend to set metadata flags to bind two-way by default if you want to have the same binding behaviour as TextBox.Text.)
This solution is a little tricky but works.
Given this user control:
<Grid>
<StackPanel>
<WpfApplication1:FilePicker SelectedFilePath ="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding MyProperty}" />
</StackPanel>
</Grid>
And its viewmodel:
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string e)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(e));
}
#endregion
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
}
XAML for FilePicker control:
<Grid>
<TextBox x:Name="TextBox_FilePath" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type WpfApplication1:FilePicker}}}" Text="{Binding SelectedFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
CodeBehind for FilePicker control:
public partial class FilePicker : UserControl
{
public FilePicker()
{
InitializeComponent();
}
/* private PROXY DP*/
private static readonly DependencyProperty TextProperty =
TextBox.TextProperty.AddOwner(typeof(FilePicker));
/* public DP that will fire getter/setter for private DP */
public static readonly DependencyProperty SelectedFilePathProperty =
DependencyProperty.Register("SelectedFilePath", typeof(string), typeof(FilePicker), new PropertyMetadata(default(string)));
public string SelectedFilePath
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Works like a charm.
As I had issues understanding H.B.s answer I made a little graphic which helped me to understand what's going on under the hood. Here it is;
Maybe it helps someone else :)

Inherited Value in Column

we have a wpf-window with some textboxes and a datagrid.
the textboxes descripe a parent (class a) object and the datagrid lists a collection of "childs" (class b => not derived from class a).
the childs can inherit values from the parent.
for example if the parent (class a) has a property Foo then the child object (class b) has a property Nullable which can either override the value of the parent or inherit the value of the parent.
now the datagrid should display the value in gray (if it is inherited) or in black (if the user overrides the value in the grid cell).
Unfortunatly Binding to InheritedText doesnt work. Does someone have any idea?
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<UserControls:InheritedTextBoxControl
Text="{Binding Path=?}"
InheritedText="{Binding Path=?}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Thanks in advance
Tobi
--UPDATE--
xaml of InheritedTextBoxControl:
<UserControl x:Class="Com.QueoMedia.CO2Simulationstool.WPF.Utils.UserControls.InheritedTextBoxControl"
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"
Width="Auto"
Height="Auto"
Name="cnt">
<Grid x:Name="LayoutRoot"
Background="White">
<TextBox TextChanged="TextBoxTextChanged"></TextBox>
<TextBlock Name="inheritedText"
IsHitTestVisible="False"
Margin="4,0"
VerticalAlignment="Center"
Opacity="0.5"
FontStyle="Italic"></TextBlock>
</Grid>
CodeBehind:
public partial class InheritedTextBoxControl : UserControl {
private bool _isInherited;
public static readonly DependencyProperty InheritedTextProperty = DependencyProperty.Register("InheritedText", typeof(String), typeof(InheritedTextBoxControl), new PropertyMetadata(""));
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(InheritedTextBoxControl), new PropertyMetadata(default(string)));
public InheritedTextBoxControl() {
InitializeComponent();
}
public string InheritedText {
get { return (string)GetValue(InheritedTextProperty); }
set {
SetValue(InheritedTextProperty, value);
inheritedText.Text = value;
}
}
private bool IsInherited {
get { return _isInherited; }
set {
_isInherited = value;
if (value) {
inheritedText.Opacity = 0.5;
} else {
inheritedText.Opacity = 0;
}
}
}
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private void TextBoxTextChanged(object sender, TextChangedEventArgs e) {
if (((TextBox)sender).Text.Length > 0) {
IsInherited = false;
} else {
IsInherited = true;
}
Text = ((TextBox)sender).Text;
}
}
The problem is the setter of your InheritedText property. WPF won't call this setter when the property is set from XAML. See Checklist for Defining a Dependency Property, section Implementing the "Wrapper" for details.
You will have to update inheritedText.Text in a PropertyChangedCallback like this:
public static readonly DependencyProperty InheritedTextProperty =
DependencyProperty.Register(
"InheritedText", typeof(string), typeof(InheritedTextBoxControl),
new PropertyMetadata(string.Empty, InheritedTextChanged));
private static void InheritedTextChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((InheritedTextBoxControl)d).inheritedText.Text = (string)e.NewValue;
}
public string InheritedText
{
get { return (string)GetValue(InheritedTextProperty); }
set { SetValue(InheritedTextProperty, value); } // only call SetValue here
}
If someone is interested in the solution:
we did it using a CellTemplate containing a CustomControl name MaskedTextbox that has three properties (MaskedText, Text, IsMaskTextVisible) and a CellEditingTemplate to override the data.
The values are bound to an InheritableValueViewModel.
Tobi

Categories

Resources