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;
}
Related
Problem: I have a ListBox with TextBlocks whos Text property are bound to different properties. I wish to drag the TextBlock onto a OxyPlot and have the plot create a new LineSeries with a collection that should be bound to the same binding as for the TextBlock (is this making sense?)
I have derived a class from TextBlock to handle the OnMouseMove event like this:
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (CanDrag && (e.LeftButton == MouseButtonState.Pressed))
{
// Make sure we have a data binding
BindingExpression binding = GetBindingExpression(TextProperty);
if(binding == null)
{ return; }
// Package the data.
DataObject data = new DataObject();
data.SetData("DragListText.Binding", binding);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Copy);
}
}
Also I have derived a class from Oxy.Plot that handles the OnDrop:
protected override void OnDrop(DragEventArgs e)
{
base.OnDrop(e);
// DataObject must contain a DragListText.Binding object
if (e.Data.GetDataPresent("DragListText.Binding"))
{
BindingExpression binding = e.Data.GetData("DragListText.Binding") as BindingExpression;
AddSeries(binding);
}
e.Handled = true;
}
The AddSeries function does the following:
public void AddSeries(BindingExpression binding)
{
plot1 = new PlotCollection();
LineSeries newSeries = new LineSeries();
newSeries.ItemsSource = plot1.Collection;
Series.Add(newSeries);
}
And lastly the PlotCollection is defined as:
public class PlotCollection : DependencyObject
{
public ObservableCollection<DataPoint> Collection;
public static DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(PlotCollection), new PropertyMetadata(0.0, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ ((PlotCollection)d).AddLast(); }
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public PlotCollection()
{
Collection = new ObservableCollection<DataPoint>();
}
protected void AddLast()
{
Collection.Add(new DataPoint(OxyPlot.Axes.DateTimeAxis.ToDouble(DateTime.Now), Value));
}
}
So my question is this: How do I create a binding on PlotCollection.Value that matches the one from the TextBlock.Text?
In your AddSeries method, try adding this line of code:
BindingOperations.SetBinding(plot1, PlotCollection.ValueProperty, binding.ParentBinding);
Found out the problem,
I needed to add a PropertyChangedCallback to the ValueProperty declaration, like this:
public static DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DynamicSeries), new PropertyMetadata(0.0, OnValueChanged));
And then handle the property changes in the callback method:
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PlotCollection ds = (PlotCollection)d;
ds.AppendValue((double)e.NewValue);
}
I guess that I have misunderstood how the Value property works?!
Thanks for taking the time to try and help me...
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();
}
}
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);
Recently i was developing a custom control in Silverlight, I created custom dependency property which is of type ObservableCollection. I have another 2 custom dependency properties of type strings. My requirement is on addition of any item to collection, I have to fire collectionChanged Event , in this event handler, i want to update the other 2 dependency properties.
public static readonly DependencyProperty itemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<ValidationErrorMessage>), typeof(SummaryUserControl), new PropertyMetadata(new ObservableCollection<ValidationErrorMessage>(), new PropertyChangedCallback(fun1)));
public ObservableCollection<ValidationErrorMessage> Items
{
get
{
return (ObservableCollection<ValidationErrorMessage>)base.GetValue(itemsProperty);
}
set
{
base.SetValue(itemsProperty, value);
}
}
public static void fun1(object sender, DependencyPropertyChangedEventArgs evt)
{
var newValue = evt.NewValue as ObservableCollection<ValidationErrorMessage>;
if(newValue!=null)
newValue.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChangedHandler);
var oldValue = evt.OldValue as ObservableCollection<ValidationErrorMessage>;
if(oldValue!=null)
oldValue.CollectionChanged -= new NotifyCollectionChangedEventHandler(CollectionChangedHandler);
}
static void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var newItems = e.NewItems as ObservableCollection<ValidationErrorMessage>;
foreach (var item in newItems)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
public static readonly DependencyProperty headerProperty = DependencyProperty.Register("Header", typeof(String), typeof(SummaryUserControl), new PropertyMetadata(String.Empty, null));
public String Header
{
get
{
return (String)base.GetValue(headerProperty);
}
set
{
base.SetValue(headerProperty, value);
RaisePropertyChange("Header");
}
}
public static readonly DependencyProperty messageTypeProperty =
DependencyProperty.Register("MessageType", typeof(MessageEnumType), typeof(SummaryUserControl), new PropertyMetadata(MessageEnumType.Error, null));
public MessageEnumType MessageType
{
get { return (MessageEnumType)GetValue(messageTypeProperty); }
set { SetValue(messageTypeProperty, value); RaisePropertyChange("MessageType"); }
}
How can I change the values of the dependency properties messageType and Header? I'm unable to access those properties in either the CollectionChanged or NotifyPropertyChanged event since all those events are static. I cannot access the instance within these static event handlers.
I tried to fix the problem with a converter, but my curosity on Silverlight makes me want to use the above approach. How can I set values for those dependency properties within CollectionChanged event or NotifyPropertyChanged events?
The sender in your static fun1 method should be the instance of the class which declares the itemsProperty DependencyProperty. Therefore you can access the concrete instance with casting the sender to your class.
public static void fun1(object sender, DependencyPropertyChangedEventArgs evt)
{
MyClass concreteInstance = sender as MyClass;
if(concreateInstance != null)
{
[...your code...]
}
}
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;