Are you setting a 'local' value (i.e. assigning directly to a dependency property setter) to a dependency property that also has a OneWay
binding on it? If so, setting the local value will remove the binding, as mentioned on the the MSDN dependency property overview:
Bindings are treated as a local value, which means that if you set another local value, you will eliminate the binding.
The dependency property mechanism doesn't have much else it can do when it gets asked to store a local value on a dependency property. It can't send the value through the binding because the binding 'points' the wrong way. After being set to the local value, it's no longer showing the value it got from the binding. Since it's not showing the value from the binding any more, it removes the binding.
Once the binding's gone, the PropertyChangedCallback
will no longer get called when the source property for the binding changes its value. This may be why the callback isn't being called.
If you set the binding to be TwoWay
, the binding system does have somewhere to store the 'local' value you've set: in the binding's source property. In this case, there's no need to eliminate the binding as the dependency property mechanism can store the value in the source property.
This situation does not cause a stack-overflow because the following happens:
- Dependency property receives 'local' value.
- Dependency property mechanism sends value 'backwards' along binding to source property,
- Source property sets property value and fires
PropertyChanged
,
- Dependency property mechanism receives
PropertyChanged
event, checks new value of source property, finds that it hasn't changed and does nothing further.
The key point here is that if you fire a PropertyChanged
event for a property whose value hasn't changed, any PropertyChangedCallback
s on dependency properties bound to your property will not be called.
For simplicity I've ignored IValueConverter
s in the above. If you do have a converter, make sure that it is correctly converting values in both directions. I've also assumed that the property at the other end is a view-model property on an object implementing INotifyPropertyChanged
. There could have been another dependency property at the source end of the binding. The dependency property mechanism can handle that as well.
As it happens, WPF (and Silverlight) contain no detection of stack overflows. If, in a PropertyChangedCallback
, you set the value of the dependency property to be different to its new value (e.g. by incrementing an integer-valued property or appending a string to a string-valued property), you will get a stack overflow.
UPDATE:
Instead of your "property" pushing changes in value into elements in your template by looking for Parts, you should instead have your template bind to properties on the control being templated.
Normally this is done using presenters within a template e.g. ContentPresenter
binds to the property designated as the "content" (it finds out that name by looking for the [ContentProperty]
attribute), and by using bindings in your template that use TemplateBinding
or TemplatedParent
to connect to the properties on your custom control.
Then there is no issue about what order you set your properties and when the template is applied....because it is the template that provides the "look" for the data/properties set on your control.
A custom control should only really need to know and interact with "parts" if it needs to provide certain behaviour/functionality e.g. hooking the click event on a button "part".
In this case instead of setting the Content in the constructor in code-behind, you should get your template to bind to the property. The example I gave below showed how that was generally done with a Content property.
Alternatively you could pull out properties more explicitly e.g. this could be inside your template.
<Label Content="{TemplateBinding MyPropertyOnMyControl}" .....
<Button Content="{TemplateBinding AnotherPropertyOnMyControl}" .....
I think it would be better to designate your "content" using [ContentProperty] attribute, and then using a ContentPresenter in your template so that it can be injected inside your Button, rather than you hooking your Content DependencyProperty. (if you inherit from ContentControl then that provides the "content" behaviour).
[TemplatePart(Name = "PART_Button", Type = typeof (Button))]
public class MyControl : Control
[ContentProperty("Content")]
and
<ControlTemplate TargetType="{x:Type local:MyControl}">
<Button x:Name="PART_Button">
<ContentPresenter/>
</Button>
</ControlTemplate>
As for you wanting to be able to specify some design time data via XAML like Grid does with ColumnDefinition....well that is just using Property Element syntax to specify the items to fill an IList/ICollection typed property.
So just create your own property that can hold a collection of the type you accept e.g.
public List<MyItem> MyItems { get; set; } // create in your constructor.
Best Answer
that's what we do - doesn't solve the problme in priciple but provides with a clear way to fix it.
Create a handler for the DP value changed event, let it be OnValueChanged().Generally no paramters needed as you know which DP is changed and can always obtain its current value.
Create a class/struct called DeferredAction with the constructor, accepting System.Action (that's going to be a ref to your OnValueChanged()). The class will have a property Action and a method, called Execute().
Here's what I use:
In your control create a List. The collection will keep the list of DeferredAction's until they can be successfully applied (typically after base.OnApplyTemplate()). Once actions are applied the collection has to be cleared to avoid double processing.
Within OnValueChanged do check if your Part(s) is not null (which it likely is) and if so add a new instance of DeferredAction(OnValueChanged() to the list created at a previous step. Note, OnValueChanged() is a dual purpose handler it can be called right from your DP value changed handler, if Parts aren't null, alterantively it's used as an executable deferred action.
Within you OnApplyTemplate loop through your deferred actions list (you know, if they're there, they haven't been applied) and call Execute for each of them. Clear the list at the end.
Cheers