I spent the last hour(s) trying to find an answer in google and stackoverflow. I followed different advices & suggestions, but nothing worked so far. My current code looks like this:
public class GlobalManager : ViewModelBase
{
static object _LockObject_GFS = new object();
static double _GlobalFontSize;
public static double GlobalFontSize
{
get
{
lock (_LockObject_GFS)
{
_GlobalFontSize = GetGlobalResource<double>(LambdaHelper.MemberToString(() => GlobalFontSize));
return _GlobalFontSize;
}
}
set
{
lock (_LockObject_GFS)
{
if (_GlobalFontSize != value)
{
_GlobalFontSize = value;
SetGlobalResource(value, LambdaHelper.MemberToString(() => GlobalFontSize));
NotifyStaticPropertyChanged(() => GlobalFontSize);
}
}
}
}
}
The getter & setter are both called. NotifyStaticPropertyChanged works and my UI does not update. I've added a TextBlock to check if it updates. Apparently it does not.
<TextBlock Text="{Binding Path=(global:GlobalManager.GlobalFontSize), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
If I define a property in my VM (current DataContext), and bind it to a TextBlock, it updates correctly with the current value.
Currently the DependencyProperty Value of a Slider is bound to this property in order to update the font size. (IsSnapToTickEnabled="True")
public double GlobalFontSize
{
get { return GlobalManager.GlobalFontSize; }
set { GlobalManager.GlobalFontSize = value; NotifyPropertyChanged(() => GlobalFontSize); }
}
How do I get the binding to work correctly with the static property ? The StaticPropertyChanged event is not null.
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
Edit 1:
public static void NotifyStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
public static void NotifyStaticPropertyChanged<T>(Expression<Func<T> > property)
{
var expr = property.Body as MemberExpression;
if (expr == null)
throw new ArgumentException("Lambda does not contain member expression. () => MyClassOrObject.Property");
NotifyStaticPropertyChanged(expr.Member.Name);
}
Make sure that your GetGlobalResource and SetGlobalResource methods work as expected and that your event signature is correct.
You could refer to the below working sample implementation and compare it to yours:
public class GlobalManager
{
static object _LockObject_GFS = new object();
static double _GlobalFontSize;
public static double GlobalFontSize
{
get
{
lock (_LockObject_GFS)
{
return _GlobalFontSize;
}
}
set
{
lock (_LockObject_GFS)
{
if (_GlobalFontSize != value)
{
_GlobalFontSize = value;
NotifyStaticPropertyChanged(()=> GlobalFontSize);
}
}
}
}
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
public static void NotifyStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
public static void NotifyStaticPropertyChanged<T>(Expression<Func<T>> property)
{
var expr = property.Body as MemberExpression;
if (expr == null)
throw new ArgumentException("Lambda does not contain member expression. () => MyClassOrObject.Property");
NotifyStaticPropertyChanged(expr.Member.Name);
}
}
Edit: It doesn't work if the event is defined in a base class though.
public abstract class MyBaseViewModel
{
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
public static void NotifyStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
public static void NotifyStaticPropertyChanged<T>(Expression<Func<T>> property)
{
var expr = property.Body as MemberExpression;
if (expr == null)
throw new ArgumentException("Lambda does not contain member expression. () => MyClassOrObject.Property");
NotifyStaticPropertyChanged(expr.Member.Name);
}
}
public class GlobalManager : MyBaseViewModel
{
static object _LockObject_GFS = new object();
static double _GlobalFontSize = 10.0;
public static double GlobalFontSize
{
get
{
lock (_LockObject_GFS)
{
return _GlobalFontSize;
}
}
set
{
lock (_LockObject_GFS)
{
if (_GlobalFontSize != value)
{
_GlobalFontSize = value;
NotifyStaticPropertyChanged("GlobalFontSize");
}
}
}
}
}
The StaticPropertyChangedEvent must be defined in same class where property resides for the binding to get updated:
View is not getting notified when value of static Property Changes
Related
public class ServerState
{
public static Action stateChanged;
private string currentMap;
public string CurrentMap
{
get { return currentMap; }
set
{
currentMap = value;
stateChanged?.Invoke();
}
}
}
I have a class with dozens of variables that each need their own property so that I can invoke an action. In other words, I am going to repeat the above code dozens of times, and I feel that there should be a better way to do so.
Is there a shorthand for the above code?
Here are a couple of ideas:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace ConsoleApp1
{
public class ServerState
{
public static Action stateChanged;
private Dictionary<string, object> _values = new Dictionary<string, object>();
private void Set(object value, [CallerMemberName] string propertyName = null)
{
_values[propertyName] = value;
stateChanged?.Invoke();
}
private T Get<T>([CallerMemberName] string propertyName = null)
{
if (_values.TryGetValue(propertyName, out var v)) return (T)v;
throw new KeyNotFoundException(propertyName);
}
public string CurrentMap
{
get => Get<string>();
set => Set(value);
}
// You can make them in one line if you want
public string CurrentMap2 { get => Get<string>(); set => Set(value); }
}
public class ServerState2
{
public static Action stateChanged;
private string currentMap;
private void Set<T>(ref T property, T value)
{
property = value;
stateChanged?.Invoke();
}
public string CurrentMap
{
get => currentMap;
set => Set(ref currentMap, value);
}
}
}
And if you want to use more than one event, or track if the value actually changed you can expand on this idea:
public class ServerState
{
public static Action currentMapChanged;
public static Action currentMap2Changed;
private Dictionary<string, object> _values = new Dictionary<string, object>();
private void Set(object value, Action onChange, [CallerMemberName] string propertyName = null)
{
var previousValue = _values[propertyName];
// Check if value has changed
if (value != previousValue)
{
_values[propertyName] = value;
onChange?.Invoke();
}
}
private T Get<T>([CallerMemberName] string propertyName = null)
{
if (_values.TryGetValue(propertyName, out var v)) return (T)v;
throw new KeyNotFoundException(propertyName);
}
public string CurrentMap
{
get => Get<string>();
// Call `currentMapChanged` if value differs
set => Set(value, currentMapChanged);
}
// You can make them in one line if you want
// Call `currentMap2Changed` if value differs
public string CurrentMap2 { get => Get<string>(); set => Set(value, currentMap2Changed); }
}
Additionally, you should look into using the event keyword to stop other classes from firing this event. Watch out for static events too, they hold a reference to the listening delegate in a static context and require care to avoid memory leaks.
Given:
public class MyClass : INotifyPropertyChanged
{
public List<string> _TestFire = new List<string>();
string _StringProp;
public string StringProp
{
get
{
return _StringProp;
}
set
{
if (_StringProp != value)
{
_StringProp = value;
RaisePropertyChanged("StringProp");
_TestFire.Add("fired " + DateTime.Now);
}
}
}
}
When you serialize and then deserialize this class, it fires the RaisePropertyChanged event, which is undesirable. Is it possible to prevent this event from being fired when the class gets deserialized?
var MyclassInstance = new MyClass() { StringProp = "None" };
MyclassInstance._TestFire.Clear(); // Clear property change history
var serobj = JsonConvert.SerializeObject();
var newitem = JsonConvert.DeserializeObject<MyClass>(serobj);
// newitem._TestFire.Count == 1, the set method was executed
Is there a way to get a bool value if the class is being deserialized?
So then I could do:
set
{
if (_StringProp != value)
{
_StringProp = value;
if (!Deserializing)
{
RaisePropertyChanged("StringProp");
_TestFire.Add("fired " + DateTime.Now);
}
}
}
Yes, you can do what you want by implementing the OnDeserializing and OnDeserialized serialization callback methods in your class. In the OnDeserializing method, set your private _isDeserializing variable to true, and in OnDeserialized set it back to false. I would recommend doing the _isDeserializing check inside the RaisePropertyChanged method so you don't have duplicate code inside every property.
So you would end up with something like this:
public class MyClass : INotifyPropertyChanged
{
public List<string> _TestFire = new List<string>();
string _StringProp;
public string StringProp
{
get
{
return _StringProp;
}
set
{
if (_StringProp != value)
{
_StringProp = value;
RaisePropertyChanged("StringProp");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
// don't raise the event if the property is being changed due to deserialization
if (_isDeserializing) return;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
_TestFire.Add(propertyName + " was fired " + DateTime.Now);
}
bool _isDeserializing = false;
[OnDeserializing]
internal void OnDeserializingMethod(StreamingContext context)
{
_isDeserializing = true;
}
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
_isDeserializing = false;
}
}
Working demo: https://dotnetfiddle.net/QkOcF4
Another way you could solve this problem is to mark your public properties with [JsonIgnore] and then mark the corresponding backing fields with [JsonProperty], specifying the property name to use in the JSON. This would allow Json.Net to set the backing fields directly and not execute any of the mutator logic.
public class MyClass : INotifyPropertyChanged
{
public List<string> _TestFire = new List<string>();
[JsonProperty("StringProp")]
string _StringProp;
[JsonIgnore]
public string StringProp
{
get
{
return _StringProp;
}
set
{
if (_StringProp != value)
{
_StringProp = value;
RaisePropertyChanged("StringProp");
_TestFire.Add("StringProp was fired " + DateTime.Now);
}
}
}
...
}
Working demo: https://dotnetfiddle.net/jc7wDu
I'm trying to create a XF component whose some properties are of a type that inherits from BindableObject. For illustrating, I have class Shadow with double Radius and Color ShadowColor properties and a class MyBoxText, that have a bool IsLoading and a Shadow Ghost properties.
My View and it's Bindings is working as expected, but I have an issue with it's custom renderer:
When I change the Ghost properties I need to redraw the entire view (of the MyBoxText control), to visually update the shadow color, for example.
Here's some mcve:
Classes code:
public class MyBoxText : Label /* It's bindable by inheritance */
{
#region Properties
public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
public Shadow Ghost
{
get { return (Shadow)GetValue(GhostProperty); }
set { SetValue(GhostProperty, value); }
}
#endregion
}
public class Shadow : BindableObject /* It's explictly bindable */
{
#region Properties
public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black) ;
public Color ShadowColor
{
get { return (Color)GetValue(ShadowColorProperty); }
set { SetValue(ShadowColorProperty, value); }
}
public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create(nameof(ShadowRadius), typeof(double), typeof(Shadow), 20) ;
public double ShadowRadius
{
get { return (double)GetValue(ShadowRadiusProperty); }
set { SetValue(ShadowRadiusProperty, value); }
}
#endregion
public Shadow()
{
}
}
My renderer's code is like this:
public class MyBoxText : LabelRenderer
{
public MyBoxText()
{
SetWillNotDraw(false);
}
public override void Draw(Canvas canvas)
{
MyBoxText myView = (MyBoxText)this.Element;
// Some drawing logic
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == MyBoxText.IsLoadingProperty.PropertyName ||
e.PropertyName == MyBoxText.GhostProperty.PropertyName )
Invalidate();
}
}
The issue is that when I change the Ghost.ShadowColor property my 'OnElementPropertyChanged' override is not called, and the View stays with the old color on the screen.
Is there a way to propagate the child's 'Property Update' event to parent view 'Property Changed' or another way to achieve this?
The issue is that when I change the Ghost.ShadowColor property my 'OnElementPropertyChanged' override is not called, and the View stays with the old color on the screen.
Is there a way to propagate the child's 'Property Update' event to parent view 'Property Changed' or another way to achieve this?
Yes, there is a way. Since your Shadow inherits from BindableObject, which implements the INotifyPropertyChanged Interface. You can set notify ShadowColor change:
Add OnPropertyChanged() to Setter of ShadowColor in Shadow.cs:
public class Shadow : BindableObject /* It's explictly bindable */
{
#region Properties
public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black);
public Color ShadowColor
{
get { return (Color)GetValue(ShadowColorProperty); }
set { SetValue(ShadowColorProperty, value);
//Notify the ShadowColorProperty Changed
OnPropertyChanged();
}
}
...
}
Modify your MyBoxText.cs like this:
public class MyBoxText : Label /* It's bindable by inheritance */
{
...
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null);
public Shadow Ghost
{
get { return (Shadow)GetValue(GhostProperty); }
set {
//register the ShadowColor change event
value.PropertyChanged += ShadowColor_PropertyChanged;
SetValue(GhostProperty, value); }
}
private void ShadowColor_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
//unregister the event
this.Ghost.PropertyChanged -= ShadowColor_PropertyChanged;
//set this.Ghost to a new object with new ShadowColor to trigger the OnPropertyChanged
this.Ghost = new Shadow
{
ShadowColor = (sender as Shadow).ShadowColor,
ShadowRadius = Ghost.ShadowRadius
};
}
}
Thanks to Elvis's answer I got it. Based on his idea I've made some changing to reuse it on other components and I'm sharing it now just in case someone else needs something like this.
I thought that use it this way we could get a cleaner and simple code:
public class MyBoxText : Label /* It's bindable by inheritance */
{
// Added this as private property
private ChangingPropagator changingPropagator;
private ChangingPropagator ChangingPropagator
{
get
{
if (changingPropagator == null)
changingPropagator = new ChangingPropagator(this, OnPropertyChanged, nameof(Shadow.ShadowColor), nameof(Shadow.ShadowRadius));
return changingPropagator;
}
}
#region Properties
public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
public Shadow Ghost
{
// Here I use the ChangingPropagator's Getter and Setter instead of the deafult ones:
get { return ChangingPropagator.GetValue<Shadow>(GhostProperty); }
set { ChangingPropagator.SetValue(GhostProperty,ref value); }
}
#endregion
}
And it's the ChangingPropagator class:
public class ChangingPropagator
{
string[] listenedProperties = new string[0];
Action<string> changesNotifyer = null;
BindableObject propagationRootObject = null;
List<KeyValuePair<string, object>> propagationProperties = new List<KeyValuePair<string, object>>();
public ChangingPropagator(BindableObject bindableObject, Action<string> onPropertyChangedMethod, params string[] propertyToListenTo)
{
changesNotifyer = onPropertyChangedMethod;
propagationRootObject = bindableObject;
listenedProperties = propertyToListenTo ?? listenedProperties;
// ToDo: Add some consistency checks
}
public void AddPropertyToListenTo(params string[] propertyName)
{
listenedProperties = listenedProperties.Union(propertyName).ToArray();
}
// I need handle it here too 'cause when I use the child `Ghost` property coming from XAML binding, it didn't hit the `set` method
public T GetValue<T>(BindableProperty property)
{
var value = propagationRootObject?.GetValue(property);
if (value != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
{
bindableSubObject.PropertyChanged -= PropagatorListener;
bindableSubObject.PropertyChanged += PropagatorListener;
if (!propagationProperties.Any(a => a.Key == property.PropertyName))
propagationProperties.Add(new KeyValuePair<string, object>(property.PropertyName, value));
}
}
return (T)value;
}
public void SetValue<T>(BindableProperty property, ref T value)
{
var oldValue = propagationRootObject?.GetValue(property);
if (oldValue != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
bindableSubObject.PropertyChanged -= PropagatorListener;
}
if (value != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
{
bindableSubObject.PropertyChanged += PropagatorListener;
propagationProperties.RemoveAll(p => p.Key == property.PropertyName);
propagationProperties.Add(new KeyValuePair<string, object>(property.PropertyName, value));
}
}
propagationRootObject.SetValue(property, value);
}
private void PropagatorListener(object sender, PropertyChangedEventArgs e)
{
if (listenedProperties?.Contains(e.PropertyName) ?? true)
PropagationThrower(sender);
}
private void PropagationThrower(object sender)
{
if (propagationProperties.Any(p => p.Value == sender))
{
var prop = propagationProperties.FirstOrDefault(p => p.Value == sender);
changesNotifyer?.Invoke(prop.Key);
}
}
}
Please assume this entire question deals in code, without any XAML.
I have a static ObservableCollection named myStaticList. It's a part of a non-static class named myClass.
public class myClass
{
public static ObservableCollection<CheckBoxStructure> myStaticList { get; set; }
static myClass()
{
myStaticList = new ObservableCollection<CheckBoxStructure>();
}
}
And the definition of CheckBoxStructure:
public class CheckBoxStructure
{
public string Description { get; set; }
public bool IsSelected { get; set; }
}
In addition, there's an array of checkboxes called checkBoxArray[], holding 3 elements. each checkbox has as content a textbox.
What I want to do is programmatically bind (two-way) these two, in such a manner that the IsChecked property of the checkboxes in the checkBoxArray[] array will bind to the IsSelected property of the myStaticList's CheckBoxStructure, and similarly so between the text of the textboxes inthe checkboxes' content and the Description property of the myStaticList's CheckBoxStructure.
In addition, I would like to avoid using loops, since it is preferable that this two lists will update each other if they change in size.
How is this possible?
Thanks!
Using XAML, an easy way would be to the declare an ItemsControl and a DataTemplate for it so that you can have a UserControl (CheckBox and TextBox inside) with its DataContext being a CheckBoxStructure. This way the bindings work between CheckBox.IsChecked and IsSelected property and between TextBox.Text and Description property.
If you need to this only in code then you would have to create same behavior (ItemsControl with a DataTemplate). You have at least 2 options
1.
DataTemplate template = new DataTemplate();
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
template.VisualTree = factory;
FrameworkElementFactory childFactory = new FrameworkElementFactory(typeof(CheckBox));
childFactory.SetBinding(CheckBox.IsChecked, new Binding("IsSelected"));
factory.AppendChild(childFactory);
childFactory = new FrameworkElementFactory(typeof(TextBox));
childFactory.SetBinding(Label.ContentProperty, new Binding("Description"));
factory.AppendChild(childFactory);
2.
MemoryStream sr = null;
ParserContext pc = null;
string xaml = string.Empty;
xaml = "<DataTemplate><StackPanel><TextBlock Text="{Binding Description"/><CheckBox IsChecked="{Binding IsSelected"/></StackPanel></DataTemplate>";
sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
pc = new ParserContext();
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
DataTemplate datatemplate = (DataTemplate)XamlReader.Load(sr, pc);
this.Resources.Add("dt", datatemplate);
Later edit, after discussion from comments; this example works only one way of binding but is easily to make it two ways. Please note that this is only a trivial example of a concept and is not complete: you need to modify the list classes to suit how you wish for objects to be paired, you may need to add more guards for corner cases, you may need to make it thread safe and so on...
First the basic binding objects:
class Binder
{
public Binder()
{
_bindings = new Dictionary<string, List<string>>();
}
private INotifyPropertyChanged _dataContext;
public INotifyPropertyChanged DataContext
{
get { return _dataContext; }
set
{
if (_dataContext != null)
{
_dataContext.PropertyChanged -= _dataContext_PropertyChanged;
}
_dataContext = value;
_dataContext.PropertyChanged += _dataContext_PropertyChanged;
}
}
void _dataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_bindings.ContainsKey(e.PropertyName))
{
var bindableType = _dataContext.GetType();
var bindableProp = bindableType.GetProperty(e.PropertyName);
if (bindableProp == null)
{
return;
}
var binderType = this.GetType();
foreach (var binderPropName in _bindings[e.PropertyName])
{
var binderProp = binderType.GetProperty(binderPropName);
if (binderProp == null)
{
continue;
}
var value = bindableProp.GetValue(_dataContext);
binderProp.SetValue(this, value);
}
}
}
Dictionary<string, List<string>> _bindings;
public void AddBinding(string binderPropertyName, string bindablePropertyName)
{
if (!_bindings.ContainsKey(bindablePropertyName))
{
_bindings.Add(bindablePropertyName, new List<string>());
}
_bindings[bindablePropertyName].Add(bindablePropertyName);
}
}
class Bindable : INotifyPropertyChanged
{
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then the holding lists for them:
class BindableList<T> : List<T> where T : Bindable
{
public event Action<T> ItemAdded;
public new void Add(T item)
{
base.Add(item);
NotifyItemAdded(item);
}
private void NotifyItemAdded(T item)
{
if (ItemAdded != null)
{
ItemAdded(item);
}
}
}
class BinderList<T> : List<T> where T : Binder
{
public BinderList()
{
_bindingRules = new Dictionary<string, string>();
}
private BindableList<Bindable> _dataContextList;
public BindableList<Bindable> DataContextList
{
get { return _dataContextList; }
set
{
if (_dataContextList != null)
{
_dataContextList.ItemAdded -= _dataContextList_ItemAdded;
}
_dataContextList = value;
_dataContextList.ItemAdded += _dataContextList_ItemAdded;
}
}
void _dataContextList_ItemAdded(Bindable obj)
{
foreach (var pair in _bindingRules)
{
this[Count-1].AddBinding(pair.Key, pair.Value);
this[Count - 1].DataContext = obj;
}
}
private Dictionary<string, string> _bindingRules;
public void AddBindingRule(string binderPropertyName, string bindablePropertyName)
{
_bindingRules.Add(binderPropertyName, bindablePropertyName);
}
}
Now the actual classes with properties:
class BinderElement : Binder
{
private string _description;
public string Description
{
get { return _description; }
set { _description = value; }
}
}
class BindableElement : Bindable
{
private string _description;
public string Description
{
get
{
return _description;
}
set
{
_description = value;
NotifyPropertyChanged("Description");
}
}
}
And an example to use them:
static void Main(string[] args)
{
var bindableList = new BindableList<Bindable>();
var binderList = new BinderList<BinderElement>()
{
new BinderElement(),
new BinderElement()
};
binderList.DataContextList = bindableList;
binderList.AddBindingRule("Description", "Description");
bindableList.Add(new BindableElement());
bindableList.Add(new BindableElement());
((BindableElement)bindableList[1]).Description = "This should arrive in BinderElement Description property";
Console.WriteLine(binderList[1].Description);
Console.ReadLine();
}
I am trying to use twoway binding to a custom datasource (other than a db or xml where twoway binding is supported out of the box) in WPF to update the UI automatically when the source is updated and update the source when the object is changed in the UI.
What I currently have is a class, CacheObject, which implements INotifyPropertyChanged and a custom (derived) ObservableCollection, CacheWrapper, which subscribes to changes in the cache and updates the appropriate CacheObject which in turn automatically updates the UI.
Is there a smart way that I can build into my CacheWrapper a way to update the cache - make a call using the cache API - when a CacheObject is updated in the list. If I subscribe to the PropertyChanged events I will receive events both when the objects are updated from code (changes from cache) and from the UI. Basically I want to do one thing if the update comes from the source and another if it comes from the target.
Some code examples.
CacheObject
public class CacheObject : INotifyPropertyChanged
{
private object _key;
private object _value;
public event PropertyChangedEventHandler PropertyChanged;
public CacheObjectViewModel(object key, object value)
{
_key = key;
_value = value;
}
public CacheObjectViewModel(KeyValuePair<object, object> keyValuePair)
: this(keyValuePair.Key, keyValuePair.Value)
{
}
public object Key
{
get { return _key; }
set
{
_key = value;
OnPropertyChanged("Key");
}
}
public object Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
CacheWrapper
public class CacheWrapper : ObservableCollection<CacheObject>, ICacheObserver
{
private ICache _cache;
public String Name { get; private set; }
public CacheWrapepr(string name, ICache cache)
{
_cache = cache;
Name = name;
cache.AddListener(this);
}
public void CacheEntryInserted(CacheEventArgs eventArgs)
{
CacheObject cacheObject = eventArgs.NewElement as CacheObject;
if(cacheObject != null)
{
this.Add(cacheObject);
}
}
public void EntryUpdated(CacheEventArgs eventArgs)
{
CacheObject cacheObjectNew = eventArgs.NewElement as CacheObject;
if(cacheObjectNew != null)
{
CacheObject cacheObjectExisting = this.FirstOrDefault(x => x.Key == cacheObjectNew.Key);
if(cacheObjectExisting != null)
{
cacheObjectExisting.Value = cacheObjectNew.Value;
}
}
}
public void EntryDeleted(CacheEventArgs eventArgs)
{
CacheObject cacheObject= = eventArgs.OldElement as CacheObject;
if(cacheObject != null)
{
CacheObject cacheObjectExisting = this.FirstOrDefault(x => x.Key == cacheObject.Key);
if(cacheObjectExisting != null)
{
this.Remove(cacheObjectExisting);
}
}
}
}
Help and ideas are much appreciated.