Search code examples
c#.netdata-bindingmvpbindingsource

Some guidelines on how to implement MVP pattern for a data binding windows forms app


I would like to build a windows forms application which uses data binding, which I assume is the use of binding sources. I am going to be loading different parts of a large datatable to 3 separate datagridviews, and I'm going to accommodate operations like:

1) Moving whole columns of data from one datagridview to another

2) Editing values inside the cells

3) Filtering data

4) In the future, accomodate getting live data as well, and showing any new data just as it comes.

My first approach, which seemed logical to me, would be that the entry point would probably be the presenter, and then the presenter would create view. I am thinking quite seriously of another approach however, which is to have a separate class create the presenter, view, and model.

The first question is: Is one of the two approaches potentially better in this particular scenario?

Second question: Right now my filtering was immediate. But if I implement the MVP, then that means that the filtering operation would have to go through the presenter class, which would update the model, and then come back to the presenter, which would update the view. Perhaps this won't be a visually appealing experience for the user. Would it be perhaps better if the view was directly connected to the model for these kinds of operations (supervised MVP)?

Third : Is it a correct approach to create presenters for other user controls that will pop up, but that they will not be able to directly access the model, and will instead have to go thrugh the main presenter of the application first?


Solution

  • This is how I have done MVP with Winforms and has worked quite well,

    1) Model - Data structure which the View is bound

    2) View - Encapsulation of all visual logic

    3) Controller - Encapsulation of all operations performed on View/Model

    Below is an example,

    //Model
    class Person : INotifyPropertyChanged
    {
        //Properties with notification
    }
    
    //Views
    abstract class View
    {
        //Base class for all views
        public abstract void SetDataSource(object source);
        protected virtual void Refresh();
        public abstract void Show();
    }
    
    //Data grid view
    class DataGridView : View
    {
        private Controller _controller = null;
        private System.Windows.Forms.DataGrid _grid = new System.Windows.Forms.DataGrid();
    
        public DataGridView(Controller controller)
        {
            _controller = controller;
        }
    
        public override void SetDataSource(object dataSource)
        {
            _grid.DataSource = dataSource;
        }
    
        public override void Show()
        {
        }
    
        protected override void Refresh
        {
            _grid.Refresh();
        }
    }
    
    //Controllers
    abstract class Controller
    {
        //Base controller
        public abstract void Init();
        public abstract void Show();
        public abstract void ViewChanged(object args);//To receive view changes
    }
    
    //Person presenter
    class PersonGridController : Controller
    {
        private DataGridView _view = new DataGridView(this);
        private BindingList<Person> _persons = new BindingList<Person>();
    
        public override void Init()
        {
            //Initialize persons
            //Optional, start thread/timer to update persons in background
        }
    
        public override void Show()
        {
            _view.SetDataSource(_persons);
            _view.Show();
        }
    
        public override void ViewChanged(object args)
        {
            //Based on arguments, perform filter, edit, save, etc.
        }
    }
    

    This is the basic structure I've used in the past. There is coupling between the Controller and View, we could decouple that using Dependency Injection(using a framework such as Unity) and move the logic of creating the View outside the Controller. I also have a Workspace manager class which hosts the View inside a Window. If you want to make it simpler, the View can inherit from Window class.

    Regarding your questions,

    1) Moving columns from one grid to another should be done through the data source

    2 & 3) Filtering and edits can be accomplished using the Controller.ViewChanged() event

    4) Live updates can be using the Controller.Init() function, a thread can be spawned so the data source can be updated in the background.