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);
}
}
}
Related
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
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 have currently the problem that my UI doesnt update as I like to do so, hope you can help me out.
I have a simulated "2 class inheritance" as recommended in this page
http://www.codeproject.com/Articles/10072/Simulated-Multiple-Inheritance-Pattern-for-C
My real life app looks like the following:
public class Item : INotifyPropertyChanged
{
private bool _isVisible;
public bool IsVisible
{
get
{
return _isVisible;
}
set
{
if (_isVisible == value)
return;
_isVisible = value;
OnPropertyChanged("IsVisible");
}
}
//NotifyPropertyChanged Implementation removed so the focus stays on problem...
}
public class ObjectItem : INotifyPropertyChanged
{
private bool _isExpanded;
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
if (_isExpanded== value)
return;
_isExpanded= value;
OnPropertyChanged("IsExpanded");
}
}
//NotifyPropertyChanged Implementation removed so the focus stays on problem...
}
public class CombinedItem : Item
{
private readonly ObjectItem _objectItem = new ObjectItem();
public bool IsExpanded
{
get { return _objectItem.IsExpanded; }
set { _objectItem.IsExpanded = value; }
}
public static implicit operator ObjectItem(CombinedItem combinedItem)
{
return combinedItem._objectItem;
}
}
I am now facing the problem that the Property IsExpanded doesnt get Notified to the UI correctly when I have a CominedItem as the DataContext, the IsVisible Property works as expected.
To overcome the problem I have changed the CominedItem to the following:
public class CombinedItem : Item
{
private readonly ObjectItem _objectItem = new ObjectItem();
public bool IsExpanded
{
get { return _objectItem.IsExpanded; }
set
{
if (_objectItem.IsExpanded == value)
return;
_objectItem.IsExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
public static implicit operator ObjectItem(CombinedItem combinedItem)
{
return combinedItem._objectItem;
}
}
Is there a way to avoid writing the OnPropertyChanged("IsExpanded") again, with this approach.
(I know there are libaries/tools, where you dont need to write it at all and just have to declare a attribute, pls dont suggest those)
Actually you should subscribe to ObjectItem PropertyChanged and raise the matching event on CombinedItem.
If _objectItem.IsExpanded is modified without using CombinedItem.IsExpanded, your UI will not see the change.
Without some magic attribute/tool if you want to wrap a property, you will have to handle changes notification.
public class CombinedItem : Item
{
private readonly ObjectItem _objectItem = new ObjectItem();
public CombinedItem()
{
_objectItem.PropertyChanged += (s, e) =>
{
if (e.PropertyName == "IsExpanded")
OnPropertyChanged("IsExpanded");
}
}
public bool IsExpanded
{
get { return _objectItem.IsExpanded; }
set { _objectItem.IsExpanded = value; }
}
public static implicit operator ObjectItem(CombinedItem combinedItem)
{
return combinedItem._objectItem;
}
}
You could expose ObjectItem as a property and bind to that
public class CombinedItem : Item
{
private readonly ObjectItem _objectItem = new ObjectItem();
public bool IsExpanded
{
get { return _objectItem.IsExpanded; }
set { _objectItem.IsExpanded = value; }
}
public ObjectItem ObjectItem
{
get { return _objectItem; }
}
public static implicit operator ObjectItem(CombinedItem combinedItem)
{
return combinedItem._objectItem;
}
}
<Expander IsExpanded={Binding ObjectItem.IsExpanded}/>
I declare two Dependency properties:
first, FilterColor of type Color
and second FilterBrush of type Brush.
I need to update value of FilterColor when FilterBrush.Color property has changed, and I need to update value of FilterBrush.Color when FilterColor property has changed.
How I can realize it?
Bind your two properties with TwoWay binding and if you change one in the UI change the other in the properties setter and Vice Versa, and use INotifyPropertyChanged to Notify your UI that the property changed.
You can either do it in the DependencyProperty definition, or you can use a DependencyPropertyDescriptor to do it afterwards
For example....
DependencyProperty Definition:
public static readonly DependencyProperty FilterColorProperty =
DependencyProperty.Register("FilterColor", typeof(Color),
typeof(MyUserControl),
new PropertyMetadata(Colors.White, FilterColorChanged));
public static void FilterColorChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is MyUserControl))
return;
MyUserControl ctrl = (MyUserControl)obj;
var brush = ctrl.GetBrushProperty();
if (brush.Color != (Color)e.NewValue)
brush.Color = (Color)e.NewValue;
}
DependencyProperty Descriptor:
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(
MyUserControl.FilterColorProperty, typeof(MyUserControl));
if (dpd != null)
dpd.AddValueChanged(this, delegate { FilterColor_Changed(); });
...
private void FilterColor_Changed()
{
Color filterColor = GetFilterColor(this);
var brush = GetBrush(this);
if (filterColor != brush.Color)
brush.Color = filterColor;
}
I may have a few syntax errors... I don't have a compiler to check the code
Where did you define these properties: in view model or in control?
If it's in view model you should use INotifyPropertyChanged instead of dependency properties like this:
Color _filterColor;
public Color FilterColor
{
get
{
return _filterColor;
}
{
if (_filterColor != value)
{
_filterColor = value;
RaisePropertyChanged(() => FilterColor);
_OnFilterColorChanged();
}
}
void _OnFilterColorChanged()
{
_filterBrush= ...
RaisePropertyChanged(() => FilterBrush);
}
Brush _filterBrush;
public Brush FilterBrush
{
get
{
return _filterBrush;
}
{
if (_filterBrush != value)
{
_filterBrush = value;
RaisePropertyChanged(() => FilterBrush);
_OnFilterBrushChanged();
}
}
void _OnFilterBrushChanged()
{
_filterColor= ...
RaisePropertyChanged(() =. FilterColor);
}
If it is in control do this:
public Color FilterColor
{
get { return (Color)GetValue(FilterColorProperty); }
set { SetValue(FilterColorProperty, value); }
}
public static readonly DependencyProperty FilterColorProperty =
DependencyProperty.Register("FilterColor", typeof(Color), typeof(MainWindow), new UIPropertyMetadata(Colors.Transparent, new PropertyChangedCallback(_OnFilterColorPropertyChanged)));
static void _OnFilterColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mw = d as MainWindow;
Color oldValue = (Color)e.OldValue;
Color newValue = (Color)e.NewValue;
if (null != mw && oldValue != newValue)
{
mw._OnFilterColorChanged(oldValue, newValue);
}
}
bool _isFilterColorUpdating = false;
void _OnFilterColorChanged(Color oldValue, Color newValue)
{
if (_isFilterBrushUpdating )
return;
_isFilterColorUpdating = true;
Brush = ...
_isFilterColorUpdating = false;
}
public Brush FilterBrush
{
get { return (Brush)GetValue(FilterBrushProperty); }
set { SetValue(FilterBrushProperty, value); }
}
public static readonly DependencyProperty FilterBrushProperty =
DependencyProperty.Register("FilterBrush", typeof(Brush), typeof(MainWindow), new UIPropertyMetadata(Brushs.Transparent, new PropertyChangedCallback(_OnFilterBrushPropertyChanged)));
static void _OnFilterBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mw = d as MainWindow;
Brush oldValue = (Brush)e.OldValue;
Brush newValue = (Brush)e.NewValue;
if (null != mw && oldValue != newValue)
{
mw._OnFilterBrushChanged(oldValue, newValue);
}
}
bool _isFilterBrushUpdating = false;
void _OnFilterBrushChanged(Brush oldValue, Brush newValue)
{
if (_isFilterColorUpdating )
return;
_isFilterBrushUpdating = true;
Color = ...
_isFilterBrushUpdating = false;
}
Note that last way is just hack and it is really bad way, I would prefer the first way.
I've got a VM class below that's being used to wire up a view to my ADO.NET Entity Data Model, utilizing a P_BUDGET class. This works fine, gets my data, makes everything pretty. I have about 15-20 pages that are all going to be based on the same structure as this, and the code is going to be virtually identical except for the EntityType (P_BUDGET, P_ACCOUNT, P_CIRCUIT, etc etc). I feel like there should be a way to abstract this out, but I tried, and failed miserably! I also feel like I should be able to use one view, one viewmodel, and just swap out the entities binding to the GV... I just haven't been able to find a way to variablize the type, which permeates the entire viewmodel.
Appreciate your help,
Scott
public class TestViewModel : ViewModelBase
{
private readonly ODADomainContext _context = new ODADomainContext();
private readonly DomainCollectionView<P_BUDGET> _view;
private readonly DomainCollectionViewLoader<P_BUDGET> _loader;
private readonly EntityList<P_BUDGET> _source;
private bool _isGridEnabled;
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
this._source = new EntityList<P_BUDGET>(this._context.P_BUDGETs);
this._loader = new DomainCollectionViewLoader<P_BUDGET>(
this.LoadSampleEntities,
this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<P_BUDGET>(this._loader, this._source);
INotifyCollectionChanged notifyingSortDescriptions =
(INotifyCollectionChanged)this.CollectionView.SortDescriptions;
notifyingSortDescriptions.CollectionChanged +=
(sender, e) => this._view.MoveToFirstPage();
using (this.CollectionView.DeferRefresh())
{
this._view.PageSize = 10;
this._view.MoveToFirstPage();
}
}
#region View Properties
public bool IsGridEnabled
{
get
{
return this._isGridEnabled;
}
private set
{
if (this._isGridEnabled != value)
{
this._isGridEnabled = value;
this.RaisePropertyChanged("IsGridEnabled");
}
}
}
public ICollectionView CollectionView
{
get { return this._view; }
}
#endregion
private LoadOperation<P_BUDGET> LoadSampleEntities()
{
this.IsGridEnabled = false;
return this._context.Load(
this._context.GetBudgetsQuery());
}
private void OnLoadSampleEntitiesCompleted(LoadOperation<P_BUDGET> op)
{
this.IsGridEnabled = true;
if (op.HasError)
{
// TODO: handle errors
_view.PageSize = 0;
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this._source.Source = op.Entities;
_view.PageSize = 10;
this._view.MoveToFirstPage();
if (op.TotalEntityCount != -1)
{
this._view.SetTotalItemCount(op.TotalEntityCount);
}
}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}
Try something like this. This is not tested (obviously), not even complied. This also assumes that the EntityTypes (P_BUDGET, P_ACCOUNT, P_CIRCUIT etc.) are not POCOs.
public class TestViewModel<TEntity> : ViewModelBase
{
private readonly ODADomainContext _context = new ODADomainContext();
private readonly DomainCollectionView<TEntity> _view;
private readonly DomainCollectionViewLoader<TEntity> _loader;
private readonly EntityList<TEntity> _source;
private bool _isGridEnabled;
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
this._source = new EntityList<TEntity>(this._context.GetEntitySet<TEntity>);
this._loader = new DomainCollectionViewLoader<TEntity>(
this.LoadSampleEntities,
this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<TEntity>(this._loader, this._source);
INotifyCollectionChanged notifyingSortDescriptions =
(INotifyCollectionChanged)this.CollectionView.SortDescriptions;
notifyingSortDescriptions.CollectionChanged +=
(sender, e) => this._view.MoveToFirstPage();
using (this.CollectionView.DeferRefresh())
{
this._view.PageSize = 10;
this._view.MoveToFirstPage();
}
}
#region View Properties
public bool IsGridEnabled
{
get
{
return this._isGridEnabled;
}
private set
{
if (this._isGridEnabled != value)
{
this._isGridEnabled = value;
this.RaisePropertyChanged("IsGridEnabled");
}
}
}
public ICollectionView CollectionView
{
get { return this._view; }
}
#endregion
private LoadOperation<TEntity> LoadSampleEntities()
{
this.IsGridEnabled = false;
return this._context.Load(
this._context.GetBudgetsQuery());
}
private void OnLoadSampleEntitiesCompleted(LoadOperation<TEntity> op)
{
this.IsGridEnabled = true;
if (op.HasError)
{
// TODO: handle errors
_view.PageSize = 0;
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this._source.Source = op.Entities;
_view.PageSize = 10;
this._view.MoveToFirstPage();
if (op.TotalEntityCount != -1)
{
this._view.SetTotalItemCount(op.TotalEntityCount);
}
}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}
// http://blog.zoolutions.se/post/2010/04/05/Generic-Repository-for-Entity-Framework-for-Pluralized-Entity-Set.aspx
public static class ObjectContextExtensions
{
internal static EntitySetBase GetEntitySet<TEntity>(this ObjectContext context)
{
EntityContainer container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
Type baseType = GetBaseType(typeof(TEntity));
EntitySetBase entitySet = container.BaseEntitySets
.Where(item => item.ElementType.Name.Equals(baseType.Name))
.FirstOrDefault();
return entitySet;
}
private static Type GetBaseType(Type type)
{
var baseType = type.BaseType;
if (baseType != null && baseType != typeof(EntityObject))
{
return GetBaseType(type.BaseType);
}
return type;
}
}