How to use to use UserControl DP and MVVM - wpf

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.

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>

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.

Why are Silverlight ContentControls not garbage collected?

I've been investigating why some of my controls aren't being garbage collected and noticed it's easy to prevent simple controls that inherit from ContentControl from ever being destroyed. Here's an example:
Here is my custom ContentControl:
public class MyCustomControl : ContentControl
{
public MyCustomControl()
{
Debug.WriteLine("Constructed");
}
~MyCustomControl()
{
Debug.WriteLine("Destroyed");
}
}
Now if I put it on a page like so:
<navigation:Page x:Class="SimpleTestBed.Views.CustomControl"
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"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:local="clr-namespace:SimpleTestBed"
d:DesignWidth="640" d:DesignHeight="480"
Title="CustomControl Page">
<Grid x:Name="LayoutRoot">
<StackPanel>
<local:MyCustomControl>
<TextBox Text="{Binding SomeProperty,Mode=TwoWay}"></TextBox>
</local:MyCustomControl>
</StackPanel>
</Grid>
With the following code behind:
public partial class CustomControl : Page
{
public CustomControl()
{
InitializeComponent();
this.DataContext = new CustomControlViewModel();
this.Unloaded += new RoutedEventHandler(OnUnloaded);
}
void OnUnloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
}
Then the view model is:
public class CustomControlViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string _someProperty = "Initial Value";
public string SomeProperty
{
get { return _someProperty; }
set
{
if (_someProperty != value)
{
string oldValue = _someProperty;
_someProperty = value;
OnPropertyChanged("SomeProperty");
OnSomePropertyChanged(oldValue, value);
}
}
}
protected virtual void OnSomePropertyChanged(string oldValue, string newValue)
{
}
}
Now when I navigate away from this page and try garbage collecting with GC.Collect(), as long if I've made no changes to the text in the Textbox, the ContentControl and Page are destroyed as expected by the GC. But if I've edited some text and navigated away from the page and then tried to GC.Collect(), the ContentControl doesn't get garbage collected.
Can anyone explain this behavior?
Actually, you can force the GC to collect the control by 'flickering' the Template of the control when you unload:
void MyCustomControl_Unloaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("MyCustomControl Unloaded");
ControlTemplate oldTemplate = this.Template;
this.Template = null;
this.Template = oldTemplate;
}
I presume this destroys the current visual tree losing references of the tree's first component to its parent (the custom control). It certainly forces the control to recall OnApplyTemplate when the control is reloaded.
Is this the correct pattern for developing Silverlight controls without leaking? If so, it strikes me as a bit bizarre that the template isn't disposed automatically when the control unloads.
A good account of this behavior would be much appreciated as it goes right to the heart of the life-cycle of Silverlight controls.
My experience showed that memory leaks in Silverlight are coused by top 2 reason :
Events => Make sure that you remove attached events once when it is not needed or in class destructor.
Templates => Solution define templates in Resource section

WPF - property change notification from UserControl

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!

INotifyPropertyChanged not working on ObservableCollection property

I have a class called IssuesView which implements INotifyPropertyChanged. This class holds an ObservableCollection<Issue> and exposes it as a DependencyProperty called Issues for consumption by Bindings. It is defined as below -
public class IssuesView : DependencyObject, INotifyPropertyChanged
{
public Issues Issues
{
get { return (Issues)GetValue(IssuesProperty); }
set
{
SetValue(IssuesProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Issues"));
}
}
}
// Using a DependencyProperty as the backing store for Issues. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IssuesProperty =
DependencyProperty.Register("Issues", typeof(Issues), typeof(IssuesView), new UIPropertyMetadata(null));
public IssuesView()
{
Refresh();
}
public void Refresh()
{
this.Issues = new Issues();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I have a test page declared like this -
<Page x:Class="Tracker.Pages.DEMO"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cont="clr-namespace:Tracker.Controls"
Title="DEMO">
<StackPanel>
<Button Click="Button_Click">Change</Button>
<cont:IssueTimeline IssuesForTimeline="{Binding Source={StaticResource issuesView},Path = Issues}"/>
</StackPanel>
The IssuesView class is defined in Application.Resources.
Now in the event hadnler for the button i have this code -
private void Button_Click(object sender, RoutedEventArgs e)
{
IssuesView iv = Application.Current.FindResource("issuesView") as IssuesView;
if (!once)
{
foreach (Issue i in iv.Issues)
{
i.DormantFor = new TimeSpan(30, 0, 0, 0);
i.AssignedUserID = 12;
i.Name = "MyName";
i.Priority = Issue.Priorities.Critical;
i.Status = Issue.Statuses.New;
i.Summary = "NewSummary";
}
once = true;
}
else
{
iv.Refresh();
}
once is a simple boolean to test mutation of the collection versus repopulation.
The first button click alters the collection's items and the UI is updated properly since the items implement INotifyPropertyChanged but the second click repopulates the collection but does not update the UI even though the event is not null and fires properly.
Why does the UI not update on the second click? How can i make it so that repopulating the collection will cause a UI update?
You really need to simplify your repro. I can see several things wrong with it, but cannot help to solve your problem without seeing all of it. Here is my simple repro, which works just fine.
Window1.xaml:
<Window x:Name="_root" x:Class="CollectionRepro.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel DataContext="{Binding ElementName=_root}">
<ItemsControl ItemsSource="{Binding Issues}"/>
<Button x:Name="_addButton">Add</Button>
<Button x:Name="_resetButton">Reset</Button>
</StackPanel>
</Window>
Window1.xaml.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
namespace CollectionRepro
{
public partial class Window1 : Window
{
public static readonly DependencyProperty IssuesProperty = DependencyProperty.Register("Issues",
typeof(ICollection<string>),
typeof(Window1));
public ICollection<string> Issues
{
get { return (ICollection<string>)GetValue(IssuesProperty); }
set { SetValue(IssuesProperty, value); }
}
public Window1()
{
InitializeComponent();
Reset();
_addButton.Click +=new RoutedEventHandler(_addButton_Click);
_resetButton.Click += new RoutedEventHandler(_resetButton_Click);
}
void _resetButton_Click(object sender, RoutedEventArgs e)
{
Reset();
}
void _addButton_Click(object sender, RoutedEventArgs e)
{
Issues.Add("Another issue");
}
private void Reset()
{
Issues = new ObservableCollection<string>();
}
}
}
First: there's no reason to implement INotifyProperty changed for DependencyProperties. DependencyProperties know when they change.
Second: I don't see an ObservableCollection in your code.
Third: it's not entirely clear to me (from the code you posted) where the issues you modify in the first click come from. I assume from another action, not posted here.
Am I correct if I assume that you want to clear the issues list with the second click (since I don't know what the Issues constructor does)?

Resources