WPF and MVVM tutorial 07, the List search.

Ok, starting from this post we are going to do something really interesting. In this episode we need to create a simple List windows, but as you will see the logic will not be so different then a one-to-many form.

This will be the final result:

image

The ViewModel for the List Window.

First of all we need to create the view model. The view model should have the following commands:

  1. New – Add a new Customer
  2. Save – Save all the changes we did …
  3. Edit – Edit the current selected customer
  4. Delete – Delete the current selected customer
  5. Text box and search button – to operate search activity
  6. Exit the form

Then we need to add the following objects:

  1. An observable collection for the list of customers
  2. A current customer object, but this is not mandatory …
  3. The search information we retrieve from the view, or better, the view sends to us.

The final result will be this view model class:

image

Let’s have a look at the View Model commands. As you know when we assign a RelayCommand, we can do it in 2 ways:

By passing with a lambda expression the corresponding action.

By passing with a lambda expression the corresponding action and a predicate (something like true/false).

So for some commands like Save or Delete we can also build a predicate action like CanSave? CanDelete? and encapsulating some validation logic inside.

So the code should look like:

 

The RelayCommands.

Simple command like Create a new customer:

  1: //Private field
  2: public ViewCommand newCommand;
  3: //Public property to be assigned in the XAML code
  4: public ViewCommand NewCommand {
  5:    get {
  6:       if (newCommand == null)
  7:          newCommand = 
  8:          //Lambda expression for assigning the action
  9:          new ViewCommand(param => this.NewCustomer());
 10:       return newCommand;
 11:    }
 12: }
 13: //Real routine executed in the ViewModel
 14: private void NewCustomer() {
 15:    NavigationActions.OpenCustomerView();
 16: }
 17: 

Or something more complex like Delete a customer:

  1: //Private command field
  2: private ViewCommand deleteCommand;
  3: //Public databinded ICommand
  4: public ViewCommand DeleteCommand {
  5:    get {
  6:       if (deleteCommand == null) {
  7:       deleteCommand = new ViewCommand(
  8:           param=>this.DeleteCustomer(),
  9:           //Lambda expression for evaluating the execution
 10:           param=>this.CanDeleteCustomer
 11:           );
 12:       }   
 13:       return deleteCommand;
 14:    }
 15: }
 16: //Real delete command
 17: public void DeleteCustomer() {
 18:    if (SelectedCustomer != null) {
 19:       if(NavigationActions.QueryConfirmation(
 20:          "Delete Customer.",
 21:          string.Format("Do you want to delete {0}?",SelectedCustomer.FirstName))){
 22:          ListOfCustomers.Remove(SelectedCustomer);
 23:          repository.DeleteCustomer(SelectedCustomer);
 24:       }
 25:    } else {
 26:       NavigationActions.ShowError(
 27:         "Delete Customer.", 
 28:         "You must select a Customer!");
 29:    }
 30: }
 31: //Additional logic can go here ...
 32: private bool CanDeleteCustomer {
 33:    get { return true; }
 34: }
 35: 

Then of course we will have two different command for Edit a Customer or create a new one, and at the end the difference will be just here:

  1: //Open the Customer window empty
  2: NavigationActions.OpenCustomerView();

  4: NavigationActions.OpenCustomerView(SelectedCustomer);
  3: //Open the Customer window passing a Customer
  5: 
  6: 

The others commands are the same, but if you want in my solution you can find the complete implementation.

Loading and working with a Collection.

After we build all the commands and we bind them to the XAML code, we need to load our entities. For this we will use a ObservableCollection List that we will implement in the initialization of our View Model, so when the Window will open we will also load the Collection inside the ListView.

  1: //We need an instance of our repository
  2: CustomerRepository repository;
  3: //This will contain our Customers
  4: ObservableCollection<Customer> listOfCustomers;
  5: //This will be the current selected customer
  6: Customer selectedCustomer;
  7: public CustomersViewModel() {
  8:    if (repository == null) {
  9:       //Initialization of the Repository
 10:       repository = new CustomerRepository();
 11:    }
 12:    Initialization of the List
 13:    listOfCustomers = 
 14:       new ObservableCollection<Customer>(
 15:          repository.GetAllCustomers());
 16:    this.SearchText = "Some text to search ...";
 17: }
 18: //Binded property containing the Customer list
 19: public ObservableCollection<Customer> ListOfCustomers {
 20:    get { return listOfCustomers; }
 21: }
 22: 

INotifyPropertyChanged

If we want to advise the UI that we loaded a new customers list, we have to build a ViewModel that implements the INotifyPropertyChanged in this way:

  1: #region INotifyPropertyChanged Members
  2: 
  3: public event PropertyChangedEventHandler PropertyChanged;
  4: 
  5: public void NotifyPropertyChanged(String info) {
  6:    if (PropertyChanged != null) {
  7:       PropertyChanged(
  8:          this, 
  9:          new PropertyChangedEventArgs(info)
 10:       );
 11:    }
 12: }
 13: 
 14: #endregion

And then change the property code in this way:

  1: public ObservableCollection<Customer> ListOfCustomers {
  2:     
  3:     get { return listOfCustomers; }
  4:     private set {
  5:         if (listOfCustomers != value) {
  6:             listOfCustomers = value;
  7:             NotifyPropertyChanged("ListOfCustomers");
  8:         }
  9:     }
 10: }

Now, everytime we are going to load something new into the collection, or we are going to use a view of the collection the ViewModel will send a message in the view saying “Hey, look I changed something in the list, update the UI!!”.

Some XAML code.

Now we have the Commands, the Model and we need to bind them to the view.

Loading the viewmodel into the view:

  1: ...
  2: <xmlns:vm=
  3:    "clr-namespace:MVVM.ViewModel;
  4:    assembly=MVVM.ViewModel />
  5: 
  6: ...
  7: <Window.DataContext>
  8:    <vm:CustomersViewModel/>
  9: </Window.DataContext>

Binding one command to a Button:

  1: <Button Name="btnNew" Command="{Binding Path = NewCommand}">

Binding the List to the ListView and assigning the selected customer:

  1: <ListView 
  2:   Name="lstCustomers" 
  3:   ItemsSource="{Binding Path=ListOfCustomers}"
  4:   SelectedItem="{Binding Path=SelectedCustomer}" 
  5:   SelectionMode="Single"
  6:   IsSynchronizedWithCurrentItem="True"
  7:   HorizontalAlignment="Stretch" 
  8:   VerticalAlignment="Stretch"
  9:   MinHeight="100">

The IsSynchronizedWithCurrentItem bind the listview and the selecteditem together.

Now, what we should be able to do here is to Load the customers, press delete and wipe one or more then one. Then:

  1. If we close the window and we reopen it the Customer will re-appear again.
  2. If we commit all the changes with the SaveCommand and the we close and re-open the window, the Customer will not be there anymore.

In this lesson we will see only how to implement the search function. In the next one we will see each single command in details.

The user types some text and after that he searches for a result.

  1: private void FindCustomers() {
  2:    //If there is no text, prompt an error
  3:    //In the future this will be a XAML trigger
  4:     if (this.SearchText == string.Empty || this.SearchText == null) {
  5:         NavigationActions.ShowError("Search Customers.", "Please enter some text ...");
  6:         return;
  7:     }
  8:    //Keep the search text and build a dynamic query for the DAL
  9:     ListOfCustomers = new ObservableCollection<Customer>(
 10:         repository.GetCustomersByQuery(
 11:         p => p.CompanyName.StartsWith(SearchText)
 12:         || p.FirstName.StartsWith(SearchText)
 13:         || p.LastName.StartsWith(SearchText)));
 14: }
 15: 

At this point we will have a first final search solution like these screenshots:

image image

 

 

 

 

 

Tags: