I have a problem with the following scenario (code cut for brevity). Basically the Setter of my User Control Property isn't being called when the dependency property is set and I need to get around this.
I have the following code in my View.xaml
<Filter:Filter x:Name="ProductFilter" PrimaryItemSource="{Binding CarrierProducts}" />
In the View.xaml.cs
public ProductPricing()
{
InitializeComponent();
ViewModel.Filter.ProductPricing vm = new ViewModel.Filter.ProductPricing();
this.DataContext = vm;
}
In my ViewModel I expose a property
public ObservableCollection<Model.FilterItem> _carrierProducts;
public ObservableCollection<Model.FilterItem> CarrierProducts
{
get
{
return _carrierProducts;
}
set
{
if (_carrierProducts != value)
{
_carrierProducts = value;
RaisePropertyChanged("CarrierProducts");
}
}
}
Finally the Filter User control is defined like so.
public static readonly DependencyProperty PrimaryItemSourceProperty =
DependencyProperty.Register("PrimaryItemSource", typeof(ObservableCollection<Model.FilterItem>), typeof(Filter), new PropertyMetadata(null));
public ObservableCollection<Model.FilterItem> PrimaryItemSource
{
get
{
return (ObservableCollection<Model.FilterItem>)GetValue(PrimaryItemSourceProperty);
}
set
{
SetValue(PrimaryItemSourceProperty, value);
ComboBox combo = _filters.ElementAt(0);
FilterSourceChange(combo, value);
}
}
For some reason the PrimaryItemSource property is set but the Setter doesn't get called. Do I have to add a PropertyChange event to the PropertyMetadata object to handle this as that seems like a lot of code for something simple.
This is how a Dependency property that requires additional code to be run on set should be written:-
public ObservableCollection<Model.FilterItem> PrimaryItemSource
{
get { return (ObservableCollection<Model.FilterItem>)GetValue(PrimaryItemSourceProperty); }
set { SetValue(PrimaryItemSourceProperty , value); }
}
public static readonly DependencyProperty PrimaryItemSourceProperty =
DependencyProperty.Register(
"PrimaryItemSource",
typeof(ObservableCollection<Model.FilterItem>),
typeof(Filter), new PropertyMetadata(null, OnPrimaryItemSourceChanged));
private static void OnPrimaryItemSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Filter filter = (Filter)d;
var oldValue = (ObservableCollection<Model.FilterItem>)e.OldValue;
var newValue = (ObservableCollection<Model.FilterItem>)e.NewValue;
filter.OnPrimaryItemSourceChanged(oldValue, newValue);
}
protected virtual void OnPrimaryItemSourceChanged(
ObservableCollection<Model.FilterItem> oldValue,
ObservableCollection<Model.FilterItem> newValue)
{
ComboBox combo = _filters.ElementAt(0);
FilterSourceChange(combo, newValue);
}
You use place a static DependencyPropertyChanged handler in the class that will cast down the dependency object to the correct type and then call an instance method to alert that instance of the change.
This change handler will get called whenever the underlying dependency property is changed be that via the SetValue call in the property Set method or by binding or any other means.
Yes, always use the callback if you need additional logic for the setter. This is a must in Silverlight and WPF.
As far as I know, the Setter would only be called when actually used from code. When you do Binding, things happen using the DependencyProperty framework.
You should also wrap your ComboBox combo = ... code into a this.Dispatcher.BeginInvoke(() => ... );, because that ensures the visual tree is initialized.
The Last parameter of the DependencyProperty.Register() method takes a PropertyMetaData where you're passing null. One of the overloads of the constructor takes a PropertyChangedCallback. Use this overload to define a callback function that will be called when your property is modified.
static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Filter filter = d as Filter;
ComboBox combo = filter._filters.ElementAt(0);
filter.FilterSourceChange(combo, filter.PrimaryItemSource);
}
Related
When I set the value of IsClosed during runtime, OnIsClosedChanged() is called fine.
However, the Designer sets the value of the property but does not call the OnIsClosedChanged().
public static DependencyProperty IsClosedProperty = DependencyProperty.Register("IsClosed", typeof(bool), typeof(GroupBox), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
public bool IsClosed {
get {
return (bool)this.GetValue(IsClosedProperty);
}
set {
if ((bool)this.GetValue(IsClosedProperty) == value)
return;
this.SetValue(IsClosedProperty, value);
OnIsClosedChanged();
}
}
private void OnIsClosedChanged() {
_rowDefContent.Height = new GridLength((IsClosed ? 0 : 1), GridUnitType.Star);
}
Obviously IsClosed is not modified by the Designer and only IsClosedProperty receives the xaml change.
My question is: How can I run IsClosed after the value has been modified in the Designer. Or at least add some logic to the non-runtime changes.
You would have to register a PropertyChangedCallback with property metadata.
The reason is that dependency properties set in XAML or by bindings or some other source do not invoke the CLR wrapper (the setter method). The reason is explained in the XAML Loading and Dependency Properties article on MSDN:
For implementation reasons, it is computationally less expensive to
identify a property as a dependency property and access the property
system SetValue method to set it, rather than using the property
wrapper and its setter.
...
Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
Your code should look like this:
public static readonly DependencyProperty IsClosedProperty =
DependencyProperty.Register(
"IsClosed", typeof(bool), typeof(GroupBox),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
(o, e) => ((GroupBox)o).OnIsClosedChanged()));
public bool IsClosed
{
get { return (bool)GetValue(IsClosedProperty); }
set { SetValue(IsClosedProperty, value); }
}
private void OnIsClosedChanged()
{
_rowDefContent.Height = new GridLength((IsClosed ? 0 : 1), GridUnitType.Star);
}
Found the answer myself now. ValidateValueCallback comes really close! (as Alex K has pointed out) But it is a static method and I don't get any reference to the instance which has been changed. The key is to use a PropertyChangedCallback in FrameworkPropertyMetadata which is also an argument passed to the Property.Register method.
See:
public static DependencyProperty IsClosedProperty = DependencyProperty.Register("IsClosed", typeof(bool), typeof(GroupBox), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnIsClosedChangedPCC)));
public bool IsClosed {
get {
return (bool)this.GetValue(IsClosedProperty);
}
set {
this.SetValue(IsClosedProperty, value);
OnIsClosedChanged();
}
}
private static void OnIsClosedChangedPCC(DependencyObject d, DependencyPropertyChangedEventArgs e) {
GroupBox current = (GroupBox)d;
current.IsClosed = current.IsClosed;
}
private void OnIsClosedChanged() {
_rowDefContent.Height = new GridLength((IsClosed ? 0 : 1), GridUnitType.Star);
}
That does now re-set the IsClosedValue which triggers the OnIsClosedChanged to run.
Thank's for your help guys!
I think this is a basic misunderstanding about how to manage dependency properties but I can't seem to find a clear example to correct me.
Looking at the following code as an example...
public class MyControl
{
public static readonly DependencyProperty ExpressionProperty =
DependencyProperty.Register("Expression",
typeof (Expression),
typeof (MyControl),
new PropertyMetadata(ExpressionChanged));
public Expression Expression
{
get { return (Expression)GetValue(ExpressionProperty); }
set { SetValue(ExpressionProperty, value); }
}
private static void ExpressionChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
... Must respond to external change of property
... Update UI to reflect external change to property
}
private void RespondToInput()
{
... Do something to expression, add new elements or something
... Now expression has changed so I want to update the dependency property
... so datacontext gets new value.
SetValue(ExpressionProperty, updatedExpression);
}
}
What I don't understand is that when I do the RespondToInput work, I want to now update the DependencyProperty, but if I do, the PropertyChanged callback is called, at which point I go in a circle and now start updating the UI, even though I initiated the change from the UI effectively.
I don't know if that makes enough sense.
What did I do wrong??
Thanks!
You can't prevent the PropertyChangedCallback from being called when the property value changes. What you can do is not to react on an internal property change:
private bool isInternalExpressionChanged;
private static void ExpressionChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!isInternalExpressionChanged)
{
...
}
}
private void RespondToInput()
{
...
isInternalExpressionChanged = true;
SetValue(ExpressionProperty, updatedExpression);
isInternalExpressionChanged = false;
}
My goal is to create a custom TextBlock control that has a new dependency property, SearchText. This property will contain a regular expression. All occurrences of this regular expression in the text of the TextBlock will be highlighted using a custom style (another DP).
My current implementation involves clearing all of the Inline objects in the TextBlock's InlineCollection. I then fill the TextBlock with runs for unhighlighted text and runs for highlighted text with the style applied (this method does not support adding inlines directly to the TextBlock, instead TextBlock.TextProperty has to be used).
Works great, but sometimes I get a strange exception when trying to clear the Inlines: InvalidOperationException: "Cannot modify the logical children for this node at this time because a tree walk is in progress."
This problem seems to be related to this one. I am modifying the inlines in the TextChanged function, but I'm using a flag to avoid infinite recursive edits.
Any thoughts on how to architect this custom control? Is there a better way to do this? How do I get around this exception?
Thanks!
In my implementation, I solved this by just adding another dependency property, called OriginalText. When it's modified, I updated both the Text property and update the highlighting. Here's the code:
public class HighlightTextBlock : TextBlock
{
public string HighlightedText
{
get { return (string)GetValue(HighlightedTextProperty); }
set { SetValue(HighlightedTextProperty, value); }
}
public static readonly DependencyProperty HighlightedTextProperty =
DependencyProperty.Register("HighlightedText", typeof(string), typeof(HighlightTextBlock), new UIPropertyMetadata(string.Empty, UpdateHighlightEffect));
public static readonly DependencyProperty OriginalTextProperty = DependencyProperty.Register(
"OriginalText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(default(string), OnOriginalTextChanged));
private static void OnOriginalTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var block = ((HighlightTextBlock)obj);
block.Text = block.OriginalText;
block.UpdateHighlightEffect();
}
public string OriginalText
{
get { return (string)GetValue(OriginalTextProperty); }
set { SetValue(OriginalTextProperty, value); }
}
private static void UpdateHighlightEffect(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (!(string.IsNullOrEmpty(e.NewValue as string) && string.IsNullOrEmpty(e.OldValue as string)))
((HighlightTextBlock)sender).UpdateHighlightEffect();
}
private void UpdateHighlightEffect()
{
if (string.IsNullOrEmpty(HighlightedText)) return;
var allText = GetCompleteText();
Inlines.Clear();
var indexOfHighlightString = allText.IndexOf(HighlightedText, StringComparison.InvariantCultureIgnoreCase);
if (indexOfHighlightString < 0)
{
Inlines.Add(allText);
}
else
{
Inlines.Add(allText.Substring(0, indexOfHighlightString));
Inlines.Add(new Run()
{
Text = allText.Substring(indexOfHighlightString, HighlightedText.Length),
Background = Consts.SearchHighlightColor,
});
Inlines.Add(allText.Substring(indexOfHighlightString + HighlightedText.Length));
}
}
private string GetCompleteText()
{
var allText = Inlines.OfType<Run>().Aggregate(new StringBuilder(), (sb, run) => sb.Append(run.Text), sb => sb.ToString());
return allText;
}
}
Still not sure if there's a better way to do this altogether, but I appear to have found a work around.
I was updating the inlines/runs in a function that was fired by the change notification for the TextProperty and the SearchTextProperty.
Now I'm firing the highlight/update code from a Dispatcher.BeginInvoke() call in the change notification with DispatcherPriority.Normal.
In case anyone wants an example of how to do this, I found this
I have a class called MyComponent and it has a DependencyProperty caled BackgroundProperty.
public class MyComponent
{
public MyBackground Background
{
get { return (MyBackground)GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register("Background", typeof(MyBackground),
typeof(MyComponent), new FrameworkPropertyMetadata(default(MyBackground), new PropertyChangedCallback(OnPropertyChanged)));
}
MyBackground is a class that derives from DependencyObject and it has some DependencyProperties.
public class MyBackground : DependencyObject
{
public Color BaseColor
{
set { SetValue(BaseColorProperty, value); }
get { return (Color)GetValue(BaseColorProperty); }
}
public static readonly DependencyProperty BaseColorProperty =
DependencyProperty.Register("BaseColor", typeof(Color),
typeof(MyBackground ), new UIPropertyMetadata(Colors.White));
[...]
}
Now, what I want is when a property from MyBackground is changed, MyComponent to be notified that MyBackground has changed and the PropertyChangedCallback named OnPropertyChanged to be called.
Bear with me for a second because it appears that you are trying to go against the grain of WPF. Since it seems you are writing code related to display logic, the typical method for getting related DependencyObjects to interact with one another is through bindings.
If, for example, MyComponent is a control of some sort and it uses the Background property in its ControlTemplate, you would use a TemplateBinding that references the Background property and any important sub-properties.
Since 1) you probably already know that and 2) you either aren't using templates or don't have them available, you can set up a binding in code in order to react to changes in to the Background property. If you provide more detail about what your OnPropertyChanged method does I can provide some sample code.
One way to do what you describe would be to derive from Freezable instead of DependencyObject. When a property of a Freezable changes the PropertyChangedCallback for any DO referencing that Freezable will be invoked so the callback for the Background property of your MyComponent. In that case the e.OldValue and e.NewValue will be the same reference. Internally WPF has some flag on the event args that indicates that it is a subobject change.
This is what the framework does for things like brushes so that an element can be invalidated if say the Color property of a SolidColorBrush is changed. If an object will never be changed (or you want to make it thread safe) then one can freezing the object (i.e. making it immutable).
BTW I would probably avoid using Background as the name of the property. Most developers will assume that is of type Brush as that is what the framework uses for that named property on several of its elements (e.g. control, border).
Sounds like you want to use a DependencyPropertyDescriptor and AddValueChanged.
Here's an article on it: http://www.codeproject.com/Articles/34741/Change-Notification-for-Dependency-Properties.aspx
..and possibly a better implementation: http://agsmith.wordpress.com/2008/04/07/propertydescriptor-addvaluechanged-alternative/
Here's a small static class of extension methods I wrote for WPF -- it allows you to register an EventHandler or an Action callback for the changing of any DependencyProperty on any DependencyObject. No changes to the dependency object are necessary.
It also prevents recursion (i.e. if you change that same property during the callback, etc..)
It takes advantage of the DependencyPropertyDescriptor that #ScottBilas linked to.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
namespace BrainSlugs83.Writes.Too.Much.Code
{
public static class WpfExtensions
{
public static void OnPropertyChanged<T>(this T obj, DependencyProperty prop, Action<T> callback) where T : DependencyObject
{
if (callback != null)
{
obj.OnPropertyChanged(prop, new EventHandler((o, e) =>
{
callback((T)o);
}));
}
}
public static void OnPropertyChanged<T>(this T obj, DependencyProperty prop, EventHandler handler) where T : DependencyObject
{
var descriptor = DependencyPropertyDescriptor.FromProperty(prop, typeof(T));
descriptor.AddValueChanged(obj, new EventHandler((o, e) =>
{
if (handler != null)
{
if (o == null) { handler(o, e); }
else
{
lock (PreventRecursions)
{
if (IsRecursing(obj, prop)) { return; }
SetIsRecursing(obj, prop, true);
}
try
{
handler(o, e);
}
finally
{
SetIsRecursing(obj, prop, false);
}
}
}
}));
}
#region OnPropertyChanged Recursion Prevention
private static readonly Dictionary<object, List<DependencyProperty>> PreventRecursions = new Dictionary<object, List<DependencyProperty>>();
private static bool IsRecursing(object obj, DependencyProperty prop)
{
lock (PreventRecursions)
{
List<DependencyProperty> propList = null;
if (PreventRecursions.ContainsKey(obj))
{
propList = PreventRecursions[obj];
}
return propList == null ? false : propList.Contains(prop);
}
}
private static void SetIsRecursing(object obj, DependencyProperty prop, bool value)
{
lock (PreventRecursions)
{
List<DependencyProperty> propList = null;
if (PreventRecursions.ContainsKey(obj))
{
propList = PreventRecursions[obj];
}
if (propList == null)
{
if (!value) { return; }
propList = PreventRecursions[obj] = new List<DependencyProperty>();
}
if (value)
{
if (!propList.Contains(prop))
{
propList.Add(prop);
}
}
else
{
while (propList.Contains(prop))
{
propList.Remove(prop);
}
if (!propList.Any())
{
propList = PreventRecursions[obj] = null;
}
}
}
}
#endregion
public static bool IsInDesignMode(this DependencyObject obj)
{
try
{
return DesignerProperties.GetIsInDesignMode(obj);
}
catch { /* do nothing */ }
return false;
}
}
}
I'm currently struggling with one of the bindings I'm trying to add to my WPF project.
In the app I have a model with a bool property that cannot be used for databinding. Behind that property is a .NET remoting object that does some validation and writes the new value into the DB.
The requirement ist that the property should be displayed as checkbox, and as the user changes the value the new value should be immediatly provided to the .NET remoting object.
My approach so far:
I've created in my ViewModel with a DependencyProperty that is bound to my checkbox.
In the propertychanged handler of the DP, I'm writting the value to the property of the remoting object.
The problems I have with this approach:
if the validation within the .net remoting object raises an exception, this exception is swallowed. In addition the checkbox state and what's in the DB is not in sync. I tried to reset the value of the DP in case of an exception, but the checkbox doesn't reflect that.
What makes the situation even worse is the fact, that this WPF controls is integrated into an existing WinForms app.
So I would like to have the same behavior for these exceptions as I have implemented in my Application.ThreadException handler.
any ideas how to approach this?
The problem is that I heard only solutions for .NET 4.0 so far, but I'm working with 3.5SP1.
tia
Martin
Short demo code:
class TestVM : DependencyObject
{
private Model _m;
public TestVM()
{
_m = new Model();
}
public bool Value
{
get { return (bool)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(bool),
typeof(TestVM),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
((sender, e) => ((TestVM)sender).Apply(e))));
private bool _suppress = false;
private void Apply(DependencyPropertyChangedEventArgs e)
{
if (_suppress) return;
try
{
_m.Value = this.Value;
}
catch
{
_suppress = true;
this.Value = _m.Value;
this.OnPropertyChanged(e);
}
finally
{
_suppress = false;
}
}
}
You don't need to use a DependencyObject as your ViewModel. You just need to implement INotifyPropertyChanged to get data binding support:
class TestVM
: INotifyPropertyChanged
{
private Model _m;
public TestVM()
{
_m = new Model();
}
public bool Value
{
get { return _m.Value; }
set
{
_m.Value = this.Value;
OnPropertyChanged("Value");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Note that if you expect the setter to throw exceptions, you may want to use an ExceptionValidationRule on the binding in your view.
Update: It sounds like your problem is that the Binding won't respond to PropertyChanged events within the call to set the source. One way to get around this is to use an asynchronous binding by setting IsAsync=True in the XAML for your binding. WPF will process the PropertyChanged event after it has finished updating the source value and won't think it is a reentrant call.
You can also get around this by using a Converter and turning off updates on PropertyChanged by doing UpdateSourceTrigger=LostFocus, but I don't think you would want that behavior.
I found a solution for my problem. I'm now deriving my own binding class that does the job.
public class ExceptionBinding : Binding
{
public ExceptionBinding(string name)
: base(name)
{
Construct();
}
public ExceptionBinding()
: base()
{
Construct();
}
private void Construct()
{
this.ValidatesOnExceptions = true;
this.UpdateSourceExceptionFilter = new UpdateSourceExceptionFilterCallback(OnException);
}
private object OnException(object bindExpression, Exception exception)
{
// ... custom error display ...
var exp = (BindingExpressionBase)bindExpression;
exp.UpdateTarget();
return null; // null needed to avoid display of the default error template
}
}