From my reading and trying to understand and implement MVVM and validation, I need some help on the following scenario.
View - User interface (expected)
Model - based on a data table, and via OnColumnChanging, tests for validation requirements on a column-by-column basis (this part works no problem)
ViewModel - glue the connects the model to the view, this works too.
For each of the textbox controls, I have them respectively binding two-way to the data table, current row (still no problem) and it shows the data as expected. The flags including: NotifyOnTargetUpdated, ValidatesOnDataErrors, ValidatesOnExceptions and NotifyOnValidationError are all set to true.
If I use the interface and put in invalid value, it properly triggers the validation and puts red border around control showing it failed.
Now the problem. I start my form, and click the "Add" button (new record, blank values), textbox controls now enabled for editing content. Click the "Save" button. I want to have all the controls refreshed that are missing "required" data. Since my view model is bound to columns of the data table, and that from my readings, all the validation should be handled in the viewmodel, how should I adjust my scenario.
I don't know how to force which controls are bound to respective controls when they've never received focus. In addition, from other readings, to allow for unit testing, you should be able to test under the assumption that there is never a user interface. So, in theory, I could automate creating my viewmodel, which loads my data model, adds a record, tries to save and forces whatever to test all "required" fields.
Am I close??? way off??? Not quite positive on this.
Implement: IDataErrorInfo and check Error within your SaveCommand
Further Reading:
WPF Apps With The Model-View-ViewModel Design Pattern
Using IDataErrorInfo for validation in MVVM with Silverlight and WPF
Validation should be done in two places. One in ModelView (User input validation), second on Model model consistency validation, if required for specific scenario.
You have every text box (TextProperty) is bound to the ModelView's property. TextBox (I presume) has a dependency property which lets to you specify ether to signal error on UI or not.
What you have to do, IMHO, immidiately inside Add event handler set default values to binded ModelView object. What will happen is: for every control, including required one, will be settuped, thow via DataBinding visualized on UI, default value. For required fields the default value can be just not valid one, so requierd field will be immediately appear with Error signal, signaling a user about presence of mandatory fields.
Related
I have a need to create a custom version of the UWP DataGridNumericalColumn that allows customization of the RadNumericBox properties (ValueFormat, ButtonsVisibility, SmallChange, LargeChange, Value) as well as the ability to edit the value as cents (199) without decimal place while editing, but display as normal dollars with decimal cents (1.99) while not editing. I've tried two different approaches to extend existing controls, neither of which I can seem to get to work fully for me.
1) Tried deriving from DataGridNumericalColumn - impossible due to inaccessible internal members down the line, even with full source code from GitHub available.
2) Tried deriving from DataGridTemplateColumn - somewhat workable for initial display, but everything relating to inline edit mode vs display mode and validation message display on the cell seems beyond reach (not override-able) and I can't seem to use CellContentTemplateSelector to choose between the inline Edit mode RadNumericBox display and the normal TextBlock display because I can't seem to detect when Edit mode is applied to the cell.
It is starting to seem like the only way I can achieve what I need is to fork the GitHub code-base so I can derive from DataGridNumericalColumn with access to internal code.
What approach could I take to achieve my desired customizations?
(I am using Telerik UI For Universal Windows Platform, version 2017.1.301.45, at the time of this writing.)
I eventually worked out workarounds that let me get past the main difficulties in extending this functionality using the derive from DataGridTemplateColumn approach. Here are the updates and customizations I made - they are described mostly at the conceptual level, but it should be enough for others to duplicate this sort of customization for themselves.
UPDATE1:
An update as I've been working on this:
Continuing along with the approach of deriving from DataGridTemplateColumn, I found that I can successfully change my displayed markup for edit mode vs display mode by creating custom commands for editing operations in the grid (CustomBeginEditCommand, CustomCancelEditCommand, and CustomCommitEditCommand very similar to the ones in http://docs.telerik.com/devtools/universal-windows-platform/controls/raddatagrid/features/commands/editing-commands/datagrid-editingcommands-begineditcommand ) along with an interface IItemAwareOfEditMode, applied to the ViewModel items for the Grid's data, that has a single bool property IsInEditMode that I set to true or false appropriately in the custom commands, which is then used in a custom DataTemplateSelector to decide when to apply my edit markup vs my display markup. This uses (DataTemplate)XamlReader.LoadWithInitialTemplateValidation(editControlMarkup) for translating dynamically created markup strings to DataTemplates. For my implementation, I create the markup in a PropertyChangedCallback for my custom column's PropertyNameProperty dependency property.
However, I still am having issues with validation and displaying the validation messages, and reverting values when the user cancels edit. I have the ViewModel for the grid row items implemented such that they derive from ValidateViewModelBase, and so they add/remove errors appropriately according to the documentation on validation at http://docs.telerik.com/devtools/universal-windows-platform/controls/raddatagrid/features/validation . If I use the DataGridNumericalColumn (not customized) with the same data, the validation messages do appear pointing to the cell when the data is invalid, but with my custom column, HasErrors is true on the items, but the validation messages don't appear. Looking at the validation code in https://github.com/telerik/UI-For-UWP/blob/master/Controls/Grid/Grid.UWP/View/Columns/TypedColumns/DataGridTypedColumn.cs in the CreateEditorContainer function, it seems there is an EditRowHostPanel and ValidationControl involved along with the editor content, but I don't have access to pieces needed to implement the container exactly as is done there.
What can I do to get the validation messages to appear as they do in the DataGridNumericalColumn?
Also, what can I do to make cancelling an edit (clicking the blue X for the row when in edit mode) actually revert my custom column's value to what it was prior to entering edit mode?
UPDATE2:
Another update as I've been working on this:
Continuing along with the approach of deriving from DataGridTemplateColumn, I've managed to successfully display validation messages for the edit mode by including a ValidationControl in the edit mode template markup, which references the RadNumericBox from the template (by Name) using the ControlPeer property, and giving its DataItem property a value of "{Binding}", and appropriately populating its PropertyName.
This is getting close to what I need, but it seems that my CustomCancelEditCommand, which uses
Owner.CommandService.ExecuteDefaultCommand(CommandId.CancelEdit, context);
, does not appropriately update the display of the cell to its previous value. It correctly doesn't call the CustomCommitEditCommand when the inline row edit is cancelled; however, it displays as the modified value (not reverted to the value prior to edit). Even if you edit again, the value remains as the modified value when displayed in the grid.
I see that in https://github.com/telerik/UI-For-UWP/blob/master/Controls/Grid/Grid.UWP/View/Services/Commands/Editing/CancelEditCommand.cs in the Execute method, that it executes its base implementation, followed by
Owner.editService.CancelEdit(context.TriggerAction)
, which I don't understand (RadDataGrid does not contain a definition for editService and I can't derive from that CancelEditCommand class because it is internal).
What can I do to make cancelling an edit (clicking the blue X for the row when in edit mode) actually revert my custom column's value to what it was prior to entering edit mode?
UPDATE3:
I have finally managed an EXTENSIVE workaround that does revert my custom columns' value on cancel.
My workaround for the cancel functionality involved:
1) Created a CustomRadDataGrid, which derives from RadDataGrid.
2) Gave my CustomRadDataGrid class a CustomEditingService property, which is a CustomEditingService, which is copied and modified code from EditingService (mostly commenting out unneeded parts, but also changing InitializeEditOperation's implementation and changing CancelEdit to have an out parameter of the operation's OriginalValues dictionary), and which derives from CustomServiceBase<RadDataGrid>, which is copied and modified code from ServiceBase (changed IsOperational to return Owner.DataContext != null), which derives from CustomAttachableObject<T> where T : RadControl, which is copied code from AttachableObject.
3) Added GetActualValueForInstance function and SetActualValueForInstance method to my custom column, which uses reflection to get/set the data row instance's value for this column (based on using my PropertyName Dependency Property's value), and made the InitializeEditOperation of my CustomEditingService just save original values of my custom columns, and made the CancelEdit of my CustomEditingService return that dictionary of original values in an out variable.
4) Made my CustomBeginEditCommand call BeginEdit on the grid's CustomEditingService after calling Owner.CommandService.ExecuteDefaultCommand(CommandId.BeginEdit, context) - that allows my custom column original values to be stored.
5) Made my CustomCommitEditCommand call CommitEdit on the grid's CustomEditingService after calling Owner.CommandService.ExecuteDefaultCommand(CommandId.CommitEdit, context) - that allows my custom editing service to properly track its editing state.
6) Made my CustomCancelEditCommand call CancelEdit on the grid's CustomEditingService AND for each original value dictionary item, use the Key (column, as my custom column) SetActualValueForInstance passing in context.CellInfo.Item and the Value (previously stored original value), BEFORE calling Owner.CommandService.ExecuteDefaultCommand(CommandId.CancelEdit, context) - that restores my custom column original values prior to the standard cancel actions occurring.
Done! Whew... It seems that this library needs a lot of changes to allow for better extend-ability. This has been logged as a feature request at Telerik according to a reply to my support ticket with them on this subject.
I think other people will want to be able to extend Telerik's various DataGridColumn controls too, so I shared my struggle and (eventually) successful customization here.
Mvvm, Wpf, VS2015
Hi All! I have googled this till my fingers bleed! But just hacks and work-arounds, or partial use of code behind.
My Wpf-Mvvm app uses Speech Synthesis.
Each button, (and in the next iteration, buttons using the selectedItem Index of Lists or ComboBoxes, to select the content(text)to be synthesized) specifies different content to be synthesized.
Sticking with Mvvm principles, in my viewModel, I need to capture which button, based on the List or ComboBoxes' SelectedItem/Value that is bound to the Parameter Command.
Why? So I can have the app synthesize the content(text/words)the user selects to hear. If not, I have to make separate commands and speech synthesizer methods for each.
There must be a simple clear-cut way. What am I missing here?
I thought of updating the Model's properties to reflect user's selection and use User Settings to persist,but have not tried yet.
Otherwise the Model's properties are accessible so far as encapsulation-wise to the ViewModel, but the property values from the Model still are not visible in viewModel.
I need the command bound control's ID/Name to route the content to be synthesized based on the model's class properties. But when I instantiate the model from the viewModel,
the model's properties are not there. I presume instantiating the Model class object from the viewModel, is in a different memory location, than the Model.
So I will try using User Setting's persistence for these properties, if no one else has a better way.
There has to be a clear solution to this, I hope :)
I know the View and ViewModel are supposed to not need to know what the other is doing. In this case though, I clearly need the List or ComboBoxes' Id or Name and their SelectedItem index.
I also need the button's Id or Name, because I have several groupings of content to choose from, and several buttons starting the synthesizing method.
Example: If the text content of an announcement to be synthesized, held in separate XML(SSML) files with identifier key values of 941, 23, 508, etc.,
I need to know which the User Selected to hear.
I imagine simply capturing in the viewModel, the Data-bound List/ComboBoxes' Selected ID index would suffice.
Thank you so very much for your help.
-Flazz
I'm trying to get the essence of my last question and bring some more clarity to the description.
Given is an WPF form to submit new data with two textfields and a "Save" button. Both textfields are required fields and are bound to ViewModel properties. Button is bound to "Execute" relay command with Execute and CanExecute methods. CanExecute returns !HasErrors of the ViewModel. ViewModel implements INotifyPropertyChanged and INotifyDataErrorInfo interfaces. Property setter runs the validation for this property only.
The goals are:
Disable the "Save" button initially.
Enable the "Save" button only if both textfields are validated successfully without validation of the whole ViewModel on every property change.
For the first goal I use the isValidated bool property which is initially false and become true as soon as first validation occurred. This property is then checked together with HasErros in the CanExecute method. Is there a more elegant way?
I have no idea how to implement the second goal. Any thought on this?
THX
Your two goals are only one goal really. Let's look at your second goal first:
• Enable the "Save" button only if both textfields are validated successfully without validation of the whole ViewModel on every property change.
Initially, your text fields will be empty, so presumably, they will not pass validation. Therefore initially, your Save Button will be disabled, as per your first goal:
• Disable the "Save" button initially.
Secondly, in your last question (which you should probably add a link to if you're going to mention it in this post), you had some problem with using !HasErros in the CanExecute handler. This time, you do need to validate all of the properties together to fulfil your second requirement... so just use !HasErros in the CanExecute handler.
I'm creating a simple database application in C# WPF using MVVM as Relay Commands and databinding. For in-memory storage of database content I use ObservableCollection, which is binded to the Datagrid as follows:
<DataGrid ItemsSource="{Binding Path=Softwares, Mode=OneWay}" SelectedItem="{Binding Path=SoftwareSelection, Mode=TwoWay}">
when the item is selected user can chose to edit it. For editation a form is opened with a bunch of textboxes with the data of given entity. All the fields are validated using IDataErrorInfo, unless all textboxes are valid, the ok button is not enabled, and therefore no changes can be saved to the collection and to the database.
Here is how the example textbox looks like:
<TextBox Text="{Binding Name, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"/>
But the tricky part is, in case I change some values in textboxes and then close the window, the new values are propagated to the ObservableCollection, which I don't want to. Do you have any idea, how to prevent such behaviour? I would like the databinding work only after clicking the button. Otherwise the databindng works well, so as the button (dis/en)abling and reflecting changes to the database and to the collection after clicking. Both views are serviced by different ViewModels, data between views are passed by firing events.
I tried to add to the DataGrid UpdateSourceTrigger=Explicit to the ItemsSource binding, but didn't help. Perhaps, I'm missing some application logic?
Thank you very much for your help.
This is where most WPF developers make mistakes of assumptions!
In MVVM dirty data can be stored in the ViewModel and that's what the layer of VM is for! It mimics the View from Model's perspective and because View is in error, the ViewModel would also be in the error. Thats perfectly valid.
So having said that, the question remains
How will you NOT allow the temporary / dirty data to flow to your
ObservableCollection?
Two ways...
If your ObservableCollection is specific to your model class (say MyItem) then if your Model class (MyItem) is an Entity class \ DAL class \ NHibernate class create a wrapper of MyItem class called ViewModelMyItem and then instead of ObservableCollection<MyItem> use ObservableCollection<ViewModelMyItem>.
This way dirty data from your View would be inside ViewModelMyItem and it can only be legitimately flown back to your model class (MyItem) ONLY when Save button is clicked. So that means in Save Command's Execute() delegate you can copy \ clone the ViewModelMyItem's properties into Item's properties, if validations in ViewModelMyItem are fine.
So if Item is an EntityType class / NHibernate class / WCF client model class, it would always only valid data as ViewModelMyItem is filtering the temporary / dirty information upfront.
You could use Explicit binding model. It stops the TwoWay data to flow back to the sorce Item unless BindingExpressions.UpdateSource() is explicitly called.
But according to me, this defeats MVVM in straightforward way because ViewModel will not have what UI is showing! Still however you can use *Attached Behavior * to govern explicit binding by staying in MVVM space!
Let me know if this helps!
You're better off putting the code into the domain object's property setter. Then synchronize with the visuals by triggering the NotifyPropertyChanged handler.
More on this topic:
http://msdn.microsoft.com/en-us/library/ms743695.aspx
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
Setting the Binding Mode to Explicit should require you to call the binding expressions UpdateSource() method to send changes back to your model. Because you only mentioned that you set Explicit on the DataGrid's binding, I'm guessing you only need to make sure that you have that mode explicitly set on any property that is being bound directly back to your model. Such as your TextBox's Text Binding, in the case above. That will likely fix your problem but require you to call UpdateSource() on each target's BindingExpression one way or another.
If you're using one of the mainstream ORM's (EF, Linq to SQL, etc), then chances are your Entities automatically implement INotifyPropertyChanged and INotifyPropertyChanging. Because you are sharing a reference to your single instance, all changes in your edit will be reflected in your main view and anything else Binding to that instance. As a dirtier alternative, you can just create a separate instance of the same type and manually copy the values back over when the window's dialog result is true.
The first approach requires you to manually update the bindings, the second approach requires you to manually update the values from the Edit's instance.
With some more code, I can help with your specific approach.
I have a form that has 3 buttons on it: Save, Approve and Reject.
Now I need validate User's input differently based on what button has been clicked.
For example User should be able to Save the object even if most of the fields are empty but in order to Approve some fields are required.
One approach is to put validation to CanSave and CanApprove methods (the buttons are bound to ICommand in the ViewModel). The problem here that buttons get disabled but there is no indication of which fields are required to fill to enable particular button.
Ideally all buttons should be enabled all the time and perform validation when the button is clicked. But how do I mark fields as invalid from VM?
Try adding something like a LastStateValidated property on your class and base your IDataErrorInfo validation off that value.
This means that the first time you load the form, the validation that appears will only be the ones that are in effect regardless of the object's state.
When you try and execute a command, the State changes and this will make IDataErrorInfo validate differently, so different validation errors will show up based on what the last action was that the user tried executing.