WPF - DependencyProperty ignoring setter with side-effects on change [duplicate] - c#

This question already has an answer here:
WPF: XAML property declarations not being set via Setters?
(1 answer)
Closed 7 years ago.
I have a WPF user control that is a wrapper for two other controls, showing only one of them depending on the situation. It possesses an ItemsSource property which sets the ItemsSource for the two underlying controls. I want to make it so that this property can be bound to on a .xaml file.
I created a DependencyProperty, and I've changed my getter and my setter to use it. However, when I debug the code, I can see that the setter is never getting called. I can see that the dependency property is changing its value, but it's not setting the underlying controls' properties.
How can I make it so that the underlying controls have their properties set when the dependency property changes?
public partial class AccountSelector : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource", typeof(IEnumerable), typeof(AccountSelector));
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
if (UseComboBox)
AccCombo.ItemsSource = value;
else
AccComplete.ItemsSource = value;
SetValue(ItemsSourceProperty, value);
}
}
}

You have to pass a propertyChangedCallback to your UIPropertyMetadata, like this:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource", typeof(IEnumerable), typeof(AccountSelector), new UIPropertyMetadata((d, e) =>
{
if (e.NewValue == null) return;
var s = d as AccountSelector;
var list = e.NewValue as IEnumerable;
if (list == null || s == null) return;
if (s.UseComboBox)
s.AccCombo.ItemsSource = list;
else
s.AccComplete.ItemsSource = list;
}));
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
}
}

Related

WPF: DependencyProperty on custom control set to value, but the bound property on the ViewModel is set to null

I'm trying to create a WPF ListView that can bind its SelectedItems property.
I inherit from ListView. When the ListView's SelectionChanged event is raised, I read the ListView's SelectedItems property, and set my DependencyProperty's value to it.
I then add a BetterListView object to the XAML and bind its SelectedItemsBindable property to the viewmodel.
And although it looks like everything works - when I break at the view model's set method, I see that although SelectedItemsBindable was set to a collection inside BetterListView, the viewmodel gets this value as null! What am I doing wrong?
class BetterListView : ListView
{
[Bindable(true)]
[Category("Appearance")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IList SelectedItemsBindable
{
get { return GetValue(SelectedItemsBindableProperty) as IList; }
set
{
SetValue(SelectedItemsBindableProperty, value);
}
}
public static readonly DependencyProperty SelectedItemsBindableProperty = DependencyProperty.Register(
"SelectedItemsBindable",
typeof(IList),
typeof (BetterListView),
new FrameworkPropertyMetadata(new List<object>(),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var items = SelectedItems.Cast<object>().ToList();
SelectedItemsBindable = items;
}
#region constructor
public BetterListView()
{
SelectionChanged += OnSelectionChanged;
}
#endregion
}

GetValue and SetValue is not called on binding

I wrote a custom control derived from Progressbar which implements animations on valuechange (the value fills up with a doubleanimation until the target is reached).
var duration = new Duration(TimeSpan.FromSeconds(2.0));
var doubleanimation = new DoubleAnimation(value, duration)
{
EasingFunction = new BounceEase()
};
BeginAnimation(ValueProperty, doubleanimation);
A new Property "TargetValue" is used, because the ControlTemplate used for the ProgressBar has to show the new value straight after changing it. For this the ProgressEx contains the following:
public static readonly DependencyProperty TargetValueProperty = DependencyProperty.Register("TargetValue", typeof (int), typeof (ProgressEx), new FrameworkPropertyMetadata(0));
public int TargetValue
{
get { return (int)GetValue(TargetValueProperty); }
set
{
if (value > Maximum)
{
//Tinting background-color
_oldBackground = Background;
Background = FindResource("DarkBackgroundHpOver100") as LinearGradientBrush;
}
else
{
if (_oldBackground != null)
Background = _oldBackground;
}
SetValue(TargetValueProperty, value);
Value = value;
}
}
When the TargetValue exceeds the maximum i will use a different color defined in xaml. This works really good - But. Now i want to use this bar in a listview where it is bind to some data. Problem is, the setter is not called in this case, so no animation is executed, even when the value is changed via TargetValue={Binding ProgressValue}. I know that the framework will always call GetValue and SetValue directly and no logic should be supplied, but is there a way around this?
Thanks in advance.
The CLR style getters and setters of DependencyPropertys are not meant to be called by the Framework... they are there for developers to use in code. If you want to know when a DependencyProperty value has changed, you need to add a handler:
public static readonly DependencyProperty TargetValueProperty =
DependencyProperty.Register("TargetValue", typeof (int), typeof (ProgressEx),
new FrameworkPropertyMetadata(0, OnTargetValueChanged));
private static void OnTargetValueChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
// Do something with the e.NewValue and/or e.OldValue values here
}

How can I update the source from a custom control with dependency properties in silverlight?

I've created a custom control that extends the RichTextBox so that I can create a binding for the xaml property. It all works well as long as I just update the property from the viewmodel but when I try to edit in the richtextbox the property is not updated back.
I have the following code in the extended version of the richtextbox.
public static readonly DependencyProperty TextProperty = DependencyProperty.Register ("Text", typeof(string), typeof(BindableRichTextBox), new PropertyMetadata(OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rtb = d as BindableRichTextBox;
if (rtb == null)
return;
string xaml = null;
if (e.NewValue != null)
{
xaml = e.NewValue as string;
if (xaml == null)
return;
}
rtb.Xaml = xaml ?? string.Empty;
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
In the view I've set the binding like such
<Controls:BindableRichTextBox Text="{Binding XamlText, Mode=TwoWay}"/>
In the viewmodel I've created the XamlText as a normal property with the NotifyPropertyChanged event being called on updates.
I want the bound XamlText to be updated when the user enters texts in the RichTextBox either on lostfocus or directly during edit, it doesn't really matter.
How can I change the code to make this happen?
You will need to listen to changes to the Xaml-property of the BindableRichTextBox and set the Text-property accordingly. There is an answer available here describing how that could be achieved. Using the approach described in that would the result in the following code (untested):
public BindableRichTextBox()
{
this.RegisterForNotification("Xaml", this, (d,e) => ((BindableRichTextBox)d).Text = e.NewValue);
}
public void RegisterForNotification(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
var binding = new Binding(propertyName) { Source = element };
var property = DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(UserControl),
new PropertyMetadata(callback));
element.SetBinding(property, binding);
}

Binding to custom Dependency Property fails

As the SelectedItems property of the ListBox control is a normal property and not a dependency property to bind to, I have derived of the ListBox and created a new dependency property SelectedItemsEx.
But my XAML compiler keeps giving me the error
A 'Binding' cannot be set on the 'SelectedItemsEx' property of the
type 'MyListBox'. A 'Binding' can only be set on a DependencyProperty
of a DependencyObject.
Why my property is not recognized as a dependency property? Any help is appreciated, thank you!
XAML:
<MyListBox ItemsSource="{Binding MyData}" SelectedItemsEx="{Binding SelectedEx}"
SelectionMode="Extended"> ... </MyListBox>
ListBox' implementation:
public class MyListBox : ListBox
{
public readonly DependencyProperty SelectedItemsExProperty =
DependencyProperty.Register("SelectedItemsEx",
typeof(ObservableCollection<MyItemsDataType>),
typeof(MyListBox),
new PropertyMetadata(default(ObservableCollection<MyItemsDataType>)));
public ObservableCollection<MyItemsDataType> SelectedItemsEx
{
get
{
var v = GetValue(SelectedItemsExProperty);
return (ObservableCollection<MyItemsDataType>)v;
}
set { SetValue(SelectedItemsExProperty, value); }
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (SelectedItemsEx != null)
{
SelectedItemsEx.Clear();
foreach (var item in base.SelectedItems)
{
SelectedItemsEx.Add((MyItemsDataType)item);
}
}
}
The DependencyProperty field must be static:
public static readonly DependencyProperty SelectedItemsExProperty = ...
Note also that in order to make your derived ListBox a little more reusable, you should not constrain the type of the SelectedItemsEx property. Use IEnumerable (or IList like SelectedItems) instead. Moreover, there is no need to specify a default value by property metadata, as it is null already and default(<any reference type>) is also null.
You will however have to get notified whenever the SelectedItemsEx property has changed. Therefore you have to register a change callback via property metadata:
public static readonly DependencyProperty SelectedItemsExProperty =
DependencyProperty.Register(
"SelectedItemsEx", typeof(IEnumerable), typeof(MyListBox),
new PropertyMetadata(SelectedItemsExPropertyChanged));
public IEnumerable SelectedItemsEx
{
get { return (IEnumerable)GetValue(SelectedItemsExProperty); }
set { SetValue(SelectedItemsExProperty, value); }
}
private static void SelectedItemsExPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var listBox = (MyListBox)obj;
var oldColl = e.OldValue as INotifyCollectionChanged;
var newColl = e.NewValue as INotifyCollectionChanged;
if (oldColl != null)
{
oldColl.CollectionChanged -= listBox.SelectedItemsExCollectionChanged;
}
if (newColl != null)
{
newColl.CollectionChanged += listBox.SelectedItemsExCollectionChanged;
}
}
private void SelectedItemsExCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
...
}
}

WPF DependencyProperty and data binding

I am working on WPF project. I create a usercontrol containing a combobox; which represent boolean value(True or false). And I register a DependencyProperty Value for my usercontrol.
Whenever combobox selection was changed, I will update the Value property and also when Value property is update I will update combobox.
But I found the problem when I use my usercontrol in MVVM. I bind the Value property with my IsEnable property in my viewModel. I set binding mode as TwoWay binding. But when I changed selection in comboBox, IsEnable property is never set.
My usercontrol:
public bool Value
{
get { return (bool)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(bool),
typeof(BooleanComboBox),
new UIPropertyMetadata(true, OnValuePropertyChanged));
private void Cmb_Selection_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox cmb = sender as ComboBox;
object selectedValue = cmb.SelectedValue;
if (selectedValue == null)
{
this.Value = false;
}
else
{
if (selectedValue.GetType() == typeof(bool))
{
this.Value = (bool)selectedValue;
}
else
{
this.Value = false;
}
}
if (this.OnValueChange != null)
this.OnValueChange(this, this.Value);
}
private static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
BooleanComboBox self = sender as BooleanComboBox;
self.Cmb_Selection.SelectedValue = (bool)args.NewValue;
}
In window, where I place my usercontrol (I already set usercontrol's datacontext to my viewModel):
<tibsExtControl:BooleanComboBox Grid.Row="4"
Grid.Column="1"
VerticalAlignment="Center"
Value="{Binding Path=NewTemporaryZone.IsEnable,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
x:Name="Cmb_AllowNonLBILogon"/>
In my model class I declare an IsEnable property:
private bool _isEnable;
public bool IsEnable
{
get { return _isEnable; }
set
{
_isEnable= value;
OnPropertyChanged("IsEnable");
}
}
What's going on with my usercontrol. I miss something ? Please help me. T.T
Please check whether you have any binding error in the output window of VS.
Try refreshing your binding in Cmb_Selection_SelectionChanged. Something like:
BindingExpression b = cmb.GetBindingExpression(MyNamespace.ValueProperty);
b.UpdateSource();
I've had the same problem; with boolean dependency properties! Try switching the bool to a INullable<bool> (bool?) and apply the appropriate type conversions. This worked for me. Don't know if this is a bug or if value types are handled somewhat different compared to reference types when creating dependency properties? Maybe someone else could verify that.

Categories