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!
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 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
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...
I've created a custom control that extends the RichTextBox so that I can create a binding for the xaml property. It all works well as long as I just update the property from the viewmodel but when I try to edit in the richtextbox the property is not updated back.
I have the following code in the extended version of the richtextbox.
public static readonly DependencyProperty TextProperty = DependencyProperty.Register ("Text", typeof(string), typeof(BindableRichTextBox), new PropertyMetadata(OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rtb = d as BindableRichTextBox;
if (rtb == null)
return;
string xaml = null;
if (e.NewValue != null)
{
xaml = e.NewValue as string;
if (xaml == null)
return;
}
rtb.Xaml = xaml ?? string.Empty;
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
In the view I've set the binding like such
<Controls:BindableRichTextBox Text="{Binding XamlText, Mode=TwoWay}"/>
In the viewmodel I've created the XamlText as a normal property with the NotifyPropertyChanged event being called on updates.
I want the bound XamlText to be updated when the user enters texts in the RichTextBox either on lostfocus or directly during edit, it doesn't really matter.
How can I change the code to make this happen?
You will need to listen to changes to the Xaml-property of the BindableRichTextBox and set the Text-property accordingly. There is an answer available here describing how that could be achieved. Using the approach described in that would the result in the following code (untested):
public BindableRichTextBox()
{
this.RegisterForNotification("Xaml", this, (d,e) => ((BindableRichTextBox)d).Text = e.NewValue);
}
public void RegisterForNotification(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
var binding = new Binding(propertyName) { Source = element };
var property = DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(UserControl),
new PropertyMetadata(callback));
element.SetBinding(property, binding);
}
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).