WPF XAML - Property update in nested controls - wpf

I have a control that exposes a string property named HeaderText in this way:
public partial class HeaderControl : UserControl
{
public static DependencyProperty HeaderTextProperty;
[Category("Header Properties")]
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
static HeaderControl()
{
HeaderTextProperty = DependencyProperty.Register("HeaderText", typeof(string), typeof(HeaderControl), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public HeaderControl()
{
this.DataContext = this;
InitializeComponent();
}
}
HeaderControl's Xaml:
<UserControl x:Class="Col.HMI.Controls.HeaderControl"
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="300" d:DesignWidth="300">
<Border Background="{Binding Path=HeaderBackground}" >
<TextBlock Text="{Binding Path=HeaderText}" Foreground="White" TextAlignment="Left" VerticalAlignment="Center" HorizontalAlignment="Stretch" FontFamily="Segoe UI Light" FontSize="36" Margin="5"/>
</Border>
and I want to use this HeaderControl in another UserControl, in this way:
OtherControl's Xaml:
<controls:HeaderControl Grid.Row="0" HeaderText="DEMO" />
And this works without problems. But if I bind the HeaderText property to a string property in the OtherControl ViewModel, in this way:
<controls:HeaderControl Grid.Row="0" HeaderText="{Binding Path=SummaryTitle}" />
the bind doesn't work.
This is the SummaryTitle property in the OtherControl ViewModel:
public string SummaryTitle
{
get
{
return _summaryTitle;
}
set
{
_summaryTitle = value; OnPropertyChanged("SummaryTitle");
}
}
PS: I have other controls binded to the OtherControl View Model and they work well.

You are setting DataContext of HeaderControl to itself in the constructor by doing this:
this.DataContext = this;
That means, when you apply some binding to any of the properties in HeaderControl, the Binding engine tries to find the bound property (in your case SummaryTitle) in this control, which it wont find and will fail.
So, to fix your problem, do not set the DataContext of HeaderControl to itself in the Constructor and the Binding engine will try find the properties in the correct DataContext.
Update your HeaderControl constructor to the following, and the bindings should start to work:
public HeaderControl()
{
InitializeComponent();
}
UPDATE
What you are trying to do here is, You want to have DependencyProperty named HeaderText in your UserControl, so that it's value can be set via DataBinding, and then update a value of TextBlock in your UserControl with the value of that DependencyProperty.
You can achieve this by two ways:
1) By updating TextBlock Binding to use ElementNme and Path syntax, XAML would look like this:
<UserControl x:Class="WpfApplication1.HeaderControl"
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="300" d:DesignWidth="300"
x:Name="_This">
<Grid>
<TextBlock Text="{Binding ElementName=_This, Path=HeaderText}" FontSize="24" />
</Grid>
</UserControl>
With this approach, whenever the property HeaderText is changed either via Binding or explicitly setting the value.
2) By listening to property value changed event for HeaderText property and then updating the TextBlock accordingly.
For this approach your HeaderControl.xaml would look like:
<UserControl x:Class="WpfApplication1.HeaderControl"
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="300" d:DesignWidth="300" >
<Grid>
<TextBlock x:Name="TextBlockInUserControl"/>
</Grid>
</UserControl>
and the HeaderControl.xaml.cs
public partial class HeaderControl : UserControl
{
public static readonly DependencyProperty HeaderTextProperty;
[Category("Header Properties")]
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
static HeaderControl()
{
HeaderTextProperty = DependencyProperty.Register("HeaderText", typeof (string), typeof (HeaderControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHeaderTextPropertyChanged));
}
private static void OnHeaderTextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var headerControl = (HeaderControl) dependencyObject;
headerControl.UpdateTextBlock((string) dependencyPropertyChangedEventArgs.NewValue);
}
void UpdateTextBlock(string text)
{
TextBlockInUserControl.Text = text;
}
public HeaderControl()
{
InitializeComponent();
}
}

Related

DataBinding problems with custom UserControl

I have a strange issue when using DataBinding on a custom UserControl.
My UserControl "UserControl1" has a dependency property LabelText which sets the content of a label within my UserControl1. Furthermore, it has a button that binds the command "MyCommand". This command just shows a message box and is implemented in UserControl1ViewModel.
When I using UserControl1 in my MainWindow with also has its view model (MainWindowViewModel), I would like to set the UserControl's LabelText property in the MainWindow.xaml using a Binding to LabelTextFromMainWindow, but when I do it I have a problem that it uses the wrong DataContext unless you specify it explicitly.
This my code:
public partial class MainWindow : Window
{
private MainWindowViewModel vm;
public MainWindow()
{
InitializeComponent();
DataContext = vm = new MainWindowViewModel();
vm.LabelTextFromMainWindow = "Hallo";
}
}
class MainWindowViewModel : System.ComponentModel.INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
#endregion
private string myLabel;
public string LabelTextFromMainWindow
{
get { return myLabel; }
set
{
myLabel = value;
OnPropertyChanged("MyLabel");
}
}
}
/////////
<UserControl x:Class="WpfApplication1.UserControl1"
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="224" d:DesignWidth="300">
<Grid>
<Button Command="{Binding MyCommand}" Content="Button" Height="55" HorizontalAlignment="Left" Margin="166,99,0,0" Name="button1" VerticalAlignment="Top" Width="104" />
<Label Margin="30,99,0,0" Name="label1" Height="55" VerticalAlignment="Top" HorizontalAlignment="Left" Width="101" />
</Grid>
</UserControl>
public partial class UserControl1 : UserControl
{
private UserControl1ViewModel vm;
private static UserControl1 instance;
public UserControl1()
{
InitializeComponent();
instance = this;
DataContext = vm = new UserControl1ViewModel();
}
public string LabelText
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("LabelText", typeof(string), typeof(UserControl1), new UIPropertyMetadata(""), OnValidateValueProperty);
private static bool OnValidateValueProperty(object source)
{
if (instance != null)
{
instance.label1.Content = source;
}
return true;
}
}
public class UserControl1ViewModel
{
private DelegateCommand myCommand;
public ICommand MyCommand
{
get
{
if (myCommand == null)
myCommand = new DelegateCommand(new Action<object>(MyExecute),
new Predicate<object>(MyCanExecute));
return myCommand;
}
}
private bool MyCanExecute(object parameter)
{
return true;
}
private void MyExecute(object parameter)
{
MessageBox.Show("Hello World");
}
}
My mainwindow logs as followed:
<Window x:Class="WpfApplication1.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"
xmlns:my="clr-namespace:WpfApplication1">
<Grid>
<my:UserControl1 LabelText="{Binding
Path=DataContext.LabelTextFromMainWindow,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"
HorizontalAlignment="Left"
Margin="114,36,0,0"
x:Name="userControl11"
VerticalAlignment="Top" Height="236" Width="292" />
</Grid>
</Window>
I exspected the following to work correctly.
LabelText="{Binding Path=LabelTextFromMainWindow}"
However, I have to write this one.
LabelText="{Binding Path=DataContext.LabelTextFromMainWindow,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"
What do I have to do in order get the simple Binding to work properly?
By default control inherits DataContext from its parent unless you set it explicitly.
In your case, you explicitly set the DataContext of your UserControl as
DataContext = vm = new UserControl1ViewModel();
which makes all the bindings on your UserControl to look for bindings in class UserControl1ViewModel instead in MainWindowViewModel.
That's why you have to use RelativeSource to get Window's DataContext i.e. you explicitly asked binding to be found in window's DataContext instead in its own DataContext and i see no issue in using RelativeSource.
But, if you want to work like simple binding without RelativeSource, first of all you need to get rid of explicitly setting DataContext and move all commands and properties in MainWindowsViewModel so that your UserControl inherits its DataContext from MainWindow.
OR
You can give name to your window and bind using ElementName -
<Window x:Class="WpfApplication1.MainWindow"
x:Name="MainWindow"> <--- HERE
<Grid>
<my:UserControl1 LabelText="{Binding
Path=DataContext.LabelTextFromMainWindow,
ElementName=MainWindow}"/>

Binding from ItemsControl DataTemplate to parent UserControls DependencyProperty

I want to create an user control which contains ItemsControl with buttons and I want to bind theirs contents to user controls dependency property (something like DisplayMemberPath property).
My xaml.cs code:
public partial class ButtonsItemsSourceControl : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(ButtonsItemsSourceControl), null);
public static readonly DependencyProperty DisplayMemberProperty = DependencyProperty.Register("DisplayMember", typeof(string), typeof(ButtonsItemsSourceControl), null);
public object ItemsSource
{
get { return (ICollection<object>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public string DisplayMember
{
get { return (string)GetValue(DisplayMemberProperty); }
set { SetValue(DisplayMemberProperty, value); }
}
public ButtonsItemsSourceControl()
{
InitializeComponent();
}
}
My xaml code:
<UserControl x:Class="Controls.ButtonsItemsSourceControl"
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" x:Name="root">
<ItemsControl x:Name="ctrl" ItemsSource="{Binding ItemsSource, ElementName=root}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content= ?????/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
What binding expression should I write in Content property to do it ?
Just for clarity, are you asking how to bind a child property to a parent property?
RelativeSource

binding to a usercontrol not working

i have encountered a databinding probleme,
so i create a usercontrole
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static DependencyProperty TestThisProperty = DependencyProperty.Register
(
"TestThis",
typeof(string),
typeof(UserControl1),
new PropertyMetadata("Some Data",new PropertyChangedCallback(textChangedCallBack))
);
public string TestThis
{
get { return (string)GetValue(TestThisProperty); }
set { SetValue(TestThisProperty, value); }
}
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
UserControl1 _us = (UserControl1)property;
_us.MyUserControl.TestThis = (string)args.NewValue;
}
}
UserControl1.xaml
<UserControl x:Class="WpfApplication1.UserControl1"
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" x:Name="MyUserControl"
d:DesignHeight="300" d:DesignWidth="300">
</UserControl>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
UserControl1 _uc = new UserControl1();
public MainWindow()
{
InitializeComponent();
DispatcherTimer _dt = new DispatcherTimer();
_dt.Interval = new TimeSpan(0, 0, 1);
_dt.Start();
_dt.Tick += new EventHandler(_dt_Tick);
}
private void _dt_Tick(object s,EventArgs e)
{
_uc.TestThis = DateTime.Now.ToString("hh:mm:ss");
}
and finaly the MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Text="{Binding Path=TestThis,ElementName=MyUserControl, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
but the problem here, whene i debug my code i get this warning
Cannot find source for binding with reference 'ElementName=MyUserControl'. and the ui does not update of course,any idea please?
ElementName only targets names in the same namescope, e.g.
<TextBox Name="tb"/>
<Button Content="{Binding Text, ElementName=tb}"/>
If you do not define the UserControl in the MainWindow XAML and give it a name there or register a name in code behind (and target that) you won't get this ElementName binding to work.
An alternative would be exposing the usercontrol as a property on the MainWindow, e.g.
public UserControl1 UC { get { return _uc; } }
Then you can bind like this:
{Binding UC.TestThis, RelativeSource={RelativeSource AncestorType=Window}}

Two way binding use a user control...binding to object, not an element?

I created an object with a simple property with a default value. I then created a user control that has a text box in it. I set the datacontext of the user control to the object.
The text box correctly shows the properties default value but I can't seem to update the property value when the user changes the text box value. I created a simple project to illustrate my code.
Thanks for the help!!
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private string _titleValue;
public string TitleValue
{
get
{
return _titleValue;
}
set
{
_titleValue = value;
textBox1.Text = _titleValue;
}
}
public static readonly DependencyProperty TitleValueProperty = DependencyProperty.Register(
"TitleValue", typeof(string), typeof(UserControl1), new FrameworkPropertyMetadata(new PropertyChangedCallback(titleUpdated))
);
//Don't think I should need to do this!!!
private static void titleUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((UserControl1)d).TitleValue = (string)e.NewValue;
}
}
<UserControl x:Class="WpfApplication1.UserControl1"
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="300" d:DesignWidth="300">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="94,97,0,0" Name="textBox1" VerticalAlignment="Top" Width="120"
Text="{Binding Path=TitleValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dummy = new DummyObject("This is my title.");
userControl11.DataContext = dummy;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("The value is: " + ((DummyObject)userControl11.DataContext).Title);
}
}
<Window x:Class="WpfApplication1.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" xmlns:my="clr-namespace:WpfApplication1">
<Grid>
<my:UserControl1 HorizontalAlignment="Left" Margin="95,44,0,0" x:Name="userControl11" VerticalAlignment="Top" Height="191" Width="293"
TitleValue="{Binding Path=Title, Mode=TwoWay}"/>
<Button Content="Check Value" Height="23" HorizontalAlignment="Left" Margin="20,12,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
The DataContext on your usercontrol isn't set. Specify a Name for it (I usually call mine "ThisControl") and modify the TextBox's binding to Text="{Binding ElementName=ThisControl, Path=TitleValue, Mode=TwoWay}". You can also set the DataContext explicitly, but I believe this is the preferred way.
It seems like the default DataContext should be "this", but by default, it's nothing.
[edit] You may also want to add , UpdateSourceTrigger=PropertyChanged to your binding, as by default TextBoxes' Text binding only updates when focus is lost.

How can I access DependencyProperty values from my code-behind constructor?

I have a UserControl called SmartForm which has a DependencyProperty called Status.
In my Window1.xaml, I have the element <local:SmartForm Status="Ready"/>.
I would think then in the constructor of the SmartForm object, that Status would equal "Ready" but instead it equals null.
Why is then the value of the Status property NULL in the constructor of SmartForm?
If not in the UserControl constructor, when do I have access to the value, then?
Window1.xaml:
<Window x:Class="TestPropertyDefine23282.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestPropertyDefine23282"
Title="Window1" Height="300" Width="300">
<Grid>
<local:SmartForm Status="Ready"/>
</Grid>
</Window>
SmartForm.xaml:
<UserControl x:Class="TestPropertyDefine23282.SmartForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<TextBlock x:Name="TestingMessage"/>
</Grid>
</UserControl>
SmartForm.xaml.cs:
using System.Windows;
using System.Windows.Controls;
namespace TestPropertyDefine23282
{
public partial class SmartForm : UserControl
{
public SmartForm()
{
InitializeComponent();
TestingMessage.Text = Status; //WHY IS STATUS NOT YET SET HERE?
}
#region DependencyProperty: Status
public string Status
{
get
{
return (string)GetValue(StatusProperty);
}
set
{
SetValue(StatusProperty, value);
}
}
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string), typeof(SmartForm),
new FrameworkPropertyMetadata());
#endregion
}
}
<local:SmartForm Status="Ready"/>
Translates to:
SmartForm f = new SmartForm();
f.Status = Status.Ready;
You will have access to that value when the setter is called.
You can set that testing message as:
...
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string), typeof(SmartForm),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnStatusChanged)));
public static void OnStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
((SmartForm)d).TestingMessage.Text = e.NewValue.ToString();
}
...
Or as:
<UserControl
x:Class="TestPropertyDefine23282.SmartForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestPropertyDefine23282"
Height="300" Width="300"
>
<Grid>
<TextBlock
x:Name="TestingMessage"
Text="{Binding Path=Status, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SmartForm}}}"
/>
</Grid>
</UserControl>
Szymon Rozga exlained the problem in a great way. You check the parameter before it is set but after the constructor is initialized.
A good solution is using the loaded event instead like so:
(Untested)
public SmartForm()
{
InitializeComponent();
Loaded += (sender, args) =>
{
TestingMessage.Text = Status;
};
}
This is kind of tertiary, but why do you need this setter at all?
<UserControl x:Class="TestPropertyDefine23282.SmartForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Control"
Height="300" Width="300">
<Grid>
<TextBlock Text="{Binding Path=Status, ElementName=Control}" />
</Grid>
</UserControl>

Resources