I don't think a PropertyChanged event is raised when the various Is___ properties change. This is a holdover from .NET behavior.
However, CslaDataProvider exposes the Is___ properties (and many other useful properties) for data binding.
This is supported by binding to the CslaDataProvider or
ViewModel class.
The limitation is that those properties (along with other
metaproperties) don’t raise PropertyChanged when they change.
I may alter that behavior at some point. The choice to not raise
the event for those properties can be traced back to data binding, and in part
to Windows Forms.
There’s a Browsable(false) attribute on those properties,
which prevents them from appearing automatically when the object is dragged
onto a form using the VS designer tools. That’s generally a good thing,
as otherwise you’d have to manually remove a bunch of properties every
time you drag on object onto a form.
What was unexpected (even by the Msft PM who originally owned
this area) is that binding is disabled for these properties even if you
manually set up the binding later…
So raising PropertyChanged for unbindable properties is
pointless, so CSLA doesn’t do it.
The thing is, the properties should be bindable, but should not
be Browsable – because otherwise the UI design experience is terrible.
Sadly, Browsable(false) blocks binding – at least in Windows Forms.
In XAML I don’t think it blocks binding, which is why I
may reconsider raising PropertyChanged for those properties. That still won’t
fix Windows Forms – I think the “fix” for Windows Forms is to
let it slowly fade away as we all move to XAML :)
Hi Rocky
Any news on this? Apologies if this is something that's been implemented for CSLA 4.0 - haven't had the chance to test this out on 4.0 yet.
I'm have a Silverlight project using CSLA 3.8.4 and the issue remains. I'm binding directly to the business object (MVVM will have to wait for later when we can do the whole project), and the property change notifications for the IsDirty, IsSavable, etc. properties aren't happening.
I have a question (based on your earlier comments) --> since Windows Forms binding considers the properties "unbindable", would it not be OK/safe to have CSLA raise the property change notifications anyway?
That way Silverlight / WPF can gain the benefit, without breaking the Windows Forms story? Is this something that can be considered for >= CLSA 3.8.4 ?
Thank you.
Jaans
Which property of ViewModel are you binding to?
I ask, because I have several examples where I have basically the same UI in WPF and SL, so the concept is sound. If there's a specific property that's not working that could be a bug.
The CanSave/CanXXX methods do work very well and also updates the UI binding when the underlying business object becomes Dirty/Valid/Auth.
But today on another project, I again needed the ability to not only bind to "IsDirty" but also have that binding refresh when the business object becomes dirty. It's a scenario where CanSave does not suffice because the object was dirty but not valid. So here CanSave would be false, but I needed to indicate to the user that there had been a change and that he/she should not just rely on the CanSave indicator to perhaps figure out that a dependant property needs to be updated or something to make it valid.
The key thing for me is to be able to indicate to the user that there are changes (IsDirty) irrespective of whether the object is valid yet.
So while I can bind to the IsDirty property in Silverlight, it doesn't do a PropertyChanged for IsDirty effectively leaving the bound UI out of date.
As Rocky states it's all about the [Browsable(false)] attribute - I recall a post flying by a short while ago about using an alternate attribute to get a same/similar result but for the life of me I cannot find that post or remember the attribute name. (Perhaps is was a dream/nightmare)
Does anyone now of an alternate attribute / implementation that may help us have our windows forms cake and eat it under a silverlight sky?
I'm late to the party on this one, but I had a similar situation. I wanted to bind to the IsValid property to display in a Silverlight DataGrid. It only showed the original value and didn't update the grid cell when IsValid changed. Luckily I have a custom base class for all my business objects, so I just did this:
protected
override void OnValidationComplete()
{
// IsValid isn't Bindable - changes to the IsValid state don't raise a PropertyChanged event, so make it do so.
OnPropertyChanged(
"IsValid");
base.OnValidationComplete();
}
The only answer in 3.8 or 4 is to have an intermediate object (CslaDataProvider or ViewModelBase<T> or something of your own design) that "elevates" the properties to a bindable status.
Thanks Rocky
I'm a little unclear how I would be able to do this for something like IsDirty. I mean, how would my view model know that this property has changed in the underlying model?
Look at ViewModelBase to see how it works.
Basically it listens for PropertyChanged and ChildChanged, and then checks to see if IsDirty changed (among other metastate properties).
Had a look and it seems interesting enough.
You have quite a bit of work in the CSLA baseclass to re-wrap existing properties from the business object. To extend that I could create an intermediate generic base class for that above and inherit from it. I wil inturn inherit from the CSLA ViewModelBase<T>.
(PS: As a policy we treat CSLA as a black box, leave it unmodified and only reference by assembly - this is easier on the juniors).
From a guidance point of view, would you agree an appropriate implementation would override the OnModelChanged method to re-use the existing logic to unhook and rehook the change events from the (changed) underlying model instance?
Maybe something along these lines...
#region
private bool _isDirty = false; /// <summary>
/// Gets a value indicating whether the Model is dirty or not
/// </summary>
public bool IsDirty
{
get { return _isDirty; }
private set
{
if ( _isDirty != value )
{
_isDirty = value;
OnPropertyChanged( "IsDirty" ); // Inherited from base class
}
}
}
#endregion #region
/// <summary>
/// Called when the underlying model changes.
/// </summary>
/// <param name="oldValue">The old model value.</param>
/// <param name="newValue">The new model value.</param>
protected override void OnModelChanged( T oldValue, T newValue )
{
base.OnModelChanged( oldValue, newValue );
// Extend with additional property change "listening"
SetCustomProperties();
}
{
ITrackStatus targetObject = Model as ITrackStatus;
if ( Model != null && targetObject != null )
{
IsDirty = targetObject.IsDirty;
}
}
#endregion
Looking at it all, most of the properties currently on the ViewModelBase are sufficient, and I just have a recurring need to track and show the current values for IsDirty, IsSelfDirty, IsValid, IsSelfValid as the object is edited - CanSave doesn't quite do it. The same goes for ease of use items that are negated like IsNotDirty, IsNotValid.
Not sure if others have a similar need for it - if it turns out to be so, perhaps it is something for the wish list - don't want to bloat the API unnessecarily.
Thanks for the help,
Jaans
I discovered a major oversight in the above and that's the fact that I'm not participating in the property changed event.
So my alternatives would be to either:
a) In the override for OnModelChanged, duplicate the logic from the ViewModelBase class and hook + unhook my own events so that I can do my own "SetProperties()" for the additional properties I would like; or
b) if it ViewModelBase was a bit more extensible, for example like making the "private void SetProperties()" virtual so that I can override it, it would certainly be a lot easier. An alternate thought here is to introduce a "protected virtaul OnPropertiesSet()" method and have SetProperties call it after setting all the properties.
Thoughts?
b) if it ViewModelBase was a bit more extensible, for example like making the "private void SetProperties()" virtual so that I can override it, it would certainly be a lot easier. An alternate thought here is to introduce a "protected virtaul OnPropertiesSet()" method and have SetProperties call it after setting all the properties.
+ 1
We too require "IsDirty" and "IsValid" at View Model level so making SetProperties() virtual would be ideal. Currently we have had to copy and modifiy CSLAs ViewModelBase which is not ideal.
In Csla 2.x we used IsValid property extensively to provide useful feedback. In my App, IsSavable is great but it would be useful to have bindable access to IsValid. After going through this thread, I added the following to my ViewModel class from which all other VMs are subclassed.
private bool _IsValid = false;
public bool IsValid
{
get
{
return _IsValid;
}
private set
{
_IsValid = value;
OnPropertyChanged("IsValid");
}
}
protected override void OnModelChanged(T oldValue, T newValue)
{
base.OnModelChanged(oldValue, newValue);
ITrackStatus targetObject = Model as ITrackStatus;
if (Model != null && targetObject != null)
{
var npc = newValue as INotifyPropertyChanged;
if (npc != null)
IsValid = targetObject.IsValid;
}
}
I have set breakpoints in the IsValid.Set and in the OnModelChanged Method. As a test I have a couple of checkboxes with the following Binding:
IsChecked="{Binding IsValid, Source={StaticResource RootObjectViewModel}}"
When the Object Graph is loaded from the DB, the checkboxes are appropriately checked, and the code breaks - more than once actually.
The part of object graph that I am working with is:
Root - Child - Child - ChildCollection - Child
I add new child to ChildCollection (AddNewCore - Child.NewChild() - DataPortal.CreateChild() - base.Child_Create and then CheckRules), Child appears in the UI, with a broken rule indication for a required field. I do not see the code breaking in my code. Using another breakpoint, I can tell that the RootObjectViewModel is NOT valid. But the test checkBoxes are still checked. (which is not a surprise because my code in vm didn't run). The Save button with a binding to CanSave is now disabled.
Instead of entering data in child, I click Delete which call ChildCollection.Remove(), the rest of the execution is not in my code. The child disappears. My RootObjectViewModel is Valid again. No breaks in my code. The CheckBoxes didn't even blink. Save button is enabled again.
I am thinking:
a. My code above may not be exactly right.
b. Something is missing in my object codes - Everything seems to run fine all the way to DB.
c. something alse ?
Jav
Hi Jav
Based on what you've posted above I suspect that you (like I did in my initial post) missed the hooking up of the various "changed" events for a model so that you could catch the property notifications.
Note that essentially OnModelChanged only fires when the actual "Model" object instance is replaced with a whole new object like what happens when you Save or Fetch. Given that, while you are working with the same instance you need to be hooked into about 3 or 4 different change events to ensure you can catch the property changes (of the given instance).
Hooking into these events is all fine and dandy, but its quite important to only subsribe to these events when the model changes so that you can be hooked into the change events of the "new" model instance, but equally as important you need to unsubscribe from the "old' events to prevent not only the listening to events from the wrong object instance but also to avoid a type of memory leak scenario where you keep the object referenced and the garbage collector won't pick it up when it comes around.
Here's our implementation of the ViewModel Base class that we inherit from (Note this inturn inherits from CSLA's ViewModel<T>). There are a couple of other properties that I've found I regularly need to bind to in XAML and also some properties that have been "inverted" for ease of use in XAML again. Feel free to discard them.
I can email you the class file if the formatting below is too screwed up.
public abstract class ViewModel<T> : Csla.Silverlight.ViewModel<T>
{
#region Custom Property Remapping
/// <summary>
/// Gets a value indicating whether this instance's model is empty.
/// </summary>
/// <value>
/// <c>true</c> if this instance is model empty; otherwise, <c>false</c>.
/// </value>
public bool IsModelEmpty
{
get { return Model == null; }
}
/// <summary>
/// Gets a value indicating whether this instance is not busy.
/// </summary>
/// <value>
/// <c>true</c> if this instance is not busy; otherwise, <c>false</c>.
/// </value>
/// <remarks>
/// Refer to the overridden property changed event to hook this property's change notification to IsBusy
/// </remarks>
public bool IsNotBusy
{
get { return !IsBusy; }
}
private bool _isDirty = false;
/// <summary>
/// Gets a value indicating whether the Model is dirty or not
/// </summary>
public bool IsDirty
{
get
{
return _isDirty;
}
private set
{
if ( _isDirty != value )
{
_isDirty = value;
OnPropertyChanged( "IsDirty" ); // Inherited from base class
OnPropertyChanged( "IsNotDirty" );
}
}
}
/// <summary>
/// Gets a value indicating whether this instance is not dirty.
/// </summary>
/// <value>
/// <c>true</c> if this instance is not dirty; otherwise, <c>false</c>.
/// </value>
public bool IsNotDirty
{
get { return !IsDirty; }
}
private bool _isValid = true;
/// <summary>
/// Gets a value indicating whether the Model is valid or not
/// </summary>
public bool IsValid
{
get
{
return _isValid;
}
private set
{
if ( _isValid != value )
{
_isValid = value;
OnPropertyChanged( "IsValid" ); // Inherited from base class
OnPropertyChanged( "IsNotValid" );
OnPropertyChanged( "ValidationRuleSummary" );
}
}
}
/// <summary>
/// Gets a value indicating whether this instance is not valid.
/// </summary>
/// <value>
/// <c>true</c> if this instance is not valid; otherwise, <c>false</c>.
/// </value>
public bool IsNotValid
{
get { return !IsValid; }
}
/// <summary>
/// Gets the validation rule summary.
/// </summary>
/// <value>The validation rule summary.</value>
public virtual string ValidationRuleSummary
{
get
{
var businessBase = Model as BusinessBase;
if ( businessBase == null )
return string.Empty;
var brokenRules = new StringBuilder();
foreach ( var brokenRule in businessBase.BrokenRulesCollection )
brokenRules.AppendLine( string.Format( "{0}", brokenRule.Description ) );
return brokenRules.ToString();
}
}
#endregion
#region Overrides - Add additional hooks for properties on Model not available on ViewModelBase
/// <summary>
/// Called when the underlying model changes.
/// </summary>
/// <param name="oldValue">The old model value.</param>
/// <param name="newValue">The new model value.</param>
protected override void OnModelChanged( T oldValue, T newValue )
{
// Retain existing logic that unhooks the change events from the old model object and re-hooks it for the new one.
base.OnModelChanged( oldValue, newValue );
if ( ReferenceEquals( oldValue, newValue ) )
return;
// Extend with additional property change "listening"
base.OnPropertyChanged( "IsModelEmpty" );
// Unhook events from old value
if ( oldValue != null )
{
var npc = oldValue as INotifyPropertyChanged;
if ( npc != null )
npc.PropertyChanged -= Model_PropertyChanged;
var ncc = oldValue as INotifyChildChanged;
if ( ncc != null )
ncc.ChildChanged -= Model_ChildChanged;
var nb = oldValue as INotifyBusy;
if ( nb != null )
nb.BusyChanged -= Model_BusyChanged;
var cc = oldValue as INotifyCollectionChanged;
if ( cc != null )
cc.CollectionChanged -= Model_CollectionChanged;
}
// Hook events on new value
if ( newValue != null )
{
var npc = newValue as INotifyPropertyChanged;
if ( npc != null )
npc.PropertyChanged += Model_PropertyChanged;
var ncc = newValue as INotifyChildChanged;
if ( ncc != null )
ncc.ChildChanged += Model_ChildChanged;
var nb = newValue as INotifyBusy;
if ( nb != null )
nb.BusyChanged += Model_BusyChanged;
var cc = newValue as INotifyCollectionChanged;
if ( cc != null )
cc.CollectionChanged += Model_CollectionChanged;
}
SetCustomProperties();
}
private void Model_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
{
SetCustomProperties();
}
private void Model_BusyChanged( object sender, BusyChangedEventArgs e )
{
SetCustomProperties();
}
private void Model_ChildChanged( object sender, ChildChangedEventArgs e )
{
SetCustomProperties();
}
private void Model_PropertyChanged( object sender, PropertyChangedEventArgs e )
{
SetCustomProperties();
}
private void SetCustomProperties()
{
ITrackStatus targetObject = Model as ITrackStatus;
if ( Model != null && targetObject != null )
{
if ( CanEditObject )
IsDirty = targetObject.IsDirty;
if ( CanEditObject )
IsValid = targetObject.IsValid;
}
}
protected override void OnPropertyChanged( string propertyName )
{
base.OnPropertyChanged( propertyName );
// Extend with additional logic
if ( propertyName == "IsBusy" )
base.OnPropertyChanged( "IsNotBusy" );
}
#endregion
}
fwiw, this is on the wish list: http://www.lhotka.net/cslabugs/edit_bug.aspx?id=817
fwiw, this is on the wish list: http://www.lhotka.net/cslabugs/edit_bug.aspx?id=817
Thanks Rocky!
Ps: You can use the sample implementation I posted here if it makes the above easier / quicker to do one day. We've tested it quite extensively.
Jaans,
I plugged you code in and it worked the very first time.
Thanks
Jav
Jaans,
Thank you so much for your detailed explanation and the code. It's perfectly legible. It would be an immense help.
Rocky - thanks for putting it on the wish list. Those of us who grew up using Csla are essentially hooked on this Isvalid/IsDirty thing, and our users have loved the visual feedback telling them when a given object in a large object graph is in a valid or invalid state, especially if those objects are scattered over multiple navigation pages.
Jav
Copyright (c) Marimer LLC