Click here to Skip to main content
Click here to Skip to main content

Exploring a Model-View-ViewModel Application; WPF Password Manager, Cipher Text

, 28 Dec 2008
Rate this:
Please Sign up or sign in to vote.
An article exploring Model-View-ViewModel (MVVM) WPF UI Design Pattern as leveraged in a WPF Password Manager. Password Manager allows user to modify the shape and behavior of a record at run-time.

The video links require Microsoft Silverlight 1.0 or later. If you do not have it, you will be prompted to install it when you click one of the links or you can download Silverlight here. Windows XP or Vista required.

 Cipher Text 'How To' Video

Introduction

This article uses the WPF Password Manager, Cipher Text application as fertile ground for exploring the WPF UI Design Pattern, Model-View-ViewModel or MVVM. The application sports many cool features and some WPF goodness.

In this article I'll cover a number of MVVM coding scenarios and techniques along with using Cipher Text.

Background

My first .NET application after graduating from the SetFocus .NET Masters Program in 2003 was a .NET 2.0 Windows Forms password manager. I've been using this same program for securing my passwords for the last five years.

I've been studying and writing WPF Applications using the WPF UI Design Pattern, MVVM.  I'm sold that using MVVM for WPF Line of Business (LOB) applications is the way to go. 

Now I wanted to see if I could create a fluid and dynamic application using MVVM.  Basically, I wanted to take MVVM out for a test drive, push the envelope and see where it took me.  The time spent on Cipher Text was time very well spent and I hope you can learn more about MVVM from this article.

This article is about MVVM coding scenarios and techniques as opposed to the application used to surface them.  I do hope that you like the application too.

Designer Developer Workflow: as I was writing Cipher Text its UI was plain and simple.  After finishing the application, I took it into Microsoft Expression Blend for a makeover.  I liked the workflow of completing the application before starting the beautification process. 

During application development I made extensive use of my XAML Power Toys Visual Studio Add-In.  If you have not yet seen XAML Power Toys, please visit my blog and download it.

Let's cover the Cipher Text application first and then dive into MVVM.

WPF Password Manager, Cipher Text Application

Application Features

  • Data stored in encrypted file
  • Flexible password generator
  • Instant full text search feature
  • Nine pre-established data entry forms (can be modified at run-time)
  • Run-time modification of a record's shape and field behavior (add, change and remove fields)
  • Run-time configurable case correction rules
  • Dynamic and rich field validation based on assigned field behavior
  • Single click copy of any data to the clipboard
  • Nice WPF UI
  • "How To" usage video for getting the most of Cipher Text
  • Single file XCOPY deployment

Application Requirements

  • .NET Framework 3.5 with SP1 applied

Application Introduction

The application stores its data in an encrypted file using symmetric encryption employing Triple Data Encryption Standard algorithm (TripleDES).  While this is not an article on the cryptography namespaces in .NET, it does demonstrate the reading and writing of a serialized object graph to an encrypted file.

This application can be used "as is" to store your passwords.  However please note two very important items:

  • Once decrypted, your data is in memory and can be written to the Windows swap file by the operating system.  If your computer is then compromised, a hacker could potentially view data in your swap file.  (I'm still trying to figure out how to allocated memory in a managed program that does not get written to the Windows swap file, when I do, I'll update the application.)
  • If you forget your password, your data is lost.  There are no hacks, tricks, workarounds or magic fairy dust to open your data file.  A brute force dictionary hack is probably the only way to decrypt the file.  Please don't lose or forget your password.

Back in 2003 when I wrote the first version the application met my needs.  Then a year later, banks and web sites started asking me to assign a security question to my accounts or for me to provide my mother's maidan name.  Since my original application didn't have the ability to update the shape of a record without an application recompile, I put the extra information in a notes field.  Now my bank asks for three security questions and an icon to view.

After five years of daily application usage I knew what features I wanted to add.  So when I set out to write this program, I needed the ability to change the shape of a record at run-time.  I needed to be able to add, change or remove fields.  I needed to be able to change which fields are required, which fields have case correction applied and which fields are validated against a set of rules.  I also wanted to have instant full text searching of all my data.

Application UI

There is a wind blowing through UI Designer spaces that is pushing UI's to be less busy; to clear UI adorners and affordances until the user is actually using them.  For example, hiding a ComboBox's familiar down arrow until the user mouses over the ComboBox.  I'm not sure I'm on board with all this yet, but decided to open the door and allow a gentle breeze to influence my design.

Login

First Time Use Login Login

The first time the application is opened the password for the application is created.  By design, the first time password is displayed in clear text so the user can view it while typing it.  The standard login form masks the password as its being entered.

Getting Started

After logging in the first time, the above application is displayed.

The set of buttons on the left control filtering and permit a new record to be added to the database.  The text at the very top is darkened until the user mouses over it.  The data viewer on the right allows for full text searching and data viewing.

Category Buttons

Not Selected Selected Mouse Over

Only one category can be selected at a time.  When selected, only that category's records are displayed.  The 'All Records' category displays all records in the database and is selected by default when the application opens.

When the user mouses over the category, the category is no longer dimmed and a green plus icon is display in the button.  Clicking the green plus icon displays an empty record that the user can edit and save to the database.  (This is an example of hiding a UI element until it is needed or available for use.  I think it produces a cleaner UI.)

Filtering

After selecting a category, the title of the data viewer (area in top red box) changes to reflect the current category.  The filter TextBox displays a watermark indicating that no filter is currently applied.

Once the user beings entering text the title of the data viewer changes, indicating a filtered list is being displayed.  Also a button appears at the end of the filter text box that allows for single click clearing of the filter TextBox.

Edit Record

Each category has a default edit form.  The user can customize each edit form on the fly.  The red "!" indicates a field needs attention before the data can be saved.  If the user mouses over the red "!" a ToolTip appears, explaining the required corrective action.

When the yellow Key Icon is clicked it opens the create password dialog.

The bottom black bar organizes the possible actions for this form.  Actions that are not currently permitted are grayed out. 

When the form is valid, the Save command will be enabled.  (In case you're wondering, that is the real customer support number for Amazon.com.)

Each of the field tags except Date Created and Date Modified are hyperlinks that when clicked, copy the corresponding field to the clipboard.  Additionally when the field tags are dragged, a drag and drop operation is initiated.  This allows the dragging of data to other applications or copying data to other fields.

After saving the data the record is added to the database and the edit form is closed.

When editing the Amazon record, the URL field was filled in.  When a record with a URL is displayed in the data viewer, the title text is white.  Notice that the Expression Blend License record title is gray.  When a title field is white, this indicates it's a hyperlink that when clicked will take the user to the web site.

Also, when editing the Amazon record, the User Name and Password fields were entered.  When a record with a User Name and/or a Password is displayed in the data viewer, the user and/or key icons are displayed.  When these icons are clicked the information is copied to the clipboard.

Modify Form

Form modification has two states.  First the form can be in a modification state and one or more fields can be in a validation rule modification state.  The above image shows the form in a modification state.  When the entire form is valid (including data), the form can be returned to a normal form state by clicking the 'Normal Form' hyperlink in the lower left corner.  The form can also be saved when it is valid.

The title and notes fields can't be modified.

Field Validation Rule Modification

The above image shows the phone field tag modified to indicate that the phone number is the Customer Support number.  The phone fields allow for notes to be added after the phone number, so I could have indicated this number is the customer support by adding this information after the phone number. 

When a field is expanded as the above Customer Support field is, if the field is not valid, the green check mark icon will be disabled, preventing the field from being collapsed until the fields are valid.

The second phone field has been marked for delete and will be removed when the form is saved.

The Max Len field sets the maximum length for the text in the data field.

When Is Required is checked, this makes the data field required entry.

The Type determines the role the field plays in the application and how it is validated.

  • Credit Card Number - validates the credit card number
  • Email - validates the email
  • IP Address - validates the IP Address
  • Password Primary - displays the key icon to the right of the field data, allowing a password to be generated.  The Password Primary field is also causes the data viewer to display the key icon.
  • Password Secondary - displays the key icon to the right of the field data, allowing a password to be generated.
  • Plain Text - no rule applied or action taken
  • Routing Number - validates the routing number
  • URL Primary - validates the URL.  The URL Primary field also causes the title field in the data viewer to be displayed in white and become a hyperlink.
  • URL Secondary - validates the URL
  • User Name Primary - causes the data viewer to display the user icon
  • User Name Secondary - no rule applied or action taken

The Case determines how the application modifies entered data in the field.

  • Lower - changes data case to lower case
  • None - no changes made to data
  • Outlook Phone - attempts to format the data like Microsoft Outlook.  Also applies Proper case to text after the phone number.
  • Proper - applies proper casing to entered text.  Proper casing rules can also be added.  See below section.
  • Upper - changes data case to upper case

Modifying Casing Rules

Casing rules as very simple.  Once the data field has been changed to proper case, the above rules are applied.  For example, let's say your data is, "125 110th St Se".  You want the address displayed as, "125 110TH St SE".  In order words, you want the abbreviation for South East to be displayed in upper case.

The first rule looks for " Se " and replaces it with " SE ".  Notice that the white space in the fields.  You wouldn't want to replace all occurrences of "Se" with "SE" as this would cause problems.

The Modify Casing Rules form is fully editable.  Each row can be changed without placing the form in an edit mode.  If you want to remove a casing rule, click the delete button.  If you delete a rule and want it back, simply press the Cancel hyperlink, the form will close and no changes written to the database.

The only validation requirement is that the, "Look For" string and "Replace With" string are the same length.

The green plus icon allows adding a new rule.

When all rules are valid, the Save command is enabled.  If any rules are not valid (i.e. not the same length) the Save command will be disabled.

Modified Form

The above form has been modified and data entry completed.  Extra fields have been removed and the Phone field tag changed to Customer Service.

Top Hyperlink Bar

The top hyperlink bar text is normally gray.  When the mouse is moved over it, the text foreground changes to white.

  • Float On Top - when clicked, floats the application on top of other windows.  This is useful when dragging and dropping text from the application to other applications like a web browser.
  • Change Password - displays the Change Password dialog for changing the password
  • 'How To Video' - is a hyperlink to the instructional video for the application.  This video requires Silverlight 1.0 or later to view.
  • Code Project Article - is a hyperlink to this article
  • Karl's Blog - is a hyperlink to my blog

Change Password

The Change Password dialog requires the current password be entered and a new password entered before the Save command is enabled.  When the Save command is executed, the database is encrypted using the new password.

Category Buttons

Most of you know Karl was not blessed with any design skills.  For this application, I wanted to break my usual pattern of Menus, ToolBars, StatusBar, square buttons and attempt to author an application that has some WPF goodness.  I spent a good bit of time trying to design a layout that I liked (or thought would not look like chimps throwing feces).  I started looking around and found a cool demo for the XCEED WPF products.  I used the demo application for inspiration and went to work.

The behavior I wanted from the category buttons was to only allow one button to be selected at a time.  This sounds like a job for the RadioButton or ToggleButton.  I chose the RadioButton.

One of the most awesome features of WPF is the ability to re-template a control to have the look, feel and behavior you need without requiring the control to be subclassed.

The category button is a multi-layer button with cool features and mouse over effects.  The multiple layers allow elements to render behind other opaque layers and show through them.  The multiple layers combined with WPF triggers also makes it very easy to dim the button when it's not selected or the mouse is not over it.  The layering is achieved by placing UI Elements as children of a Grid in a single row.

I used Microsoft Expression Blend to create the below control template.  Blend makes it very easy to draw paths, layer controls, set gradient values and create required triggers.  I also like the zoom & pan features of blend that makes it super easy to zoom into view any portion of your scene.

The below CardTypeCommandView UserControl is our category button and is also the DataTemplate for the CardTypeCommandViewModel.

<UserControl
  x:Class="CardTypeCommandView"
  xmlns:local="clr-namespace:CipherText"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d">
  <UserControl.Effect>
    <DropShadowEffect
      Color="#FF545454"
      ShadowDepth="5" />
  </UserControl.Effect>
  <UserControl.Resources>
    <local:IsAllRecordsConverter
      x:Key="isAllRecordsConverter" />
  </UserControl.Resources>
  <RadioButton
    x:Name="rdo"
    IsTabStop="False"
    GroupName="CardTypeCommands"
    Command="{Binding Path=FilterCommand}"
    CommandParameter="{Binding Path=CardType}"
    IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
    Background="{x:Null}"
    BorderBrush="{x:Null}"
    Foreground="{x:Null}">
    <RadioButton.Template>
      <ControlTemplate
        TargetType="{x:Type RadioButton}">
        <Border
          Height="70"
          Width="90"
          Margin="0,0,0,0"
          BorderBrush="#FF2F2E2E"
          BorderThickness="1,1,1,1"
          Padding="0,0,0,0"
          x:Name="border"
          ClipToBounds="True"
          Visibility="Visible"
          CornerRadius="7,0,7,7"
          RenderTransformOrigin="0.5,0.5">
          <Border.RenderTransform>
            <TransformGroup>
              <ScaleTransform
                ScaleX="1"
                ScaleY="1" />
            </TransformGroup>
          </Border.RenderTransform>
          <Border.Background>
            <LinearGradientBrush
              EndPoint="0.016,0.137"
              StartPoint="0.965,0.695">
              <GradientStop
                Color="#FF000000"
                Offset="0.147" />
              <GradientStop
                Color="#FFA9A9A9"
                Offset="1" />
            </LinearGradientBrush>
          </Border.Background>
          <Grid
            Background="{x:Null}"
            ClipToBounds="True">
            <Image
              Stretch="None"
              Source="{Binding Path=CardType.Icon}"
              HorizontalAlignment="Stretch"
              Margin="0,0,0,0"
              VerticalAlignment="Stretch" />
            <Border
              Visibility="Visible"
              Background="#83000000"
              CornerRadius="7,0,7,7"
              x:Name="bdrDarken"
              d:IsHidden="True" />
            <Path
              Stretch="Fill"
              Stroke="{x:Null}"
              StrokeThickness="0"
              Margin="-0.602,-0.41,-3.718,-0.043"
              ClipToBounds="True"
              SnapsToDevicePixels="True"
              Data="M87.662375,57.350326 C95.267257,46.41438 ... ... 

...">
              <Path.Fill>
                <LinearGradientBrush
                  EndPoint="0.278,0.121"
                  StartPoint="0.535,0.728">
                  <GradientStop
                    Color="#FF000000"
                    Offset="0.469" />
                  <GradientStop
                    Color="#FF606060"
                    Offset="1" />
                </LinearGradientBrush>
              </Path.Fill>
            </Path>
            <Rectangle
              Fill="#FF000000"
              Stroke="#FF000000"
              RadiusX="7"
              RadiusY="7"
              VerticalAlignment="Bottom"
              Height="21"
              Opacity="0.36"
              StrokeThickness="1"
              Margin="0,0,0,0"
              Visibility="Visible" />
            <TextBlock
              Text="{Binding Path=CardType.CardTypeName}"
              Margin="0,3.5"
              HorizontalAlignment="Center"
              VerticalAlignment="Bottom"
              Foreground="#FFFFFFFF" />
            <Button
              x:Name="btnAdd"
              Visibility="Collapsed"
              Margin="0,35,10,0"
              HorizontalAlignment="Right"
              VerticalAlignment="Top"
              IsTabStop="False"
              Command="{Binding Path=NewCommand}"
              CommandParameter="{Binding Path=CardType}"
              ToolTip="Click to add this card type to the database."
              Style="{StaticResource gridButtonStyle}">
              <Image
                Stretch="Uniform"
                Height="16"
                Width="16"
                Source="{StaticResource addImage}" />
            </Button>
          </Grid>
        </Border>

        <ControlTemplate.Triggers>
          <MultiTrigger>
            <MultiTrigger.Conditions>
              <Condition
                Property="IsMouseOver"
                Value="True" />
              <Condition
                Property="IsChecked"
                Value="False" />
            </MultiTrigger.Conditions>
            <MultiTrigger.EnterActions>
              <BeginStoryboard>
                <Storyboard>
                  <DoubleAnimation
                    Duration="00:00:00.3"
                    Storyboard.TargetName="border"
                    Storyboard.TargetProperty="(UIElement.RenderTransform).
                        (TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
                    To="1.1" />
                  <DoubleAnimation
                    Duration="00:00:00.3"
                    Storyboard.TargetName="border"
                    Storyboard.TargetProperty="(UIElement.RenderTransform).
                        (TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
                    To="1.1" />
                </Storyboard>
              </BeginStoryboard>
            </MultiTrigger.EnterActions>
            <MultiTrigger.ExitActions>
              <BeginStoryboard>
                <Storyboard>
                  <DoubleAnimation
                    Duration="00:00:00.3"
                    Storyboard.TargetName="border"
                    Storyboard.TargetProperty="(UIElement.RenderTransform).
                        (TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
                    To="1" />
                  <DoubleAnimation
                    Duration="00:00:00.3"
                    Storyboard.TargetName="border"
                    Storyboard.TargetProperty="(UIElement.RenderTransform).
                        (TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
                    To="1" />
                </Storyboard>
              </BeginStoryboard>
            </MultiTrigger.ExitActions>
            <Setter
              Property="Visibility"
              TargetName="bdrDarken"
              Value="Collapsed" />
          </MultiTrigger>
          <Trigger
            Property="IsChecked"
            Value="True">
            <Setter
              Property="RenderTransform">
              <Setter.Value>
                <ScaleTransform
                  ScaleX="1.1"
                  ScaleY="1.1" />
              </Setter.Value>
            </Setter>
            <Setter
              Property="Visibility"
              TargetName="bdrDarken"
              Value="Collapsed" />
          </Trigger>
          <Trigger
            Property="IsMouseOver"
            Value="True">
            <Setter
              Property="Visibility"
              TargetName="btnAdd"
              Value="Visible" />
          </Trigger>
          <DataTrigger
            Binding="{Binding Path=CardType.CardTypeName, 
              Converter={StaticResource isAllRecordsConverter}}"
            Value="True">
            <Setter
              Property="Visibility"
              TargetName="btnAdd"
              Value="Collapsed" />
          </DataTrigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </RadioButton.Template>
  </RadioButton>

</UserControl>

The triggers provide a scale animation when the mouse is moved over or leaves the category button.  A trigger also displays the green plus Add icon when the mouse is over the button unless the category is the, "All Records" category.  The IsAllRecordsConverter provides the "All Records" testing and set the Visibility is of the Add icon in the above DataTrigger.

All buttons data bind to an ICommand property on the DataContext, in this case the CardTypeCommandViewModel. 

Exploring MVVM Coding Scenarios

MVVM is a WPF UI Design pattern; it's a set of guidelines to help solve a problem.  I like MVVM because it facilitates application Unit Testing provided for free in Visual Studio 2008 Professional.  I'm not a purest by any means.  I do what is required to get the work done.  Delivering applications that can be Unit Tested and maintained over time is my primary objective and always overrides strict adherence to a pattern or line of thinking.  So like my great friend and mentor Josh Smith says, "put away the flame throwers" and let's get into some code.

Rather than walk the reader though the entire application, I thought it best to pick some coding scenarios and see how MVVM can help solve the problem.

For those that are brand new to MVVM, please see the references at the bottom of this article.  I will also post an Introduction to MVVM on my blog soon.

RelayCommand

The RelayCommand was originally posted by MVVM Master Josh Smith in his Crack .NET application.  Josh and I also used it in our Creating an Internationalized Wizard in WPF Code Project article. 

The RelayCommand comes in two flavors, Generic and non-Generic.  When using the Generic version, it allows the developer to specify the Type that the Command Parameter is.  In the non-Generic version the Command Parameter is Type Object.

The RelayCommand radically reduces the number of command classes an application would require without it by it.

For example, a ViewModel needs to expose four commands to the UI.  Your first thought may be to create four unique command objects to handle the requirement.  A simpler and much cleaner approach is to expose the commands as read-only properties on the ViewModel of Type ICommand.  In the property getter create a RelayCommand to handle the command Execute and command CanExecute methods.

Notice the lazy instantiation of the private command fields.

Public ReadOnly Property GeneratePasswordCommand() As ICommand
    Get

        If _cmdGeneratePasswordCommand Is Nothing Then
            _cmdGeneratePasswordCommand = _
                New RelayCommand(AddressOf GeneratePasswordExecute, _
                                 AddressOf CanGeneratePasswordExecute)
        End If

        Return _cmdGeneratePasswordCommand
    End Get
End Property

Public ReadOnly Property RemoveFieldCommand() As ICommand
    Get

        If _cmdRemoveFieldCommand Is Nothing Then
            _cmdRemoveFieldCommand = _
            New RelayCommand(AddressOf RemoveFieldExecute)
        End If

        Return _cmdRemoveFieldCommand
    End Get
End Property

In the above code snippet, the first ICommand property has assigned the Execute delegate to the GeneratePasswordExecute method and the CanExecute delegate to the CanGeneratePasswordExecute method. 

Note the naming convention I've adopted.  After coding ViewModel's with more than a few commands, appending "Execute" to the Execute command name makes it very easy to locate the method in IntelliSense.  For the CanExecute method names, I've pre-pended, "Can" and appended "Execute" for the same reason.

The second ICommand property does not have a CanExecute delegate assigned.  This command is enabled all the time.  The RelayCommand takes care of returning True when called, which in effects keeps the UI Element that is bound to the ICommand enabled all the time.

In WPF there is a static class, CommandManager that handles the task of automatically enabling and disabling commands.  This feature (like many others) comes at a price.  When using many RoutedCommands in an application, CommandManager can causes some performance issues because the CommandManager raises the RequerySuggested RoutedEvent a lot.  For example, every time a key is pressed this event and its corresponding preview event travels through the WPF Element Tree.

The ICommand pattern here does not use RoutedCommands.  When the UI Element executes a command, the RelayCommand delegates are executed directly.

If however you want to take advantage of the CommandManager automatically enabling and disabling your UI Element commands you have to sign up for this service.

The RelayCommand in this article has a modification that makes registering with the CommandManager SuggestedRequery event optional.  Note the overloaded constructor in the above RelayCommand class diagram.  If the developer does not supply a delegate for the CanExecute method, the RelayCommand does not register for the SuggestedRequery event.

The RelayCommand is very performant.  I have a test application with 100 unique buttons with the Command property bound to an ICommand property with a backing RelayCommand with automatic enabling and disabling enabled with no effect on performance. 

Tip:  You can use the RelayCommand even if you're not using MVVM.  The pattern is the same.  This coding technique can give you the power and flexibility of WPF commands without the CommandBinings and all the associated overhead.

List of Commands Scenario

The developer has a list of objects and wants to associate one of more actions with each object.  The objects need to be rendered as a list in the UI.  The objects can be sourced from a database or a static set of application data.

One solution made easy by MVVM is to wrap each object in a ViewModel.  Then associate that wrapper ViewModel with a DataTemplate and allow the WPF Resources system to look up the correct DataTemplate for the data object (ViewModel).  Wrapping each object in a ViewModel allows the DataTemplate (View) to data bind required commands to the ViewModel and for reshaping of data if required.

The concrete example of this scenario in Cipher Text is the category buttons on the left side of the application.

The driving data for this scenario is the CardTypes collection in the Database object.  The CardTypes collection defines each of the application card types by specifying its title, icon and default form fields.

We now use MVVM to reshape the CardType data making it selectable and associating two commands with it. 

The below block of code from the ApplicationMainWindowViewModel class.  When the CardTypeCommands property is first accessed by the UI, it creates a ReadOnlyCollection of Type CardTypeCommandViewModels.  The LoadCardTypeCommands function is passed in the constructor of the ReadOnlyCollection.

Public ReadOnly Property CardTypeCommands() As _
    ReadOnlyCollection(Of CardTypeCommandViewModel)
    Get

        If _objCardTypeCommands Is Nothing Then
            _objCardTypeCommands = LoadCardTypeCommands
        End If

        Return _objCardTypeCommands
    End Get
End Property


Private Function LoadCardTypeCommands() As _
    ReadOnlyCollection(Of CardTypeCommandViewModel)

    Dim obj As New List(Of CardTypeCommandViewModel)

    For Each objCardType As CardType In Application.DataBase.CardTypes
        obj.Add(New CardTypeCommandViewModel( _
                New RelayCommand(Of CardType)(AddressOf NewExecute), _
                New RelayCommand(Of CardType)(AddressOf FilterExecute), _
                objCardType))
    Next

    Dim objAllRecordsViewModel As CardTypeCommandViewModel = _
        (From c In obj _
         Where c.CardType.CardTypeName = _
            Application.STR_ALLRECORDS).FirstOrDefault
    
    If objAllRecordsViewModel IsNot Nothing Then
        objAllRecordsViewModel.IsSelected = True
    End If

    Return New ReadOnlyCollection(Of CardTypeCommandViewModel)(obj)
End Function


Private Sub FilterExecute(ByVal objCardType As cardtype)
    Me.DataBaseViewModel.SetCardTypeFilter(objCardType.CardTypeName)
End Sub


Private Sub NewExecute(ByVal objCardType As CardType)
    
    Me.EditingRecord = True
    Me.DataEditorViewModel = _
        New DataEditorViewModel(objCardType)
    
    AddHandler DataEditorViewModel.RequestClose, _
        AddressOf RequestCloseEventHandler

End Sub
<RadioButton GroupName="CardTypeCommands" 
    Command="{Binding Path=FilterCommand}" 
    CommandParameter="{Binding Path=CardType}" 
    IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" ... >

The LoadCardTypeCommands function iterates through the DataBase.CardTypes collection, creating one CardTypeCommandViewModel for each CardType.  Notice that the RelayCommands are of Type CardType.  This makes it clean when the FilterExecute and NewExecute methods are called by assigning a concrete Type for the method parameter instead of Object that would require casting.

The RadioButton XAML code snippet shows CardTypeCommandViewModel being consumed by the CardTypeCommandView

Take note of the GroupName property.  By assigning each RadioButton to the same GroupName we get the behavior we are looking for, single selected Category button.

Binding to a Command and passing a Command Parameter in XAML could not be easier.

By binding to the IsChecked to the IsSelected property, the button can be selected in code on the UI.

Doesn't this just feel natural?  Model exposed by a ViewModel to a View.  ViewModel adapting Model to the View.  View responding to data changes in the ViewModel.  ViewModel responding to data changes from the Model and View.

List of Commands Scenario Note

Normally when a ViewModel exposes an ICommand property, it also handles the Execute and CanExecute delegates. 

In the above scenario, the NewExecute and FilterExecute methods are members of the ApplicationMainWindowViewModel class.  The ApplicationMainWindowViewModel class handles the adding and filtering operations for the application so it makes sense to want this class to handle command Execute methods.

Pointing the CardTypeCommandViewModel command Execute method delegates to the ApplicationMainWindowViewModel class methods makes for a cleaner design. 

If we didn't use this technique, the CardTypeCommandViewModel would either have to raise an event when a command was executed or have knowledge of the ApplicationMainWindowViewModel and call methods on it.

Rending CardTypeCommandViewModel

<Window.Resources>
  <DataTemplate DataType="{x:Type local:CardTypeCommandViewModel}">
      <local:CardTypeCommandView />
  </DataTemplate>
  
  <Style x:Key="alternatingListViewItemStyle" 
       TargetType="{x:Type ListViewItem}">
  
    <Style.Triggers>
        <Trigger Property="ItemsControl.AlternationIndex" 

Value="0">
            <Setter Property="Margin" Value="16,7" />
        </Trigger>
        <Trigger Property="ItemsControl.AlternationIndex" 

Value="1">
            <Setter Property="HorizontalAlignment" 

Value="Right" />
            <Setter Property="Margin" Value="16,-20" />
        </Trigger>
    </Style.Triggers>

</Style>
  
</Window.Resources>

<ListView 
  Margin="0,49,0,0" 
  Background="{x:Null}" 
  BorderThickness="0" 
  ScrollViewer.VerticalScrollBarVisibility="Hidden" 
  ItemContainerStyle="{StaticResource alternatingListViewItemStyle}" 
  AlternationCount="2" 
  ItemsSource="{Binding Path=CardTypeCommands}" />

The ListView's ItemsSource is a ReadOnlyCollection(Of CardTypeCommandViewModel).  When WPF renders the ListView Content, it looks up the DataTempate for the CardTypeCommandViewModel and finds it in the above DataTemplate.  The CardTypeCommandView is the cool button we described earlier.

Getting the buttons to stagger within the ListView took some thought.  I was going to write my own panel to lay the buttons out.  Then I remembered the new AlternationCount property and wrote the above alternatingListViewItemStyle Style.  By simply adjusting the margins of the buttons in the Trigger, I got the effect I wanted without having to author a panel control.

Showing and Hiding Views and Focus Setting Scenario

The developer wants to display an edit form in response to a command.  When the edit form is brought into view, Keyboard Focus needs to be set to the first UI Control on the form.

We could display the form in another window; could add the form to a TabControl or we could layer the window on top of other UI Elements in the application.

The concrete example of this scenario in Cipher Text is the record edit form that is displayed when the user double clicks a record in the data viewer.

ApplicationMainWindowView.xaml Code Snippet

<Grid Grid.Column="2" Margin="0,25,7,7">
    <local:DatabaseView DataContext=
                        "{Binding Path=DataBaseViewModel}" />
  
    <local:DataEditorView DataContext=
                          "{Binding Path=DataEditorViewModel}" />
</Grid>

DataEditorView.xaml Code Snippet

<UserControl 
  x:Class="DataEditorView" 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  xmlns:local="clr-namespace:CipherText" 
  TargetUpdated="VisibilityChanged_EventHandler" 
  Visibility="{Binding 
                Path=DataEditorVisibility, 
                NotifyOnTargetUpdated=True, 
                FallbackValue={x:Static Visibility.Hidden}}">
                
   <!--<span class="code-comment">
    The FallbackValue must be Hidden.  If it's Collapsed
    the DataBindings don't set up correctly initially.
    After this DataEditorView has been used once, it can
    either be Collapsed or Hidden.
    --></span>

To accomplish the simple task of showing and hiding a View we will be using data binding (big surprise right).  Again we see the simplicity of MVVM in action.  A View responding to data changes in the ViewModel.

The top snippet has two Views in the same Grid Row.  The bottom View DataEditorView will be rendered on top of DatabaseView if it is visible.  The DataEditorView is initially hidden so the DatabaseView will be in view in the UI.

When the DataEditorView.DataContext Is Nothing (nulll), the above Visibility property would assume its FallbackValue of Hidden because the binding returned Nothing (null). 

The only thing left is to set focus to the first field in the View when the View is displayed.  Here we assign the TargetUpdate event handler to below VisibilityChanged_EventHandler.  This event will be raised each time the Visibility property is changed.

This event handler gives Keyboard Focus to the Title TextBox each time the View's Visibility changes to Visible.

'HACK - Setting Focus To a View field
'When the View is brought into View, this event handler is called 
' by the DataEditorView.Visibility binding and the TargetUpdated attached event 
Private Sub VisibilityChanged_EventHandler( _
        ByVal sender As System.Object, _
        ByVal e As System.Windows.Data.DataTransferEventArgs)

    e.Handled = True

    If Me.Visibility = Windows.Visibility.Visible Then

        Keyboard.Focus(Me.txtTitle)

    End If

End Sub

Oh NO!  You have code in the View code behind.  Yes I do and it's all good.  MVVM like most design patterns is a set of guidelines, not hard and fast rules.  Design patterns when followed keep you out of trouble and prevent you from reinventing the wheel so they are a good thing.

Josh and I spoke about this a good bit when we wrote the Creating an Internationalized Wizard in WPF application.  If the code is UI specific and does not require Unit Testing then having the code in the code behind is fine.  Have a look at the code behind files, you'll see some UI specific code in some. 

My guidance is to follow the MVVM design pattern in most cases.  On occasion a scenario will present itself where deviation may make sense.  Weigh the pros and cons, make a decision and move on.  Remember, you and your team members are responsible for delivering and maintaining the code.

In the next scenario you'll see two more examples of UI specific code in the code behind.

Data Driven Dynamic Form Scenario

The developer needs to render a data driven dynamic form.  The form needs its behavior determined at run-time based on the source data. 

The concrete example of this scenario in Cipher Text is the record editor.  Each field in the form is data driven along with the field validation and casing rules.

FieldEditorViewModel & FieldEditorView

The FieldEditorViewModel adapts the CardField Model to the FieldEditorView.  The CardField class implements IDataErrorInfo.  This class has built in data validation rules and casing rules that run based on the FieldType and FieldCase properties.

AvailableFieldTypes and AvailableFieldCases properties are ReadOnlyCollections of OptionViewModelsOptionViewModel is a Generic class that Josh and I authored and described in our Creating an Internationalized Wizard in WPF article.  OptionViewModel specializes in wrapping values from Enumerations, providing human readable descriptions and customized sorting of values.  Additionally it simplifies programming with Flags EnumerationsOptionViewModel is another example of the power and simplicity of MVVM.  A ViewModel effortlessly adapting a Model to the UI.   

There are three Boolean properties that determine the state of the UI.

  • InSchemaEditingMode - This value is toggled when the user clicks the, "Modify Form" hyperlink on the data editor form.  The DataEditorViewModel holds the collection of FieldEditorViewModels.  The DataEditorViewModel handles the "Modify Form" command and iterates through the FieldEditorViewModels setting this property value.  UI Elements on the FieldEditorView are bound to this property and display when the value is True.
  • IsFieldInEditMode - This value is toggled when the user clicks the, "Edit Field" icon.  UI Elements on the FieldEditorView are bound to this property and display when its value is True.
  • IsMarkedForDelete - This value is toggled when the user clicks the red, "Remove Field" icon or the green check, "Restore Field" icon.  When the value is True, a gray border overlays the field as in the below image. 

Below are three instances of the FieldEditorView in three different states.  The top Address field is expanded but is invalid because the Address field has not yet been filled in and it's a required field.  The Phone field has been marked for delete.  The Web Site fields are complete and can be contracted when the green check icon is clicked. 

The green icon is really a XAML asset that I made that has a trigger data bound to the IsEnabled property of the button.  The button's Command CanExecute method checks the Error property on the CardField to determine if the Command is enabled and the field can be collapsed.

The FieldEditorView is completely data driven.  This unique and complex, yet powerful application feature was simple to design and code because of WPF's impressive data binding capabilities.  This UI is also super simple to Unit Test because it's data driven.

Hiding Adorners

The red "!" indicates an invalid field and are displayed in the Adorner Layer of the TextBox as defined in the Validation.ErrorTemplate.  However, if the user elects to delete a field that has one or more displayed adorners those adorner need to be removed otherwise it would display on top of the gray border.

The below OnIsMarkedForDeleteUpdated method is called when the Visibility property of the gray border is changed.  If the FieldEditorViewModel.IsMarkedForDelete is True then the border is displayed and any adorners on the FieldEditorView are removed.

'HACK - this a one example of using MVVM View code behind.
'We need to remove any Adorners that are placed due to 
'  invalid an invalid field.
'By using data binding, we can tap into when the ViewModel 
'  sets the field up for removal and remove the Adorners.
'
'If the required programming task is UI only, 
'  then using code behind is fine.
'
Private Sub OnIsMarkedForDeletetUpdated( _
  ByVal sender As System.Object, _
  ByVal e As System.Windows.Data.DataTransferEventArgs)

    e.Handled = True

    If _objFieldEditorViewModel Is Nothing Then
        Exit Sub
    End If

    If _objFieldEditorViewModel.IsMarkedForDelete Then

        Dim objAdornerLayer As AdornerLayer = AdornerLayer.GetAdornerLayer(Me)

        If objAdornerLayer IsNot Nothing Then
            ClearAdorders(objAdornerLayer, Me.txtFieldData)
            ClearAdorders(objAdornerLayer, Me.txtFieldSortOrder)
            ClearAdorders(objAdornerLayer, Me.txtFieldTag)
            ClearAdorders(objAdornerLayer, Me.txtMaximumLength)
        End If

    End If

End Sub

Private Sub ClearAdorders(ByVal objAdornerLayer As AdornerLayer, _
  ByVal objUIElement As UIElement)

    Dim objAdorder() As Adorner = objAdornerLayer.GetAdorners(objUIElement)
    If objAdorder IsNot Nothing Then
        For Each obj As Adorner In objAdorder
            objAdornerLayer.Remove(obj)
        Next
    End If
End Sub

FieldEditorView Commands

The Commands except for the GeneratePasswordCommand all simply changed data values in the ViewModel.  The View reacts to changes in the ViewModel's data to correctly render itself.

The two below command methods contract and expand the field in and out of edit mode. 

You'll notice that I make a sanity check by calling the corresponding CanExecute method.  This is a good practice since there is no guarantee that another developer didn't just make a direct call to the method.

Private Sub ContractEditFieldExecute(ByVal param As Object)
    If CanContractEditFieldExecute(param) Then
        Me.IsFieledInEditMode = False
        OnPropertyChanged("CardField")
    End If
End Sub

Private Sub EditFieldExecute(ByVal param As Object)
    If CanEditFieldExecute(param) Then
        Me.IsFieledInEditMode = True
        OnPropertyChanged("CardField")
    End If
End Sub

WPF and MVVM

This application shows off the symbionic couple WPF & MVVM.  This natural application development style typified by smooth data flow from the Model to the ViewModel that is consumed by the View simplifies WPF application development and Unit Testing.

If you're coming from Windows Forms, ASP.NET or another event driven programming platform, it takes a little adjustment to grasp the full power and simplicity to code a WPF MVVM application where the UI reacts to data changes rather than event handlers that modify named controls.  Give yourself time, write simple applications, you'll be so glad you did.  Then convert a small application that you're familiar with, add the Unit Tests to the new application and take that to work on Monday and show off your new developer skills. 

One area I've been paying special attention to is, memory profiling.  I use the Red-Gate Ants Profiler.  Ants has helped me understand .NET and memory management.  You may see areas in my code where I have code something that at first glace you think, "why did he do that?"  It was probably because I was memory profiling and found that some prudent clean up of objects prevented objects from hanging around in memory. 

History

  • 28 December 2008 : Initial Release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Karl Shifflett
Architect Gayle Manufacturing Company
United States United States
Karl loves .NET, WPF, WCF, ASP.NET, VB.NET and C#.
 
Awards:
 
  • December 2008 VB.NET Code Project Article Award
  • 2009 Code Project MVP
  • 2008 Code Project MVP
  • 2008 Microsoft MVP - Client App Dev
  • December 2007 VB.NET Code Project Article Award
  • Gold Medal Winner at IBM's 1998 PROIV Programming Contest in Las Vegas
Click here to check out my Blog
 
Click here to learn about Mole 2010 debugging tool for Visual Studio 2010
 
Click here to read about XAML Power Toys
 

Just a grain of sand on the worlds beaches.

Follow on   Twitter

Comments and Discussions

 
QuestionRegarding the Code in the View code-behind Pinmembernickkennard00727-Apr-14 15:59 
QuestionMessage Removed Pinmemberwannabeuk11-Jun-13 3:15 
AnswerRe: c# Conversion : Custom Event issue PinmemberKarl Shifflett11-Jun-13 4:02 
GeneralRe: c# Conversion : Custom Event issue Pinmemberwannabeuk11-Jun-13 4:43 
GeneralMy vote of 5 PinmemberShabana Parveen26-Apr-13 7:44 
QuestionHow To Video Link Pinmembermakedp23-Jan-12 2:09 
AnswerRe: How To Video Link PinmemberKarl Shifflett4-Feb-12 10:13 
GeneralRe: How To Video Link Pinmembermakedp6-Mar-12 21:21 
QuestionPossible coding error (typo?) PinmvpHenry Minute12-Aug-11 2:54 
AnswerRe: Possible coding error (typo?) PinmemberKarl Shifflett12-Aug-11 6:16 
GeneralPassword [modified] PinmemberMember 770016413-Mar-11 16:16 
GeneralRe: Password PinmemberKarl Shifflett13-Mar-11 18:32 
GeneralMy feedback PinmemberRick Rat15-Aug-10 11:38 
QuestionQuestion about the code in code behind Pinmemberstg60912-Feb-10 21:42 
AnswerRe: Question about the code in code behind PinmemberKarl Shifflett13-Feb-10 14:50 
QuestionWhat to build first? M, V, or VM? Pinmemberleah.hurst19-Jan-10 10:09 
AnswerRe: What to build first? M, V, or VM? PinmemberKarl Shifflett20-Jan-10 2:59 
AnswerRe: What to build first? M, V, or VM? PinmemberJimAnderson24-Jan-10 6:12 
QuestionException occurs: ( Expression Bend 3 + SketchFlow Spanish Version v3.0.1938.0 ) Can not instantiate DataEditorView + connecting from SQL Server 2008.- Pinmemberaadisanto9-Dec-09 5:13 
AnswerRe: Exception occurs: ( Expression Bend 3 + SketchFlow Spanish Version v3.0.1938.0 ) Can not instantiate DataEditorView + connecting from SQL Server 2008.- PinmvpKarl Shifflett12-Dec-09 6:07 
GeneralRe: Exception occurs: ( Expression Bend 3 + SketchFlow Spanish Version v3.0.1938.0 ) Can not instantiate DataEditorView + connecting from SQL Server 2008.- Pinmemberaadisanto12-Dec-09 14:42 
GeneralRe: Exception occurs: ( Expression Bend 3 + SketchFlow Spanish Version v3.0.1938.0 ) Can not instantiate DataEditorView + connecting from SQL Server 2008.- PinmvpKarl Shifflett14-Dec-09 1:41 
GeneralRe: Exception occurs: ( Expression Bend 3 + SketchFlow Spanish Version v3.0.1938.0 ) Can not instantiate DataEditorView + connecting from SQL Server 2008.- Pinmemberaadisanto14-Dec-09 6:44 
GeneralRe: Exception occurs: ( Expression Bend 3 + SketchFlow Spanish Version v3.0.1938.0 ) Can not instantiate DataEditorView + connecting from SQL Server 2008.- PinmvpKarl Shifflett17-Dec-09 2:01 
GeneralRe: Exception occurs: ( Expression Bend 3 + SketchFlow Spanish Version v3.0.1938.0 ) Can not instantiate DataEditorView + connecting from SQL Server 2008.- Pinmemberaadisanto17-Dec-09 2:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 29 Dec 2008
Article Copyright 2008 by Karl Shifflett
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid