In the following example of my custom control, why I can't change a TransparentColor property in a setter of a SelectedColor property? A solution with callback method works fine, what's the difference between them in the case of change another property?
public class MyColorPicker : Control
{
static MyColorPicker()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyColorPicker), new FrameworkPropertyMetadata(typeof(MyColorPicker)));
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register("SelectedColor", typeof(Color), typeof(MyColorPicker), new PropertyMetadata(Color.FromRgb(0, 201, 201), OnSelectedColorChange));
private static void OnSelectedColorChange(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var colorPicker = (MyColorPicker)sender;
var selectedColor = colorPicker.SelectedColor;
colorPicker.TransparentColor = Color.FromArgb(0, selectedColor.R, selectedColor.G, selectedColor.B);
}
public Color SelectedColor
{
get { return (Color)GetValue(SelectedColorProperty); }
set
{
// Why this not working? BorderStopColor = Color.FromArgb(0, value.R, value.G, value.B);
SetValue(SelectedColorProperty, value);
}
}
private static readonly DependencyPropertyKey TransparentColorPropertyKey =
DependencyProperty.RegisterReadOnly("TransparentColor", typeof(Color), typeof(MyColorPicker), new PropertyMetadata(Color.FromArgb(0, 0, 201, 201)));
public static readonly DependencyProperty TransparentColorProperty = TransparentColorPropertyKey.DependencyProperty;
public Color TransparentColor
{
get { return (Color)GetValue(TransparentColorProperty); }
protected set { SetValue(TransparentColorPropertyKey, value); }
}
}
The setter of a CLR wrapper for a dependency property should only call the SetValue method to set the actual value of the dependency property.
Any other logic should be implemented in the callback.
Also note that the CLR property wrappers are bypassed at runtime when setting dependency properties in XAML (but the callbacks are not):
Why are .NET property wrappers bypassed at runtime when setting dependency properties in XAML?
Related
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...
I was creating a control for my WP8 app. I am trying to set value for dependency property irrespective of its default value. Below is my code
public BitmapImage EnabledImage { get; set; }
public BitmapImage DisabledImage { get; set; }
public bool ControlEnabled
{
get { return (bool)GetValue(ControlEnabledProperty); }
set { SetValue(ControlEnabledProperty, value); }
}
public static readonly DependencyProperty ControlEnabledProperty =
DependencyProperty.Register("ControlEnabled", typeof(bool), typeof(ucControl), new PropertyMetadata(OnImageStatePropertyChanged));
private static void OnImageStatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (d as ucControl);
control.ControlEnabled = Convert.ToBoolean(e.NewValue);
control.OnImageStateChanged(e.NewValue);
}
private void OnImageStateChanged(object newValue)
{
if (Convert.ToBoolean(newValue) == true)
imgControl.Source = EnabledImage;
else
imgControl.Source = DisabledImage;
}
this how i am calling it in xaml
<WP8:ucControl Grid.Row="0" Grid.Column="0" Height="92" Width="92" EnabledImage="/Images/img_on.png" DisabledImage="/Images/img_off.png" ControlEnabled="True"/>
it does not set the value when I set ControlEnabled = "False". Means disabled image is not set on image control.
I want this control to set property irrespective of it default value.
I refer this post as well but solution not working : Windows Phone 8, use DependencyProperty for a usercontrol, PropertyChangedCallback and CoerceValueCallback issue
Any Ideas what wrong here.
you can modify your code to this, and try it:
public static readonly DependencyProperty ControlEnabledProperty =
DependencyProperty.Register("ControlEnabled", typeof(bool?), typeof(ucControl), new PropertyMetadata(null, OnImageStatePropertyChanged));
I have class that is derived from FrameworkElement, and I want WPF to update its Location property by using DoubleAnimation. I register the property as DependendencyProperty:
public class TimeCursor : FrameworkElement
{
public static readonly DependencyProperty LocationProperty;
public double Location
{
get { return (double)GetValue(LocationProperty); }
set
{
SetValue(LocationProperty, value);
}
}
static TimeCursor()
{
LocationProperty = DependencyProperty.Register("Location", typeof(double), typeof(TimeCursor));
}
}
Following code sets up the storyboard.
TimeCursor timeCursor;
private void SetCursorAnimation()
{
timeCursor = new TimeCursor();
NameScope.SetNameScope(this, new NameScope());
RegisterName("TimeCursor", timeCursor);
storyboard.Children.Clear();
DoubleAnimation animation = new DoubleAnimation(LeftOffset, LeftOffset + (VerticalLineCount - 1) * HorizontalGap + VerticalLineThickness,
new Duration(TimeSpan.FromMilliseconds(musicDuration)), FillBehavior.HoldEnd);
Storyboard.SetTargetName(animation, "TimeCursor");
Storyboard.SetTargetProperty(animation, new PropertyPath(TimeCursor.LocationProperty));
storyboard.Children.Add(animation);
}
Then I call storyboard.Begin(this) from another method of the object which contains the above SetCursorAnimation() method and this object is derived from Canvas. However the Location property is never updated(set accessor of Location is never called) and no exception is thrown. What am I doing wrong?
When a dependency property is animated (or set in XAML, or set by a Style Setter, etc.), WPF does not call the CLR wrapper, but instead directly accesses the underlying DependencyObject and DependencyProperty objects. See the Implementing the "Wrapper" section in Checklist for Defining a Dependency Property and also Implications for Custom Dependency Properties.
In order to get notified about property changes, you have to register a PropertyChangedCallback:
public class TimeCursor : FrameworkElement
{
public static readonly DependencyProperty LocationProperty =
DependencyProperty.Register(
"Location", typeof(double), typeof(TimeCursor),
new FrameworkPropertyMetadata(LocationPropertyChanged)); // register callback here
public double Location
{
get { return (double)GetValue(LocationProperty); }
set { SetValue(LocationProperty, value); }
}
private static void LocationPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var timeCursor = obj as TimeCursor;
// handle Location property changes here
...
}
}
And note also that animating a dependency property does not necessarily require a Storyboard. You could simply call the BeginAnimation method on your TimeCursor instance:
var animation = new DoubleAnimation(LeftOffset,
LeftOffset + (VerticalLineCount - 1) * HorizontalGap + VerticalLineThickness,
new Duration(TimeSpan.FromMilliseconds(musicDuration)),
FillBehavior.HoldEnd);
timeCursor.BeginAnimation(TimeCursor.LocationProperty, animation);
I have a read-only dependency property which is shared between two WPF classes. So I made this property shared property. While binding this property on XAML, It takes its default value and can't be updated. But If I use the main owner of this property, its value can be updated.
So what is your advice?
public class A
{
private static readonly DependencyPropertyKey XPropertyKey =
DependencyProperty.RegisterReadOnly("X",
typeof(Point), typeof(A),
new FrameworkPropertyMetadata(new Point(0, 0),
FrameworkPropertyMetadataOptions.Inherits |
FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty XProperty =
RelativeMousePointPropertyKey.DependencyProperty;
public Point X
{
get { return (Point)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public void SetRelativeMousePoint(Point point)
{
SetValue(XPropertyKey, point);
}
}
public class B
{
//I use this class for binding
public static readonly DependencyProperty XProperty =
A.XProperty.AddOwner(typeof(B));
public Point X
{
get { return (Point)GetValue(X); }
}
}