Additionally, if you haven't downloaded the modified Northwind database or the SharedAssemblies, you will need to download them at the bottom of the Lesson intropage. (Click me)
(Click on the link and the DB is at the bottom of the lesson.)
Please be sure to modify the app.config file to point to the machine you which you setup restore the database too.
All the assemblies used for the demo are contained in the SharedAssemblies folder so you may need to set your reference paths accordingly.
As with all the lessons, I would like to thank the following:
Telerik for making wonderful RAD controls which make our development lives a lot easier. The Demo versions of 2010 Q1 WPF controls were used and can be found at the Telerik website.
http://www.telerik.com/account/free-...l.aspx?pid=601
A big thanks to Sacha Barber for Cinch, NSVMVVM is a slightly modified version of Sacha's Cinch Framework. His DataWrapper related code is used extensively in this Lesson.
Josh Smith for all his work posted on the internet. Finally to the others who have taken the time to post helpful work on the internet.
The requirements for this lesson.
- The New & Edit QuickAccessToolBar (QATB) buttons should be enabled by default and the Save & Cancel Changes buttons disabled.
- When the Edit button is clicked on the Customer or Employee views, the New & Edit buttons will be disabled. The Save & cancel changes buttons will be enabled.
- When the cancel button is clicked, the user is prompted with a message box to verify their actions. If canceled, no changes. If accepted, all fields go back to their original values & the QATB’s go back to their original state.
- The Save button will save the state to the data provider and the QATB’s will go back to their original state.
We use the EditableValidatingObject as it implements IDataErrorInfo which we use for Validation. It also implements the IEditableObject which gives use the ability to commit or rollback changes on our Data Models.
The DataWrapper also provides an IsEnabled property. If we are using TextBoxes to display our data values, we can set its Style to our ValidatingTextBox. (Found in the NSTPrismSample assembly.) We will set the IsEnabled of our TextBox controls to the Datawrapper’s IsEnabled property for the Model's DataWrapper Property which the textbox is bound too. (We'll go into this in more detail later in this lesson.)
The framework contains a DataWrapperHelper function which contains a SetMode method that allows us to set all DataWrapper properies of our model to EditMode or VisibleOnlyMode.
If you look at the BaseModel class defined in the Infrastructure assembly. You'll see that it inherits from the EditableValidatingObject and implements all the overrides needed for Editing and Validation.
In the BaseModel, you'll notice the following collection defined:
protected IEnumerable<DataWrapperBase> CachedListOfDataWrappers;
As Sacha points out in his articles, he creates this as a way to cache all the DataWrappers for future references. In the constructor of all our Model's, you'll find the following code which populates the collection:
CachedListOfDataWrappers = DataWrapperHelper.GetWrapperProperties<BaseModel>(this);
One thing I added is a public method for setting the DataWrapper's ViewMode.
Code:
public void SetEditMode(ViewMode viewMode) { DataWrapperHelper.SetMode(CachedListOfDataWrappers, viewMode); }
First notice, we've created Textboxes and bound their Text property to the appropriate DataValue of our CustomerViewModel's CurrentCustomerModel's DataWrapper properties. We set the the Bind Mode to TwoWay so any change that gets made when the Textboxes will automatically get updated back to the CustomerModel DataWrapper DataValues property for which they are bound to.
Also, based on our the DataWrapper's IsEditable property described above, we set the IsEnabled property of our TextBox's to this value. This way the TextBox will be enabled or disabled based on what Mode the DataWrapper's IsEditable property is set to.
There are serveral more properties of our Textbox's defined. However, we need to backtrack a bit to the DataWrapper class functionality to give you a better understanding of how these settings work.
The EditableValidatingObject also gives us the ability to create validation rules via the IDataError interface in WPF. If you go to the CustomerModel class. You'll notice we've created a SimpleRule for all our string properties.
Code:
private static SimpleRule _companyNameRule; private static SimpleRule _contactNameRule; private static SimpleRule _contactTitleRule; private static SimpleRule _addressRule; private static SimpleRule _cityRule; private static SimpleRule _regionRule; private static SimpleRule _postalCodeRule; private static SimpleRule _countryRule; private static SimpleRule _phoneRule; private static SimpleRule _faxRule;
Here is a rule for PostalCode:
Code:
_postalCodeRule = new SimpleRule("DataValue", "Length must be less than 10 characters.", (Object domainObject) => { DataWrapper<string> obj = (DataWrapper<string>)domainObject; return obj.DataValue != null ? obj.DataValue.Length > 10 : false; });
In the public CustomerModel() constructor, the final step is to add our rules.
Now, we jump back to our CustomerView Xaml. Lets look at the TextBox named Zip. (It binds to the PostalCode Property in our CustomerModel.) We have assigned the UpdateSourceTrigger to the LostFocus event. We also set the ValidatesOnDataErrors & ValidatesOnExceptions to True. Then we set our Style to ValidatingTextBox. Now, when a user enters in a value and changes focus, the validation rules we defined for the DataWrapper Property will show in the ToolTip for the textbox control. Also, the ValidatingTextBox will change the color of the TextBox lines to red as demonstrated in Figure 1 below:
Figure 1
The EditableValidatingObject also gives us an IsValid property. If a Validation error or exception error occurs, the our Model's IsValid gets set to false. We can then check this property on any functions that will send the data to the database.
In our CustomerViewModel, our we demonstrate this in our SaveCustomer method.
Code:
private void SaveCustomer() { if (CurrentCustomerModel.IsValid) { _customerService.UpdateCustomers(CurrentCustomerModel); CurrentCustomerModel.EndEdit(); CurrentCustomerModel.SetEditMode(ViewMode.ViewOnlyMode); } else { _messageBoxService.ShowError("One of the text fields has invalid data, please correct the problem before saving."); } }
Now we have some of our plumbing setup, let's get back to our requirements and see where we're at.
Requirement 1, when the RibbonBar loads in our Customer & Employee views, the New & Edit buttons are enabled and Save & Cancel disabled.
To do this we looked for a setting we can bind their IsEnabled property to in order to have their state changed automatically. Since all in our example all our our TextBoxes will either be in edit or ViewOnly mode, I selected a random DataWrapper from our CurrentCustomerModel & CustomerEmployeeModel in their respective ViewModel's to bind to. In this case, I chose to use CompanyName but it could be any of the DataWrapper properties of the CurrentCustomerModel.
The down side of this, is when the cancel button is clicked, the user gets a prompt to discard changes even if they didn't make any. However, for the purposes of our demonstration, it works fine.
You'll notice the New & Edit buttons will always be in an opposite state of our Save & Cancel buttons but how do we achieve this. WPF provides a powerful way to convert data in bindings through the use of Converters. In this case we need two buttons set to the actual value of the bool IsEditable and our other two buttons set to the Inverse.
To accomplish, we create a converter in the NSTPrismSample assembly called InverseBooleanConverter. This converter takes in a value of Bool and returns the inverse of it's value.
We create a ResourceDictionary called GlobalConverters.xaml also in the NSTPrismSample assembly. Finally we add it to our app.xaml MergedDictionaries.
Once that's complete, we go to our New RadRibbonButton in CustomerView.Xaml and we bind it's IsEnabled property to CurrentCustomerModel.CompanyName.IsEditable and we also assign a Converter to our InverseBoolConverter as shown in the following Xaml.
Code:
IsEnabled="{Binding CurrentCustomerModel.CompanyName.IsEditable, Converter={StaticResource InverseBoolConverter}}" />
Now when we click the Edit Button and our DataWrappers in the CustomerModel get set to EditMode, our QATB's IsEnabled properties are automatically set. The same when Save or Cancel is clicked. Now we have satisfied requirement 2.
Requirement 3 we discussed briefly earlier. When the CancelChanges command is ran, we display a messagebox asking the user to confirm or cancel. If they click confirm, you'll see that all the TextBoxes go back to their original values and back to read only mode.
We get this functionality from WPF's IEditableObject. It is implemented in Cinch's EditableValidatingObject object. It provides us with a BeginEdit() which we call when we set the CustomerModel's DataWrapper properties to EditMode as shown in the CustomerViewModels EditCustomer method below.
Code:
private void EditCustomer( { CurrentCustomerModel.SetEditMode(ViewMode.EditMode); CurrentCustomerModel.BeginEdit(); }
So now we have met the criteria of our third requirement.
Finally we're down to our 4th and final requirement. With our Save button wired up to a RelayCommand and the capability of the IsValid property of our BaseModel, we check our CurrentCustomerModel to make sure there are any fields which have data that broke our validation rules. If not, we call our Update function in our service, we make a call to EndEdit() which clears up our cached values for editing.
Finally, we set CurrentCustomerModel Mode to ViewOnlyMode which will cause all of our Textbox's which are bound to go back to a disabled mode. Requiring the user to click the edit button if they want to make further changes.
Most everything we've discussed for the Customer module is interchangeable with how our Employee module is setup.
This has been a lengthy lesson. Please download the source. Also, if you haven't downloaded the SharedAssemblies folder, please remember to go to Lesson 6 to download them and make sure all your references paths are correct.
http://www.compositedevpatterns.com/content/158-Lesson-6-Prism-NW-Creating-a-Dynamic-Ribbon-Bar







Section Widget
Categories Widget (top-down)

