Trigger PropertyChanged when sub member property changes - c#

I have a WPF control that is supposed to be simple to (re)use. For that I have a custom type containing all the settings the control is supposed to represent and bind it over a DependencyProperty.
However, whenever I change one of the members in the control, the parent control gets the changes in the member (when evaluated through other means), but the PropertyChanged-Callback never gets triggered in the parent control.
public class Setting
{
public int Prop {get;set;}
//Other Properties, Constructor & Copy Constructor, etc.
public override bool Equals(object obj)
{
if (!(obj is Setting other)) return false;
return Prop == other.Prop;
}
}
public class SettingControl : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty SettingProperty = DependencyProperty.Register
(nameof(Settings), typeof(Setting), typeof(SettingControl),
new PropertyMetadata(default(Setting), OnValuePropertyChanged));
public Setting Settings
{
get => (Setting)GetValue(SettingProperty);
set
{
SetValue(SettingProperty, value);
OnPropertyChanged(nameof(Settings));
}
}
public int Prop
{
get => ((Setting)GetValue(SettingProperty))?.Prop ?? 0;
set
{
//Does not work:
var temp = (Setting)GetValue(SettingProperty);
temp.Prop = value;
Settings = temp;
OnPropertyChanged(nameof(Prop));
//Does not work:
Settings.Prop = value;
OnPropertyChanged(nameof(Prop));
OnPropertyChanged(nameof(Settings));
//**Does work**, and triggers the OnSettingChanged in the parent control,
//but is simply not great memory usage
Settings = new Setting(Settings){ Prop = value };
OnPropertyChanged(nameof(Prop));
}
}
}
//Relevant snippet from parent Control ViewModel:
public static readonly DependencyProperty SettingProperty =
DependencyProperty.Register(nameof(Settings), typeof(Setting), typeof(ControlViewModel),
new PropertyMetadata(default(Setting), (d, e) => ((ControlViewModel)d).OnSettingChanged()));
//OnSettingChanged() is never called
public Setting Settings
{
get => (Setting)GetValue(SettingProperty);
set //Set is never called when the member properties are changed
{
SetValue(SettingProperty, value);
OnPropertyChanged(nameof(Settings));
}
}
//Relevant snippet from parent xaml:
<local:SettingControl Width="300"
Settings="{Binding Path=Settings, Mode=TwoWay}"/>
// UpdateSourceTrigger=PropertyChanged doesn't help here either
An obvious solution of course would be to either wrap the Setting class into a SettingViewModel, or implement it as a ViewModel itself (small testing didn't show results anyway). This however would make the usage of the control a lot harder, and to some degree break MVVM (more than this already). There are also some XML things in the Setting class for serialization that I don't want to mess with.
One thing I noticed is that if the Equals function in Setting is coded to always return true the two ways of setting the member property that normally don't work, suddenly work and trigger the desired behavior.
Thanks in Advance.

Related

How can I make Prism's DelegateCommand observe a child view-model's property?

My "parent" view-model (AnalyzeVm) has a child view-model (ScanVm) that represents something that can be saved to disk. I want to give the parent a command (SaveCmd) that is enabled only when the ScanVm.IsDirty property is true.
I already use DelegateCommand and its ObservesProperty functions all over the place so I tried to use it for this. But in this one case I cannot get the ObservesProperty expression to trigger a call to CanExecute.
The difference here (from all the other places I use ObservesProperty that work fine) is that I'm monitoring a property of the child object. Not of my own
I need to understand why this is not working. I thought this was a valid thing to do.
Here are the two view models (stripped down for clarity)
// Parent view-model. Derives from a class that implements INotifyPropertyChanged.
public class AnalyzeVm : BaseViewModel
{
private ScanVm _scan;
public AnalyzeVm(ScanVm scan)
{
_scan = scan;
// Set up the command. Try to make it monitor a property of the ScanVm
// object instead of one of our own.
SaveScanCmd = new DelegateCommand(
() => { _scan.Save() } // execute -- saves the scan
() => { return _scan.IsDirty; }) // only valid when scan is dirty
.ObservesProperty(() => scan.IsDirty); // So observe IsDirty property
// Now just as a sanity check, add a handler for that property changing
// to the PropertyChangedEventManager and log when it does.
PropertyChangedEventManager.AddHandler(
scan,
(_, _) => Debug.WriteLine("IsDirty changed"),
nameof(ScanContext.IsDirty));
}
public ICommand SaveScanCmd { get; }
// other code...
}
// ScanVm class.
public class ScanVm : BaseViewModel
{
private bool _isDirty;
public bool IsDirty
{
get => _isDirty;
set => SetProperty(ref _isDirty, value); // Raises PropertyChanged event
}
public void Save()
{
// Code here to save the ScanVm
IsDirty = false; // No longer dirty
}
// ... other code
}
I am sure that the IsDirty property is properly changing and firing the event. I confirmed it many times. And I'm already using Observes property successfully when I observe my own class' properties. For example, I were to, take the SaveCmd and move it into the ScanVm class itself -- so that the property expression just observed the ScanVm's own property, then it works fine. I do that all over the place in my code.
This is the only place I'm trying to observe another object's property. Is it valid to do? if so, what am I doing wrong?
ObservesProperty primarily works on properties of the containing object
public bool IsDirty {...}
...ObservesProperty( () => IsDirty );
plus nested properties
public ScanVM Child {...}
...ObservesProperty( () => Child.IsDirty );
It just doesn't observe properties on any given instance, because it starts looking from the containing object (AnalyzeVm in this case).

MarkupExtension that uses a non-string DataBinding value

I'm trying to create a MarkupExtension for WPF for use with translation. I have found some similar questions asked here including
MarkupExtension that uses a DataBinding value
How do I resolve the value of a databinding inside a MarkupExtension?
ultimately, this lead to the response by Torvin that looks really promising. However, just as a person in the comments, I have an issue where the value obtained by the target.GetValue() is always returning null.
Here's some of the code.
Ultimately I have a set of static classes that contains a static KeyDefinition object that looks like the following
Public class KeyDefinition
{
Public string Key {get; set;}
Public string DefaultValue {get; set;}
}
The key ties back to a JSON resource while the DefaultValue is an English translation that we can use for Design Time display of the xaml.
Localization occurs through a static class like so Localize.GetResource(key)
My goal is to write XAML like this
<TextBlock Text="{Localize {Binding KeyDefinitionFromDataContext}}">
where KeyDefinitionFromDataContext is a property in the view model that returns a reference to a KeyDefinition object.
As per Torvin's response I created a MarkupExtension like so
public class LocalizeExtension : MarkupExtension
{
private readonly BindingBase _binding;
private static readonly DependencyProperty _valueProperty = DependencyProperty.RegisterAttached("Value", typeof(KeyDefinition), typeof(LocalizeExtension));
[ConstructorArgument("keyDefinition")
public KeyDefinition KeyDefinition {get; set;}
public LocalizeExtension(Binding binding)
{
_binding = binding;
}
public LocalizeExtension(KeyDefinition keyDefinition)
{
KeyDefinition = keyDefinition;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var pvt = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var target = pvt.TargetObject as DependencyObject;
var property = pvt.TargetProperty as DependencyProperty;
//If inside a template, WPF will call again when its applied
if (target == null)
return this;
BindingOperations.SetBinding(target, property, _binding);
KeyDefinition = (KeyDefinition)target.GetValue(_valueProperty);
BindingOperations.ClearBinding(target, property);
return Localize.GetResource(KeyDefinition.Key);
}
}
Now please forgive me, because I do not usually do WPF work, but this task has fallen to me. Whenever I run this code the value returned is always Null. I've tried using strings directly instead of the 'KeyDefinition' object but run into the same problem.
I think what confuses me here is how the DependencyProperty on the target ever gets set because its private.
Any help is appreciated. Thanks!
This is not how it works. The result of the MarkupExtension is always null, because that's what you return. You must know that the Binding (the BindingExpression) is not resolved at the time the extension is invoked. The XAML engine invokes the extension and expects a expression in case of a Binding. Normally, the MarkupExtension would return the result of Binding.ProvideValue(serviceProvider), which is a BindingExpressionBase. The XAML engine will later use this expressions to generate data by actually attaching the binding.
In other words, you return the result prematurely.
Aside from that, you must also know that the MarkupExtension.ProvideValue is only called once. This means your extension does not handle property changes (in case the binding source changes) and clearing the binding is not the desired handling of the binding. It actually even fails to handle a OneTime binding mode.
In the context of localization, it makes pretty much sense to expect the source property to change, at least when the user changes the localization.
There more errors in your code, like an unset _valueProperty field. And what is the purpose of defining a DependencyProperty on a type that does not extend DependencyObject? It's even private! You should also avoid mixing properties and fields. Better define (read-only) properties instead of fields. Returning this (the instance of type MarkupExtension) from your extension will not work where the expected type is other than object e.g., a string - return null instead.
What you want is very simple to realize.
First, you must attach the Binding to a proxy object in order to allow the binding engine to activate the BindingExpression (in the example this is the BindingResolver class).
Second, you must configure the incoming binding to raise notification when the target is updated. Then listen to the Binding.TargetUpdated event to realizes OneWay binding. To realize TwoWay and OneWayToSource binding modes, you must also enable and observe the Binding.SourceUpdated event.
And finally, retrieve the changed value from the source/binding proxy to set it to the target of the MarkupExtension.
Since data binding usually involve having the DataContext as source i.e. requires the visual tree in order to resolve, the binding proxy is a simple attached property. This has the advantage that we can use the original DataContext of the target element and don't have to worry about how to inject our proxy into the visual tree.
LocalizeExtension.cs
public class LocalizeExtension : MarkupExtension
{
private Binding Binding { get; };
private DependencyObject LocalizationTarget { get; set; }
private DependencyProperty LocalizationTargetProperty { get; set; }
private object LocalizationSource { get; set; }
private string LocalizationPropertyName { get; set; }
private bool IsInitialized { get; set; }
public LocalizeExtension(Binding binding)
{
this.Binding = binding;
this.Binding.NotifyOnTargetUpdated = true;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var serviceProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
this.LocalizationTarget = serviceProvider.TargetObject as DependencyObject;
// If inside a template, WPF will call again when its applied
if (this.LocalizationTarget == null)
{
return null;
}
this.LocalizationTargetProperty = serviceProvider.TargetProperty as DependencyProperty;
BindingOperations.SetBinding(this.LocalizationTarget, BindingResolver.ResolvedBindingValueProperty, this.Binding);
Binding.AddTargetUpdatedHandler(this.LocalizationTarget, OnBindingSourceUpdated);
return null;
}
private void OnBindingSourceUpdated(object sender, EventArgs e)
{
if (!this.IsInitialized)
{
InitializeLocalizationSourceInfo();
}
LocalizeBindingSource();
}
private void InitializeLocalizationSourceInfo()
{
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(this.LocalizationTarget, BindingResolver.ResolvedBindingValueProperty);
this.LocalizationSource = bindingExpression.ResolvedSource;
this.LocalizationPropertyName = bindingExpression.ResolvedSourcePropertyName;
this.IsInitialized = true;
}
private void LocalizeBindingSource()
{
object unlocalizedValue = BindingResolver.GetResolvedBindingValue(this.LocalizationTarget);
object localizedValue = LocalizeValue(unlocalizedValue);
this.LocalizationTarget.SetValue(this.LocalizationTargetProperty, localizedValue);
}
private object LocalizeValue(object value)
{
return value is KeyDefinition keyDefinition
? Localize.GetResource(keyDefinition.Key)
: string.Empty;
}
}
BindingResolver.cs
class BindingResolver : DependencyObject
{
public static object GetResolvedBindingValue(DependencyObject obj) => (object)obj.GetValue(ResolvedBindingValueProperty);
public static void SetResolvedBindingValue(DependencyObject obj, object value) => obj.SetValue(ResolvedBindingValueProperty, value);
public static readonly DependencyProperty ResolvedBindingValueProperty =
DependencyProperty.RegisterAttached(
"ResolvedBindingValue",
typeof(object),
typeof(BindingResolver),
new PropertyMetadata(default));
}

DependencyProperty default value depends on coercion logic

I have this simple example in the ViewModel of a WPF application:
class VM_DiskPartition : DependencyObject
{
// (...) Other properties
public bool IsLowOnSpace
{
get { return (bool)GetValue(IsLowOnSpaceProperty); }
set { SetValue(IsLowOnSpaceProperty, value); }
}
public static readonly DependencyProperty IsLowOnSpaceProperty = DependencyProperty.Register("IsLowOnSpace", typeof(bool), typeof(VM_DiskPartition), new PropertyMetadata(false, OnLowOnSpaceChanged));
private static void OnLowOnSpaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
((VM_DiskPartition)d).CoerceValue(BgColorProperty);
}
public Brush BgColor
{
get { return (Brush)GetValue(BgColorProperty); }
set { SetValue(BgColorProperty, value); }
}
public static readonly DependencyProperty BgColorProperty = DependencyProperty.Register("BgColor", typeof(Brush), typeof(VM_DiskPartition), new PropertyMetadata(Brushes.Red, null, Coerce_BgColor));
private static object Coerce_BgColor(DependencyObject d, object baseValue)
{
return UIUtils.GetBgColor(((VM_DiskPartition)d).IsLowOnSpace);
}
}
I want the BgColor property to have its default value automatically set by its coercion function.
Is there a more elegant way to achieve this instead of calling CoerceValue(BgColorProperty) from the constructor?
The reason is that I may have many properties like this in the future and it doesn't look very clean to use a lot of CoerceValue() calls in the constructor.
Maybe it's better to use Converters in this scenario? I was trying to go without them and create new ViewModel properties instead.
You seem to be somewhat confused... the DependencyObject and DependencyProperty classes are UI classes. They don't belong in a view model. In view models, we use normal CLR properties and the INotifyPropertyChanged interface to handle property change notification. Therefore, there's no need to use them in a view model at all.
If you want to set a default value in a view model, you simply do this:
private int number = 5; // <-- default value
public int Number
{
get { return number; }
set { number = value; NotifyPropertyChanged("Number"); }
}
If you want property value coercion in a view model, you just do this:
public int Number
{
get { return number; }
set { number = Math.Max(0, value); NotifyPropertyChanged("Number"); }
}
UPDATE >>>
Looking again at your code, it occurs to me that it shouldn't be in a view model at all. It looks like it should be in the code behind of some UserControl. We put data in view models, not UI elements like Brushes. If you want to set a default value for a DependencyProperty, the correct way to do it is how you have shown us:
public static readonly DependencyProperty BgColorProperty =
DependencyProperty.Register("BgColor", typeof(Brush), typeof(VM_DiskPartition),
new PropertyMetadata(Brushes.Red/* <-- default value */, null, Coerce_BgColor));
Property coercion is for ensuring that a value stays within certain bounds like the example I gave above that ensures that the value will never be negative.

Bridge DependencyProperty from two binding sources

Consider this scenario, using MVVM:
On my ModelView, I have one property, of type "string", it does notify the change of properties through INotifyPropertyChanged.
In the view, there is (or not) one control, with a DependencyProperty "Notification" of a type which is not a string. That control may or may not change that property depending on facts that only the control knows (neither the ModelView or the View knows about those). That control might even be on other View which may or may not be on the current visual tree.
In the View, I need a bridge between that control's DependencyProperty and the ViewModel's property, so that changing the view property makes the control change its property, and changing the control's DependencyProperty makes the viewmodel's property change its value.
I've got it to work, but I don't think it's an elegant solution. I might be thinking fuzzy these days so I'm asking if there's something obvious that I might have missed.
The obvious way would be either having the ViewModel Property be a DependencyProperty (so it could be bound two ways), however that is not possible right now (plus, it'd break the MVVM pattern, adding view-specific implementations to the viewmodel).
The other obvious way would be binding the Control's DependencyProperty to the ViewModel's property: this works, but just for one view... several properties cannot (or, I don't know how to do it) be bound to the same DependencyProperty: when I set one binding, I lose the other.
Currently this is what I do:
public class BaseViewUserControl : UserControl
{
// Dependency property, bound to the view's property
public string AudioNotification
{
get { return (string)GetValue(AudioNotificationProperty); }
set { SetValue(AudioNotificationProperty, value); }
}
public static readonly DependencyProperty AudioNotificationProperty = DependencyProperty.Register("AudioNotification", typeof(string), typeof(BaseViewUserControl), new FrameworkPropertyMetadata("None", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnAudioNotificationPropertyChanged));
// Dependency property, bound to the control's dependency property
public AudioNotificationType AudioNotificationToControl
{
get { return (AudioNotificationType)GetValue(AudioNotificationToControlProperty); }
set { SetValue(AudioNotificationToControlProperty, value); }
}
public static readonly DependencyProperty AudioNotificationToControlProperty = DependencyProperty.Register("AudioNotificationToControl", typeof(AudioNotificationType), typeof(BaseViewUserControl), new FrameworkPropertyMetadata(AudioNotificationType.None, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, OnAudioNotificationToControlCoerceValue));
// Converter
private static IValueConverter _audioNotificationTypeConverter;
private static IValueConverter AudioNotificationTypeConverter
{
get { return _audioNotificationTypeConverter ?? (_audioNotificationTypeConverter = new AudioNotificationConverter()); }
}
private Binding _audioNotificationBinding;
private bool PrepareAudioNotificationControlBinding()
{
if (_audioNotificationBinding != null) return true;
var b = this.FindVisualTreeRoot().TryFindChild<AudioNotification>();
if (b == null) return false;
_audioNotificationBinding = new Binding { Source = b, Mode = BindingMode.TwoWay, Path = new PropertyPath("Notification") };
SetBinding(AudioNotificationToControlProperty, _audioNotificationBinding);
return true;
}
private static void OnAudioNotificationPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (!(source is BaseViewUserControl)) return;
var src = (BaseViewUserControl)source;
if(src.PrepareAudioNotificationControlBinding())
{
var val = AudioNotificationTypeConverter.ConvertValue<AudioNotificationType>(e.NewValue);
src.AudioNotificationToControl = val;
}
}
private static object OnAudioNotificationToControlCoerceValue(DependencyObject source, object basevalue)
{
if (!(source is BaseViewUserControl)) return basevalue;
var src = (BaseViewUserControl)source;
var val = AudioNotificationTypeConverter.ConvertBackValue<string>(basevalue);
src.AudioNotification = val;
return basevalue;
}
public BaseViewUserControl()
{
var ab = new Binding { Path = new PropertyPath("AudibleNotification"), Mode = BindingMode.TwoWay };
SetBinding(AudibleNotificationProperty, ab);
}
}
NOTE: I'm using this for several things, not just for audio notification (that's an example only). Do not rely on the names to give a solution (if any), this needs to be quite generic. Also, any typos come from simplifying the code to the problem (I've removed much code and changed some property names for clarification).
As I said, it works... I just find it quite not-elegant and I'm sure there should be a better solution than this.
Any suggestions will be more than welcome.
Update
Based on Julien's code, I made this Behavior, which does exactly what I wanted. I implemented it using Converter, but for clarity's sake, I ended up doing the conversion on the control itself and using strings to pass variables along (with an undocumented property in the control's if I still want to use the native data type)
public class BridgePropertyBinderBehavior : Behavior<DependencyObject>
{
public static BridgePropertyBinderBehavior PrepareBindingToControl(FrameworkElement sourceView, string viewModelPropertyPath, FrameworkElement targetControl, string controlPropertyPath)
{
var b = new BridgePropertyBinderBehavior();
BindingOperations.SetBinding(b, AProperty, new Binding(viewModelPropertyPath) { Source = sourceView.DataContext, Mode = BindingMode.TwoWay, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
BindingOperations.SetBinding(b, BProperty, new Binding(controlPropertyPath) { Source = targetControl, Mode = BindingMode.TwoWay });
Interaction.GetBehaviors(sourceView).Add(b);
return b;
}
public object A { get { return GetValue(AProperty); } set { SetValue(AProperty, value); } }
public static readonly DependencyProperty AProperty = DependencyProperty.Register("A", typeof(object), typeof(BridgePropertyBinderBehavior), new FrameworkPropertyMetadata(null, (d, e) => ((BridgePropertyBinderBehavior)d).OnAChanged(e.NewValue)));
public object B { get { return GetValue(BProperty); } set { SetValue(BProperty, value); } }
public static readonly DependencyProperty BProperty = DependencyProperty.Register("B", typeof(object), typeof(BridgePropertyBinderBehavior), new FrameworkPropertyMetadata(null, (d, e) => ((BridgePropertyBinderBehavior)d).OnBChanged(e.NewValue)));
private void OnAChanged(object value) { B = value; }
private void OnBChanged(object value) { A = value; }
protected override Freezable CreateInstanceCore()
{
return new BridgePropertyBinderBehavior();
}
}
Which I use like this on my view:
var audioNotificationControl = this.FindVisualTreeRoot().TryFindChild<AudioNotification>();
BridgePropertyBinderBehavior.PrepareBindingToControl(this, "AudioNotification", audioNotificationControl, "Notification");
or
<AudioNotification x:Name="Control">
<ia:Interaction.Behaviors>
<BridgePropertyBinderBehavior
A="{Binding Path=Notification, ElementName=Control, Mode=TwoWay}"
B="{Binding Path=AudioNotification, Mode=TwoWay}" />
</ia:Interaction.Behaviors>
</AudioNotification>
I've accepted his answer since it's what got me on the right track, thanks
If I understand you correctly, you need to bind one DP to two sources, one as a source and the other as a target. I actually have a behavior to do that.
The principle of this behavior is quite simple: it uses two dependency properties and makes the data of one (In) flows into the other (Out). Bind In with a one way binding and Out with a one way to source binding and you're done.
public class BindingBehavior : Behavior<DependencyObject> {
public static readonly DependencyProperty InProperty = DependencyProperty.Register(
"In",
typeof(object),
typeof(BindingBehavior),
new FrameworkPropertyMetadata(null, (d, e) => ((BindingBehavior) d).OnInPropertyChanged(e.NewValue)));
public static readonly DependencyProperty OutProperty = DependencyProperty.Register(
"Out",
typeof(object),
typeof(BindingBehavior),
new FrameworkPropertyMetadata(null));
// Bind OneWay
public object In {
get { return GetValue(InProperty); }
set { SetValue(InProperty, value); }
}
// Bind OneWayToSource
public object Out {
get { return GetValue(OutProperty); }
set { SetValue(OutProperty, value); }
}
private void OnInPropertyChanged(object value) {
Out = value;
}
protected override Freezable CreateInstanceCore() {
return new BindingBehavior();
}
}
This behavior needs a reference to System.Windows.Interactivity from Blend SDK, which you might be familiar with.
Assuming you remove your string property and only keep a AudioNotificationType one named AudtioNotification, the usage should be similar to:
<YourView x:Name="View">
<YourControl x:Name="Control" AudioNotification="{Binding Notification, ElementName=View}>
<i:Interaction.Behaviors>
<BindingBehavior
In="{Binding AudioNotification, ElementName=Control, Mode=OneWay}"
Out="{Binding YourVmProperty, Mode=OneWayToSource, Converter=YourConverter}" />
</i:Interaction.Behaviors>
</YourControl>
</YourView>
You can place the behavior on any element being on the correct name scope for resolving element names and having the view model as the data context.
This looks like it might be a useful time to add a layer of abstraction. I know. Ick. But bear with me.
What if you had a bridge object that an abitrary number of things can bind to that handles the notifications on change. It doesn't even need to be that complex. Just something that implements INotifyPropertyChanged and then has a property (or properties) that release a notification on change. That way, your ViewModel, your View and your control can all bind to the same property on this bridge object and when one of those changes the bridge object's property, all the others will know that it's time to change as well. As long as all the objects are bound two-way, everything should synch just fine.
That's essentially what you've done on your BaseViewUserControl, but encapsulating the behavior in a separate object might provide you flexibility benefits.

How can I update my usercontrol when I set a value in a dependency object?

This is the part where is not working. My dependency property has a default value which is Entradas.Entero, and that value must be run this line:
Grid.SetColumnSpan(button0, 3);
And it should refresh it in my user control design, however there's no changes in it.
public partial class TableroUserControl : UserControl
{
public enum Entradas
{
Entero, Decimal
}
public Entradas Entrada
{
get { return (Entradas)GetValue(EntradaProperty); }
set { SetValue(EntradaProperty, value); }
}
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
Button button0 = ((TableroUserControl)property).button0;
switch ((Entradas)args.NewValue)
{
case Entradas.Entero:
Grid.SetColumnSpan(button0, 3);
break;
case Entradas.Decimal:
Grid.SetColumnSpan(button0, 2);
break;
}
}
public static readonly DependencyProperty EntradaProperty =
DependencyProperty.Register("Entrada", typeof(Entradas), typeof(TableroUserControl), new PropertyMetadata(Entradas.Entero, new PropertyChangedCallback(textChangedCallBack)));
public TableroUserControl()
{
InitializeComponent();
}
}
You might also consider doing this with a value converter. You should be able to bind the Grid.ColumnSpan attached property of button0 to the Entrada property of your user control. Then use a value converter to convert it to an integer. This way you don't have to deal with callbacks and state/timing issues.
Your dependency property is initialized to Entero. So unless the value is changed to Decimal and then again changed back to Entero you wont hit the property changed callback code.
Make sure that the colspan setter code is hit.

Categories