How to dissable DependencyProperty callback - wpf

How to disable calling callback methods in MyDP1_Changed and MyDP1_Changed?
I need dissable callback when I change value from other callback method
class MyDependencyObject : DependencyObject
{
public object MyDP1 {
get{ return GetValue(MyDP1Property); }
set{ return SetValue(MyDP1Property, value); }
}
public object MyDP2 {
get{ return GetValue(MyDP2Property); }
set{ return SetValue(MyDP2Property, value); }
}
public static readonly DependencyProperty MyDP1Property =
DependencyProperty.Register("MyDP1", typeof(object), typeof(MyDependencyObject )
, new UIPropertyMetadata(MyDP1_Changed));
public static readonly DependencyProperty MyDP2Property =
DependencyProperty.Register("MyDP2", typeof(object), typeof(MyDependencyObject )
, new UIPropertyMetadata(MyDP2_Changed));
private static void MyDP1_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetValue(MyDP2Property, e.NewValue);
}
private static void MyDP2_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetValue(MyDP1Property, e.OldValue);
}
}

OK firstly, you need to use SetCurrentValue not SetValue - otherwise you'll blow your bindings.
Secondly, you can use these methods to temporarily add and remove callbacks
TypeDescriptor.GetProperties(d)["MyDP1"].RemoveValueChanged(d, MyDP1_Changed)
TypeDescriptor.GetProperties(d)["MyDP1"].AddValueChanged(d, MyDP1_Changed)

Related

WPF - Access DependencyObject inside a CollectionChanged event

I have an user control that combines the values of 2 DepedenciyProperties:
int numberPeople and ingredients Lists<>
I want when either of these values updates to redo the combination.
My current implementation uses a static variable to keep track of the object instance (objectInstance). I am wondering if there is a cleaner way to do this.
private static DependencyObject objectInstance;
public int numberPeople
{
get { return (int)GetValue(numberPeopleProperty); }
set { SetValue(numberPeopleProperty, value); }
}
public static readonly DependencyProperty numberPeopleProperty =
DependencyProperty.Register("numberPeople", typeof(int), typeof(ListDisplayer), new PropertyMetadata(0, Combine));
public ObservableCollection<ListModel> ingredients
{
get { return (ObservableCollection<ListModel>)GetValue(ingredientsProperty); }
set { SetValue(ingredientsProperty, value); }
}
public static readonly DependencyProperty ingredientsProperty =
DependencyProperty.Register("ingredients", typeof(ObservableCollection<ListModel>), typeof(ListDisplayer), new PropertyMetadata(null, AssignCollectionChangedToList));
private static void AssignCollectionChangedToList(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as ListDisplayer;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
instance.ItemsSource = (ObservableCollection<ListModel>)e.NewValue;
objectInstance = instance;
instance.ItemsSource.CollectionChanged += ItemsSource_CollectionChanged;
}
}
private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// MY PROBLEM: when a new item is added in this list trigger again Combine(), is there
// another way to trigger the Combine so that it will process the IngredientList and numberPeople attached to the object ?
Combine(objectInstance, new DependencyPropertyChangedEventArgs());
}
private static void Combine(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// process numberPeople and ingredientList
}
Edit: Remove the static keyword from the definition of the ItemsSource_CollectionChanged event handler and hook it up using the "instance" reference:
private static void AssignCollectionChangedToList(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as ListDisplayer;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= instance.ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
instance.ingredients = (ObservableCollection<ListModel>)e.NewValue;
objectInstance = instance;
instance.ingredients.CollectionChanged += instance.ItemsSource_CollectionChanged;
}
}
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Combine(this, new DependencyPropertyChangedEventArgs());
}
Also the CLR wrappers of your dependency properties are not correctly implemented. You should pass the dependency property to the GetValue and SetValue methods:
public int numberPeople
{
get { return (int)GetValue(numberPeopleProperty); }
set { SetValue(numberPeopleProperty, value); }
}
public static readonly DependencyProperty numberPeopleProperty =
DependencyProperty.Register("numberPeople", typeof(int), typeof(ListDisplayer), new PropertyMetadata(0, Combine));
public ObservableCollection<ListModel> ingredients
{
get { return (ObservableCollection<ListModel>)GetValue(ingredientsProperty); }
set { SetValue(ingredientsProperty, value); }
}
public static readonly DependencyProperty ingredientsProperty =
DependencyProperty.Register("ingredients", typeof(ObservableCollection<ListModel>), typeof(ListDisplayer), new PropertyMetadata(null, AssignCollectionChangedToList));

Dependent DependencyProperty

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();
}
}

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.

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