Binding to a custom control in edit mode [duplicate] - wpf

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!

Related

WPF: TwoWay Binding doesn't update back

I have the following custom control:
<abc:MyControl MyProperty="{Binding FieldInMyModel, Mode=TwoWay}">
And in my custom control I have
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(double), typeof(MyControl),
new PropertyMetadata(0.0, OnMyPropertyChanged));
public double MyProperty
{
get { return (double)GetValue(MyPropertyProperty ); }
set { SetValue(MyPropertyProperty , value); }
}
private static void OnMyPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (d is MyControl mc)
{
// here I check if the new value is valid for my scope.
// if it is not I update it here to be valid
var v = (double)e.NewValue;
if (!mc.IsValidValue(v))
{
v = mc.MakeValidValue(v);
mc.MyProperty = v;
}
}
}
In my model I change the value of FieldInMyModel to be not valid for my scope. OnMyPropertyChanged is called, and after making a valid value from received invalid value I expect that the FieldInMyModel will be updated to have the new value of MyProperty but actually nothing happens. Any thought?
Setting MyProperty from code will break your TwoWay Binding.
Instead you can use SetCurrentValue method.

Binding is not working for the first time in WPF

I have the following code in my sample.
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(string), typeof(MainWindow), new PropertyMetadata("Hello"));
private TestClass1 test;
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Binding binding = new Binding
{
Path = new PropertyPath("MyProperty"),
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
test.SetValueBinding(binding);
test.DataContext = this;
Console.WriteLine(test.Value);
}
public class TestClass1 : FrameworkElement
{
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(TestClass1), new PropertyMetadata(null));
public void SetValueBinding(Binding binding)
{
this.SetBinding(ValueProperty, binding);
}
}
After binding is set, if i access test.Value, it returns null for the first time. After that,if i access it (from another method) it returns correct value "Hello".
But i do not know first time why binding is not working and null value is returned? any suggestion?
Thanks in advance.
It's not that you can't get the value for the first time. It's that you can't get it so fast. When binding is set at the time the DataContext is NULL, the source is resolved in a deferred way, perhaps later by the UI thread. You ask for value just after the binding is set, too early to get valid results.
You should switch lines in your code to:
test.DataContext = this;
test.SetValueBinding(binding);
This way the binding gets the value immediately from DataContext in the first instruction.

DependencyProperty - How to set a step change in value?

I created a DependencyProperty for UserControl that should be in the range of -2 .. 2
When rotating the mouse scroll wheel in the properties window.
The property value changes by one. And I want to value changed by 0.1
How do I set a step change in DependencyProperty?
I work with properties in XAML editor.
public double Value
{
get { return (double)GetValue(BarValueProperty); }
set { SetValue(BarValueProperty, value); }
}
public static readonly DependencyProperty BarValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(MeterBar), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
When adding the FrameworkPropertyMetadata options into the definition of a DependencyProperty, there is an option to provide a CoerceValueCallback handler. You can change the incomming values in this handler. For complete details, please see the Dependency Property Callbacks and Validation page on MSDN. From the linked page:
public static readonly DependencyProperty CurrentReadingProperty =
DependencyProperty.Register(
"CurrentReading",
typeof(double),
typeof(Gauge),
new FrameworkPropertyMetadata(
Double.NaN,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnCurrentReadingChanged),
new CoerceValueCallback(CoerceCurrentReading)
),
new ValidateValueCallback(IsValidReading)
);
public double CurrentReading
{
get { return (double)GetValue(CurrentReadingProperty); }
set { SetValue(CurrentReadingProperty, value); }
}
...
private static object CoerceCurrentReading(DependencyObject d, object value)
{
// Do whatever calculation to update your value you need to here
Gauge g = (Gauge)d;
double current = (double)value;
if (current
<g.MinReading) current = g.MinReading;
if (current >g.MaxReading) current = g.MaxReading;
return current;
}

Highlight Search TextBlock

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

Bound Property Setter not getting Called

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

Resources