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 :)
Related
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}" />
This is a mostly out of curiosity question and to hopefully help me better understand binding, XAML, and extension syntax.
So I simply want to change the binding source from the MainWindow to an object I have instantiated in MainWindow.
Here is my C# code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
favclass myfavclass = new favclass();
InitializeComponent();
this.DataContext = this;
}
string _myString = "hello";
public string MyString
{
get { return _myString; }
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
public class favclass : INotifyPropertyChanged
{
int _myint = 34;
public int MyInt
{
get { return _myint; }
set { _myint = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
}
and my XAML
<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" >
<Grid>
<TextBlock Height="50" Width="50" Text="{Binding MyString}"/>
<TextBlock Height="50" Width="48" Margin="200,100,100,100"
Text="{Binding Source=myfavclass, Path=MyInt}"/>
</Grid>
</Window>
So as you can see I want first display the MyString property from main window.
Then I want to display the MyInt from the myfavclass object. But of course MyInt doesn't appear. I've tried every variation I can think of.
What XAML am I missing? Why doesn't the XAML I have work?
Thanks
Source=myfavclass this is wrong. Source can be only assigned directly using element syntax like this:
<Binding>
<Binding.Source>
<!-- value here -->
</Binding.Source>
</Binding>
Or you can use StaticResource or DynamicResoure or some custom MarkupExtension like this:
Text="{Binding Source={StaticResource someKey}, Path=MyInt}"
Or use the new feature {x:Reference} to get reference directly to some named element inside XAML:
Text="{Binding Source={x:Reference someName}, Path=MyInt}"
Moreover the myfavclass is declared as local variable inside your code behind. There is no way it can be used (referenced) inside XAML code.
You're doing something called multiple viewmodels. If so you should provide multiple DataContext for your controls. I prefer to using nested viewmodels. To implement this, you can try modifying the MainWindow like this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
_myfavclass = new favclass();
InitializeComponent();
this.DataContext = this;
}
private readonly favclass _myfavclass;
//we will use this property inside XAML code
public favclass MyFavClass {
get {
return _myfavclass;
}
}
}
Now in XAML code, you can bind the Text to the MyFavClass.MyInt, note that the DataContext is implicitly the source for the Binding, so you just need to specify the Path:
<TextBlock Height="50" Width="48" Margin="200,100,100,100"
Text="{Binding Path=MyFavClass.MyInt}"/>
Your MyInt is not properly implemented using INotifyPropertyChanged (but I hope you already know that).
favclass myfavclass = new favclass(); should be declared out of the init method,or you won't get this.myfavclass instance
I have two custom UserControl in my project code: TableControl and DeckControl. In the code of the latter I would be able to access the former when it's needed. So in my DeckControl I implemented the following property:
private TableControl m_Table;
public TableControl Table
{
get { return m_Table; }
set { m_Table = value; }
}
The problem is that I'm not able to set the property from XAML code:
<Canvas Core:Name="Layout" Loaded="OnLayoutLoaded">
<Namespace:TableControl Core:Name="Table" Canvas.Left="0" Canvas.Top="0" Height="{Binding ElementName=Layout, Path=ActualHeight}" Width="{Binding ElementName=Layout, Path=ActualWidth}"/>
<Namespace:DeckControl Core:Name="Deck" Canvas.Left="50">
</Canvas>
I tried using Reference but compiler says that the method or opera
<Namespace:DeckControl Core:Name="Deck" Canvas.Left="50" Table="{Core:Reference Name=Table}">
I tried this but it isn't working either:
<Namespace:DeckControl Core:Name="Deck" Canvas.Left="50" Table="{Core:Static Table}">
I also tried using Binding:
<Namespace:DeckControl Core:Name="Deck" Canvas.Left="50" Table="{Binding ElementName=Table}">
Ok so... it's my first approach to XAML and I'm still working on it... but I really can't get it!
If you want to bind to a property in youe Model(window/Usercontrol) codebehind you have to set the DataContext in your Xaml.
There are may ways to do this but the simpliest is just naming your window or usercontrol and binding using ElementName.
Example for a Window:
<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="233" Width="143" Name="UI">
<Canvas DataContext="{Binding ElementName=UI}" > <!-- Set dataContext to Window -->
<Namespace:DeckControl Canvas.Left="50" Table="{Binding ElementName=Table}">
</Canvas>
</Window>
And if you want the Xaml to update when Table changes your code behind should implement INotifyPropertyChanged, this will inform the Xaml that the property has changed.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
private TableControl m_Table;
public TableControl Table
{
get { return m_Table; }
set { m_Table = value; NotifyPropertyChanged("Table"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
If your Table property is not a DependancyProperty you will have to chage this so you can bind.
Example:
public class DeckControl : UserControl
{
.......
// Using a DependencyProperty as the backing store for Table. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TableProperty =
DependencyProperty.Register("Table", typeof(TableControl), typeof(DeckControl), new UIPropertyMetadata(null));
public TableControl Table
{
get { return (TableControl)GetValue(TableProperty); }
set { SetValue(TableProperty, value); }
}
}
Also any property that is being binded outside the scope of the UserControl has to be a DependancyProperty.
Example:
public partial class DeckControl : UserControl
{
public DeckControl()
{
InitializeComponent();
}
private int myVar;
public int MyProperty
{
get { return myVar; }
set { myVar = value; }
}
}
This will bind inside the usercontrol when it is a simple property as it is inscope.
<UserControl .....
d:DesignHeight="300" d:DesignWidth="300" Name="UI">
<TextBlock Text="{Binding MyProperty}" />
</UserControl>
This will not bind as its out of scope of the UserControl, MyProperty will have to be a DependancyProperty to bind here
<Window ....
Title="MainWindow" Height="233" Width="143" Name="UI">
<Grid>
<local:DeckControl MyProperty="{Binding Width}" /> // Will not bind
</Grid>
</Window>
Hope that makes sense :)
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
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");
}
}
}