WPF - property change notification from UserControl - wpf

I have two UserControls (uc1 and uc2) loading into a third UserControl (shell). Shell has two properties, uc1 and uc2, of type UserControl1 and UserControl2, and each have a DependencyProperty registered to their own classes called IsDirty:
public static readonly DependencyProperty IsDirtyProperty = DependencyProperty.Register("IsDirty", typeof (bool), typeof (UserControl1));
public bool IsDirty
{
get { return (bool) GetValue(IsDirtyProperty); }
set { SetValue(IsDirtyProperty, value); }
}
(same code for UserControl2)
Shell has TextBlocks bound to the IsDirty properties:
<TextBlock Text="{Binding ElementName=shell, Path=Uc1.IsDirty}"/>
<TextBlock Text="{Binding ElementName=shell, Path=Uc2.IsDirty}"/>
When I change the values of IsDirty in uc1 and uc2, Shell never gets notified. What am I missing? UserControl is descendant of DependencyObject...
The same behavior occurs if I have regular properties notifying changes via INotifyPropertyChanged.
If I raise a routed event from uc1 and uc2, bubbling up to Shell, then I can catch the Dirty value and everything works, but I shouldn't have to do that, should I?
Thanks
Edit: The answer is to raise property changed event on the Uc1 and Uc2 properties or make them DPs.

I tried reproducing your problem using a simple setup, and it works fine for me. I'm not sure though if this setup is correct enough to replicate your situation. Anyway, I'm posting it just in case. It might be helpful:
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
x:Name="shell"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Click="Button_Click">Click</Button>
<TextBlock Text="{Binding ElementName=shell, Path=Uc1.IsDirty}"/>
</StackPanel>
</Window>
Code-Behind:
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyUserControl uc1 = new MyUserControl();
public MyUserControl Uc1
{
get { return this.uc1; }
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.uc1.IsDirty = !this.uc1.IsDirty;
}
}
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
}
public bool IsDirty
{
get { return (bool)GetValue(IsDirtyProperty); }
set { SetValue(IsDirtyProperty, value); }
}
// Using a DependencyProperty as the backing store for IsDirty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsDirtyProperty =
DependencyProperty.Register("IsDirty", typeof(bool), typeof(UserControl), new UIPropertyMetadata(false));
}
}

Karmicpuppet's answer works well. However it didn't solve my problem because Shell is also of type UserControl. For it to work I needed to raise the property changed on Uc1 and Uc2. When I declared them as DependencyProperties all worked as expected. Duh!

Related

Data binding doesn't work on Custom controls inside collection

WPF Data binding doesnt work for custom controls that are defined inside a xaml collection tag. I just want to define a collection of custom widgets inside a custom control and bind some widgets properties against viewmodel properties. Like so.
<Window x:Class="WpfApp1.MainWindow"
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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget ImportantToggle="{Binding SomeToggle}"/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</Grid>
</Window>
That is my custom control. I use an obseravblecollection for the widgets and call SetValue in the constructor to get propertychanged callback later (right now not used in example)
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
namespace WpfApp1
{
public class MyCustomControl : FrameworkElement
{
public ObservableCollection<MyCustomWidget> Widgets
{
get { return (ObservableCollection<MyCustomWidget>)this.GetValue(WidgetsProperty); }
set { this.SetValue(WidgetsProperty, value); }
}
public static DependencyProperty WidgetsProperty = DependencyProperty.Register("Widgets", typeof(ObservableCollection<MyCustomWidget>), typeof(MyCustomControl), new PropertyMetadata(null, (e, args) => ((MyCustomControl)e).WidgetsChanged(args)));
public void WidgetsChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("widgets collection object changed inside my custom control!");
}
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
}
}
}
and that is my custom widget:
namespace WpfApp1
{
public class MyCustomWidget : FrameworkContentElement
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty = DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget), new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
}
And finally my simplistic ViewModel:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp1
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _someToggle;
public bool SomeToggle
{
get { return this._someToggle; }
set
{
this._someToggle = value;
this.NotifyPropertyChanged();
}
}
public MainViewModel()
{
this.SomeToggle = !this.SomeToggle;
}
}
}
Thats the output I get from Debug.Writeline: widgets collection object changed inside my custom control!
Observation: I cant bind against properties of MyCustomWidget. I understand that the binding might fail in this scenario because the observablecollection is created inside of the constructor of mycustomcontrol, but I dont know how to fix it to get the binding working inside mycustomwidget.
For that binding to work, your local:MyCustomWidget needs to have the same DataContext as the main window. WPF elements inherit their logical parent's DataContext. MyCustomWidget doesn't, because it's not in the logical tree. It's just sitting there. You're not adding it to any kind of normal child collection of its parent, just to a random ObservableCollection that the framework doesn't know about.
The code below is probably a crude hack. I haven't investigated this corner of WPF. I urge you with the utmost sincerity to find out the right way of doing this. But with this addition to your code, I hit the propertychanged event in MyCustomWidget when the binding is initialized.
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
Widgets.CollectionChanged += Widgets_CollectionChanged;
}
private void Widgets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems is System.Collections.IEnumerable)
{
foreach (MyCustomWidget widget in e.NewItems)
{
AddLogicalChild(widget);
}
}
}
By the way, you can save the trouble of toggling the toggle in the MainViewModel constructor. That happens long before the binding exists. I added a checkbox instead:
<StackPanel>
<CheckBox IsChecked="{Binding SomeToggle}">Test Toggle</CheckBox>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</StackPanel>
Update:
This omits your Widgets collection entirely, and the binding works without any effort on our part. The child widgets will be in MyCustomControl.Children. Importantly that we aren't limiting the child type to MyCustomWidget any more. That's a significant design change, and may not fit your requirements. You could examine the Panel class closely, and write a class that works the same way, but accepts only one type of child (that would mean writing an analog of UIElementCollection, which will be mostly a big pile of tedious boilerplate).
MyCustomControl.cs
[ContentProperty("Children")]
public class MyCustomControl : Panel
{
}
MyCustomWidget.cs
public class MyCustomWidget : Control
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty =
DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget),
new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
MainWindow.xaml
<local:MyCustomControl>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl>

DependencyProperty issues with respect to a UserControl and WPF Dialog

I've dumbed down the code as much as I could to try and get a working piece of code yet I'm still coming up short. Some advice would be appreciated.
I'm trying to get a DependencyProperty working, it's that simple and yet the data I'm setting on the main window isn't showing up in the user control.
In the MainWindow I'm setting the TextValue to "hi" in the xaml. TextValue is showing in the xaml up and compiling just fine so I'm pretty sure I have the DependencyProperty set right. Once the dialog is fully open I take a look in the debugger and my property TextValue is still null.
Am I missing setting the data context? Maybe I'm off base in what I'm looking to do.
Thanks for taking the time to figure out what I'm doing wrong.
My User Control is: 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"
Loaded="UserControl_Loaded"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
</Grid>
</UserControl>
UserControl1.xaml.cs is:
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("TextValue", typeof(string), typeof(UserControl1));
private string _tv;
public string TextValue
{
get
{
return _tv;
}
set
{
_tv = value;
}
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
}
}
}
My calling window xaml is:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:usercontrols="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
Loaded='Window_Loaded'>
<Grid>
<usercontrols:UserControl1 x:Name="usercontroltest1" TextValue="hi"/>
</Grid>
</Window>
My calling window .cs is:
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
}
The getter and setter of the "property wrapper" must call the GetValue and SetValue methods of the DependencyObject base class like shown below. Besides that, there is a naming convention that mandates that a dependency property's identifier field is named like the property plus a Property suffix. See Custom Dependency Properties for all the details.
public static readonly DependencyProperty TextValueProperty =
DependencyProperty.Register(
nameof(TextValue), typeof(string), typeof(UserControl1));
public string TextValue
{
get { return (string)GetValue(TextValueProperty); }
set { SetValue(TextValueProperty, value); }
}
In order to access a UserControl's dependency property in its own XAML, you would typically use a RelativeSource Binding like this:
<UserControl x:Class="WpfApplication1.UserControl1" ...>
<Grid>
<TextBlock Text="{Binding TextValue,
RelativeSource={RelativeSource AncstorType=UserControl}}" />
</Grid>
</UserControl>

How to use to use UserControl DP and MVVM

I haven`t found answer for this question in internet.
Is there some way to use in UserControl DP and MVVM together?
For example, I created UserControl, and i need to display some text on it. My UserControl has DP which takes some data (f.e. string) from consumer.
I have some code behind:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
***
public string Text
{
get { return (PointCollection)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text),
typeof(string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(TextChanged))
);
private static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyUserControl)d).DataContext.Txt = (string)e.NewValue;
}
***
}
Here is code of ViewModel class:
class MyUserControlViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _txt;
public MyUserControlViewModel(){}
***
public string Txt
{
get{return _txt;}
set
{
if(_txt!= value)
{
_txt= value;
OnPropertyChanged("Txt");
}
}
}
***
}
And here some XAML code of UserControl in which i want to bind text of TextBlock to property Txt of ViewModel, so:
<UserControl x:Class="SimpleWPFChartExample.ChartSurface"
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:SimpleWPFChartExample"
mc:Ignorable="d">
<UserControl.DataContext>
<local:SimpleWPFChartExample />
</UserControl.DataContext>
<Grid>
<TextBlock Text="{Binding Path=Txt}"/>
</Grid>
</UserControl>
So, if I do this way I don`t see any changes of DP Text and any changes of Txt, when i put some data in DP Text in consumer application.
What should I do?
You didn't mention why you want to use both dependency properties and a viewmodel, but it's easy to get one working. You just need to use the viewmodel you want to use instead of a different one. This is the wrong viewmodel:
<UserControl.DataContext>
<local:SimpleWPFChartExample />
</UserControl.DataContext>
This is the right one:
<UserControl.DataContext>
<local:MyUserControlViewModel />
</UserControl.DataContext>
At least, that would appear to be the case based on your question. But you have a number of copy and paste errors in there: For example, your Text string property getter casts the return value from GetValue() to PointCollection; that won't even compile. Neither will this: ((MyUserControl)d).DataContext.Txt = (string)e.NewValue;
This will compile:
private static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = ((MyUserControl)d);
var vm = (MyUserControlViewModel)ctl.DataContext;
vm.Txt = (String)e.NewValue;
}
So for all I know you really are using the correct viewmodel in your actual code, in which case the problem is in code you're not sharing here. It may be that the problem is in whatever you're doing to set Text.
It would help if you could provide a complete, minimal, single-property example that compiles, but and then fails at runtime to meet your expectations.

observable collection and inotifypropertychanged

An ObservableCollection is self-contained when it comes to raising the CollectionChanged event because it implements INotifyPropertyChanged and INotifyCollectionChanged. So i think , we don't need to implement INotifyPropertyChanged again.
but i have seen some example where folks are defining ObservableCollection as property and raising property changed event in setter. i don't understand why this is done again or in better words why they are Raising property changed event in setter(see below code). As we already know that ObservableCollection automatically raises when add,update is done, then we need not to raise again.right?
please clarify my doubt.
public class TheViewModel()
{
private ObservableCollection<Camper> _campers;
public ObservableCollection<Camper> Campers
{
get { return _campers; }
set
{
if (Equals(_campers, value)) return;
_campers = value;
RaisePropertyChanged("Campers"); //Or however you implement it
}
}
If you set Campers to point to a new instance, that RaisePropertyChanged will do the job for you. Otherwise you will have a reference to the old instance and the View will remain out of sync. The other solution to this is, every time you set Campers to point to a new collection, set again the ItemsSource for your DataGrid or ListView or whatever control you use.
Indeed this works as long as you Add or Remove items from your collection. To conclude, that's the difference, when you set again
Campers = new ObservableCollection<Camper>();
your RaisePropertyChanged will be triggered.
Code update:
XAML:
<Window x:Class="ObservablePropertyChanged.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>
<ListView ItemsSource="{Binding items}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Change collection" Click="btnChangeCollection_Click"/>
</StackPanel>
</Grid>
Code behind:
public partial class MainWindow : Window
{
public ObservableCollection<string> items { get; set; }
public MainWindow()
{
InitializeComponent();
items = new ObservableCollection<string>();
items.Add("One");
items.Add("Two");
this.DataContext = this;
}
private void btnChangeCollection_Click(object sender, RoutedEventArgs e)
{
items = new ObservableCollection<string>();
items.Add("Three");
items.Add("Four");
}
}
As i don't have INPC interface implemented and no PropertyChanged added on the set of the items collection, after clicking the Button you will not get the View updated with items "Three" and "Four".
And here is another way to accomplish this behavior:
public ObservableCollection<string> items
{
get { return (ObservableCollection<string>)GetValue(itemsProperty); }
set { SetValue(itemsProperty, value); }
}
// Using a DependencyProperty as the backing store for items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty itemsProperty =
DependencyProperty.Register("items", typeof(ObservableCollection<string>), typeof(MainWindow), new UIPropertyMetadata(null));
Using this dependency property, the ListView will remain in sync.

WPF dependency property Databinding

I have very basic question regarding dependency property and data-binding. I have created a simple class name TDTVm its my ViewModel class. It has one bool dependency property named IsShaftMovingUp and its initial value is 'False' I have bound this value to one text box on UI. Now I want to show real-time value of 'IsShaftMovingUp' on the screen.
Below is my VM.
public class TDTVm : DependencyObject
{
public static DependencyProperty ShaftMovingUpProperty =
DependencyProperty.Register(
"ShaftMovingUp",
typeof(bool),
typeof(TDTVm),
new PropertyMetadata(false, ShaftMovingUpChanged));
private static void ShaftMovingUpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("ok");
}
public bool IsShaftMovingUp
{
get => (bool)GetValue(TDTVm.ShaftMovingUpProperty);
set => SetValue(TDTVm.ShaftMovingUpProperty, value);
}
}
Below is my xamal code.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Button" Click="Button_Click"/>
<TextBox Text="{Binding IsShaftMovingUp,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
and below is my code behind:
public partial class MainWindow : Window
{
TDTVm datacontext = new TDTVm();
public MainWindow()
{
InitializeComponent();
this.DataContext = datacontext;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
///Even after this line 'true' value is not getting updated on UI.
datacontext.IsShaftMovingUp = true;
}
}
When I click on button I am setting value of 'IsShaftMovingUp' to true. But still on UI its not getting updated. ( I have achieved this using INotifyPropertyChanged but want to try same with dependency property to understand exact difference between the two )
Thanks
To fix your problem, you need to change this code
DependencyProperty.Register("ShaftMovingUp",
into
DependencyProperty.Register("IsShaftMovingUp",
Check this post, if you want to know the difference between INotifyPropertyChanged and Dependency Property.

Resources