Intersoft ClientUI 8 > ClientUI Fundamentals > MVVM Pattern Overview > Advanced MVVM |
Model-View-ViewModel pattern is a simple and effective set of guidelines for designing and implementing a Silverlight/WPF application. It allows you to create a strong separation between data, behavior, and presentation, making it easier to control the software development process primarily on the user interface engineering.
Intersoft ClientUI includes comprehensive frameworks such as commanding and base classes as well as hundreds of rich presentation controls that support MVVM pattern entirely. To learn more about the basic concepts of MVVM pattern, its benefits, and how ClientUI supports MVVM pattern, see MVVM Pattern Overview.
This topic discusses a number of more advanced techniques in implementing MVVM using the rich user interface controls available in ClientUI. For an instance, many developers found it very difficult to implement busy state management or searching capability in their applications due to the lacking of controls that specifically engineered to support the MVVM semantics. In ClientUI, you use UXSearchBox for searching functionality that provides properties such as IsSearching and SearchResult which you can use to implement MVVM pattern in an elegant fashion.
This topic contains the following sections:
ClientUI includes frameworks and presentation controls that make it easy for you to create compelling line-of-business applications that target the Silverlight and WPF platform. One of the most common user experiences pattern in modern applications is the busy state management which often requires a lot of works, effort and code to make it work.
Certain flagship controls in ClientUI provide built-in features to handle busy state management using MVVM semantics allowing you to consistently control the busy state from within the ViewModel. The busy management also complies with strict user experience standards. For more information on MVVM pattern, see MVVM Pattern Overview. For more information on UX standards implementation in ClientUI, see User Experiences Overview.
The following sections describe the flagship controls that support built-in busy state management feature.
ClientUI features UXFrame, an advanced navigation frame that built on the top of Silverlight's navigation infrastructure and extended it with ClientUI's powerful frameworks such as routed events and commanding. This allows UXFrame to provide more advanced features that rely on routed events architecture such as busy state management, authentication and the navigation life cycle.
One of the most requested features in UXFrame is the built-in state management that provides developers with an easy way to manage the busy state when a longer asynchronous operation occurred in the page that currently hosted by the navigation frame. The busy state management feature is designed in a way that lets you rapidly manage the busy state of the page with MVVM pattern using centralized approach. This eliminates the needs to duplicate the busy template to each modular page.
You can set a page to busy state by settings the IsBusy property of the UXPage to true. Similarly, set the property to false to set the page back to the normal state. To disable user interaction while the page is busy, you set the BlockUIOnBusy property of the page to true. In this case, the UXFrame will automatically disable the content of the page and display the busy indicator defined in its BusyIndicatorTemplate property.
The following example shows how to use the busy state feature in UXFrame and UXPage using the MVVM pattern.
RegisterForm.xaml |
Copy Code
|
---|---|
<Intersoft:UXPage ... xmlns:Intersoft="http://intersoft.clientui.com/schemas" xmlns:ViewModels="clr-namespace:ClientUIBusinessApp1.ViewModels" x:Class="ClientUIBusinessApp1.RegisterForm" IsBusy="{Binding Path=IsBusy}" BlockUIOnBusy="True"> <Intersoft:UXPage.DataContext> <ViewModels:RegisterFormViewModel/> </Intersoft:UXPage.DataContext> <Grid x:Name="LayoutRoot"> ... </Grid> </Intersoft:UXPage> |
The following code shows the ViewModel of the RegisterForm that responsible to manage the busy state according to the business logic. Notice that the IsBusy property of the ViewModel is bound to the IsBusy property of the UXPage.
RegisterFormViewModel.cs |
Copy Code
|
---|---|
public class RegisterFormViewModel : ValidationViewModelBase { // Fields private bool _isBusy = false; // Views public bool IsBusy { get { return _isBusy; } private set { if (_isBusy != value) { _isBusy = value; OnPropertyChanged("IsBusy"); } } } // Methods private void ExecuteCreateAccount(object parameter) { if (this.Validate()) { // Enables the page's busy state. // This will block user interaction while the login // is being processed and optionally display busy indicator. IsBusy = true; // Perform asynchronous server-side call to create user account _registrationContext.CreateUser(this.RegistrationData, this.RegistrationData.Password, this.SelectedRoles, CreateAccount_Completed, null); } else { this.FocusErrorField(); } } ... } |
Finally, when the UXFrame detects that the UXPage that it currently hosted changes its IsBusy property, the UXFrame will show the busy indicator defined in the BusyIndicatorTemplate such as shown in the following example.
MainPage.xaml |
Copy Code
|
---|---|
... <Intersoft:UXPage.Resources> <DataTemplate x:Key="FrameBusyTemplate"> <Grid Opacity="0.8"> <Border BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" CornerRadius="8" Background="Black"> <Intersoft:UXProgressBar HorizontalAlignment="Left" Width="150" Height="18" IsIndeterminate="True" Margin="20"/> </Border> </Grid> </DataTemplate> </Intersoft:UXPage.Resources> <Intersoft:UXFrame x:Name="ContentFrame" BusyIndicatorTemplate="{StaticResource FrameBusyTemplate}"> ... </Intersoft:UXFrame> |
Notice that the user interface elements will become disabled when the page is busy, this is done through the BlockUIOnBusy property which is shown in the above XAML example code.
The following illustration shows the result of the above example.
To learn more about the concepts and features of navigation frame and page model in ClientUI, see Navigation Overview.
Search box is among the input controls that requires a sophisticated busy state management due to its default functionality that performs an asynchronous query to server to obtain search results. ClientUI includes a UXSearchBox control with built-in busy state management feature that makes it easy for you to control the busy status using MVVM pattern.
The MVVM semantics for the UXSearchBox is centered around three properties: IsSearching, QueryText and SearchResult. In this section, you learn how to use IsSearching property to indicate that a searching operation is currently in progress. When this property is set to true, the text specified in the BusyText property will be displayed in the result box.
The following example shows how to setup UXSearchBox to maintain its busy state using MVVM pattern.
XAML |
Copy Code
|
---|---|
<Intersoft:UXSearchBox IsSearching="{Binding IsSearching, Mode=TwoWay}" QueryText="{Binding QueryText, Mode=TwoWay}" SearchResult="{Binding SearchResult}" DisplayMemberPath="DisplayName" AutoShowResultBox="True" /> |
C# |
Copy Code
|
---|---|
public class SearchViewModel : ViewModelBase { public SearchViewModel() { } private bool _isSearching; public bool IsSearching { get { return this._isSearching; } set { if (this._isSearching != value) { this._isSearching = value; this.OnPropertyChanged("IsSearching"); this.DoSearch(); } } } private void DoSearch() { // write logic that executes search query var data = QueryToServer(); this.SearchResult = data; // Indicate the searching is completed this.IsSearching = false; } ... } |
The result of the above example is shown in the following illustration.
To learn more about the busy state and other features of the search box control, see UXSearchBox.
Similar to the navigation framework, the windowing controls in ClientUI such as UXWindow and UXDialogBox provide built-in busy state management that supports MVVM semantics. It also consistently exposes the BlockUIOnBusy semantic which disables the UI controls in the window content. Consequently, users cannot make further changes to the content while the window is busy. This is one of the user experience standards that implemented in ClientUI's windowing controls. To learn more about user experiences implemented in ClientUI controls, see User Experiences Overview.
The following example shows how to setup a UXDialogBox to show as busy when the Save button is clicked. The example uses MVVM pattern that controls the busy state from within the ViewModel.
XAML |
Copy Code
|
---|---|
<Intersoft:UXDialogBox ... xmlns:Intersoft="http://intersoft.clientui.com/schemas" xmlns:ViewModels="clr-namespace:Intersoft.ClientUI.Samples.ViewModels" BlockUIOnBusy="True" IsBusy="{Binding IsBusy}"> <Intersoft:UXDialogBox.DataContext> <ViewModels:EditBookViewModel/> </Intersoft:UXDialogBox.DataContext> ... <Intersoft:UXCommandBar Height="44" VerticalAlignment="Bottom" Grid.Row="1"> <Intersoft:UXButton Content="OK" Command="{Binding SaveCommand}" /> <Intersoft:UXButton Content="Cancel" IsCancel="True"/> </Intersoft:UXCommandBar> </Intersoft:UXDialogBox> |
C# |
Copy Code
|
---|---|
public class BookViewModel : ViewModelBase { // Fields private Book _book; private bool _isBusy = false; private DelegateCommand _saveCommand = null; // Constructor public BookViewModel() { this._saveCommand = new DelegateCommand(ExecuteSave, CanExecuteSave); } public BookViewModel(Book book): this() { _book = book; } // Views public Book Book { get { return this._book; } set { if (this._book != value) { this._book = value; OnPropertyChanged("Book"); } } } public bool IsBusy { get { return _isBusy; } set { if (_isBusy != value) { _isBusy = value; OnPropertyChanged("IsBusy"); } } } public DelegateCommand SaveCommand { get { return _saveCommand; } set { if (_saveCommand != value) { _saveCommand = value; OnPropertyChanged("SaveCommand"); } } } private bool CanExecuteSave(object parameter) { return true; } private void ExecuteSave(object parameter) { this.IsBusy = true; // perform save logic } } |
The result of the above example is shown in the following illustration.
To learn more about the window and dialog boxes in ClientUI, see Window and Dialog Boxes Overview.
MVVM Pattern promotes the separation of concerns between the user interface, the user interaction logic and behaviors of the View, and the entities of the application. While it makes UI programming a lot easier by introducing a pattern that you can consistently apply, the MVVM pattern fall short in certain user interaction logics that require a direct reference to the view, such as input handling and focus management.
For example, consider you have a login form where users provide their credentials in the text boxes and then press enter on any of the text boxes to submit (which is actually calling the same function as the button is clicked).
In the classic UI programming, the above scenario is straightforward and can be easily achieved such as shown in the following code.
C# |
Copy Code
|
---|---|
public partial class Default : UXPage { public Default() { InitializeComponent(); txtUserID.KeyUp += new KeyEventHandler(UXTextBox_KeyUp); btnLogin.Click += new RoutedEventHandler(UXButton_Click); } private void UXTextBox_KeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { DoLogin(); } } private void DoLogin() { // write code that perform login } private void UXButton_Click(object sender, RoutedEventArgs e) { DoLogin(); } } |
Using MVVM pattern, you cannot use the above technique since MVVM pattern disallows you to reference the user interface elements from the ViewModel. In other words, a ViewModel should have no ideas about the existence of a View.
ClientUI is engineered to address such UI programming and common scenarios with comprehensive features that specifically designed for MVVM pattern usage, such as discussed in the following sections.
All input controls in ClientUI are designed with an advanced input handling mechanism that takes account the focus scope and special keyboard keys such as Enter and Escape key. This allows you to define a logical characteristic on your command buttons with a simple semantic that associates whether a command button is a default button or a cancel button. For a default button, pressing Enter on a valid input field like text box will execute the button as if the button is clicked. The same goes true for a cancel button invoked with an Escape key.
Many of these characteristics are engineered to meet the ISO-standards in usability and user interactions. For more information about UX standards implemented in ClientUI controls, see User Experiences Overview.
With the IsDefault and IsCancel feature consistently implemented in the entire ClientUI command controls, creating application with rich user interaction using MVVM pattern becomes easy and straightforward. The following example shows how you can setup the Login button to respond on the Enter key, while the Cancel button to respond on the Escape key.
XAML |
Copy Code
|
---|---|
<Intersoft:DockPanel FillChildMode="Custom" Margin="10"> <Intersoft:UXItemsControl Intersoft:DockPanel.IsFillElement="True"> <Intersoft:FieldLabel Header="User ID:"> <Intersoft:UXTextBox Width="150" WatermarkTextVisibility="Visible" WatermarkText="user@domain.com" WatermarkForeground="#FF929292"/> </Intersoft:FieldLabel> <Intersoft:FieldLabel Header="Password:"> <Intersoft:UXPasswordBox WatermarkTextVisibility="Visible" WatermarkText="enter your password" WatermarkForeground="#FF929292"/> </Intersoft:FieldLabel> <Intersoft:FieldLabel Header=""> <Intersoft:UXCheckBox Content="Keep me logged in"/> </Intersoft:FieldLabel> <Intersoft:FieldLabel Header=""> <Intersoft:UXItemsControl Intersoft:DockPanel.Dock="Bottom"> <Intersoft:UXButton Content="Login" IsDefault="True"/> <Intersoft:UXButton Content="Cancel" IsCancel="True"/> </Intersoft:UXItemsControl> </Intersoft:FieldLabel> </Intersoft:UXItemsControl> </Intersoft:DockPanel> |
Notice that there is no C# or VB code needed to achieve this scenario since the user interaction related behaviors are addressed in the View layer. Consequently, this implementation is the most ideal technique for MVVM pattern development that emphasizes separation of concerns.
The IsDefault and IsCancel also work closely with DialogResult semantic which is particularly useful when using the input and command controls are hosted in a window control such as UXDialogBox. The DialogResult property, which is consistently implemented in all button lineups such as UXButton, UXHyperlinkButton, UXSplitButton and more; automatically determines the action of the window, for instance, closing the current window when Escape key is pressed, which is actually mapped to a Cancel button with DialogResult set to Cancel. To learn more about the concepts and features in windowing, see Window and Dialog Boxes Overview.
The following example enhances the previous example by adding the DialogResult property to the Login and Cancel button.
XAML |
Copy Code
|
---|---|
<Intersoft:DockPanel FillChildMode="Custom" Margin="10"> <Intersoft:UXItemsControl Intersoft:DockPanel.IsFillElement="True"> ... <Intersoft:FieldLabel Header=""> <Intersoft:UXItemsControl Intersoft:DockPanel.Dock="Bottom"> <Intersoft:UXButton Content="Login" IsDefault="True" DialogResult="OK"/> <Intersoft:UXButton Content="Cancel" IsCancel="True" DialogResult="Cancel"/> </Intersoft:UXItemsControl> </Intersoft:FieldLabel> </Intersoft:UXItemsControl> </Intersoft:DockPanel> |
With these properties applied, the window can now be automatically closed when Escape key is pressed. In addition, the DialogResult of which command button is invoked will be synchronized to the DialogResult of the current window. This allows you to write logics that process certain task based on the value submitted in the DialogResult of the dialog box.
Similar to the scenario described in the previous section, focus management is one of the key challenges in implementing MVVM pattern because you cannot reference to a user interface control directly from a ViewModel and hence disallow you to write code such as textBox1.Focus() in the ViewModel.
ClientUI addresses this particular scenario by exposing DefaultFocus property in several components that make sense to have an initial focus, such as in UXPage and UXWindow. To set the focus to a control in the first load, set the DefaultFocus property of the UXPage and UXWindow to the name of the control to be focused, such as shown in the following example.
XAML |
Copy Code
|
---|---|
<Intersoft:UXDialogBox xmlns:Intersoft="http://intersoft.clientui.com/schemas" xmlns:Converters="clr-namespace:Contacts_MVVM.Converters" mc:Ignorable="d" x:Class="Contacts_MVVM.Views.ContactEditDialogBox" Header="{Binding DialogTitle}" ContentMargin="0" DefaultFocus="txtID"> ... </Intersoft:UXDialogBox> |
In addition, the ClientUI framework also provides a static SetFocus API allowing you to set focus to a certain control in the ViewModel. Since it accepts the name of the control in form of string as the parameter, it does not require a direct reference to the control thus makes it an ideal technique for use in MVVM pattern.
C# |
Copy Code
|
---|---|
private bool ValidateInput() { if (string.IsNullOrEmpty(this.Name)) { // set focus to the name text box ISFocusManager.SetFocus("Name_Input"); return false; } return true; } |
ClientUI includes a comprehensive windowing controls such as dialog boxes and message boxes to let you create desktop-rich user interface. While these window controls are intuitive and straightforward to implement using classic UI programming, it requires more advanced techniques to implement using MVVM pattern due to the loosely coupling concept between the View and the ViewModel.
The following sections explain how to work with UXDialogBox and UXMessageBox using MVVM pattern.
The UXDialogBox class implements IModalWindow interface which allows you to create an instance of a UXDialogBox from within the ViewModel and reference it as the IModalWindow interface. The interface approach allows the ViewModel to interact with the object without actually knowing the type of the object.
In addition, ClientUI also provides a number of classes that take advantage of solid design patterns such as provider and shell pattern to interact with the UXDialogBox in the ViewModel. The use of these design patterns are recommended for code efficiency that promotes separation of concerns which is particularly useful for cross-platform development such as in Silverlight and WPF.
The following example shows how to create an instance of a UXDialogBox asynchronously, show the dialog box and handle the callback when the dialog box is closed.
C# |
Copy Code
|
---|---|
private void EditContact(object parameter) { DialogBoxServiceProvider.GetInstanceAsync( DialogBoxServiceProvider.ContactEditView, // view result callback (dialogBox) => { // assign a clone copy of the selected contact to the dialog's data context Contact editContact = this.SelectedItem.Contact.Clone(); dialogBox.DataContext = new ContactEditViewModel(editContact, false); // tell view model that we're in editing mode, // so that it can disable/enable related commands as necessary this.IsInEditing = true; // show the Edit Contact dialog box (view) DialogBoxServiceProvider.Show(dialogBox, // dialogbox closing callback (dialogResult) => { if (dialogResult == DialogResult.OK) { // user pressed OK, adds the new contact ContactViewModel oldSelection = this.SelectedItem; ContactViewModel newSelection = new ContactViewModel(editContact); int oldSelectionPos = this.Contacts.IndexOf(oldSelection); // clears existing selection this.SelectedItem = null; // apply changes this.Contacts.Remove(oldSelection); this.Contacts.Insert(oldSelectionPos, newSelection); // set the new contact as selection this.SelectedItem = newSelection; } this.IsInEditing = false; }); }); } |
The DialogBoxProvider class is included in several project templates for Visual Studio 2010. For more information about templates that are included in ClientUI installation, see Introduction to ClientUI Project Templates. |
For more information about dialog boxes, see Window and Dialog Boxes Overview.
Similar to the UXDialogBox, you can also display a desktop-like message box using MVVM pattern with the provided UXMessageBox API. You can show a message box using intuitive syntaxes that already common in general UI development such as using UXMessageBox.Show static method.
The following example shows how to show a UXMessageBox from ViewModel and handle the close callback.
C# |
Copy Code
|
---|---|
private void DeleteContact(object parameter) { MessageBoxServiceProvider.Show( "Are you sure you want to delete the selected contact?", "Confirmation", MessageBoxButton.YesNo, MessageBoxImage.Question, (dialogResult) => { if (dialogResult == DialogResult.Yes) { ContactViewModel currentSelection = this.SelectedItem; this.SelectedItem = null; this.Contacts.Remove(currentSelection); } }); } |
The MessageBoxProvider class is included in several project templates for Visual Studio 2010. For more information about templates that are included in ClientUI installation, see Introduction to ClientUI Project Templates. |
For more information about message boxes, see Windowing Controls Overview.
Certain controls that expose interactivity features such as UXPopup and UXCallout are also designed to support MVVM pattern by exposing their behaviors and features as dependency properties which allows you to easily interact with the control in the ViewModel. For instances, instead of calling the Show method of the control which breaks the compliance with MVVM pattern, you use the IsOpen property to determine when the UXCallout control should open.
When paired with button controls that support MVVM semantics such as UXToolBarButton, showing a UXCallout becomes easy and straightforward through a two-way data binding to the IsChecked property of the UXToolBarButton, while the IsOpen property of the callout is bound to a property in the ViewModel. This allows you to design consistent user experiences without writing extensive code.
The following example shows how to setup a UXCallout to be consistently displayed when you click on a button.
XAML |
Copy Code
|
---|---|
<Intersoft:UXToolBarButton x:Name="Button_Write" Content="Write" IsToggleButton="True" IsChecked="{Binding IsOpen, ElementName=Write_Callout, Mode=TwoWay}"/> <Intersoft:UXCallOut x:Name="Write_Callout" IsOpen="{Binding IsAdding, Mode=TwoWay}" DisableOverlay="False" DisplayAnimation="Fly" HideAnimation="Fly" DataContext="{Binding Source={StaticResource NewFeedbackViewModel}}"> ... </Intersoft:UXCallOut> |
Notice that the button will remain in "pressed" state while the callout is open. The button state will be consistently synchronized when the callout is closed due to user interaction, such as when you click on the modal area of the callout.
To learn more about data binding concept, see Data Binding Overview. To learn more about popup controls in ClientUI, see Popup Overview.
In addition to the frameworks and controls, ClientUI also includes a number of windowing controls that you can use to display basic information or gather user input in a modal form. For more information about the windowing controls and dialog boxes in ClientUI, see Window and Dialog Boxes Overview.
The windowing controls such as UXWindow, UXDialogBox and UXNavigationWindow expose several API that you can use to interact with the window such as Show and Close method. However, these methods are not suitable for use in MVVM pattern as the ViewModel cannot have a direct reference to the user interface controls existed in the View layer. To address this particular MVVM requirement, the ClientUI windowing controls take advantage of the advanced data binding and commanding architecture available in ClientUI's framework. For more information about commanding concept, see Commanding Overview.
The following sections describe how to work with the windowing controls using commanding and MVVM pattern.
Using commands, you can setup a button to open a new UXWindow based on its Uri location using declarative XAML code. This means that you can interact with the windowing controls without have to write code, which is an ideal solution for a MVVM pattern application.
The following example shows how to setup the Command, CommandTarget and CommandParameter of a UXButton to show the UXWindow1.xaml when clicked.
XAML |
Copy Code
|
---|---|
<Intersoft:UXButton Content="Open Window" Command="Intersoft:WindowCommands.LaunchApplication" CommandTarget="{Binding ElementName=uxDesktop}"> <Intersoft:UXButton.CommandParameter> <Intersoft:WindowOptions StartupParameters="Some data" Uri="/Views/UXWindow1.xaml"/> </Intersoft:UXButton.CommandParameter> </Intersoft:UXButton> |
In the above example, notice that a new WindowOptions instance can be created directly in the XAML and then assigned to the CommandParameter of the button. When the target, in this case is the UXDesktop, dispatches the command, it recognizes the WindowOptions object in its parameter and execute the action according to the data in the WindowOptions.
ClientUI includes a number of window-related commands that are readily usable in your project. These commands expose the same results as if they were called via API. The following list shows several examples of window-related commands:
For more information about commanding concept, see Commanding Overview. For more information about windowing, see Window and Dialog Boxes Overview.