I have Custom User Control which uses MVVM :INotifyPropertyChanged. My app also uses MVVM and INotifyPropertyChanged, but when I try to bind value to custom user control, app fails.
"Exception Failed to assign to property '%0'."
I need to use TwoWay Binding. Thank you for help.
Custom User Control
private double _value = 0;
public double Value
{
get
{
return _value;
}
set
{
_value = value;
RaisePropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Binding to element:
private double _startMinutes;
public double StartMinutes
{
get
{
return _startMinutes;
}
set
{
_startMinutes = value;
RaisePropertyChanged("StartMinutes");
}
}
<controls:RadialSlider
Value="{Binding StartMinutes}"
/>
You can't just bind to a normal property.
Binding is done through a DependencyProperty. The quickest way to make one is through the propdp snippet. They look like:
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(RadialSlider), new PropertyMetadata(0));
Now your binding will work as expected. See MSDN for more
Related
I have a property for my custom control.
Now I want to create a ValueChanged event for my property. (I don't need callback for my property as I want user can use this event)
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(double), typeof(SpeedoMeter),
new PropertyMetadata(0.0, null, OnCoerceValueChanged));
Define an event as usual (it may be defined as routed event if it makes sense) and then register a dependency property callback where you raise this event. Something like this:
public event EventHandler ValueChanged;
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(SpeedoMeter), new PropertyMetadata(0.0,
OnChanged,
OnCoerceValueChanged));
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SpeedoMeter speedoMeter = (SpeedoMeter)d;
EventHandler handler = speedoMeter.ValueChanged;
if (handler != null)
{
handler(speedoMeter, EventArgs.Empty);
}
}
Obviously you may name your event and property whatever you want. If you have a Value property and want a ValueChanged event, you may for example derive from something like RangeBase or similar. This is just an example of how you would raise a custom event when a dependency property changes.
I created a custom UserControl. Following a blog post my control code-behind looks like this:
public BasicGeoposition PinGeoposition
{
get { return (BasicGeoposition) GetValue(PropertyPinGeoposition); }
set { SetValueDp(PropertyPinGeoposition, value);}
}
public static readonly DependencyProperty PropertyPinGeoposition =
DependencyProperty.Register("PinGeoposition", typeof(BasicGeoposition), typeof(CustomMapControl), null);
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value, [System.Runtime.CompilerServices.CallerMemberName] String p = null)
{
ViewModel.SetMode(ECustomMapControlMode.Default);
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
Use of my control:
<customControls:CustomMapControl Mode="ForImage" PinGeoposition="{Binding Geoposition, Mode=TwoWay}" Grid.Row="1"/>
Finally in the pages' ViewModel where I use my control I have:
public BasicGeoposition Geoposition
{
get { return _geoposition; }
set
{
if (Set(ref _geoposition, value))
{
RaisePropertyChanged(() => Geoposition);
}
}
}
I expect that every change of Geoposition in ViewModel to be reflected in SetValueDp. Unfortunately, it doesn't work.
Not sure what Jerry Nixon was trying to do on his blog article, as he didn't assign his SetValueDp method anywhere.
If you want it to be called, you can do something like that:
public static readonly DependencyProperty PropertyPinGeoposition =
DependencyProperty.Register("PinGeoposition", typeof(BasicGeoposition), typeof(CustomMapControl), new PropertyMetadata(null, SetPosition));
public BasicGeoposition PinGeoposition
{
get { return (BasicGeoposition) GetValue(PropertyPinGeoposition); }
set { SetValue(PropertyPinGeoposition, value);}
}
private static void SetPosition(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = (CustomMapControl)sender;
var position = e.NewValue as BasicGeoposition;
// Do whatever
}
Edit: After reading and re-reading the blog article, I think I got it backwards (and so did you, probably). From what I now understand, SetValueDp is a helper method you're supposed to call whenever you want to change the value of the dependency property. This is not something called automatically. So if you want a method that is called whenever the DP is modified, check my solution instead.
I have a custom control with a label on it. This control has a property Label like this:
public string Label
{
get { return (string)GetValue(LabelProperty); }
set
{
label.Text = value;
SetValue(LabelProperty, value);
}
}
public static DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(SuperButton), new PropertyMetadata(null));
Note, label with small l is an internal textblock. SuperButton is the name of the control.
Then I have this simple object:
class Student : INotifyPropertyChanged
{
public string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged( new PropertyChangedEventArgs("Name")); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
So then I bind with this XAML:
<UIFragments:SuperButton Margin="531,354,555,367" Label="{Binding Name}"></UIFragments:SuperButton>
And then I have this in the same page as the button instance
Student s = new Student { Name = "John Smith" };
DataContext = s;
I have tried setting the control's datacontext to itself but nothing is working. Setting the Label to a string works.
If I use the data binding the set{} block is never fired...
XAML doesn't call your Setter method, as is pointed out at MSDN:
The WPF XAML processor uses property system methods for dependency
properties when loading binary XAML and processing attributes that are
dependency properties. This effectively bypasses the property
wrappers. When you implement custom dependency properties, you must
account for this behavior and should avoid placing any other code in
your property wrapper other than the property system methods GetValue
and SetValue.
What you need to do is register a callback method that fires whenever the dependency property changes:
public static DependencyProperty LabelProperty = DependencyProperty.Register(
"Label",
typeof(string),
typeof(SuperButton),
new PropertyMetadata(null, PropertyChangedCallback)
);
Note the PropertyChangedCallback in the last line! This method is implemented as follows:
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
SuperButton userControl = ((SuperButton)dependencyObject);
userControl.label.Text = (string) args.NewValue;
}
The dependency property's getter and setter now can be reduced to:
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
Now whenever the Label property is changed, e.g. through binding it in your page, PropertyChangedCallback is called and that passes the text to your actual label!
I have created a custom TextEditor control that inherits from AvalonEdit. I have done this to facilitate the use of MVVM and Caliburn Micro using this editor control. The [cut down for display purposes] MvvTextEditor class is
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public MvvmTextEditor()
{
TextArea.SelectionChanged += TextArea_SelectionChanged;
}
void TextArea_SelectionChanged(object sender, EventArgs e)
{
this.SelectionStart = SelectionStart;
this.SelectionLength = SelectionLength;
}
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.SelectionLength = (int)args.NewValue;
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
set { SetValue(SelectionLengthProperty, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Now, in the view that holds this control, I have the following XAML:
<Controls:MvvmTextEditor
Caliburn:Message.Attach="[Event TextChanged] = [Action DocumentChanged()]"
TextLocation="{Binding TextLocation, Mode=TwoWay}"
SyntaxHighlighting="{Binding HighlightingDefinition}"
SelectionLength="{Binding SelectionLength,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
Document="{Binding Document, Mode=TwoWay}"/>
My issue is SelectionLength (and SelectionStart but let us just consider the length for now as the problem is the same). If I selected something with the mouse, the binding from the View to my View Model works great. Now, I have written a find and replace utility and I want to set the SelectionLength (which has get and set available in the TextEditor control) from the code behind. In my View Model I am simply setting SelectionLength = 50, I implement this in the View Model like
private int selectionLength;
public int SelectionLength
{
get { return selectionLength; }
set
{
if (selectionLength == value)
return;
selectionLength = value;
Console.WriteLine(String.Format("Selection Length = {0}", selectionLength));
NotifyOfPropertyChange(() => SelectionLength);
}
}
when I set SelectionLength = 50, the DependencyProperty SelectionLengthProperty does not get updated in the MvvmTextEditor class, it is like the TwoWay binding to my control is failing but using Snoop there is no sign of this. I thought this would just work via the binding, but this does not seem to be the case.
Is there something simple I am missing, or will I have to set up and event handler in the MvvmTextEditor class which listens for changes in my View Model and updated the DP itself [which presents it's own problems]?
Thanks for your time.
This is because the Getter and Setter from a DependencyProperty is only a .NET Wrapper. The Framework will use the GetValue and SetValue itself.
What you can try is to access the PropertyChangedCallback from your DependencyProperty and there set the correct Value.
public int SelectionLength
{
get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectionLength. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor), new PropertyMetadata(0,SelectionLengthPropertyChanged));
private static void SelectionLengthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textEditor = obj as MvvmTextEditor;
textEditor.SelectionLength = e.NewValue;
}
Here is another answer if you are still open. Since SelectionLength is already defined as a dependency property on the base class, rather than create a derived class (or add an already existing property to the derived class), I would use an attached property to achieve the same functionality.
The key is to use System.ComponentModel.DependencyPropertyDescriptor to subscribe to the change event of the already existing SelectionLength dependency property and then take your desired action in the event handler.
Sample code below:
public class SomeBehavior
{
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(SomeBehavior), new PropertyMetadata(OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject dpo, bool value)
{
dpo.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsEnabledProperty);
}
private static void OnIsEnabledChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var editor = dpo as TextEditor;
if (editor == null)
return;
var dpDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(TextEditor.SelectionLengthProperty,editor.GetType());
dpDescriptor.AddValueChanged(editor, OnSelectionLengthChanged);
}
private static void OnSelectionLengthChanged(object sender, EventArgs e)
{
var editor = (TextEditor)sender;
editor.Select(editor.SelectionStart, editor.SelectionLength);
}
}
Xaml below:
<Controls:TextEditor Behaviors:SomeBehavior.IsEnabled="True">
</Controls:TextEditor>
This is how I did this...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
if (target.SelectionLength != (int)args.NewValue)
{
target.SelectionLength = (int)args.NewValue;
target.Select(target.SelectionStart, (int)args.NewValue);
}
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
//get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
Sorry for any time wasted. I hope this helps someone else...
Suppose I have the following code in a WPF UserControl. I want to bind to Asset.ChildProperty. It doesn't work presently because I don't get notifications when the Asset property changes. How do I arrange things so that notifications trigger for the Asset property whenever AssetID changes?
public static readonly DependencyProperty AssetIdProperty = DependencyProperty.Register("AssetId", typeof(string), typeof(GaugeBaseControl));
[Browsable(false), DataMember]
public string AssetId
{
get { return (string)GetValue(AssetIdProperty); }
set { SetValue(AssetIdProperty, value); }
}
[DisplayName("Asset Item"), Category("Value Binding")]
public AssetViewModel Asset
{
get { return Manager.Models.FirstOrDefault(m => m.Model.UniqueId == AssetId); }
set
{
if (value == null)
AssetId = string.Empty;
else
AssetId = value.Model.UniqueId;
}
}
You can specify a callback method in the PropertyMetadata of the DependencyProperty to be called when the value of a DependencyProperty changes and raise a PropertyChanged event from that callback method.
public class MyClass : DependencyObject, INotifyPropertyChanged
{
public MyClass ()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public bool State
{
get { return (bool)this.GetValue(StateProperty); }
set { this.SetValue(StateProperty, value); }
}
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register(
"State",
typeof(bool),
typeof(MyClass),
new PropertyMetadata(
false, // Default value
new PropertyChangedCallback(OnDependencyPropertyChange)));
private static void OnDependencyPropertyChange(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(d,
new PropertyChangedEventArgs(e.Property.Name);
}
}
}
If you raising PropertyChanged events from the State property's setter then they will not fire when the property is bound because bindings invoke StateProperty directly, not State.
Implement INotifyPropertyChanged and raise the PropertyChanged event when Asset changes (in the setter method).