Ioannis Panagopoulos blog

Tutorials on HTML5, Javascript, WinRT and .NET

Going from Model-View-Presenter to MV-VM with WPF Commands (Part #1: MVP)

by Ioannis Panagopoulos

In this post I will try to show the application of the MVP and MV-VM programming approaches and the use of WPF commands in them. I will start with a simple Window in WPF with all the code intertwined in the code-behind file and transform it to a Window with no code at all at the code-behind file, showing the separation between the view and the logic that is achieved with MV-VM and Commands.

The Window is the following:

Window Screenshot

The "Add Client" adds a new client, the "Delete Client" removes the selected client and the "Reset Client" resets the values of the selected client. Note that the two menu items that require a client to be selected are currently disabled. This is the functionality we want to provide.

We create a support class named "ClientRepository" that will play the role of our database-datalayer in this trivial example:

public static class ClientRepository
{
    private static ObservableCollection<Client> _clients = new ObservableCollection<Client>()
    {
        new Client(){FirstName="Test1",LastName="Test1",Phone="9999999999"},
        new Client(){FirstName="Test2",LastName="Test2",Phone="9999999999"},
        new Client(){FirstName="Test3",LastName="Test3",Phone="9999999999"},
        new Client(){FirstName="Test4",LastName="Test4",Phone="9999999999"},
        new Client(){FirstName="Test5",LastName="Test5",Phone="9999999999"},
    };

    public static void AddClient(Client me)
    {
        _clients.Add(me);
    }

    public static ObservableCollection<Client> GetClients()
    {
        return _clients;
    }

    public static void DeleteClient(Client me)
    {
        _clients.Remove(me);
    }
}

Approach #1 - View and Model fully intertwined

In this approach everything will reside in the code-behind file. In the XAML file Menu Items are related to event handlers as follows:


<MenuItem Header="Add Client" Click="MenuItemAddClient_Click" />
<MenuItem Header="Delete Client" Click="MenuItemDeleteClient_Click" />
<MenuItem Header="Reset Client" Click="MenuItemEditClient_Click" />
 

The clients in the ListBox, are displayed using a DataTemplate as follows:


<DataTemplate x:Key="DataTemplate1">
  <Canvas Width="502" Height="37">
    <TextBox ... Text="{Binding Path=FirstName,Mode=TwoWay}" TextWrapping="Wrap"/>
    <TextBox ... Text="{Binding Path=LastName,Mode=TwoWay}" TextWrapping="Wrap"/>
    <TextBox ... Text="{Binding Path=Phone,Mode=TwoWay}" TextWrapping="Wrap"/>
  </Canvas>
</DataTemplate>

 The code-behind file handles all jobs, from binding the ListBox's ItemsSource to the clients' list to handling the MenuItems' events:

public partial class Window1 : Window
    {
        private Client _selectedClient = null;
        public Client SelectedClient
        {
            get { return _selectedClient; }
            set
            {
                _selectedClient = value;
                this.MenuItemEditClient.IsEnabled = (_selectedClient!=null);
                this.MenuItemDeleteClient.IsEnabled = (_selectedClient != null);
            }
        }

        public Window1()
        {
            InitializeComponent();
            ListBoxClients.ItemsSource = ClientRepository.GetClients();
        }

        private void MenuItemEditClient_Click(object sender, RoutedEventArgs e)
        {
            if (_selectedClient != null)
            {
                _selectedClient.FirstName = "RESET";
                _selectedClient.LastName = "RESET";
                _selectedClient.Phone = "9999999999";
            }
        }

        private void ListBoxClients_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            SelectedClient = ListBoxClients.SelectedItem as Client;
        }

        private void MenuItemDeleteClient_Click(object sender, RoutedEventArgs e)
        {
            if (_selectedClient != null)
            {
                ClientRepository.DeleteClient(_selectedClient);
            }
        }

        private void MenuItemAddClient_Click(object sender, RoutedEventArgs e)
        {
            ClientRepository.AddClient(new Client());
        }
    }

Note that the logic for enabling/disabling the MenuItems resides in the setter of the SelectedClient property. Additionally, the logic for the MenuItems' actions is implemented within the event handlers. This means that if we need to change the view in the future (implement another Window for the same functionailty), we practically need to do everything from scratch. Not to mention, that it is nearly impossible to test the GUI.

Approach #2 - Converting the App to MVP (Model-View-Presenter)

We want to separate the logic from the view. So we apply the following rules:

  • The View (code-behind) will not have any properties related to the Business Logic (eg the property SelectedClient should go). Those properties will reside in the Presenter Class.
  • The View will do nothing more in its event handlers other than calling a method of the presenter.
  • If the Presenter needs to do something in the View (disable the MenuItems), it will do it by calling a method of the View.
  • To decouple the implementations both View and Presenter should implement an Interface (IView and IPresenter) respectively which will be used in the method calls.

This in action is as follows. The XAML remains intact, but the code-behind file:

    public partial class Window2 : Window,IView
    {
        IPresenter myPresenter = new Presenter();

        public Window2()
        {
            myPresenter.SetView(this);

            InitializeComponent();

            myPresenter.InitGUI();
        }

        private void MenuItemEditClient_Click(object sender, RoutedEventArgs e)
        {
            myPresenter.ResetClient();
        }

        private void ListBoxClients_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            myPresenter.SetSelectedClient(ListBoxClients.SelectedItem as Client);
        }

        private void MenuItemDeleteClient_Click(object sender, RoutedEventArgs e)
        {
            myPresenter.DeleteClient();
        }
        private void MenuItemAddClient_Click(object sender, RoutedEventArgs e)
        {
            myPresenter.AddClient();
        }

        #region IView Members

        public void ToggleMenuItems(bool Enable)
        {
            this.MenuItemEditClient.IsEnabled =Enable;
            this.MenuItemDeleteClient.IsEnabled = Enable;
        }
        public void InitializeList(ObservableCollection<Client> Clients)
        {
            this.ListBoxClients.ItemsSource = Clients;
        }

        #endregion
    }

First thing to notice is that the View implements the IView interface.The IView defines two methods which (as you will see) are used by the Presenter. Second, note that the View is bound to the IPresenter interface and whenever logic need to be executed it diverts the call to a method in the Presenter. The Presenter's implementation is as follows:

 public class Presenter:IPresenter
{
    public IView MyView = null;
    private Client _selectedClient = null;

    public void AddClient()
    {
        ClientRepository.AddClient(new Client());
    }
    public void SetSelectedClient(Client me)
    {
        _selectedClient = me;
        MyView.ToggleMenuItems(_selectedClient != null);
    }
    public void InitGUI()
    {
        MyView.InitializeList (ClientRepository.GetClients());
    }
    public void DeleteClient()
    {
        if (_selectedClient != null)
        {
            ClientRepository.DeleteClient(_selectedClient);
        }
    }
    public void ResetClient()
    {
        if (_selectedClient != null)
        {
            _selectedClient.FirstName = "RESET";
            _selectedClient.LastName = "RESET";
            _selectedClient.Phone = "9999999999";
        }
    }
    public void SetView(IView view)
    {
        MyView = view;
    }
}

By using the MVP approach, we have succesfully separated the GUI from the logic. Now, if we need to test the GUI we can just implement a dummy class that implements the IView interface and attach it to the Presenter. Moreover, we can provide more than one Presenter for the same View. Finally, if we need to change the GUI, we just implement a new Window and all we have to do is make sure that the new Window implements the IView interface.

But there is still some nusty wiring to be done in the code-behind file. Is there a better approach? As it turns out for WPF there is and it ows its existence to WPF's powerful Binding Engine (its ability to have any property of the GUI data bound to something) and to the Command Pattern implemented for the event handlers in WPF.

The next post will be following by the end of the week.

 

kick it on DotNetKicks.com Shout it
blog comments powered by Disqus
hire me