Up Silverlight
DevForce 2010 Resource Center » Code samples » Additional code samples » Silverlight » Code sample: Silverlight search form using Intersoft's Client UI

Code sample: Silverlight search form using Intersoft's Client UI

Last modified on August 15, 2012 17:22


This tutorial describes the code used to implement a member search and results viewer for a client of mine. They needed a Members Listing Page to display a list of members. The listing page contains a set of text boxes for the user to enter search criteria and a grid to display the results. The client needed to have the ability to search for members by social security number, membership card number, employee number, and/or either a whole or part of a last or first name. Multiple search criteria could be entered, but because the database contained thousands of members, it was necessary to ensure that at least one of the fields was entered.

I am not an employee of either Intersoft or IdeaBlade. I am an independent developer who uses these tools on a daily basis to be more productive with my time.
-William H. Gower


Here is a screen shot of the finished product:

form.png

This tutorial will consist of four sections. The first section will describe the creation of the Members Listing page, the view, the second, the creation of the view model class to handle the commands and the code, the third section will how to tie the view and view model classes together and the final section will discuss the repository classes.

This tutorial will use the Model View View Model (MVVM) framework. If you are not familiar with this framework or want additional information, just google MVVM and you will find a wealth of information on the subject.

  I will be using the Intersoft ClientUI 5 tools for the development of the views and the binding of the commands on the view to the view model. I will use IdeaBlade DevForce 6.1.1 to create the entity framework and provide  access to the database. I will provide the entire source code as an attachment but for the purposes of the tutorial discuss only code and xaml pertinent to the tutorial at hand.

Preliminary work

Since this is a limited tutorial, a lot of preliminary work is already assumed to have been done. It is assumed that a project was created using an MVVM template provided by Intersoft. It is assumed that IdeaBlade DevForce for Silverlight was used to create the entity framework. Here is the structure of the solution that I will be using.

solutionexplorer.png

The following is an explanation of each of the folders and files above.

  • The DomainModel project holds the classes that represents my database table structure. 
  • The TicketWareSL2012 project is my Silverlight application that was created for me using the MVVM template.
  • The Asserts folder contains my styles and icons.
  • The Helpers folder contains the various Query Objects which will be used to pass the criteria back and forth between the view and view model classes.
  • The Models folder holds a partial class of my domain model classes. I may want to customize properties or use the Validation and Verification features of DevForce. Those will be placed in here.
  • The Model services folder holds my repository class, my EntityManagerFactory class, and my repository manager. I never allow my view model classes to access the entity framework. That is done through the repository class. The repository has my Create, Add, Get, and Delete classes for each object that is in my entity framework. Rather than have a separate repository for each class, I use one class unless it becomes too large and unwieldy. Then I try to break it down by modules etc. For example, in an accounting application, I would have a General Ledger Repository, Accounts Receivable Repository, and Accounts Payable Repository etc.
  • The View Models folder holds my view model classes and my sand box editor classes. The sandbox editor classes are not used in this tutorial. They will be for another tutorial in the future.
  • The View Models Infrastructure holds miscellaneous classes like DialogBoxServiceProvider which allows the view model to display a dialog box. It also contains a MessagePresenter which allows the view model to display a message box to the user. Remember this is an MVVM application. All commands are processed in the view model. The purpose of the view is only to display information to the user. There is no code or a minimum of code in the view class.
  • Finally, we have the views folders which will hold the views. Then there are a couple of files such as the App.xaml and MainPage.xaml.

Developing the view

The view is developed using a UXPage. It represents a page in a browser or a screen in an out of browser application. Notice that the Members Listing UXPage actually sits inside of Main Page. The Main Page consists of the header and a ItemControl that runs vertically on the left hand side of the UXPage. The right hand side is the content holder for all the various listing classes. In the case of this application, there is an Inventory listing, a Sales Invoice listing etc. The listing page is for locating records to work with while the actual data entry will take place using dialog boxes.

I added a new UXPage to the project and named it MembersListView.cs. I divided the page into five parts, the header, the toolbar, the criteria panel, the grid panel, and the status bar at the bottom of the screen. There are many ways to code this in xaml. My way may or may not be the best, but it works.

I start by creating a DockPanel using the Intersoft:DockPanel. It will be the container for entire screen. I then use a GlassLabel control to display the header. 

Intersoft
<Grid x:Name="LayoutRoot">
 
<Intersoft:DockPanel FillChildMode="Custom">
     <Intersoft:GlassLabel Intersoft:DockPanel.Dock="Top" Content="Members Listing"
VerticalAlignment="Top" Height="50" BorderThickness="0" CornerRadius="{Binding
ElementName=membersViewPage, Path=HeaderCornerRadius}" FontSize="21.333"/>

I create a toolbar using the UXToolBar class. Ignore the commands at this point, they will be explained in a later part of the tutorial.

Intersoft
         
<Intersoft:UXToolBar Intersoft:DockPanel.Dock="Top" CornerRadius="0" GripHandleVisibility="Collapsed" Height="30" OverflowHandleVisibility="AsNeeded">
 <Intersoft:UXToolGroup MinWidth="450">
 <Intersoft:UXToolBarButton Command="{Binding AddCommand}" Content="Add New Member" ToolTipService.ToolTip="Add a new member (Ctrl + Shift + A)"/>
 <Intersoft:UXSeparator/>
 <Intersoft:UXToolBarButton Command="{Binding EditCommand}" Content="Edit Member" ToolTipService.ToolTip="Edit selected member (Ctrl + Shift + E)" />
 <Intersoft:UXToolBarButton Command="{Binding EditCommand}" Content="View Member" ToolTipService.ToolTip="View selected member (Ctrl + Shift + V)" />
 <Intersoft:UXToolBarButton Command="{Binding DeleteCommand}" Content="Delete Member" ToolTipService.ToolTip="Delete selected member (Ctrl + Shift + X)"/>
 <Intersoft:UXSeparator/>
     <Intersoft:UXToolBarButton Command="{Binding FindCommand}" Content="Find Member" ToolTipService.ToolTip="Find member" />
    <Intersoft:UXSeparator/>
   <Intersoft:UXToolBarButton Command="{Binding ShowReportsCommand}" Content="Member Reports" ToolTipService.ToolTip="" />
  </Intersoft:UXToolGroup>
 </Intersoft:UXToolBar>

The toolbar consists of six buttons and 3 separators. Each button has a content which displays the button text, a tooltip for when the mouse is passed over the button, and a command that will be sent to the view model. We will describe the commands in more detail in the next section.
The search criteria panel is then created using a GroupBox. I use a grid to break the panel into 3 rows and 4 columns for the labels and data entry fields.
Intersoft
     
<Intersoft:GroupBox Width="900" Header="Search Criteria" Height="135" Intersoft:DockPanel.Dock="Top" CornerRadius="25">
 <Grid>
  <Grid.RowDefinitions>
   <RowDefinition />
    <RowDefinition />
   <RowDefinition />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
   <ColumnDefinition Width="100" />
   <ColumnDefinition />
   <ColumnDefinition Width="100" />
   <ColumnDefinition />
  </Grid.ColumnDefinitions>
 <TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Margin="0,5,0,5">SSN:</TextBlock>
 <TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" Margin="0,5,0,5">Employee #:</TextBlock>
 <TextBlock Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Margin="0,5,0,5">Card #:</TextBlock>
 <TextBlock Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" Margin="0,5,0,5">Last Name:</TextBlock>
 <TextBlock Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" Margin="0,5,0,5">First Name:</TextBlock>
 <Intersoft:UXTextBox Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" Margin="5,0,0,0" Width="150" Height="25" Text="{Binding MemberQuery.SSN, Mode=TwoWay}" />
 <Intersoft:UXTextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Margin="5,0,0,0" Width="150" Height="25" Text="{Binding MemberQuery.EmployeeNumber, Mode=TwoWay}"/>
 <Intersoft:UXTextBox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" Margin="5,0,0,0" Width="150" Height="25" Text="{Binding MemberQuery.CardNo, Mode=TwoWay}"/>
 <Intersoft:UXTextBox Grid.Row="0" Grid.Column="3" HorizontalAlignment="Left" Margin="5,0,0,0" Width="200" Height="25" Text="{Binding MemberQuery.LastName, Mode=TwoWay}" />
 <Intersoft:UXTextBox Grid.Row="1" Grid.Column="3" HorizontalAlignment="Left" Margin="5,0,0,0" Width="200" Height="25" Text="{Binding MemberQuery.FirstName, Mode=TwoWay}" />
  </Grid>
 </Intersoft:GroupBox>

A panel is created or the grid which will display the returned rows..
Intersoft
        
<Intersoft:GroupBox Width="900" Height="540" Intersoft:DockPanel.Dock="Top" CornerRadius="25">
 <Intersoft:UXGridView AutoGenerateColumns="False"
   Name="MemberGrid"
   ItemsSource="{Binding Members}"
   QueryOperation="Server"
   SortDescriptors="{Binding QueryDescriptor.SortDescriptors, Mode=TwoWay}"
   IsBusy="{Binding IsBusy}"
   SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
    SelectionMode="Single"
   CanUserSortColumns="True">
                                
   <Intersoft:UXGridView.Columns>
   <Intersoft:UXGridViewTextColumn Header="Card #" Binding="{Binding CardNo}" Width="70"
    CanUserSort="True">
   <Intersoft:UXGridViewTextColumn.HeaderStyle>
    <Style TargetType="Intersoft:UXGridViewColumnHeader">
   <Setter Property="HorizontalContentAlignment" Value="Center"/>
   </Style>
   </Intersoft:UXGridViewTextColumn.HeaderStyle>                        
   </Intersoft:UXGridViewTextColumn>
   <Intersoft:UXGridViewTextColumn Header="Empl #" Binding="{Binding EmployeeNumber}" Width="90"
    CanUserSort="True">
   <Intersoft:UXGridViewTextColumn.HeaderStyle>
   <Style TargetType="Intersoft:UXGridViewColumnHeader">
   <Setter Property="HorizontalContentAlignment" Value="Center"/>
   </Style>
   </Intersoft:UXGridViewTextColumn.HeaderStyle>                  
   </Intersoft:UXGridViewTextColumn>
   <Intersoft:UXGridViewTextColumn Header="Last Name" Binding="{Binding LastName}" Width="170"
    CanUserSort="True">
   <Intersoft:UXGridViewTextColumn.HeaderStyle>
   <Style TargetType="Intersoft:UXGridViewColumnHeader">
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
   </Style>
  </Intersoft:UXGridViewTextColumn.HeaderStyle>
  </Intersoft:UXGridViewTextColumn>
  <Intersoft:UXGridViewTextColumn Header="First Name" Binding="{Binding FirstName}" Width="170"
   CanUserSort="True">
  <Intersoft:UXGridViewTextColumn.HeaderStyle>
  <Style TargetType="Intersoft:UXGridViewColumnHeader">
  <Setter Property="HorizontalContentAlignment" Value="Center"/>
  </Style>
  </Intersoft:UXGridViewTextColumn.HeaderStyle>
  </Intersoft:UXGridViewTextColumn>
  <Intersoft:UXGridViewTextColumn Header="Address 1" Binding="{Binding Addr1}" Width="155"
  CanUserSort="True">
  <Intersoft:UXGridViewTextColumn.HeaderStyle>
  <Style TargetType="Intersoft:UXGridViewColumnHeader">
   <Setter Property="HorizontalContentAlignment" Value="Center"/>
   </Style>
  </Intersoft:UXGridViewTextColumn.HeaderStyle>
  </Intersoft:UXGridViewTextColumn>
  <Intersoft:UXGridViewTextColumn Header="City" Binding="{Binding City}" Width="100"
   CanUserSort="True">
  <Intersoft:UXGridViewTextColumn.HeaderStyle>
   <Style TargetType="Intersoft:UXGridViewColumnHeader">
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
   </Style>
  </Intersoft:UXGridViewTextColumn.HeaderStyle>
  </Intersoft:UXGridViewTextColumn>
  <Intersoft:UXGridViewTextColumn Header="State" Binding="{Binding State}" Width="40"
   CanUserSort="True">
  <Intersoft:UXGridViewTextColumn.HeaderStyle>
  <Style TargetType="Intersoft:UXGridViewColumnHeader">
   <Setter Property="HorizontalContentAlignment" Value="Center"/>
    </Style>
   </Intersoft:UXGridViewTextColumn.HeaderStyle>
   </Intersoft:UXGridViewTextColumn>
  <Intersoft:UXGridViewTextColumn Header="Zip" Binding="{Binding Zip}" Width="80"
   CanUserSort="True">
   <Intersoft:UXGridViewTextColumn.HeaderStyle>
  <Style TargetType="Intersoft:UXGridViewColumnHeader">
  <Setter Property="HorizontalContentAlignment" Value="Center"/>
   </Style>
  </Intersoft:UXGridViewTextColumn.HeaderStyle>
  </Intersoft:UXGridViewTextColumn>
  </Intersoft:UXGridView.Columns>
 </Intersoft:UXGridView>  
 </Intersoft:GroupBox>

Notice several things about the grid. I turn off the AutoGenerateColumns because I want to define my own columns. The ItemsSource for the grid will be an object called Members which we will instantiate and populate in the View Model in the next section. I have told it to perform all operations on the server rather than the client. I created a sort descriptor which will allow the user to click on any column in the grid for sorting. We will see how to use that in the next section. Finally I told the grid that when the user selects a row, the row will be sent to the view model object that is tied to this view and stored in a property called SelectedItem. The user is only able to select one row at a time.

I next create the columns in the grid providing the header text, what column in the Members object to use for binding, and also indicate whether that column can be sorted. It may not make sense to allow all columns in the grid to be sorted.

Finally I create the status bar which will display to the user the number of records displayed in the grid and which one if any of them has been selected.

Intersoft
        
<Border BorderBrush="Silver" BorderThickness="0" Intersoft:DockPanel.Dock="Bottom" Height="30" CornerRadius="{Binding ElementName=membersViewPage, Path=FooterCornerRadius}" Background="#FF2A2A2A">
  <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
   <TextBlock Foreground="White" Text="{Binding Path=MembersCount}" VerticalAlignment="Center"/>
   <Intersoft:UXSeparator Orientation="Vertical" Height="16" Width="1" Margin="10,0"/>
   <TextBlock Foreground="White" Text="{Binding Path=SelectionStatus}" VerticalAlignment="Center" />
  </StackPanel>
 </Border>
  </Intersoft:DockPanel>
 </Grid>

The two properties that interest us here are MembersCount and SelectionStatus. They will be populated in the view model. The view will then be notified to display the properties.

In this first section, we looked at the view which was based on a UXPage. We created a search criteria panel and a grid panel and placed controls in the panels. At this point if you compiled this project and tried to run it, the view would display similar to the display above but nothing would happen as the commands on the view are not yet hooked up to the view model. In Section 2, we will create the view model and in section 3 hook up the commands.

Creating the View Model

The view holds all the objects that are displayed to the user. But the objects displayed do not do anything without some code attached to them. Because we are using MVVM, the controls reside in the View and the code to handle the commands and track the state will reside in the ViewModel class. We will create a new class in our project and name it MembersListViewModel.cs. Note the naming structure I use. The View is called MembersListView and the View Model is named MembersListViewModel. Okay I didn’t invent the naming structure, but I did think that it was a good idea to use in all my applications.

The view model will be derived from a class called ViewModelBase. ViewModelBase is extended from INotifyPropertyChanged. Every time a property in my View Model changes I will raise the OnPropertyChanged event and pass as a parameter the name of the property that was changed. The View attached to this View Model will be notified of the change and can redisplay the changed object on the view accordingly.

C#
   public abstract class ViewModelBase : INotifyPropertyChanged
    {
       public event PropertyChangedEventHandler PropertyChanged;

       protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

           if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Back in the View Model class I create several fields which will be used to hold critical information.
C#
      private MemberViewModel _selectedItem;
       private bool _isInEditing;
       private bool _isBusy;
       private QueryDescriptor _queryDescriptor;
       private MemberQuery _memberQuery;

Remember from section 1, _selectedItem is the row that was selected in the grid. I can access the selected row through this private field or a public property SelectedItem. The _isBusy field will be used to determine when to indicate to the view whether it should display the busy indicator alerting them to be patience.

The QueryDescriptor is a new class that was introduced in the previous release of ClientUI. Its purpose is to hold the query information that will be passed to the GetMembers function. Later in the tutorial we will take the fields from the search criteria panel and build the QueryDescriptor object. The QueryDescriptor will be passed to the GetMembers function and the entity framework will parse the QueryDescriptor and retrieve the appropriate records if any. Intersoft and IdeaBlade are closely working together as partners to ensure that the ClientUI controls work with the DevForce classes and integrate well. The MemberQuery object holds the criteria data filled in on the View.

Create a new class named MemberQuery.cs and save it to the Helpers folder

C#
  public class MemberQuery
    {
       string _ssn = string.Empty;
       string _lastName = string.Empty;
       string _firstName = string.Empty;
       string _employeeNo = string.Empty;

       public string SSN
        {
           get { return _ssn; }
           set { _ssn = value; }
        }

       public string LastName
        {
           get { return _lastName; }
           set { _lastName = value; }
        }

       public string FirstName
        {
           get { return _firstName; }
           set { _firstName = value; }
        }

       public string EmployeeNo
        {
           get { return _employeeNo; }
           set { _employeeNo = value; }
        }

       public Int32? CardNo { get; set; }
    }

We will see how to bind the MemberQuery object to the controls on the View and how to populate the QueryDescriptor object later.

Let’s add several properties to the class and then instantiate them in the constructor.

C#
      public ITicketWareRepository Repository { get; set; }
      public ObservableCollection<MemberViewModel> Members { get; set; }
      public IMessagePresenter MessagePresenter { get; set; }


    public MembersListViewModel()
        {
           this.Members = new ObservableCollection<MemberViewModel>();
           this.Members.CollectionChanged += Member_CollectionChanged;

           this.Repository = TicketWareRepository.Instance;
           this.MessagePresenter = new MessagePresenter();



            _memberQuery = new MemberQuery();

        }

TicketWareRepository is the name of the repository class and Repository is the object that will be used in this class to make all calls to the data source. In my constructor, I grab an instance of the currently running repository for the application or if this is the first time the repository has been instantiated it creates a new copy We will look at how this works in the last section of the tutorial.

The Members object is the collection that will hold the members returned from the data source and stored in the grid. Notice that both the Members object and the _selectedItem object are an instance of the MemberViewModel class and not the Member class. The MemberViewModel class represents the columns of the member class that will be displayed in the grid. I can also create custom properties in the MemberViewModel class to represent columns from related tables or derived columns. For example, I have two tables Inventory and Vendor. Inventory is related to Vendor because it contains the vendor id of the vendor for that product. So there is a one to many- relationship between Vendor and Inventory. In a grid in which I am displaying the inventory rows I would also like to display the vendor name not just the vendor id. In my InventoryViewModel class I would create a property as follows:

C#
     public string VendorName
        {
           get
            {
               return _inventory.Vendor.VendorName;
            }
        }

I bind the grid column to VendorName, but the inventory object will be navigated to the Vendor object that has a relationship with that particular inventory item and the vendor name property will be displayed in the column.

Here is the code for the MemberViewModel. I need to create a property for every column I wish to display in the grid.

C#
public class MemberViewModel : ViewModelBase
    {
       // Fields
       private Member _member;//

       // Constructor
       public MemberViewModel(Member member)
        {
            _member = member;
        }//

       // Views
       public Member Member
        {
           get { return this._member; }
        }//

       public string EmployeeNumber
        {
           get { return _member.EmployeeNumber; }
        }

       public Int32? CardNo
        {
           get { return _member.CardNo; }
        }

       public string LastName
        {
           get { return _member.LastName; }
        }

       public string FirstName
        {
           get { return _member.FirstName; }
        }

       public string Addr1
        {
           get { return _member.Addr1; }
        }

       public string Addr2
        {
           get { return _member.Addr2; }
        }

       public string City
        {
           get { return _member.City; }
        }

       public string State
        {
           get { return _member.State; }
        }

       public string Zip
        {
           get { return _member.Zip; }
        }

    }
 

Continuing on with the MembersListViewModel class, I then create several getter/setters to access private properties. These public properties will be accessed by the View when it is notified that the value of the properties has changed.
C#
     public string MembersCount
        {
           get
            {
               if (this.Members.Count == 0)
                   return "No members";
               if (this.Members.Count == 1)
                   return "1 member";
               return this.Members.Count + " members";
            }
        }

       public MemberQuery MemberQuery
        {
           get { return _memberQuery; }
           set
            {
                _memberQuery = value;
                OnPropertyChanged("MemberQuery");
            }
        }

       public MemberViewModel SelectedItem
        {
           get { return _selectedItem; }
           set
            {
               if (_selectedItem != value)
                {
                   if (_selectedItem != null)
                        _selectedItem.Member.PropertyChanged -= Member_PropertyChanged;

                   if (value != null)
                       value.Member.PropertyChanged += Member_PropertyChanged;

                    _selectedItem = value;
                    OnPropertyChanged("SelectedItem");
                    OnPropertyChanged("SelectionStatus");
                    InvalidateCommands();
                }
            }
        }

       public string SelectionStatus
        {
           get
            {
               return this.SelectedItem == null
                         ? "No member selected"

: "Selected member: " + this.SelectedItem.Member.LastName + ", " + this.SelectedItem.Member.LastName;
            }
        }

       public bool IsBusy
        {
           get { return _isBusy; }
           internal set
            {
               if (_isBusy != value)
                {
                    _isBusy = value;
                    OnPropertyChanged("IsBusy");
                }
            }
        }


The MembersCount and the SelectionStatus are used by the View to populate the values in the fields that show the number of records displayed in the grid and the selected record in the status bar. The IsBusy property indicates to the View that something is happening in the code that is taking time and to alert the user of that fact.

Next we have a public property that provides access to the QueryDescriptor object _queryDescriptor. Just copy this code as is in each project. Notice that the getter checks to see if an instance of the _queryDescriptor has already been instantiated, otherwise it uses the constructor to create a new one.

C#
     public QueryDescriptor QueryDescriptor
        {
           get
            {
               if (this._queryDescriptor == null)
                {
                   this.QueryDescriptor = new QueryDescriptor();
                }
               return this._queryDescriptor;
            }
           set
            {
               if (this._queryDescriptor != value)
                {
                   if (this._queryDescriptor != null)
                       this._queryDescriptor.QueryChanged -= new System.EventHandler(OnQueryChanged);

                   this._queryDescriptor = value;
                   this._queryDescriptor.QueryChanged += new System.EventHandler(OnQueryChanged);

                   this.OnPropertyChanged("QueryDescriptor");
                }
            }
        }

Next we have the code that builds a filter based on the criteria fields filled in on the View.
C#
     public void BuildFilter()
        {

           if (this.QueryDescriptor.FilterDescriptors.Count >= 1)
               this.QueryDescriptor.FilterDescriptors.Clear();

            CompositeFilterDescriptorCollection groupFilter1 = new CompositeFilterDescriptorCollection();

            groupFilter1.LogicalOperator = FilterCompositionLogicalOperator.And;

           if (MemberQuery.SSN != string.Empty)
            {
                groupFilter1.Add(
                   new FilterDescriptor()
                    {
                        PropertyName = "SocSecNo",
                        Operator = FilterOperator.StartsWith,
                        Value = MemberQuery.SSN
                    }
                   );
            }
           if (MemberQuery.EmployeeNo != string.Empty)
            {
                groupFilter1.Add(
                   new FilterDescriptor()
                    {
                        PropertyName = "EmployeeNumber",
                        Operator = FilterOperator.StartsWith,
                        Value = MemberQuery.EmployeeNo
                    }
                   );
            }
           if (MemberQuery.CardNo != null)
            {
                groupFilter1.Add(
                   new FilterDescriptor()
                    {
                        PropertyName = "EmployeeNumber",
                        Operator = FilterOperator.StartsWith,
                        Value = MemberQuery.EmployeeNo
                    }
                   );
            }   
           if (MemberQuery.LastName != string.Empty)
            {
                groupFilter1.Add(
                   new FilterDescriptor()
                    {
                        PropertyName = "LastName",
                        Operator = FilterOperator.StartsWith,
                        Value = MemberQuery.LastName
                    }
                   );
            }
           if (MemberQuery.FirstName != string.Empty)
            {
                groupFilter1.Add(
                   new FilterDescriptor()
                    {
                        PropertyName = "FirstName",
                        Operator = FilterOperator.StartsWith,
                        Value = MemberQuery.FirstName
                    }
                    );
            }
           if (groupFilter1.Count >= 1)
                _queryDescriptor.FilterDescriptors.Add(groupFilter1);
        }

I first check to see if _queryDescriptor is already populated from a previous filter and if it has, I clear it out. I then create a group filter object, which will contain a specific logical operation like using “And” to join the various search fields together. If I wanted to also have an “or” in the search, I would create a second group filter for that.

In a series of “if” statements, I check each property of the Member Query object and see if the user typed criteria into that field on the View. Each property of the query object is mapped to a text box on the criteria search panel If the property has a value, I add it to the group filter. The property name is the field domain object being searched, in this case, the member table. I then indicate the type of Operator to use such as “Equals”, “Less than”, Greater than” etc. In the case of LastName and FirstName I use “Starts With” to allow for partial name searches. Finally I assign the property in the query object to the Value property of the FilterDescriptor object.
 
I check to see if the user entered any criteria at all. If so I add all the various filterOperators to the QueryDescriptor.FilterDescriptors object.

Next we have the BuildSort function. While the user can click on any column to sort the grid after the data is displayed, I would like to have it initially display in a specific order.

C#
      public void BuildSortOrder()
        {
            _queryDescriptor.SortDescriptors.Add(
               new SortDescriptor()
                {
                    PropertyName = "LastName",
                    Direction = System.ComponentModel.ListSortDirection.Ascending
                }
            );
            _queryDescriptor.SortDescriptors.Add(
              new SortDescriptor()
               {
                   PropertyName = "FirstName",
                   Direction = System.ComponentModel.ListSortDirection.Ascending
               }
           );
        }

Each column to be sorted is represented by a SortDescriptor object. Notice that the SortDescriptor is a part of the QueryDescriptor class. The SortDescriptor has two properties; PropertyName is the column in the table we wish to sort by and the Direction is either “Ascending” or “Descending.” In our example, we sort by LastName followed by FirstName.

Finally we get have the Fetch method that will call the repository function GetMembers, pass it the QueryDescriptor object and return the rows retrieved.

C#
      private void FetchMember()
        {
           this.IsBusy = true;

            Repository.GetMembers(_queryDescriptor,

              (members) => // query success
             {
                 this.IsBusy = false;
                  ResetMemberList(members);
              },
              (error) => // failed
             {
                 this.IsBusy = false;
                  DisplayMessage(error.Message);
              });
        }

Notice the code sets the IsBusy indicator to true, notifying the View to begin displaying the busy indicator to the user. It then calls the calls the GetMember function of the repository class. I will talk about the repository class in section 4. The GetMembers method is called with the QueryDescriptor object as the first parameter, while the second and third parameters will be used to indicate the success or failure of the call. Remember in Silverlight, all data access is asynchronous and as soon as the call is made, the code returns. So we will create a method that will be notified when the data retrieval is finished. If the data call is successful, the members variable holds the rows returned. set the IsBusy flag to false and repopulate the Members collection created earlier in the tutorial.
C#
     private void ResetMemberList(IEnumerable<Member> member)
        {
           this.Members.Clear();
            member.ForEach(c => Members.Add(new MemberViewModel(c~)~)~);
        }

If the call to the database was not successful for any reason, then we will turn off the IsBusy signal and display an error message to the user.
C#
    private void DisplayMessage(string message)
        {
           this.MessagePresenter.ShowInformationMessage(message);
        }

Remember MessagePresenter allows for the view model class to display a message box to the user notifying them of the error.
Finally, we have a few miscellaneous methods in the view model.
C#
      private void Member_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
           if (e.PropertyName == "LastName" || string.IsNullOrEmpty(e.PropertyName))
                OnPropertyChanged("SelectionStatus");
        }

       private void Member_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            OnPropertyChanged("MembersCount");
        }

These allow for notifications to be sent to the view if the SelectionStatus or MembersCount properties are changed.

There are several things that we have not yet covered in the view model class but they will be covered in section 3. The source code will have the complete code from all the classes seen up to this point.

In section 2, we covered the creation of the view model class. Remember the View Model class handles all the commands from the View class and holds the state of the View class. In the next section we will see how commands are sent from the view to the view model and how the view is notified of any changes to the state.

Integrating the View and View Model class

In sections 1 and 2, we looked at how to create the view and view model classes. In this section, we will see how the View talks to the View Model class and vice versa. In MVVM, we want to couple the two classes together as loosely as possible. I may in the future want to use a different view with my view model and do not want to have to tear a lot of code out in order to do that. I would like to just create a new view and maybe add some properties to my view model but that is it.

In this section I am just going to show the appropriate code from the View and View Model as it pertains to sending commands from the view to view model. I will not explain code and xaml that was discussed in section 1.
In the View class, I need to tell it which ViewModel it is going to send it is associated with. Just above the opening Grid tag, I will place this piece of code.

Intersoft
   <Intersoft:UXPage.DataContext>
        <ViewModels:MembersListViewModel />
    </Intersoft:UXPage.DataContext>

This tells the View that the DataContext or information to be displayed in the objects on the View come from the MembersListViewModel in the ViewModels folder. Just below my opening Grid tag I will set up the bindings for the various commands on the toolbar.

Intersoft
     
<Grid.Resources>
 <Intersoft:CommandReference x:Key="AddCommand" Command="{Binding AddCommand}"/>
 <Intersoft:CommandReference x:Key="EditCommand" Command="{Binding EditCommand}"/>
 <Intersoft:CommandReference x:Key="ViewCommand" Command="{Binding ViewCommand}"/>
 <Intersoft:CommandReference x:Key="DeleteCommand" Command="{Binding DeleteCommand}"/>
 <Intersoft:CommandReference x:Key="FindCommand" Command="{Binding FindCommand}"/>
 <Intersoft:CommandReference x:Key="ShowReportsCommand" Command="{Binding ShowReportsCommand}"/>
</Grid.Resources>
<Intersoft:CommandManager.InputBindings>
 <Intersoft:InputBindingCollection>
  <Intersoft:KeyBinding Command="{StaticResource AddCommand}" Gesture="Ctrl+Shift+A"/>
  <Intersoft:KeyBinding Command="{StaticResource EditCommand}" Gesture="Ctrl+Shift+E"/>
  <Intersoft:KeyBinding Command="{StaticResource ViewCommand}" Gesture="Ctrl+Shift+V"/>
  <Intersoft:KeyBinding Command="{StaticResource DeleteCommand}" Gesture="Ctrl+Shift+X"/>
  <Intersoft:KeyBinding Command="{StaticResource FindCommand}" Gesture="Ctrl+Shift+F"/>
  <Intersoft:KeyBinding Command="{StaticResource ShowReportsCommand}" Gesture="Ctrl+Shift+R"/>
 </Intersoft:InputBindingCollection>
</Intersoft:CommandManager.InputBindings>

In the Grid Resources tag, I set up 6 command references which represent the 6 buttons on the toolbar. Each command reference has a key which is the name of the command and a command property which is the field on the view model it ties that will be the target of the command.

In the InputBindings tag, I can indicate special key is used to access the buttons . For example, Ctrl+Shift+F is used for the Find command. This is not a necessary section.

Then within the xaml that sets up the toolbar I bind the command property of each button to one of the above commands.

Intersoft
            
<Intersoft:UXToolGroup MinWidth="450">
<Intersoft:UXToolBarButton Command="{Binding AddCommand}" Content="Add New Member" ToolTipService.ToolTip="Add a new member (Ctrl + Shift + A)"/>
<Intersoft:UXSeparator/>
<Intersoft:UXToolBarButton Command="{Binding EditCommand}" Content="Edit Member" ToolTipService.ToolTip="Edit selected member (Ctrl + Shift + E)" />
<Intersoft:UXToolBarButton Command="{Binding EditCommand}" Content="View Member" ToolTipService.ToolTip="View selected member (Ctrl + Shift + V)" />
<Intersoft:UXToolBarButton Command="{Binding DeleteCommand}" Content="Delete Member" ToolTipService.ToolTip="Delete selected member (Ctrl + Shift + X)"/>
<Intersoft:UXSeparator/>
<Intersoft:UXToolBarButton Command="{Binding FindCommand}" Content="Find Member" ToolTipService.ToolTip="Find member" />
<Intersoft:UXSeparator/>
<Intersoft:UXToolBarButton Command="{Binding ShowReportsCommand}" Content="Member Reports" ToolTipService.ToolTip="" />
</Intersoft:UXToolGroup>
</Intersoft:UXToolBar>

As far as the grid goes we have already seen the ItemsSource property, the SelectedItem property, and the column definitions, so I won’t go through those again. If you have forgotten them or wish to refresh your memory go back to section 1.

In the View Model class, I set up the various command variables.

C#
      
public DelegateCommand AddCommand { get; private set; }
public DelegateCommand EditCommand { get; private set; }
public DelegateCommand DeleteCommand { get; private set; }
public DelegateCommand FindCommand { get; private set; }
public DelegateCommand ShowReportsCommand { get; private set; }

And in the constructor for the class, I instantiate each one.

C#
          
this.AddCommand = new DelegateCommand(AddMember, CanAddMember);
this.EditCommand = new DelegateCommand(EditMember, CanEditMember);
this.DeleteCommand = new DelegateCommand(DeleteMember, CanDeleteMember);
this.FindCommand = new DelegateCommand(FindMember, CanFindMember);
this.ShowReportsCommand = new DelegateCommand(ShowReports, CanShowReports);

Notice that we use the DelegateCommand class to create the command objects and the constructor takes two parameters the first parameter is the method to call when the command is triggered on the View. The second parameter is the method that is checked continuously to see if the button or simply the command should be allowed to occur. For example, if the grid has no rows in it,  it would be useless the user to click on the “Edit” or “Delete” button. If they do I have to check in my code to make sure the user is situated on a valid row in the grid. Instead, I will disable the buttons if no row is selected. In addition,some of the users may not have  permission to delete rows from tables. I can check for security permissions in the CanDelete() method and enable or disable  the appropriately. 

Let’s just look at the Find Command section for the purposes of this tutorial.

C#
      
private bool CanFindMember(object parameter)
        {
           return true;
        }

       private void FindMember(object parameter)
        {
            BuildFilter();
           if (this.QueryDescriptor.FilterDescriptors.Count >= 1)
            {
                BuildSortOrder();
                FetchMember();
            }
           else
            {
                DisplayMessage("Some criteria must be selected before searching records.");
            }
        }

I can’t think of any reason why a user should not be able to find a record, so I always return true to enable the button at all times. I could check to see if any of the fields are filled in on the search panel and enable or disable the button, but I will leave that task for the reader to do.

In the FindMember method, I call the BuildFilter() method covered in the last section. I check to see if any filters have been set. Because a member table might have thousands upon thousands of records in them and this is a Silverlight application with data flowing back and forth over the internet, I want to ensure that at least one criteria field was filled in to attempt to limit the returned number of rows. If so, I call the BuildSortOrder and FetchMember methods as covered in the section 2. If no criteria was selected, I display a message to the user notifying them of that fact.

Finally we have the InvalidateCommand. This method is called by the SelectedItem property and indicates that the selection has changed and the Can methods should be rechecked to see if any of the buttons should be enabled or disabled.

C#
      private void InvalidateCommands()
        {
           // Invalidate the commands that related to this view scope//

           this.AddCommand.RaiseCanExecuteChanged();
           this.EditCommand.RaiseCanExecuteChanged();
           this.DeleteCommand.RaiseCanExecuteChanged();
           this.FindCommand.RaiseCanExecuteChanged();
           this.ShowReportsCommand.RaiseCanExecuteChanged();
        }

Well that covers how the commands are sent from the view to the view model class and how they are processed by the view model and state is returned to the view. In the next section we will show how to create the repository.

The repository

In the first three sections of this tutorial, we built the view, the view model, and we did it all using the MVVM framework. The view contains only the objects the user will see and interact with. The view could have been developed by a designer using a tool such as Microsoft Expression Blend 4. The view model contains all the commands and state of certain properties and fields. The view model could have been separately created by a programmer at the same time the view created by a designer. 

In this section we will add a repository class to access the data source. By keeping the data access to a separate class, if in the future we switch to a different entity framework, it will easy to update the program. There are actually several classes we will create in this section. First let’s take a quick review at how the repository is accessed by the view model.

In the view model class, we declare an object to represent the repository and instantiate the object in the constructor either to an instance of an existing repository or a new one is created.

C#
public ITicketWareRepository Repository { get; set; }

       this.Repository = TicketWareRepository.Instance;
 

In the Model Services folder, let’s add a new class and name it TicketWareRepository. We will have it extend from the interface, ITicketWareRepository. For the purposes of this tutorial the ITicketRepository is not used. Create a local variable for the repository and a public property for accessing it.

C#
     private static ITicketWareRepository _repository;

       internal TicketWareEntities Manager { get; private set; }

We only want one copy of the repository running in memory. To do that we will create an instance that checks to see if a repository object already exists otherwise it creates a new instance.
C#
     public static ITicketWareRepository Instance
        {
           get
            {
               return _repository ?? (_repository = CreateRepository());
            }
           set
            {
                _repository = value;
            }
        }


      public TicketWareRepository(TicketWareEntities entityManager)
        {
            Manager = entityManager;
        }

And here is the CreateRepository method.

C#
     public static ITicketWareRepository CreateRepository()
        {
           return new TicketWareRepository(EntityManagerFactory.Create());
        }

Note that the CreateRepository method calls the EntityManagerFactory class

 public static class EntityManagerFactory
    {
       public static TicketWareEntities Create()
        {
            var manager = new TicketWareEntities();
           return manager;
        }
    }

Create a new class and name it EntityManagerFactor. It is a static class. The create method creates a manager object which it then returns. The Entities class was created for us by IdeaBlade DevForce. It is assumed that you already know how to do this part of the project and it is thus out of the scope of things to be covered in this tutorial.

While the repository contains all the create methods, the delete methods, the save, cancel, and Get methods, all that we care about in this tutorial is the GetMembers method that we saw called from the FetchMembers of the view model class in section 2.

C#
     public void GetMembers(QueryDescriptor queryDescriptor, Action<IEnumerable<Member>> onSuccess, Action<Exception> onFail)
        {

            Manager.Members.Parse(queryDescriptor)
              .ExecuteAsync(
                  op =>
                  {
                     if (op.CompletedSuccessfully)
                      {
                         if (onSuccess != null)
                              onSuccess(op.Results);
                      }
                     else
                      {
                         if (onFail != null)
                          {
                              op.MarkErrorAsHandled();
                              onFail(op.Error);
                          }
                      }
                  }
               );
        }

Remember the GetMembers method takes as its first parameter, the QueryDescriptor object detailing the filter and sort order to use for accessing the data source. The action parameter is the action to take if the data request is successful and finally the last parameter is used if the data access is unsuccessful. Because this is an asynchronous method call, we use the ExecuteAsync method provided by IdeaBlade after telling the entity framework to parse the QueryDescriptor.

So let’s review. In this tutorial, we learned how to create a view that had a search criteria panel and a grid panel. We saw how to send commands from the view to the view model and how to access the state of properties and fields to refresh the objects on the view. The entire tutorial was built using the View Model View-Model framework using just Intersoft ClientUI and IdeaBlade DevForce for Silverlight. The View contains all the objects the user interacts with on the screen. The View Model class contains all the commands, the state of the variables, and calls to the repository. Finally the repository calls the entity framework to access the data source.

While it may seem like it took a lot of work to get to this point, it is amazing how much background work both ClientUI and Intersoft have done for us and have eased our development time. Without them, we would have needed additional frameworks in order to accomplish the MVVM that was built into the UI controls themselves. DevForce from the moment it is used to create the entity framework to the calls we use to access the data, is doing a lot of work in the background. Without a tool like DevForce, our job as a programmer would be much harder.

Creating this part from scratch took me only a couple of hours. Now there is a lot more functionality to add in and many more views and view Models and repository methods to add to make this a sellable project, but I hope that you have seen how easy it is to begin an MVVM application using Intersoft ClientUI 5 and IdeaBlade DevForce for Silverlight.

I am not an employee of either Intersoft or IdeaBlade. I am an independent developer who uses these tools on a daily basis to be more productive with my time.
-William H. Gower

Created by DevForce on July 05, 2011 10:14

This wiki is licensed under a Creative Commons 2.0 license. XWiki Enterprise 3.2 - Documentation. Copyright © 2015 IdeaBlade