I have a control bind to an index property of an object which implements INotifyPropertyChanged.
The problem is, I don't know how to notify the property changed signal for that particular index string.
I was told that I can use OnPropertyChanged("") to notify the whole object need to be changed.
But what I need is something like OnPropertyChanged("Some index property string").
Is there anyway to do it?
Many thanks.
ps:
What I am trying to do is apply MVVM pattern.
I use a viewmodel class to wrap a normal POCO object. So when I bind, I bind to the [index property], so that I can notify changed. This method saves me from:
wrap the inner domain POCO object for EVERY property I need.
notify property changed in every wrapped property.
CODE
public class ViewModelEx<T_Self, T_Core> : ViewModelEx<T_Self> where T_Self : ViewModelEx<T_Self, T_Core>
{
private static Type _s_coreType = typeof(T_Core);
private static Dictionary<string, PropertyInfo> _s_corePropInfos = new Dictionary<string, PropertyInfo>();
private static PropertyInfo GetPropertyInfo(string prop)
{
if (_s_corePropInfos.ContainsKey(prop) == false)
_s_corePropInfos.Add(prop, _s_coreType.GetProperty(prop));
return _s_corePropInfos[prop];
}
public T_Core Core { get; set; }
public object this[string propName]
{
get
{
return GetPropertyInfo(propName).GetValue(Core, null);
}
set
{
GetPropertyInfo(propName).SetValue(Core, value, null);
IsModified = true;
//RaisePropertyChanged(propName);
RaisePropertyChanged("");
}
}
public R Val<R>(Expression<Func<T_Core, R>> expr)
{
return (R)this[Core.GetPropertyStr(expr)];
}
public void Val<R>(Expression<Func<T_Core, R>> expr, R val)
{
this[Core.GetPropertyStr(expr)] = val;
}
You cannot create notifications for specific index bindings in WPF, you can only notify all index-bindings:
RaisePropertyChanged(Binding.IndexerName);
Which should be the same as:
RaisePropertyChanged("Item[]");
You could override this string using the IndexerNameAttribute.
(In Silverlight you can actually specify an index inside the brackets to only affect that specific binding.)
Related
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));
}
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.
I need key and Value property in all controls which are drives from FrameworkElement class in wpf. key and value property are needed for some internal purpose. I know we have a Tag property which is used for saving custom data. I need two more such properties.
Any suggestions?
You don't need to create another property, as you know Tag property will allow you to save custom data.
This is example of how you can store data in the Tag.
public class Customdata
{
public int Id { get; set; }
public int value { get; set; }
}
private void setDataInTag(FrameworkElement obj, Customdata objCustomData)
{
obj.Tag = objCustomData;
}
private Customdata GetValueFromElement(FrameworkElement obj)
{
Customdata objCustomData = new Customdata();
if (obj.Tag!=null && obj.Tag.GetType() == typeof(Customdata))
{
objCustomData = (Customdata)obj.Tag;
return objCustomData;
}
}
I think it is simple now :)
You should create your own attached property.
Just create new class, write propa codesnippet, press tab, tab :)
in xaml you can then set and get the property on any dependency object, just like you can use Grid.Column or Canvas.Left on any element.
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.
When .NET 4.5 was released i started using such great Attribute as CallerMemberName. It's easier to understand code, developers can write it faster also. It's like a snippet, not only a feature for debug/test purposes.
So I have a question. Is it normal to create and use something like this?
public class PropertyStore
{
Dictionary<string, object> data = new Dictionary<string,object>();
ViewModelBase modelBase;
internal PropertyStore(ViewModelBase _base)
{
modelBase = _base;
}
public void SetValue<T>(T value = default(T), [CallerMemberName] string prop = "")
{
T prev = GetValue<T>(prop);
if ((prev == null && value == null) || (prev != null && prev.Equals(value))) return;
data[prop] = value;
modelBase.OnPropertyChanged(prop);
}
public T GetValue<T>([CallerMemberName] string prop = "")
{
if (!data.ContainsKey(prop))
data[prop] = default(T);
return (T)data[prop];
}
}
Class-helper, that makes other class more readable, and also we have list of our properties without need to use Reflection.
The usage is:
public class SampleClass : ViewModelBase
{
PropertyStore PropertyStore;
public SampleClass ()
{
PropertyStore = new PropertyStore(this);
}
public string Key
{
get { return PropertyStore.GetValue<string>(); }
set { PropertyStore.SetValue(value); }
}
public DateTime Date
{
get { return PropertyStore.GetValue<DateTime>(); }
set { PropertyStore.SetValue(value); }
}
public bool IsSelected
{
get { return PropertyStore.GetValue<bool>(); }
set { PropertyStore.SetValue(value); }
}
}
The class ViewModelBase here simply implements INotifyPropertyChanged interface.
As I understand, this approach is something like Microsoft Dependency Properties, but I don't need all power of DependencyObject class, and I don't want inherit it.
With something like this I can use Binding, because it's enough to implement INotifyPropertyChanged, also we have no fields (as for me, i try to use properties smarter, than using fields directly (however, there is no problem to use Dictionary directly ^_^))
Sorry for my bad English... Not main language and not much practice.
Another Sample (after moving Methods to base class)
public class SampleClass : ViewModelBase
{
public string Key
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
public DateTime Date
{
get { return GetValue<DateTime>(); }
set { SetValue(value); }
}
public bool IsSelected
{
get { return GetValue<bool>(); }
set { SetValue(value); }
}
}
No diff with Microsoft's WPF Property System.
Only feature you'll get with it is an ability to access property values via Dictionary.Get|Set methods.
You can get this ability with field based implementation of INotifyPropertyChanged. You can access property values by its name using dictionary, with property name to precompiled delegate mapping like it done in Yappi project.
var dateValue= Property<SampleClass>.Get<DateTime>(this,"Date");
Property<SampleClass>.Set<DateTime>(this,"Date",DateTime.Now);
Both can be rewritten as extension methods.
Nice idea, property bag without reflection and it will even work with obfuscation.
I don't see major problems with it but you may consider the following:
The prop parameter is optional so potentially a bug can be introduced by given a value in the call.
Value types will get boxed.
Access to the fields is relatively more expensive, can be a factor more expensive as you have much more code in a simple get (especially with boxing).
Dictionary takes more space than the number of properties you keep in (especially with boxing).
Each property also stores a string of the property name adding to the overhead.