Responsive User Interface
Tags: csharp, dotnet, beginners
Published: 2020-10-17 23:54:55

In this part of the series, we'll implement some logic to create a responsive user interface. We'll begin by adding a notification base class, which will allow us to inherit a property changed event and callback in our view-model. We may move the notification base class into the models folder next week when we look at editing existing entries. For now, simply create a new class in the 'ViewModels' folder. Name the class 'NotificationBase.cs', and add the following code.

```csharp
using System.ComponentModel;

namespace MVVMIntroUI.ViewModels
{
    class NotificationBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
```

The next thing we will create is a command system, which will allow us to respond to UI events from within our view-model. Again, create a new class in the 'ViewModels' folder. This time, we'll simply name the class 'Command.cs', and implement the 'ICommand' interface. We'll add an 'Action' field to the class, which will allow us to store a callback in each instance of our command class.

```csharp
using System;
using System.Windows.Input;

namespace MVVMIntroUI.ViewModels
{
    class Command : ICommand
    {
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        private readonly Action _function;

        public Command(Action function)
        {
            this._function = function;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            this._function.Invoke();
        }
    }
}
```

Now, we need to completely overhaul our people view-model, and add properties and methods, which we will bind to our people view. Open 'PeopleViewModel.cs' and make the following changes.

```csharp
using MVVMIntroUI.Models;
using System.Collections.ObjectModel;

namespace MVVMIntroUI.ViewModels
{
    class PeopleViewModel : NotificationBase
    {
        public ObservableCollection<Person> People { get; set; }

        private string _firstNameTextBox;

        public string FirstNameTextBox
        {
            get { return this._firstNameTextBox; }
            set
            {
                this._firstNameTextBox = value;
                this.OnPropertyChanged(nameof(this.FirstNameTextBox));
            }
        }

        private string _lastNameTextBox;

        public string LastNameTextBox
        {
            get { return this._lastNameTextBox; }
            set
            {
                this._lastNameTextBox = value;
                this.OnPropertyChanged(nameof(this.LastNameTextBox));
            }
        }

        private string _emailTextBox;

        public string EmailTextBox
        {
            get { return this._emailTextBox; }
            set
            {
                this._emailTextBox = value;
                this.OnPropertyChanged(nameof(this.EmailTextBox));
            }
        }

        public Command OnSubmitCommand { get; set; }

        public PeopleViewModel()
        {
            this.People = new ObservableCollection<Person>();
            this.OnSubmitCommand = new Command(this.OnSubmit);
        }

        public void OnSubmit()
        {
            this.People.Add(
                new Person
                (
                this.FirstNameTextBox,
                this.LastNameTextBox,
                this.EmailTextBox
                )
            );
            this.FirstNameTextBox = string.Empty;
            this.LastNameTextBox = string.Empty;
            this.EmailTextBox = string.Empty;
        }
    }
}
```

You'll notice that we're calling 'OnPropertyChanged' from within the setter method of each property we need to track, and passing in the name of the property. We're also initializing the class with a command and passing in the 'OnSubmit' method. We've changed our people collection from a 'List' to an 'ObservableCollection<T>'. This collection will raise a property changed event when a change is made to the collection.

We're now ready to create the necessary UI elements in our people view, and bind our properties and commands. Open 'PeopleView.xaml', and make the following changes. Feel free to arrange the elements however you would like. Just be sure to include the text boxes and button necessary for adding a new entry.

```xml
<UserControl x:Class="MVVMIntroUI.Views.PeopleView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:vm="clr-namespace:MVVMIntroUI.ViewModels"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <vm:PeopleViewModel x:Key="PeopleViewModel" />
    </UserControl.Resources>
    <Grid DataContext="{StaticResource PeopleViewModel}"
          Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <DataGrid Grid.Row="0"
                  ItemsSource="{Binding People}" />
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition Height="auto" />
                <RowDefinition Height="auto" />
                <RowDefinition Height="auto" />
            </Grid.RowDefinitions>
            <TextBox Grid.Row="0"
                     Text="{Binding FirstNameTextBox, Mode=TwoWay}" />
            <TextBox Grid.Row="1"
                     Text="{Binding LastNameTextBox, Mode=TwoWay}" />
            <TextBox Grid.Row="2"
                     Text="{Binding EmailTextBox, Mode=TwoWay}" />
            <Button Grid.Row="3"
                    Content="Submit"
                    Command="{Binding OnSubmitCommand}" />
        </Grid>
    </Grid>
</UserControl>
```

Take note of the two-way binding. This allows us to control the properties of our view-model from the view, and control the content of each element from within the view-model. This way, we're able to clear each text box from within the 'OnSubmit' method of our people view-model.

In the following part of this series, we'll look into data persistence, and add some logic to manipulate existing entries.