Binding model with multiple properties in UserControl using one DependencyProperty [duplicate] - c#

This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
Closed 4 years ago.
I would like to be able to bind complex model (many properties) to UserControl through DependencyProperty, and if model would be edited in UserControl I would like to see this edited information inside my binded model.
Example application: Model, UserControl (xaml + cs), MainWindow (xaml + cs). I have no ViewModel to simplify idea.
Model:
public class MyModel : INotifyPropertyChanged
{
private string _surname;
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string Surname
{
get => _surname;
set
{
_surname = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MyModelEditor.xaml (inside Grid):
<DockPanel>
<TextBox Text="{Binding MyModel.Name}"/>
<TextBox Text="{Binding MyModel.Surname}"/>
</DockPanel>
Also contains this line in UserControl root element:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
MyModelEditor.xaml.cs:
public partial class MyModelEditor : UserControl
{
public MyModel MyModel
{
get => (MyModel)GetValue(MyModelProperty);
set => SetValue(MyModelProperty, value);
}
public static readonly DependencyProperty MyModelProperty =
DependencyProperty.Register("MyModel", typeof(MyModel), typeof(MyModelEditor), new FrameworkPropertyMetadata(null));
public MyModelEditor()
{
InitializeComponent();
}
}
MainWindow.xaml (inside Grid):
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Press Me!" Click="ButtonBase_OnClick"/>
<controls:MyModelEditor MyModel="{Binding MyModel}"/>
</DockPanel>
MainWindow.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private MyModel _myModel;
public MyModel MyModel
{
get => _myModel;
set
{
_myModel = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show(MyModel?.Name);
}
}
My test scenario: type text in textbox, press button.
Current behavior: Message after pressing button is empty.
Expected behavior: Message after pressing button is same like in textbox.
I wold not like to bind to all properties separately, because in future I will have much more then two properties.
Why current approach does not work?
How can I achieve my goal?

You are apparently not using the UserControl instance as Binding source in your UserControl's XAML. One way to do this would be to set the Binding's RelativeSource:
<TextBox Text="{Binding MyModel.Name,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
However, you don't need a new dependency property at all for this purpose. Just bind the UserControl's DataContext to a MyModel instance, like
<controls:MyModelEditor DataContext="{Binding MyModel}"/>
The Bindings in the UserControl's XAML would automatically work with the MyModel object, like this:
<DockPanel>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Surname}"/>
</DockPanel>

For both of your TextBox controls, you should define their Binding with a TwoWay mode (ms docs on binding modes). Which, basically, would assure that the data flow is working in both direction (i.e. from the view model into the view and the other way around):
<DockPanel>
<TextBox Text="{Binding MyModel.Name, Mode=TwoWay}"/>
<TextBox Text="{Binding MyModel.Surname, Mode=TwoWay}"/>
</DockPanel>
As a good practice, you should always explicitly define what is the mode of the the Binding (NOTE: by default it's OneWay TwoWay - how to know which is the default?).
Another tip would be to go ahead and use MvvmHelpers nuget (github project), which could spare you the time of implementing INotifyPropertyChanged. Besides, you shouldn't re-invent the wheel
EDIT: Fixes are in your GitHub repo
Two things to note here
You have not instantiated your ViewModel (i.e. MyModel), so it was always null
You don't need to create DependencyPropery every time you want to pass some information to your UserControl. You could simply bind the DataContext itself

Related

WPF DependencyProperty OnPropertyChanged returns default value as NewValue if using DataTemplate on UserControl

I have an application in which I set the content of a contentpresenter, dependent on the datatype by a datatemplate (see MainWindow). The Datatemplate is a usercontrol, which is actually datatype specific. (The small example below is only for demonstration, but in my "real" application the user shall be able to switch between different data.)
The usercontrol (UserControl1) has a DependencyProperty which I assign a value (in my application this is actually a binding to a VM, just set it to a string in example for simplicity).
Setting the value is still working fine. However In my UserControl I need to react to changes of the DependencyProperty to change the view of my UserControl (or later on CustomControl). So I implemented a OnPropertyChangend method.
When application starts OnPropertyChanged works as I expect it and I get the "correct" newvalue of my DependencyProperty. However, if I change my VM (i.e. my datatemplate changes) during runtime by clicking on a button, OnPropertyChanged returns the DependencyProperty's defaultvalue.
In my small example application, I can see that the value is set correctly, as the Textblock content changes to the correct value.
It only seems that OnPropertyChanged gets fired before my DependencyProperty's value gets the new value. So, it's not possible for me to react on the new value.
It is not really clear why this happens. Seems to have something to do with the order in which WPF resolves internal stuff?
Does anyone have a clue, how I can fix this behavior and get access to the current/last value when changing my VM and don't miss an update? As stated out before, I need to react on that value.
Maybe I am doing something totally stupid here. Is the approach I decided to use here a bad one? Are DataTemplates the wrong approach to switch between two pairs? What would be a better approach then? However, I guess it won't be possible to avoid the DependencyProperty and the UserControl in my application.
MainWindow.xaml
<!--MainWindow.xaml -->
<Grid>
<StackPanel>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ContentPresenter Content="{Binding ActiveVM}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<local:UserControl1 MyProperty="Test1"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<local:UserControl1 MyProperty="Test2"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
vmParent = new VMParent();
DataContext = vmParent;
var vm1 = new VM1();
var vm2 = new VM2();
}
VMParent vmParent;
private void Button_Click(object sender, RoutedEventArgs e)
{
vmParent.ChangeActiveVM();
}
}
UserControl1.xaml
<!--UserControl1.xaml -->
<TextBlock Text="{Binding MyProperty, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}"/>
UserControl1.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(UserControl1), new PropertyMetadata("DefaultString", OnMyPropertyChangend));
private static void OnMyPropertyChangend(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == "DefaultString")
{
;
//xxxxxx
//unexpectedly i get stuck here
//Would expect/need NewValue to be Text1/Text2 to react to it
//xxxxxx
}
}
}
VMParent
class VMParent : INotifyPropertyChanged
{
public VMParent()
{
vm1 = new VM1();
vm2 = new VM2();
ActiveVM = vm1;
}
public event PropertyChangedEventHandler PropertyChanged;
VM1 vm1;
VM2 vm2;
public object ActiveVM
{
get => m_activeVM;
set { m_activeVM = value; OnPropertyChanged("ActiveVM"); }
}
private object m_activeVM;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public void ChangeActiveVM()
{
if (ActiveVM is VM1)
ActiveVM = vm2;
else
ActiveVM = vm1;
}
}
VMs are only used to apply Datatemplate
class VM1
{
}
class VM2
{
}

Databinding textbox read from property

I want to fill the text property from a textbox using binding. (My first try with binding).
I have this:
public string TestProperty { get; set; }
private void Window_Loaded(object sender, RoutedEventArgs e)
{
TestProperty = 'Test';
}
in xaml:
<TextBox x:Name="TextBox_Test" HorizontalAlignment="Left" Height="23" Margin="49,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="288" Text="{Binding ElementName=TextBox_Test, Path=TestProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The property is filled when the form loads. The textbox keeps empty. How can i fill the textbox?
You've got to fix a couple of things before this will work.
First, you're binding expression isn't quite right. You specified that the binding source is the TextBox by using the ElementName. That's not correct. Your source should actually be the Window since that's where your property exists. So, give your Window a name, and change ElementName to that Windowname. For example ..
<TextBox x:Name="TextBox_Test" HorizontalAlignment="Left" Height="23" Margin="49,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="288"
Text="{Binding ElementName=Window_Test, Path=TestProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Second, your Windowneeds to implement INotifyPropertyChangedfor changes on the source to be reflected on the target.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _testProperty;
public string TestProperty
{
get { return _testProperty; }
set
{
_testProperty = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TestProperty"));
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
TestProperty = "Test";
}
public MainWindow()
{
InitializeComponent();
}
}
Note that I modified the class to implement the interface, and I raise the event in the property setter.
With these changes, your binding will work. I should note that this type of binding is a bit unusual. In cases like this, it's more common for the Window to use a DependencyProperty, or for you to bind to a non-UI class (A view model, for example). You may want to look into both as you learn about binding.
Dependency Properties
MVVM Pattern

UserControl DataContext Binding

I have three projects in my solution:
My main WPF Application which contains a MainWindow + MainViewModel
UserControl Library with a UserControl (ConfigEditorView)
UIProcess class with the ViewModel for the UserControl (ConfigEditorViewModel)
In my MainWindow I want to use the UserControl with the ViewModel of UIProcess.
First I set the UserControl in my MainWindow:
<TabItem Header="Editor">
<Grid>
<cel:ConfigEditorView DataContext="{Binding ConfEditModel, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</TabItem>
I don't know which of these properties I need here, so I put all together but it still doesn't work.
Then I've set this in my MainViewModel:
public ConfigEditorViewModel ConfEditModel { get; set; }
With simple method that is bound to a Button:
private void doSomething()
{
ConfEditModel = new ConfigEditorViewModel("Hello World");
}
My ConfigEditorViewModel looks basically like this:
public class ConfigEditorViewModel : ViewModelBase
{
private string _Description;
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
base.RaisePropertyChanged();
}
}
public ConfigEditorViewModel(string t)
{
Description = t;
}
}
The description is bound to a TextBox in my UserControl.
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Description}"/>
When I start the application and click the Button the TextBox should contain "Hello World" but it's empty.
What I've done wrong?
i gave you a general answer:
within a "real(a usercontrol you wanna use with different viewmodels with different property names)" usercontrol you bind just to your own DependencyProperties and you do that with ElementName or RelativeSource binding and you should never set the DataContext within a UserControl.
<UserControl x:Name="myRealUC" x:class="MyUserControl">
<TextBox Text="{Binding ElementName=myRealUC, Path=MyOwnDPIDeclaredInMyUc, Path=TwoWay}"/>
<UserControl>
if you do that you can easily use this Usercontrol in any view like:
<myControls:MyUserControl MyOwnDPIDeclaredInMyUc="{Binding MyPropertyInMyViewmodel}"/>
and for completeness: the Dependency Property
public readonly static DependencyProperty MyOwnDPIDeclaredInMyUcProperty = DependencyProperty.Register(
"MyOwnDPIDeclaredInMyUc", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));
public bool MyOwnDPIDeclaredInMyUc
{
get { return (string)GetValue(MyOwnDPIDeclaredInMyUcProperty); }
set { SetValue(MyOwnDPIDeclaredInMyUcProperty, value); }
}
Your view models (and, optionally, models) need to implement INotifyPropertyChanged.
Binding's aren't magic. There is no inbuilt mechanism that allows for code to be notified when a plain old property's value changes. You'd have to poll it in order to check to see if a change happened, which would be very bad, performance-wise.
So bindings will look at the objects they are bound against and see if they implement INotifyPropertyChanged and, if so, will subscribe to the PropertyChanged event. That way, when you change a property and fire the event, the binding is notified and updates the UI.
Be warned, you must implement the interface and use it correctly. This example says it's for 2010, but it works fine.

binding instanciated property to UI

I have this class :
public class property : DependencyObject, INotifyPropertyChanged
{
private string _myproperty;
public string MyProperty
{
get
{
return this._myproperty;
}
set
{
this._myproperty = value;
NotifyPropertyChanged("MyProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string sproperty)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sproperty));
}
}
}
In the main window I have created an instance of this class myclass xx = new myclass();, where I populate my property with string data and bind it to XAML like so:
<Window.Resources>
<local:property x:Key="prop"></local:property>
</Window.Resources>
In my TextBox i have set the binding :
Text="{Binding Path=MyProperty, Source={StaticResource prop}}" BorderBrush="#FFC7CACC" />
This will not work unless if i use the existing resources:
var property = (local:property)Resources["prop"];
Is there another way to update the TextBox rather than using the resources? I want to use the normal class instantiation.
if you say Text="{Binding Path=MyProperty, Source={StaticResource prop}}" BorderBrush="#FFC7CACC" />
means that your VM is an instance of property class.
Try to surround your textbox with a Grid and set the grid dataContext with an instance of your poperty clas.
I mean
<Grid DataContext="from view or from behind assign your vm= new property()">
<TextBox Text="{Binding Path=MyProperty" ....../>
</Grid>
Try this:
<Window.DataContext>
<local:property/>
<Window.DataContext>
<TextBox Text="{Binding MyProperty}"/>
After setting the data context, just try to build the application, the build will succeed if it can find the property class in the local namespace.
After building your app, if succeeded, you can try to set the binding and also the Intellisense will automatically show MyProperty in Binding Options.
If this doesn't work, try to set the data context and binding using the Properties panel. Maybe visually you can get things right.
Try it, and if it fails, tell me where it went wrong

Dependency Property vs INotifyPropertyChanged in ViewModel for Windows 8 application

I have created blank C#/XAML Windows 8 application. Add simple XAML code:
<Page
x:Class="Blank.MainPage"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel
Margin="0,150"
HorizontalAlignment="Center">
<TextBlock
x:Name="xTitle"
Text="{Binding Title, Mode=TwoWay}"/>
<Button Content="Click me!" Click="OnClick" />
</StackPanel>
</Grid>
</Page>
And the simple code in C# part:
public sealed partial class MainPage
{
private readonly ViewModel m_viewModel;
public MainPage()
{
InitializeComponent();
m_viewModel = new ViewModel
{
Title = "Test1"
};
DataContext = m_viewModel;
}
private void OnClick(object sender, RoutedEventArgs e)
{
m_viewModel.Title = "Test2";
}
}
Now I want to implement ViewModel. I have two way:
Use Dependency Property
Implement INotifyPropertyChanged
For first approach it is:
public class ViewModel : DependencyObject
{
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string)
, typeof(ViewModel)
, new PropertyMetadata(string.Empty));
}
For second it is:
public class ViewModel : INotifyPropertyChanged
{
private string m_title;
public string Title
{
get
{
return m_title;
}
set
{
m_title = value;
OnPropertyChanged("Title");
}
}
protected void OnPropertyChanged(string name)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I prefer the first way, because it allows use coerce (Silverlight for web and for WP7 doesn't have coerce functionality.. WinRT too.. but I'm still looking and hope) and looks more natural for me. But unfortunately, it works as OneTime for the first approach.
Could anybody explain to me why MS abandon using Dependency Property for implementing view model?
You should not be using a DependencyProperty in your ViewModel - you should only use them in your controls. You will never want to bind one ViewModel to another, also ViewModels do not need to persist their values nor provide default values, nor provide property metadata.
You should only use INotifyPropertyChanged in your ViewModels.

Categories