Databinding not working for user control - PropertyChanged always null - c#

I have a problem with data binding. A test application that I have looks as follows:
There's a mainwindow:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:WpfApplication2"
x:Name="main"
Title="MainWindow" >
<StackPanel >
<Button Content="Button" Click="Button_Click"/>
<Controls:UserControl1 />
</StackPanel>
</Window>
And a user control:
<UserControl x:Class="WpfApplication2.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="uc"
>
<Grid >
<TextBox Width="40" Text="{Binding ElementName=main,
Path=Status, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
I want to click the button on the main window to have the value of text box in user control updated:
The code of MainWindow file:
namespace WpfApplication2
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _status;
public string Status
{
get { return _status; }
set
{
if (value != _status)
{
_status = value;
RaisePropertyChanged("Status");
}
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Status == "one")
Status = "two";
else
Status = "one";
}
}
}
And the code of UserControl:
namespace WpfApplication2
{
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, e: new PropertyChangedEventArgs(propertyName));
}
public UserControl1()
{
InitializeComponent();
}
}
}
I don't understand why doesn't it work, but the PropertyChanged is always null. The example is in the simplest form I can imagine...

You are trying to access the parent window using the ElementName binding, as far as I am aware, that is not possible. You can however use a relative source binding to get the parent window:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Status}" ... />
Follow up edit:
Your child user control should look like this:
<UserControl
...
x:Name="usr">
<Grid>
<TextBlock Text="{Binding Message, ElementName=usr}"/>
</Grid>
</UserControl>
You will then need to create a dependency property called 'Message' (This is just an example, I'm not sure what you want to call this property).
public partial class YourUserControl: UserControl
{
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
// Using a DependencyProperty as the backing store for Message. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string), typeof(YourUserControl), new PropertyMetadata(""));
public UserControl1()
{
InitializeComponent();
}
}
Then, when you declare this in your parent user control, simply set the binding of the Message property to whatever property you need to bind to in your parent user control:
<YourNamespace:YourUserControl Message="{Binding PropertyName, ElementName=elementName}" />

Related

Exception Occurring Infinitely in Content Control

First, this is a simplified version from a wizard control using MVVM. The problem is just easier to reproduce as described below
After much narrowing down, I have resolved an infinite exception in my code to be due to the WPF ContentControl. However, I have yet to figure out how to handle it, other than try-catch wrapping all of my possible instantiation code. Here is sample code that reproduces this...any help on how to keep this infinite exception from occurring would be greatly appreciated.
Additional Details
To sum up, the problem is that if the content control changes its contents, and the thing being loaded in throws an exception, then it will throw, then retry the load, causing the throw again and again.
MainWindow.xaml
<Window x:Class="WpfApplication8.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" Name ="Main">
<Grid>
<ContentControl Name="bar" Content="{Binding ElementName=Main, Path=foo}"/>
<Button Click="ButtonBase_OnClick" Margin="20" Width="50"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private UserControl _foo;
public UserControl foo
{
get { return _foo; }
set { _foo = value; OnPropertyChanged("foo"); }
}
public MainWindow()
{
InitializeComponent();
foo = new UserControl1();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
foo = new UserControl2();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
UserControl1 is blank and all default
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
throw new Exception();
}
Do not bind ContentControl to MainWindow. Instead use DataTemplates to select the content for the MainWindow. One example-contrived way of doing it is to bind the ContentControl's Content to the DataContext of the MainWindow.
First some observable test data is needed. The specifics of this data are not important. The main point is to have two different classes of test data from which to choose - TestData.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace fwWpfDataTemplate
{
// Classes to fill TestData
public abstract class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Student : Person { }
public class Employee : Person
{
float _salary;
public float Salary
{
get { return _salary; }
set
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
public class TestData : ObservableCollection<Person>
{
public TestData()
: base(new List<Person>()
{
new Student { Name = "Arnold" },
new Employee { Name = "Don", Salary = 100000.0f }
}) { }
}
}
Then add DataTemplates to MainWindow's resources - MainWindow.xaml:
<Window x:Class="fwWpfDataTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:fwWpfDataTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type me:Student}">
<StackPanel>
<TextBlock Text="Student"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type me:Employee}">
<StackPanel>
<TextBlock Text="Employee"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Salary"/>
<TextBlock Text="{Binding Salary}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Change Data Context" Click="Button_Click" />
<ContentControl Grid.Row="1" Content="{Binding}"/>
</Grid>
</Window>
Note: instead of the StackPanels the contents of the DataTemplates could be UserControl1, UserControl2, etc.
Then add some code to change the data context - MainWindow.cs:
using System.Windows;
namespace fwWpfDataTemplate
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
TestData testData = new TestData();
int testIndex = -1;
private void Button_Click(object sender, RoutedEventArgs e)
{
testIndex = (testIndex + 1) % testData.Count;
this.DataContext = testData[testIndex];
}
}
}
Enjoy.

Super simple binding example

I'm trying to do a simple binding but I'm having some problems. I have a text block and a button. The textblock is binded to a property called "word". When you press the button the value of word changes and I want to automacally update the text block. This is a classic example, please explain me what I'm doing wrong:
namespace WpfApplication5
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _word;
public string word
{
get { return _word; }
set
{
_word= value;
RaisePropertyChanged(word);
}
}
private void change_Click(object sender, RoutedEventArgs e)
{
word= "I've changed!";
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
And my XAML with the binding:
<Window x:Class="WpfApplication5.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>
<TextBlock HorizontalAlignment="Left" Margin="210,152,0,0" TextWrapping="Wrap" Text="{Binding word}" VerticalAlignment="Top"/>
<Button x:Name="change" Content="Change" HorizontalAlignment="Left" Margin="189,235,0,0" VerticalAlignment="Top" Width="75" Click="change_Click"/>
</Grid>
</Window>
You are raising a PropertyChanged event for a property named I've changed!, because you pass the value of the property word to RaisePropertyChanged. You need to pass the name of the property instead:
RaisePropertyChanged("word");
This answer assumes that the data context is set correctly. If not, you need to fix that too:
DataContext = this;

Why is the Dependency Property not returning its value?

I have a MyUserControl with the following Xaml:
<TextBox Text="{Binding InputValueProperty}" />
In the MyUserControl.xaml.cs I have:
public string InputValue
{
get { return (string)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value); }
}
public static readonly DependencyProperty InputValueProperty =
DependencyProperty.Register("InputValueProperty", typeof(string),
typeof(MyUserControl));
In my MainWindow.xaml I create a user control:
<local:MyUserControl InputValue="My Input" />
Later on in my MainWindow.xaml.cs I am trying to access this string. All instances of MyUserControl are contained in a List and I access them with a foreach.
string temp = userControl.InputValue;
This is always null. In my MainWindow.xaml I can see the "My Input" in the text box of the user control but I can't ever seem to get it out of there.
DependencyProperty.Register("InputValueProperty", ...
That should be:
DependencyProperty.Register("InputValue", ...
XAML depends on the registered name of the property, not the name of the property accessor.
It looks like the problem is in your binding. Here's a working example that's modeled off your code with a relative source binding:
Here's the user control:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public string InputValue
{
get { return (string)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value); }
}
public static readonly DependencyProperty InputValueProperty =
DependencyProperty.Register("InputValueProperty", typeof(string),
typeof(MyUserControl));
}
<UserControl x:Class="WpfApplication4.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication4" Height="30" Width="300">
<Grid>
<TextBox Text="{Binding Path=InputValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyUserControl}}}" />
</Grid>
</UserControl>
And here's the window:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
string text1 = ctrl1.InputValue;
string text2 = ctrl2.InputValue;
string text3 = ctrl3.InputValue;
//breakpoint here
}
}
<Window x:Class="WpfApplication4.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication4" Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<local:MyUserControl x:Name="ctrl1" InputValue="My Input" />
<local:MyUserControl x:Name="ctrl2" InputValue="2" />
<local:MyUserControl x:Name="ctrl3" InputValue="3" />
<Button Click="Button_Click" Height="25" Content="debug"/>
</StackPanel>
</Grid>
</Window>
If i throw a breakpoint in the click event i can see the bound values of each of the controls. (if you copy and paste from this be sure to change WpfApplication4 to whatever your project is called.
You need to implement INotifyPropertyChanged on your class that has the property
public class YourClassThatHasTheInputValuePropertyInIt: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public string InputValue
{
get { return (string)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value);
NotifyPropertyChanged("InputValue"); }
}
}
This will allow the binding to pick up the property

Data Binding problem

I want to do something like this diagram Data Binding Diagram.
If i update TextBox text Then update TextBlock text and Property and if i change Property Value then also update Textbox and textBlock text. Please tell me how can i do it using WPF ????
Thank`s For Help.
Im not sure if I understand right your question. Are the two Textboxes in the same view or in different?
Here a solution with 2 textboxes in the same view:
View (xaml):
<Window x:Class="Sandbox.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"
Name="mainWindow">
<StackPanel>
<TextBox Name="UpperTextBox" Text="{Binding ElementName=LowerTextBox, Path=Text,UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Name="LowerTextBox" Text="{Binding MyValue, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
View-Codebehind (xaml.cs):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _myValue;
public string MyValue
{
get { return _myValue; }
set
{
_myValue = value;
OnPropertyChanged("MyValue");
}
}
}

How to create a UserControl in the MVVM pattern?

Currently, within a real-world application development, I am struggling with the consumption of a custom UserControl in the MVVM pattern.
In my application, there is a DataGrid where the user can select an entry. The DataGrid's SelectedItem is TwoWay-bound to a field of the ViewModel set as DataContext. When the user selects an entry, the field is properly updated (tested). In the Page where holds the DataGrid, the field is bound through XAML to a DependencyProperty of a custom UserControl devised in the MVVM pattern : it bares its own ViewModel which is set as DataContext. The trouble is that the UserControl's DependencyProperty is not updated when the field changes even though the INotifyPropertyChanged interface is correctly implemented (see the comparison with a traditional control in the next minimal working example).
This example is constituted of a Label and bares ViewModelUserControl as a DataContext, UserControl1is consumed by the MainWindow and the binding is compared to that of a Label.
The file MainWindow.xaml:
<Window x:Class="UserControlWithinUserControlDataContext.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Local="clr-namespace:UserControlWithinUserControlDataContext"
Title="MainWindow"
Height="350" Width="525"
>
<StackPanel Orientation="Horizontal"
>
<ListBox SelectedItem="{Binding Text, Mode=TwoWay}"
x:Name="listbox"
Height="150"
>
</ListBox>
<Local:UserControl1 Text="{Binding Text, Mode=OneWay}"
Height="50" Width="150"
/>
<Label Content="{Binding Text, Mode=OneWay}"
/>
</StackPanel>
</Window>
The code-behind MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public ViewModelWindow view_model_window
{
get { return _view_model; }
}
private ViewModelWindow _view_model = new ViewModelWindow();
public MainWindow()
{
InitializeComponent();
DataContext = view_model_window;
IList<String> list = new List<String>();
list.Add("A");
list.Add("B");
list.Add("C");
listbox.ItemsSource = list;
}
}
The ViewModel of the MainWindow, the file ViewModelWindow.cs :
public class ViewModelWindow : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public String Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
NotifyPropertyChanged("Text");
}
}
}
private String text = "Bli";
}
The file UserControl1.xaml:
<UserControl x:Class="UserControlWithinUserControlDataContext.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>
<Label Content="{Binding Text}"
Background="Magenta"
HorizontalAlignment="Stretch"
/>
</Grid>
</UserControl>
The code-behind file UserControl1.xaml.cs:
public partial class UserControl1 : UserControl
{
public ViewModelUserControl view_model_usercontrol
{
get { return _view_model; }
}
private ViewModelUserControl _view_model = new ViewModelUserControl();
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(UserControl1),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(TextPropertyChangedCallback)));
private static void TextPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 user_control = d as UserControl1;
if(user_control != null)
{
user_control.view_model_usercontrol.Text = user_control.Text;
}
}
public UserControl1()
{
InitializeComponent();
DataContext = view_model_usercontrol;
}
}
The ViewModel of UserControl1, the file ViewModelUserControl.cs:
public class ViewModelUserControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public String Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
NotifyPropertyChanged("Text");
}
}
}
private String text = "";
}
As you can see when executing this code, the MainWindow's Label gets updated while the UserControl1's Label doesn't.
What am I doing wrong? Is there a way to makes this works?
Many thanks in advance for any clue.
first you do not need to add anything in the UserControl just the XAML. Remove all the code of the UserControl and try.
Let's explain why:
Content="{Binding Text}" you set this in the usercontrol xaml, it's binded to the ViewModelWindow. and that works. and remove in
<Local:UserControl1 => Text="{Binding Text, Mode=OneWay}"
Ok, but it is correct to define a property in the user control in case of other situation?, that's right, in order to do that:
<UserControl x:Name="UserControlInstance"...>
<Label Content="{Binding Text, ElementName=UserControlInstance}" ...>
Where in this case Text is the dependency property and not the datacontext property.
Try the first option, and then the second defining just a Dependency Property and in this case bind the dependency property as you did.
And a tip, if a dependency property is in the visual element tree like in your case you do not need to call the callback.
Thank you Juan for your answer, here is the solution for conceiving the UserControl in the MVVM pattern:
I gave the name root to the Grid of UserControl1 and set its DataContext:
root.DataContext = view_model_usercontrol;
instead of:
DataContext = view_model_usercontrol;
Everything works fine.
Happy ending :)

Categories

Resources