How to detect Clean, Dirty, Invalid status of View (not ViewModel)

Topics: Getting Started
Feb 24, 2013 at 4:55 PM
Consider this sequence of events:

User clicks in TextBox (View is Clean, ViewModel is Clean)
User edits value in TextBox (View is now Dirty)
User presses Tab, this triggers LostFocus and Binding.UpdateSource() (View is now Clean, but ViewModel is now Dirty)

The Binding object has all the information to determine this, but does nothing with it.

From the Users' perspective "Dirty" = ViewModel.IsDirty || View.IsDirty

Does CM have a solution to this?

Or, if not, can I control how CM sets up the Binding for certain Elements?

Then I could replace the WPF Binding with my own.
Feb 25, 2013 at 12:01 AM
I believe its been left up to the end developer, where the "Dirty" Mechanism should reside. Some say data layer some say viewmodel. etc. What ever is easiest to implement imo.
Feb 25, 2013 at 10:19 AM
Edited Feb 25, 2013 at 10:19 AM
Only the Binding knows the View's Dirty (and Valid) state

given that both the ViewModel and View needs this information

it would make sense for the Binding (or BindingGroup) to communicate this to the ViewModel

(in much the same way it communicates TextBox.Text to the ViewModel)

For most use cases the following would suffice:
public bool IsDirty { get; set; }
public bool IsValid { get; set; }
This could be convention-bound by CM

and then:
public bool CanSave { get { return IsDirty && IsValid; } }
public bool CanCancel { get { return IsDirty; } }
Without this mechanism the UI is not communicating with the User correctly and the experience won't be high-quality
Feb 25, 2013 at 12:43 PM
You can use the ValidationExceptionBehavior from Attributes-based Validation in a WPF MVVM Application to communicate validation errors to the ViewModel.
Feb 25, 2013 at 1:11 PM
tibel: thanks for the link but that is no help - it uses the standard (broken) WPF Binding behavior - just what I have already
Feb 25, 2013 at 3:15 PM
I am failing to explain the issue, please consider this image:

Image

The key thing is to understand how the idea of "Dirty" and "Valid" is handled during an edit-in-progress
with a TextBox with UpdateSourceTrigger = LostFocus (the default)

The behavior on the left is expected, but WPF apps tend to the behavior on the right (due to a design flaw in the Binding / BindingGroup classes)

WPF apps only correctly activate the buttons AFTER LostFocus

but that is confusing for users who expect things to work normally
Feb 25, 2013 at 3:25 PM
Regarding the lost focus issue: are you sure it is not enough to set the UpdateSourceTrigger to PropertyChanged in the Binding, where needed? Some controls (i.e. TextBox) by default use a OnFocusLost trigger, which is not desiderable in your case.
Feb 25, 2013 at 3:37 PM
Very sure

(1) I don't want to UpdateSource() during an Edit-in-progress, I only want to reflect the true Clean, Dirty, Invalid status of the form
(2) This will incorrectly update the TextBox with the reformatted value everytime the edit-in-progress reaches a valid format (which may not be the users final input)

This is a limitation of the built-in Binding class - for some strange reason MS did not realise there is a half-way between PropertyChanged and LostFocus which involves updating the status of the View (not ViewModel)

I have a working solution to this, by reimplementing Binding, but it is not pretty - I was hoping CM would have solved this
as my WPF apps can not pass quality control until they work as expected
Feb 25, 2013 at 3:52 PM
The real issue is not with Binding, in my opinion, but with the TextBox control itself. Or better, with any control used for input, where such input is not 'atomic'.
In a Checkbox, the UpdateSourceTrigger = PropertyChanged is perfectly fine, since there is no mid-way value to take into consideration. The TextBox is not so forgiving, since before reaching a valid value, different values can be provided.

That said, I would still go with an UpdateSourceTrigger=PropertyChanged (triggering the format update on focus lost, instead of during typing). Even if it is true that an invalid value can be triggered by a mid-way modification, it is still an invalid/dirty value from the view-model point of view.
Feb 25, 2013 at 4:12 PM
Edited Feb 25, 2013 at 4:16 PM
The TextBox is working exactly as it should

The Binding class is at fault for 2 reasons

(1) It wasn't designed correctly
(2) It's design is too sealed / black-boxy to allow us to fix it

My working solution is horrendous - I derive from Binding and then use two semi-clones of Binding
one with a dummy Source and one with a dummy Target
This allows me to get the current SourceValue and TargetValue
Whilst also triggering the default IConvert and Validation behavior
(But avoiding the unwanted UpdateSource())
Then I can perform a typed equality check

This means my WPF forms pass quality control, but the hack doesn't!

I can replace the default Binding like so:
ConventionManager.SetBinding = delegate (Type viewModelType, string path, PropertyInfo property, FrameworkElement element, ElementConvention convention, DependencyProperty bindableProperty) {
    Binding binding = new TestBinding(path);
    ConventionManager.ApplyBindingMode(binding, property);
    ConventionManager.ApplyValueConverter(binding, bindableProperty, property);
    ConventionManager.ApplyStringFormat(binding, convention, property);
    ConventionManager.ApplyValidation(binding, viewModelType, property);
    ConventionManager.ApplyUpdateSourceTrigger(bindableProperty, element, binding, property);
    BindingOperations.SetBinding(element, bindableProperty, binding);
};
Feb 25, 2013 at 8:44 PM
Solution I settled on: use PropertyChanged as you suggested
but expose types such as DateTime to the View as strings (for TextBoxes)

Turning off Binding's parsing, formatting and validation
Feb 25, 2013 at 8:49 PM
I used a similar approach, but embedded parsing in a control, and dealt with formatting on the control itself (it is a constrained TextBox with additional properties for numerical/DateTime/TimeSpan values), so that I could retain consistent values on the VM (I don't use validation, tho... If I have to deal with it, I suppose I would find a way to provide validation on the internal Text property).