I'm attempting to understand the best way of wiring up a custom control to use Dependency Properties and a View Model. The Dependency Properties are implemented to expose properties that can be used in XAML to initialise the properties in the View Model. For example, in the code behind of a custom control I can define the following dependency property:
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
"MyProperty", typeof(string), typeof(MyControl), new PropertyMetadata(null));
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
where the View Model is defined as
public class MyControlViewModel : INotifyPropertyChanged
{
public MyControlViewModel()
{
_myProperty = "Default View Model string";
}
private string _myProperty;
public string MyProperty
{
get
{
return _myProperty;
}
set
{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
and the custom control binds to the View Model MyProperty as follows
<UserControl x:Class="MyProject.MyControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject">
<UserControl.DataContext>
<local:MyControlViewModel/>
</UserControl.DataContext>
<Grid>
<TextBlock Text="{Binding MyProperty}"/>
</Grid>
</UserControl>
Now since I have defined the Dependency Property MyProperty in the custom control's code behind, I want to be able to use this to initialise MyProperty in the ViewModel. So something like this
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:MyControlView MyProperty="This was set in XAML"/>
</Grid>
</Window>
Running the above will display the string "Default View Model string" that was set in the View Model's constructor. How do I hook up the Dependency Property value so that it correctly initialises the string in the View Model? i.e. it should display "This was set in XAML".
UPDATE
I can set a property changed callback in the code behind and set the value in the View Model, i.e.
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(MyControlView), new PropertyMetadata("Default", OnMyPropertyChanged));
private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var view = d as MyControlView;
if (view != null)
{
var viewModel = view.DataContext as MyControlViewModel;
if (viewModel != null)
{
viewModel.MyProperty = e.NewValue as string;
}
}
}
Is this correct, or does it smell?
You have created dependency property that is nice. But, you have made tightly coupled by instiantiating your view model and using its property into code behind. Its wrong way.
You should inherit your class from "Control" and for binding value and create one DP like below in custom control class:
public static readonly DependencyProperty myValueProperty = DependencyProperty.Register(
"MyProperty", typeof(object), typeof(FieldControl), new FrameworkPropertyMetadata(null, myValueChanged));
Then,
private static void myValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var fc = dependencyObject as FieldControl;
if (fc != null)
{
fc.SetValue(BindingExpression1Key, fc.GetBindingExpression(ValueProperty));
fc.RaiseValueChangedEvent(dependencyPropertyChangedEventArgs);
}
}
Now, use it and bind your property like below:
<local:MyControlView MyProperty="{Binding MyProperty, Mode=TwoWay}"/>
You can update the ViewModel (INPC) property based on the View's Dependency Property (DP) using a Blend Behaviour. This solution avoids having to add a property changed callback in the code-behind:
<UserControl x:Class="MyProject.MyControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
xmlns:local="clr-namespace:MyProject"
x:Name="MyControl">
<UserControl.DataContext>
<local:MyControlViewModel/>
</UserControl.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ic:ChangePropertyAction
TargetObject="{Binding}"
PropertyName="MyProperty"
Value="{Binding ElementName=MyControl, Path=MyProperty}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<TextBlock Text="{Binding MyProperty}"/>
</Grid>
You will have to add references to the System.Windows.Interactivity and Microsoft.Expression.Interactivity.Core assemblies to your projects in order to use Blend Behaviours (note the i and ic namespaces that I added).
In this case I hook into the UserControl Loaded event with an EventTrigger that calls a ChangePropertyAction. By specifying {Binding} for the TargetObject, I'm informing the Behaviour to use the View Model from the bound DataContext. The PropertyName refers to the property on the View Model. The Value refers to the DP on the View. I gave your UserControl a name so that I could easily reference it when querying the value of MyProperty.
The benefit of doing it this way means that you could swap out your View Model in the DataContext (as long as the new one also has a MyProperty property) without having to update the code-behind.
Related
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.
I have created a custom control CustomTextBox inherited from TextBox class. I have created a dependency property named CustomTextProperty.
I have binded this DP with my Viewmodel property.
While Registering the DP i have given the property change callback but it is only get called one time when my control gets the binded data initially when my xaml loads.
When i try to set my control from view the binded VM property setter does not gets called and also the propertychangecallback not gets fired.
Please help!!
Code snipet below:
My Custom control
class CustomTextBox : TextBox
{
public static readonly DependencyProperty CustomTextProperty = DependencyProperty.Register("CustomText",
typeof(string), typeof(CustomTextBox),
new FrameworkPropertyMetadata("CustomTextBox",
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnCustomPropertyChange)));
public string CustomText
{
get { return (string)GetValue(CustomTextProperty); }
set { SetValue(CustomTextProperty, value); }
}
private static void OnCustomPropertyChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// This is Demo Application.
// Code to be done Later...
}
}
My View Model:
public class ViewModel : INotifyPropertyChanged
{
private string textForTextBox;
public string TextForCustomTextBox
{
get
{
return this.textForTextBox;
}
set
{
this.textForTextBox = value;
this.OnPropertyChange("TextForCustomTextBox");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
My Xaml Code with Binding:
<custom:CustomTextBox x:Name="CustomTextBox"
CustomText="{Binding TextForCustomTextBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" HorizontalAlignment="Center" Width="200" Height="50" />
My Code Behind to set DataContext:
// My View Constructor
public View1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
You said that you declared a CustomText DependencyProperty and data bound it to your view model TextForCustomTextBox property and that much is correct. However, when you said that you tried to set your property from the view, you were mistaken.
What you were actually doing was setting the CustomTextBox .Text property from the view and that wasn't connected to your CustomTextBox.CustomText property. You can connect them like this, although I'm not quite sure what the point of that would be:
<Views:CustomTextBox x:Name="CustomTextBox" Text="{Binding CustomText, RelativeSource={
RelativeSource Self}, UpdateSourceTrigger=PropertyChanged}" CustomText="{Binding
TextForCustomTextBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" HorizontalAlignment="Center" Width="200" Height="50" />
Try setting your DataContext BEFORE the actual initialization so it is available when the form/control objects are created. If it can't find before, is that what may be causing the failed bindings.
I have created a user control like numeric updown as follows
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<TextBox x:Name="InputTextBox" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1"
Style="{StaticResource NumericUpDownTextBoxStyle}"
KeyDown="InputTextBox_KeyDown"
KeyUp="InputTextBox_KeyUp"
GotFocus="InputTextBox_GotFocus"
LostFocus="InputTextBox_LostFocus"
MouseWheel="InputTextBox_MouseWheel"
MouseEnter="InputTextBox_MouseEnter"
LayoutUpdated="InputTextBox_LayoutUpdated"
Text="{Binding Path=ControlValue, Mode=TwoWay,ValidatesOnDataErrors=True,ValidatesOnExceptions=True,NotifyOnValidationError=True}"/>
</StackPanel>
I have bind a ViewModel to this control where I Set ControlValue property to TextBox property of the user control template textbox.
Everthing works fine at a control level. I have exposed from usercontrol.
public static readonly DependencyProperty MaximumValueProperty;
public static readonly DependencyProperty MinimumValueProperty;
public static readonly DependencyProperty StepValueProperty;
public static readonly DependencyProperty TextValueProperty;
My Properties are
public double Maximum
{
get
{
return (double)GetValue(MaximumValueProperty);
}
set
{
SetValue(MaximumValueProperty, value);
this.ViewModel.Maximum = this.Maximum;
}
}
public double Minimum
{
get
{
return (double)GetValue(MinimumValueProperty);
}
set
{
SetValue(MinimumValueProperty, value);
this.ViewModel.Minimum = this.Minimum;
}
}
public double Step
{
get
{
return (double)GetValue(StepValueProperty);
}
set
{
SetValue(StepValueProperty, value);
this.ViewModel.Step = this.Step;
}
}
public double TextValue
{
get
{
return (double)GetValue(TextValueProperty);
}
set
{
SetValue(TextValueProperty, value);
this.ViewModel.ControlValue = Convert.ToString(value);
}
}
Initialization of the property.
static NumericUpDown()
{
MaximumValueProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(NumericUpDown), new PropertyMetadata(null));
MinimumValueProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(NumericUpDown), new PropertyMetadata(null));
StepValueProperty = DependencyProperty.Register("Step", typeof(double), typeof(NumericUpDown), new PropertyMetadata(null));
TextValueProperty = DependencyProperty.Register("TextValue", typeof(double), typeof(NumericUpDown), new PropertyMetadata(null));
}
My Usercontrol implementation in the MainPage.xaml page as follows
<local:NumericUpDown Maximum="28" Minimum="-28" Step="0.25" TextValue="{Binding ElementName=FranePrice, Path=DataContext.FranePrice}"></local:NumericUpDown>
Where I have another ViewModel which i bind to the XAML page and there is a Property in the ViewModel which i bind to the TextValue property of the Usercontrol.
FramePrice is property in the View model that i bind to the TextValue property of the user control
and Main page XAML is
<UserControl x:Class="DatePicker.MainPage"
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:DatePicker"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Vertical">
<local:NumericUpDown Maximum="28" Minimum="-28" Step="0.25" TextValue="{Binding ElementName=FranePrice, Path=DataContext.FranePrice}"></local:NumericUpDown>
<Button Content="Show Date" Height="23" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click"/>
</StackPanel>
</Grid>
This View model of the page where i used user control. On click event i showing TextValue to user.
public class MainPageViewModel : EntityViewModel
{
public MainPageViewModel()
{
}
private double framePrice;
public Double FramePrice
{
get
{
return framePrice;
}
set
{
framePrice = value;
PropertyChangedHandler("FramePrice");
}
}
}
When I change the TextValue in the User control it doesnot change in the FramePrice property of the page viewmodel.
Is anything wrong in the code.???
As per Luke Woodward's post I have updated code as follows
public static readonly DependencyProperty MaximumValueProperty;
public static readonly DependencyProperty MinimumValueProperty;
public static readonly DependencyProperty StepValueProperty;
public static readonly DependencyProperty TextValueProperty;
public static double Max;
public static double Min;
public static double Stp;
public static double Val;
public double Maximum
{
get
{
return (double)GetValue(MaximumValueProperty);
}
set
{
SetValue(MaximumValueProperty, value);
}
}
public double Minimum
{
get
{
return (double)GetValue(MinimumValueProperty);
}
set
{
SetValue(MinimumValueProperty, value);
}
}
public double Step
{
get
{
return (double)GetValue(StepValueProperty);
}
set
{
SetValue(StepValueProperty, value);
}
}
public double TextValue
{
get
{
return (double)GetValue(TextValueProperty);
}
set
{
SetValue(TextValueProperty, value);
}
}
static NumericUpDown()
{
MaximumValueProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(NumericUpDown), new PropertyMetadata(new PropertyChangedCallback(onMaximumValueChanged)));
MinimumValueProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(NumericUpDown), new PropertyMetadata(new PropertyChangedCallback(onMinimumValueChanged)));
StepValueProperty = DependencyProperty.Register("Step", typeof(double), typeof(NumericUpDown), new PropertyMetadata(new PropertyChangedCallback(onStepValueChanged)));
TextValueProperty = DependencyProperty.Register("TextValue", typeof(double), typeof(NumericUpDown), new PropertyMetadata(new PropertyChangedCallback(onTextValueChanged)));
}
private static void onStepValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Stp = (double)e.NewValue;
}
private static void onMinimumValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Min = (double)e.NewValue;
}
private static void onMaximumValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Max = (double)e.NewValue;
}
private static void onTextValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Val = (double)e.NewValue;
}
Then i accessed Max, Min , Stp and Val property in user control's view model to perform my logic.
and XAML code is follows
<local:NumericUpDown x:Name="ctlUpDown" Maximum="28" Minimum="-28" Step="0.25" TextValue="{Binding Path=FramePrice}"></local:NumericUpDown>
and XAML of user control
<StackPanel Margin="5" Orientation="Horizontal" VerticalAlignment="Center">
<TextBox x:Name="InputTextBox" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1"
Height="23" VerticalAlignment="Top"
Width="50" TextAlignment="Center"
KeyDown="InputTextBox_KeyDown"
KeyUp="InputTextBox_KeyUp"
GotFocus="InputTextBox_GotFocus"
LostFocus="InputTextBox_LostFocus"
MouseWheel="InputTextBox_MouseWheel"
MouseEnter="InputTextBox_MouseEnter"
Text="{Binding Path=TextValue, ElementName=ctlUpDown, Mode=TwoWay,ValidatesOnDataErrors=True,ValidatesOnExceptions=True,NotifyOnValidationError=True}"
/>
</StackPanel>
The first thing I noticed wrong about your code was the properties Maximum, Minimum, Step and TextValue. Here's the TextValue property:
public double TextValue
{
get
{
return (double)GetValue(TextValueProperty);
}
set
{
SetValue(TextValueProperty, value);
this.ViewModel.ControlValue = Convert.ToString(value);
}
}
Properties that are backed by a dependency property, such as the four I mentioned above, should ALWAYS look like the following:
public double TextValue
{
get { return (double)GetValue(TextValueProperty); }
set { SetValue(TextValueProperty, value); }
}
In other words, the getter should contain nothing more than a call to GetValue, and the setter should contain nothing more than a call to SetValue.
The reason for this is that when Silverlight changes the value of the TextValue dependency property, it won't do it by using the property above. The values of dependency properties are stored within the Silverlight dependency system, and when Silverlight wants to change the value of one of them, it goes directly to this dependency system. It doesn't call your code at all. Properties like that above are provided only for your convenience, giving you an easy way to access and change the value stored in the dependency property. They will never be called by anything other than your own code.
Generally, if you want a method to be called whenever a dependency property value changes, you need to pass a PropertyChangedCallback in the PropertyMetadata when registering the dependency property. However, I suspect that in your case you won't need to do that.
It seems to me that you have three properties:
the FramePrice property in your view-model class,
the TextValue dependency property of your NumericUpDown user control,
the Text dependency property of the TextBox within your NumericUpDown user control's XAML.
My impression is that you want the FramePrice property in your view-model to always have the same value as the Text property of the TextBox. To do that, you need to bind the FramePrice property to the NumericUpDown's TextValue property, and then bind that to the Text property of the TextBox.
To bind the first two of these properties together, there are a couple of things to change. Firstly, the TextValue property in your <local:NumericUpDown> element should look like
TextValue="{Binding Path=FramePrice}"
The binding {Binding ElementName=FramePrice, Path=DataContext.FramePrice} won't work, because there's no element in your XAML with the attribute x:Name="FramePrice". The value of an ElementName property in a {Binding ...} must match the x:Name of an object in the XAML.
You also need to set up the DataContext for your main page. If your main page view-model object has a zero-argument constructor, one way of doing this is to follow this answer.
To bind the second two properties together, I would:
add an x:Name attribute to the <UserControl> element of your NumericUpDown control (x:Name="ctlUpDown", say),
replace the Text property of the TextBox within your NumericUpDown control with the following:
Text="{Binding Path=TextValue, ElementName=ctlUpDown, Mode=TwoWay, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, NotifyOnValidationError=True}"/>
Once you've done that, you can then remove all of the lines this.ViewModel.SomeProperty = ... from your code-behind class. They're not necessary, and as I've already explained they won't be run when you wanted them to.
Finally, is there a reason you're not using the Silverlight Toolkit's NumericUpDown control?
EDIT 2: Against my better judgement I took a look at one of the two Silverlight projects you uploaded (I ignored the one with _2 in it). It bears very little resemblance to your question.
I can only assume you want the two textboxes (one of which is in a user control) to always have the same value. I was able to do this after making the following changes:
MainPageViewModel.cs: add ClearErrorFromProperty("DPropertyBind"); to the property setter. (Otherwise the validation error never gets cleared.)
MyUserControlWVM.xaml: removed reference to LostFocus event handler, added binding on Text property and added add x:Name attribute to the <UserControl> element. In other words, it now looks like the following:
<UserControl x:Class="DependencyPropertyBinding.MyUserControlWVM"
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"
x:Name="ctlWVM"
d:DesignHeight="300" d:DesignWidth="205">
<StackPanel Orientation="Horizontal" Width="204" Height="32">
<TextBox x:Name="textbox" Height="30" Width="200" Text="{Binding Path=DProperty, ElementName=ctlWVM, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" />
</StackPanel>
</UserControl>
MyUserControlWVM.xaml.cs: renamed dependency property DependencyPropertyValue to DPropertyProperty (the naming convention is that the static readonly field has the name of the property (in this case DProperty) with Property appended). I also removed the TextBox_LostFocus event handler.
If the code above is accurate you have spelt FramePrice as FranePrice in the binding
The output window should have shown this as a binding error when the page loaded.
it is currently
Binding ElementName=FranePrice, Path=DataContext.FranePrice
should be:
Binding ElementName=FramePrice, Path=DataContext.FramePrice
"With great binding capabilities comes great responsibility" :)
I'm trying to create a user control with dependency properties to bind to. Internally I have a ComboBox that is bound to these same properties, but the binding only works one way. The ComboBox fills from the ItemsSource, but SelectedItem doesn't get updated back to the viewmodel I'm binding to.
A simplified example:
This is the view model to bind with the user control:
public class PeopleViewModel : INotifyPropertyChanged
{
public PeopleViewModel()
{
People = new List<string>( new [] {"John", "Alfred","Dave"});
SelectedPerson = People.FirstOrDefault();
}
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<string> _people;
public IEnumerable<string> People
{
get { return _people; }
set
{
_people = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("People"));
}
}
}
private string _selectedPerson;
public string SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPerson"));
}
}
}
}
This is the User control:
<UserControl x:Class="PeopleControlTest.PeopleControl"
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="56" d:DesignWidth="637">
<StackPanel >
<ComboBox Margin="11"
ItemsSource="{Binding BoundPeople, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding BoundSelectedPerson, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</StackPanel>
with code behind
public partial class PeopleControl : UserControl
{
public PeopleControl()
{
InitializeComponent();
}
public static readonly DependencyProperty BoundPeopleProperty =
DependencyProperty.Register("BoundPeople", typeof(IEnumerable<string>), typeof(PeopleControl), new UIPropertyMetadata(null));
public static readonly DependencyProperty BoundSelectedPersonProperty =
DependencyProperty.Register("BoundSelectedPerson", typeof(string), typeof(PeopleControl), new UIPropertyMetadata(""));
public IEnumerable<string> BoundPeople
{
get { return (IEnumerable<string>)GetValue(BoundPeopleProperty); }
set { SetValue(BoundPeopleProperty, value); }
}
public string BoundSelectedPerson
{
get { return (string)GetValue(BoundSelectedPersonProperty); }
set { SetValue(BoundSelectedPersonProperty, value); }
}
}
And this is how I bind the user control in the main window (with the windows data context set to an instance of the viewmodel)
<Window x:Class="PeopleControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:PeopleControlTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<controls:PeopleControl
BoundPeople="{Binding People}"
BoundSelectedPerson="{Binding SelectedPerson}"/>
</Grid>
</Window>
The combobox in the user control fills with the names, but when I select a different name this doesn't get updated back to the view model. Any idea what I'm missing here?
Thanks!
Some properties bind two-way by default (Including SelectedItem) but your BoundSelectedPerson does not. You can set the Mode of the binding:
<controls:PeopleControl
BoundPeople="{Binding People}"
BoundSelectedPerson="{Binding SelectedPerson, Mode=TwoWay}"/>
Or you can make it TwoWay by default by setting a flag on the DependencyProperty:
public static readonly DependencyProperty BoundSelectedPersonProperty =
DependencyProperty.Register("BoundSelectedPerson", typeof(string), typeof(PeopleControl), new FrameworkPropertyMetadata("",FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
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!