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}/>
Related
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);
}
}
}
I am working with WPF and MVVM model. I have a base viewmodel class called ViewModelBase. It has a property on it called Config that is a complex type. I need a derived class to be able to databind to the base class Config property in a View.
public class ViewModelBase : INotifyPropertyChanged
{
private Configuration _config;
public event PropertyChangedEventHandler PropertyChanged;
public Configuration Config
{
get { return _config; }
set
{
if(_config == null || !_config.Equals(value))
{
_config = value;
OnPropertyChanged(new PropertyChangedEventArgs("Config"));
}
}
}
public ViewModelBase()
{
}
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Databinding seems to be working in a read capacity, but when a property of the Config is altered in the OptionsView, the changes are not reflected in the Config itself. Any suggestions?
Configuration implementation, per request.
public class Configuration : IEquatable<Configuration>, INotifyPropertyChanged
{
private string _primaryUrl;
private string _secondaryUrl;
private DateTime _scheduledStart;
private DateTime _scheduledEnd;
private string _buffer;
private bool _isScheduleEnabled;
private int _logDays;
private int _retryDuration;
private int _maxRetryAttempts;
private string _registrationKey;
private string _email;
public string PrimaryURL
{
get { return _primaryUrl; }
set
{
if(_primaryUrl != value)
{
_primaryUrl = value;
OnPropertyChanged(new PropertyChangedEventArgs("PrimaryURL"));
}
}
}
public string SecondaryURL
{
get { return _secondaryUrl; }
set
{
if(_secondaryUrl != value)
{
_secondaryUrl = value;
OnPropertyChanged(new PropertyChangedEventArgs("SecondaryURL"));
}
}
}
public DateTime ScheduledStart
{
get { return _scheduledStart; }
set
{
if(_scheduledStart != value)
{
_scheduledStart = value;
OnPropertyChanged(new PropertyChangedEventArgs("ScheduledStart"));
}
}
}
public DateTime ScheduledEnd
{
get { return _scheduledEnd; }
set
{
if(_scheduledEnd != value)
{
_scheduledEnd = value;
OnPropertyChanged(new PropertyChangedEventArgs("ScheduledEnd"));
}
}
}
public string Buffer
{
get { return _buffer; }
set
{
if(_buffer != value)
{
_buffer = value;
OnPropertyChanged(new PropertyChangedEventArgs("Buffer"));
}
}
}
public bool IsScheduleEnabled
{
get { return _isScheduleEnabled; }
set
{
if(_isScheduleEnabled != value)
{
_isScheduleEnabled = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsScheduleEnabled"));
}
}
}
public int LogDays
{
get { return _logDays; }
set
{
if(_logDays != value)
{
_logDays = value;
OnPropertyChanged(new PropertyChangedEventArgs("LogDays"));
}
}
}
public int RetryDuration
{
get { return _retryDuration; }
set
{
if(_retryDuration != value)
{
_retryDuration = value;
OnPropertyChanged(new PropertyChangedEventArgs("RetryDuration"));
}
}
}
public int MaxRetryAttempts
{
get { return _maxRetryAttempts; }
set
{
if(_maxRetryAttempts != value)
{
_maxRetryAttempts = value;
OnPropertyChanged(new PropertyChangedEventArgs("MaxRetryAttempts"));
}
}
}
public string RegistrationKey
{
get { return _registrationKey; }
set
{
if(_registrationKey != value)
{
_registrationKey = value;
OnPropertyChanged(new PropertyChangedEventArgs("RegistrationKey"));
}
}
}
public string Email
{
get { return _email; }
set
{
if(_email != value)
{
_email = value;
OnPropertyChanged(new PropertyChangedEventArgs("Email"));
}
}
}
public Configuration() { }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Here is one of the culprit bindings:
<xctk:DateTimePicker Grid.Column="1" Value="{Binding Config.ScheduledStart}" Height="20" VerticalAlignment="Top"/>
The INotifyPropertyChanged implementation only applies to the class directly. So in your case, to the ViewModelBase class and its subtypes.
In this case, the PropertyChangedEvent is raised in the setter of the Config property, so whenever the Config property is set (and the setter is called), the event is raised.
This however does not mean that when mutating the Config object that the even is also raised. In general, this is not the case.
In order to raise the event when the Config object is changed, you would have to reassign the object to the view model (calling the setter again). This however does not work when data binding to the object.
A better solution is to make the Configuration implement INotifyPropertyChanged interface itself. So when a property within that object is changed, an event is raised as well. WPF will also recognize this for subobjects, so it will automatically work.
Databinding seems to be working in a read capacity..
Which is fine but if you want a change capacity, then the class Configuration will have to adhere to INotifyPropertyChanged and each property on the class needs to report PropertyChange notifications for any changes to be shown in bound xaml controls.
but when a property of the Config is altered in the OptionsView, the changes are not reflected
What you have now only notifies if the instance of Configuration has been replaced; not individual property changes within the current instance.
I do have a WPF binding question here.
Following Setup:
I do have a class (ActionService) having a name and a ObservableCollection of subitems (also a class named Step). A Step has a flag that shows if the Step is allready done (IsDone).
I bind a form to the ActionService and display all kind of things.
Everything works as aspected and i have just the essential parts in my snippet.
Now I need one more thing that i can not get work. I want the ActionService to know by binding how many of its Steps are open (IsDone == false). I you open a childform with one of the steps and change the IsDone-State, the mother form should get the new count on the fly.
And I'm to dumb to get a correct solution on the way ;-)
Thanks for your help or a best practise.
public class ActionService : BaseObject
{
public ActionService()
{
}
private String name;
public String Name
{
get { return this.name; }
set
{
this.name = value;
raisePropertyChanged("Name");
}
}
public ObservableCollection<Step> actionsteps;
public ObservableCollection<Step> ActionSteps
{
get { return this.actionsteps; }
set
{
this.actionsteps = value;
raisePropertyChanged("ActionSteps");
}
}
}
public class Step : BaseObject
{
public Step()
{
}
private String description;
public String Description
{
get { return this.description; }
set
{
this.description = value;
raisePropertyChanged("Description");
}
}
private Boolean isdone;
public Boolean IsDone
{
get { return this.isdone; }
set
{
this.isdone = value;
raisePropertyChanged("IsDone");
}
}
}
public class BaseObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void raisePropertyChanged(String parPropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(parPropertyName));
}
}
}
You can create a new property in your ActionService class:
public bool IsDone
{
get
{
return ActionSteps.Count(x => x.IsDone) == ActionSteps.Count;
}
}
If the count of Steps in the ActionSteps list where the IsDone property is true is equal to the number of Steps in the ActionSteps list, then return true, else, return false.
To subscribe to the Steps property changed event, when you add an item to the collection, you simply need to subscribe to the PropertyChanged event:
//Create the item and subscribe to propertychanged.
Step item = new Step();
item.PropertyChanged += item_PropertyChanged;
//Add the item to the list.
ActionSteps.Add(item);
And your method will look like this:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsDone")
raisePropertyChanged("IsDone");
}
How can I refresh the following ObservableCollection?
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties;
public ObservableCollection<ServerProperties> Properties
{
get
{
properties = new ObservableCollection<ServerProperties>();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
return properties;
}
}
}
public class ServerProperties
{
private string property;
private string value;
public ServerProperties()
{
}
public ServerProperties(string property, string value)
{
Property = property;
Value = value;
}
public string Property
{
get
{
return this.property;
}
set
{
this.property = value;
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
}
}
public override string ToString()
{
return string.Format("[Property : {0}]", Value);
}
}
I changed the value of textProperties[,] and now I'd like to overwrite the previous content of the collection with the current content of textProperties[,].
What would be the simplest way to do this?
Any help would be appreciated.
Start off by implementing INotifyPropertyChanged in your ViewModel as well as in the ServerProperties object. This way you can raise the PropetyChanged event which will pass back to the user interface.
ViewModel
public class ViewModelProperties : INotifyPropertyChanged {
public event ProeprtyChangedEventHandler PropertyChanged;
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties {
get { return properties;}
set {
properties = value;
this.RaisePropertyChangedEvent("Properties");
}
}
private void RaisePropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Implementing this on the ServerProperties object as well will allow you to change the objects, at any level, and have it bubble up to the interface.
Lastly look into your population code and in order to get the property to update successfully first populate it to a List then re-initialise the ObservableCollection using the List.
Properties = new ObservableCollection<ServerProperties>(propertiesList);
This also allows you to better handle the creation of your ObservableCollection and perform tests before posting the output to the interface. Hope it helps.
For example, one of the simpler solutions could be
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties
{
get
{
return properties;
}
}
public void SetProperties()
{
properties.Clear();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
}
}
Any time you wish to add new items to OC, just call the SetProperties method.
My view (InformationView) Binded to InformationViewModel and I use a nested class to maintain current Bank
My nested class :
public class MainController : NotificationObject
{
public MainController()
{
Initialize();
}
private void Initialize()
{
// TODO implement
}
public static MainController Instance
{
get { return Nested.instance; }
}
private BankModel _currentBank;
public BankModel CurrentBank
{
get { return _currentBank; }
set
{
if (_currentBank== value)
{
return;
}
_currentBank= value;
RaisePropertyChanged(() => CurrentBank);
}
}
private class Nested
{
static Nested()
{
}
internal static readonly MainController instance = new MainController();
}
}
My BankModel :
private string _name ="test";
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
_name= value;
RaisePropertyChanged(()=>Name);
}
}
My XAML
xmlns:Controller="clr-namespace:MyProject.Controller"
/****/
<Label Content="{Binding Controller:MainController.CurrentBank.Name}"/>
first I can't see the "test" in my label and if I execute I change this value and always my label is empty, how I do this with the correct approach
You need to use a combination of "Path" and "Source" in your binding declaration. You also need to alert the binding engine to the fact that you're accessing static members.
<Label Content="{Binding Source={x:Static Controller:MainController.Instance}, Path=CurrentBank.Name}" />