Dependent DependencyProperty - wpf

I want to make a dependency property whose value depend on other dependency property but still settable/writable. For example, ‘DateOfBirth’ is an independent DependencyProperty. Now I want to make another DependencyProperty with name YearOfBirth. When DateOfBirth with Change it will Coerce the value of YearOfBirth property. i.e. d.CoerceValue(YearOfBirthProperty).
But How can I make dependent dependency property (e.g. YearOfBirth) writeable/settable?
If I code using CLR Properties, it works fine but how can do by using Dependency Properties!

What about this:
public partial class MainWindow : Window
{
public static DependencyProperty DateofBirthProperty = DependencyProperty.Register("DateofBirth", typeof(DateTime), typeof(MainWindow), new FrameworkPropertyMetadata(new PropertyChangedCallback(DateofBirth_Changed)));
public DateTime DateofBirth
{
get { return (DateTime)GetValue(DateofBirthProperty); }
set { SetValue(DateofBirthProperty, value); }
}
private static void DateofBirth_Changed(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
MainWindow thisClass = (MainWindow)o;
thisClass.SetDateofBirth();
}
private void SetDateofBirth()
{
DOBYear = DateofBirth.Year;
}
public static DependencyProperty DOBYearProperty = DependencyProperty.Register("DOBYear", typeof(int), typeof(MainWindow), new FrameworkPropertyMetadata(new PropertyChangedCallback(DOBYear_Changed)));
public int DOBYear
{
get { return (int)GetValue(DOBYearProperty); }
set { SetValue(DOBYearProperty, value); }
}
private static void DOBYear_Changed(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
MainWindow thisClass = (MainWindow)o;
thisClass.SetDOBYear();
}
private void SetDOBYear()
{
//Put Instance DOBYear Property Changed code here
}
public MainWindow()
{
InitializeComponent();
}
}

Related

WPF PropertyPath on Custom control

I have a problem, I create a control named Tile (like the Tile on Windows 10).
This tile simulate rotation 3D by using a projection class, like the projection class in Silverlight.
We have a base projection class like this :
abstract public class Projection
: FrameworkElement
{
static public readonly DependencyProperty RotationZProperty = DependencyProperty.Register(
nameof(RotationZ),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
static public readonly DependencyProperty RotationYProperty = DependencyProperty.Register(
nameof(RotationY),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
static public readonly DependencyProperty RotationXProperty = DependencyProperty.Register(
nameof(RotationX),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
public double RotationZ
{
get { return this.GetValue<double>(RotationZProperty); }
set { SetValue(RotationZProperty, value); }
}
public double RotationY
{
get { return this.GetValue<double>(RotationYProperty); }
set { SetValue(RotationYProperty, value); }
}
public double RotationX
{
get { return this.GetValue<double>(RotationXProperty); }
set { SetValue(RotationXProperty, value); }
}
public FrameworkElement Child
{
get;
set;
}
static private void OnRotationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Projection pl)
{
pl.OnRotationChanged();
}
}
private void OnRotationChanged()
{
// Some code
}}
After that, we have the PlaneProjectionClass :
[ContentProperty("Child")]
sealed public class PlaneProjection
: Projection
{
}
The Tile class use a dependency property of type Projection :
public class Tile
{
static public readonly DependencyProperty PlaneProjectionProperty = DependencyProperty.Register(
nameof(Projection),
typeof(Projection),
typeof(Tile),
new UIPropertyMetadata());
public Projection Projection
{
get { return (Projection)GetValue(PlaneProjectionProperty); }
private set { SetValue(PlaneProjectionProperty, value); }
}
override public void OnApplyTemplate()
{
Projection = GetTemplateChild("PART_Projection") as Projection;
}
}
So for the XAML, we have this in ControlTemplate :
<controls:PlaneProjection x:Name="PART_PlaneProjection">
<Border>
<Grid>
<!-- Some design -->
</Grid>
</Border>
</controls:PlaneProjection>
Now I would like to animate the planeprojection.
So I create the storyboard and animate the projection with rotationX :
static public void CreateAnimation(Tile tile)
{
Storyboard.SetTarget(anim, tile);
Storyboard.SetTargetProperty(doubleAnim, new PropertyPath("(Tile.Projection).(PlaneProjection.RotationX"));
}
But at debug, I have this error : Cannot resolve all references of the property on the path of the property '(Tile.Projection).(PlaneProjection.RotationX)
I don't understand the mistake :( Any ideas on using PropertyPath on custom control ?
The Projection property in class Tile does not follow the naming conventions for dependency properties.
It should e.g. look like this:
public static readonly DependencyProperty PlaneProjectionProperty =
DependencyProperty.Register(nameof(PlaneProjection), typeof(Projection), typeof(Tile));
public Projection PlaneProjection
{
get { return (Projection)GetValue(PlaneProjectionProperty); }
private set { SetValue(PlaneProjectionProperty, value); }
}
The property path would simply be this:
Storyboard.SetTargetProperty(anim, new PropertyPath("PlaneProjection.RotationX"));
You wouldn't even need a Storyboard. Just call
tile.PlaneProjection.BeginAnimation(Projection.RotationXProperty, anim);
As a note, a private setter does not make the dependency property read-only. See Read-Only Dependency Properties for details.

How can I fix the DependencyPropertyDescriptor AddValueChanged Memory Leak on AttachedBehavior?

I know I need to call RemoveValueChanged, but I have not been able to find a reliable place to call this. I'm learning that there probably isn't one.
I looks like I need to find a different way to monitor the change then adding a handler using AddValueChanged. I'm looking for advice on the best way to achieve this. I've seen the recommendation of using a PropertyChangedCallback in the PropertyMetadata, but I'm not sure how to do this when my TextBox and Adorner are not static. Also, the IsFocused property is not a DependencyProperty created in my class.
public sealed class WatermarkTextBoxBehavior
{
private readonly TextBox m_TextBox;
private TextBlockAdorner m_TextBlockAdorner;
private WatermarkTextBoxBehavior(TextBox textBox)
{
if (textBox == null)
throw new ArgumentNullException("textBox");
m_TextBox = textBox;
}
#region Behavior Internals
private static WatermarkTextBoxBehavior GetWatermarkTextBoxBehavior(DependencyObject obj)
{
return (WatermarkTextBoxBehavior)obj.GetValue(WatermarkTextBoxBehaviorProperty);
}
private static void SetWatermarkTextBoxBehavior(DependencyObject obj, WatermarkTextBoxBehavior value)
{
obj.SetValue(WatermarkTextBoxBehaviorProperty, value);
}
private static readonly DependencyProperty WatermarkTextBoxBehaviorProperty =
DependencyProperty.RegisterAttached("WatermarkTextBoxBehavior",
typeof(WatermarkTextBoxBehavior), typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(null));
public static bool GetEnableWatermark(TextBox obj)
{
return (bool)obj.GetValue(EnableWatermarkProperty);
}
public static void SetEnableWatermark(TextBox obj, bool value)
{
obj.SetValue(EnableWatermarkProperty, value);
}
public static readonly DependencyProperty EnableWatermarkProperty =
DependencyProperty.RegisterAttached("EnableWatermark", typeof(bool),
typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(false, OnEnableWatermarkChanged));
private static void OnEnableWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
var enabled = (bool)e.OldValue;
if (enabled)
{
var textBox = (TextBox)d;
var behavior = GetWatermarkTextBoxBehavior(textBox);
behavior.Detach();
SetWatermarkTextBoxBehavior(textBox, null);
}
}
if (e.NewValue != null)
{
var enabled = (bool)e.NewValue;
if (enabled)
{
var textBox = (TextBox)d;
var behavior = new WatermarkTextBoxBehavior(textBox);
behavior.Attach();
SetWatermarkTextBoxBehavior(textBox, behavior);
}
}
}
private void Attach()
{
m_TextBox.Loaded += TextBoxLoaded;
m_TextBox.TextChanged += TextBoxTextChanged;
m_TextBox.DragEnter += TextBoxDragEnter;
m_TextBox.DragLeave += TextBoxDragLeave;
m_TextBox.IsVisibleChanged += TextBoxIsVisibleChanged;
}
private void Detach()
{
m_TextBox.Loaded -= TextBoxLoaded;
m_TextBox.TextChanged -= TextBoxTextChanged;
m_TextBox.DragEnter -= TextBoxDragEnter;
m_TextBox.DragLeave -= TextBoxDragLeave;
m_TextBox.IsVisibleChanged -= TextBoxIsVisibleChanged;
}
private void TextBoxDragLeave(object sender, DragEventArgs e)
{
UpdateAdorner();
}
private void TextBoxDragEnter(object sender, DragEventArgs e)
{
m_TextBox.TryRemoveAdorners<TextBlockAdorner>();
}
private void TextBoxIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
UpdateAdorner();
}
private void TextBoxTextChanged(object sender, TextChangedEventArgs e)
{
var hasText = !string.IsNullOrEmpty(m_TextBox.Text);
SetHasText(m_TextBox, hasText);
}
private void TextBoxLoaded(object sender, RoutedEventArgs e)
{
Init();
}
#endregion
#region Attached Properties
public static string GetLabel(TextBox obj)
{
return (string)obj.GetValue(LabelProperty);
}
public static void SetLabel(TextBox obj, string value)
{
obj.SetValue(LabelProperty, value);
}
public static readonly DependencyProperty LabelProperty =
DependencyProperty.RegisterAttached("Label", typeof(string), typeof(WatermarkTextBoxBehavior));
public static Style GetLabelStyle(TextBox obj)
{
return (Style)obj.GetValue(LabelStyleProperty);
}
public static void SetLabelStyle(TextBox obj, Style value)
{
obj.SetValue(LabelStyleProperty, value);
}
public static readonly DependencyProperty LabelStyleProperty =
DependencyProperty.RegisterAttached("LabelStyle", typeof(Style),
typeof(WatermarkTextBoxBehavior));
public static bool GetHasText(TextBox obj)
{
return (bool)obj.GetValue(HasTextProperty);
}
private static void SetHasText(TextBox obj, bool value)
{
obj.SetValue(HasTextPropertyKey, value);
}
private static readonly DependencyPropertyKey HasTextPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("HasText", typeof(bool),
typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(false));
public static readonly DependencyProperty HasTextProperty =
HasTextPropertyKey.DependencyProperty;
#endregion
private void Init()
{
m_TextBlockAdorner = new TextBlockAdorner(m_TextBox, GetLabel(m_TextBox), GetLabelStyle(m_TextBox));
UpdateAdorner();
DependencyPropertyDescriptor focusProp = DependencyPropertyDescriptor.FromProperty(UIElement.IsFocusedProperty, typeof(FrameworkElement));
if (focusProp != null)
{
focusProp.AddValueChanged(m_TextBox, (sender, args) => UpdateAdorner());
}
DependencyPropertyDescriptor containsTextProp = DependencyPropertyDescriptor.FromProperty(HasTextProperty, typeof(TextBox));
if (containsTextProp != null)
{
containsTextProp.AddValueChanged(m_TextBox, (sender, args) => UpdateAdorner());
}
}
private void UpdateAdorner()
{
if (GetHasText(m_TextBox) ||
m_TextBox.IsFocused ||
!m_TextBox.IsVisible)
{
// Hide the Watermark Label if the adorner layer is visible
m_TextBox.ToolTip = GetLabel(m_TextBox);
m_TextBox.TryRemoveAdorners<TextBlockAdorner>();
}
else
{
// Show the Watermark Label if the adorner layer is visible
m_TextBox.ToolTip = null;
m_TextBox.TryAddAdorner<TextBlockAdorner>(m_TextBlockAdorner);
}
}
}
AddValueChanged of dependency property descriptor results in memory leak as you already know. So, as described here, you can create custom class PropertyChangeNotifier to listen to any dependency property changes.
Complete implementation can be found here - PropertyDescriptor AddValueChanged Alternative.
Quote from the link:
This class takes advantage of the fact that bindings use weak
references to manage associations so the class will not root the
object who property changes it is watching. It also uses a
WeakReference to maintain a reference to the object whose property it
is watching without rooting that object. In this way, you can maintain
a collection of these objects so that you can unhook the property
change later without worrying about that collection rooting the object
whose values you are watching.
Also for sake of completeness of answer I am posting complete code here to avoid any rot issue in future.
public sealed class PropertyChangeNotifier : DependencyObject, IDisposable
{
#region Member Variables
private readonly WeakReference _propertySource;
#endregion // Member Variables
#region Constructor
public PropertyChangeNotifier(DependencyObject propertySource, string path)
: this(propertySource, new PropertyPath(path))
{
}
public PropertyChangeNotifier(DependencyObject propertySource, DependencyProperty property)
: this(propertySource, new PropertyPath(property))
{
}
public PropertyChangeNotifier(DependencyObject propertySource, PropertyPath property)
{
if (null == propertySource)
throw new ArgumentNullException("propertySource");
if (null == property)
throw new ArgumentNullException("property");
_propertySource = new WeakReference(propertySource);
Binding binding = new Binding
{
Path = property,
Mode = BindingMode.OneWay,
Source = propertySource
};
BindingOperations.SetBinding(this, ValueProperty, binding);
}
#endregion // Constructor
#region PropertySource
public DependencyObject PropertySource
{
get
{
try
{
// note, it is possible that accessing the target property
// will result in an exception so i’ve wrapped this check
// in a try catch
return _propertySource.IsAlive
? _propertySource.Target as DependencyObject
: null;
}
catch
{
return null;
}
}
}
#endregion // PropertySource
#region Value
/// <summary>
/// Identifies the <see cref="Value"/> dependency property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
typeof(object), typeof(PropertyChangeNotifier), new FrameworkPropertyMetadata(null, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PropertyChangeNotifier notifier = (PropertyChangeNotifier)d;
if (null != notifier.ValueChanged)
notifier.ValueChanged(notifier, EventArgs.Empty);
}
/// <summary>
/// Returns/sets the value of the property
/// </summary>
/// <seealso cref="ValueProperty"/>
[Description("Returns/sets the value of the property")]
[Category("Behavior")]
[Bindable(true)]
public object Value
{
get
{
return GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
#endregion //Value
#region Events
public event EventHandler ValueChanged;
#endregion // Events
#region IDisposable Members
public void Dispose()
{
BindingOperations.ClearBinding(this, ValueProperty);
}
#endregion
}
A more lightweight solution for FrameworkElements and FrameworkContentElements is to subscribe to the Unloaded event and remove the handler. This requires a non-anonymous delegate (UpdateAdorner in that case) though:
focusProp.AddValueChanged(m_TextBox, UpdateAdorner);
m_TextBox.Unloaded += (sender, args) => focusProp.RemoveValueChanged(sender, UpdateAdorner);

wpf twoway bound DependencyProperty setcurrentvalue not working

I'm working on a custom behavior for the visiblox chart. This custom behavior has a dependency property Value that identifies the position of a cursor that consists of vertical line draw in the chart. This cursor follows the mouse if I set the property FollowMouse to true.
If I bind the Value property the changedvaluecallback only gets 0 as the newValue, while if the value is not bound it works properly. But if i change the source property of the binding (property on ViewModel) it works too. So the problem is setting the value with SetCurrentValue on PointerMoved.
Here is the source code of the behavior:
public class TimeCursorBehavior : BehaviourWithAxesBase
{
private System.Windows.Shapes.Line _line;
public TimeCursorBehavior()
: base("TimeCursor")
{
_line = new System.Windows.Shapes.Line();
_line.Stroke = System.Windows.Media.Brushes.Black;
_line.StrokeThickness = 2;
}
public override void DeInit()
{
base.DeInit();
Chart.BehaviourCanvas.Children.Remove(_line);
}
protected override void Init()
{
base.Init();
Chart.BehaviourCanvas.Children.Add(_line);
}
public override void PointerMoved(IBehaviourEventSource sender, PointerEventContext context)
{
base.PointerMoved(sender, context);
if (!FollowMouse)
return;
IComparable xDataValue = XAxis.GetRenderPositionAsDataValueWithZoom(context.Point.X);
SetCurrentValue(ValueProperty, xDataValue);
}
public override void BehaviourCanvasSizeChanged(IBehaviourEventSource sender, SizeChangedEventArgs e)
{
base.BehaviourCanvasSizeChanged(sender, e);
_line.Y2 = e.NewSize.Height;
}
#region Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(IComparable), typeof(TimeCursorBehavior), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
(sender as TimeCursorBehavior).OnValueChanged(args.OldValue as IComparable, args.NewValue as IComparable);
}
private void OnValueChanged(IComparable oldValue, IComparable newValue)
{
if (XAxis == null)
return;
double x = XAxis.GetDataValueAsRenderPositionWithZoom(newValue);
_line.X1 = x;
_line.X2 = x;
}
public IComparable Value
{
get
{
return GetValue(ValueProperty) as IComparable;
}
set
{
SetValue(ValueProperty, value);
}
}
#endregion
#region FollowMouse
public static readonly DependencyProperty FollowMouseProperty = DependencyProperty.Register("FollowMouse", typeof(bool), typeof(TimeCursorBehavior), new PropertyMetadata(false));
public bool FollowMouse
{
get
{
return (bool)GetValue(FollowMouseProperty);
}
set
{
SetValue(FollowMouseProperty, value);
}
}
#endregion
}
Does anyone know why setcurrentvalue is not updating the value accordingly?
Found the problem.
My property in the ViewModel is an decimal, and the property returned by the line below is a double.
IComparable xDataValue = XAxis.GetRenderPositionAsDataValueWithZoom(context.Point.X);
I added an converter to the binding it everything worked as expected.

Scenario with dependency properties-how to access each other

I have a two dependency properties(both List of strings) in a custom user control.The binding for one of these dependency properties can be changed several times for the life of the application. I need to do some action in the user control when the binding is changed, and I need to access all the dependency properties in the class for doing the action.
For example,
public class UC:UserControl
{
public List<string> AvailableItems
{
get { return (List<string>)this.GetValue(AvailableItemsProperty); }
set { this.SetValue(AvailableItemsProperty, value); }
}
public static readonly DependencyProperty AvailableItemsProperty = DependencyProperty.Register(
"AvailableItems", typeof(List<string>), typeof(ItemSelectionUserControl), new FrameworkPropertyMetadata(OnAvailableItemsChanged) { BindsTwoWayByDefault = true });
public List<string> SelectedItems
{
get { return (List<string>)this.GetValue(SelectedItemsProperty); }
set { this.SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems", typeof(List<string>), typeof(ItemSelectionUserControl), new FrameworkPropertyMetadata { BindsTwoWayByDefault = true });
public static void OnAvailableItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
//How to access SelectedItems here??
}
}
The trouble is the the callback when dependency property changed should be static, so how can I access the non static dependency property wrapper in the function?? Or is there any other way to do this??
Use the following:
public static void OnAvailableItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UC uc = sender as UC;
List<string> selectedItems = uc.SelectedItems;
}

How do I create a public event for a dependency property?

In the code below you can see what I'm trying to do, but it doesn't work. I want an event that I can attach to outside of my user control and fires whenever the dependency property changes.
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value"
, typeof(Double)
, typeof(ucSlider)
, new PropertyMetadata(50d, new PropertyChangedCallback(OnValueChanged)));
public Double Value
{
get { return (Double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public event PropertyChangedCallback OnValueChanged;
Dependency properties are static, but your event is related with the instance of the class. So you need an intermediate method between the static property and the event of instance.
In this example I pass the static method OnValuePropertyChanged as a callback parameter and inside it I raise the event:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value"
, typeof(Double)
, typeof(ucSlider)
, new PropertyMetadata(50d, new PropertyChangedCallback(OnValuePropertyChanged)));
public Double Value
{
get { return (Double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var sl = sender as ucSlider;
if (sl != null)
sl.RaiseValueChangedEvent(e);
}
private void RaiseValueChangedEvent(DependencyPropertyChangedEventArgs e)
{
if(this.OnValueChanged != null)
this.OnValueChanged(this, e);
}
public event PropertyChangedCallback OnValueChanged;

Resources