0
votes

What is the best approach in dealing with events fired from the model (not UI events such as button clicks, window resizes, etc) that have to update the UI when working with the MVP (passive view) pattern?

There are a lot of similar questions asking how to update the UI from a background thread, an event, etc. However, all the answers seem to suggest to use the Control.Invoke method, which is fine for a simple Winforms application but it cannot be used when using the MVP pattern.

Would the simplest way of doing this be by using a SynchronizationContext object in the presenter? Then using the SynchronizationContext.Post method to run the event code on the UI thread.

I am using .NET Framework version 4.8

1

1 Answers

0
votes

These are the solutions I came out with that do not use Control.Invoke.

The code for IMainView, MainView, and Program is the same in both solutions:

IMainView

namespace Tester1
{
    interface IMainView
    {
        string ControlProperty { get; set; }
    }
}

MainView

using System.Windows.Forms;

namespace Tester1
{
    public partial class MainView : Form, IMainView
    {
        public MainView()
        {
            InitializeComponent();
        }

        public string ControlProperty
        {
            get => MyControl.Text;
            set => MyControl.Text = value;
        }
    }
}

Program

using System;
using System.Windows.Forms;

namespace Tester1
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            // models
            EventRaisingObject eventRaisingObject = new EventRaisingObject();

            // views
            MainView mainView = new MainView();

            // presenters
            MainPresenter mainPresenter = new MainPresenter(mainView, eventRaisingObject);

            Application.Run(mainView);
        }
    }
}

Solution 1: Using SynchronizationContext

MainPresenter

using System.Threading;

namespace Tester1
{
    class MainPresenter
    {
        private readonly IMainView mainView;
        private readonly EventRaisingObject eventRaisingObject;

        private readonly SynchronizationContext viewContext;

        public MainPresenter(IMainView mainView, EventRaisingObject eventRaisingObject)
        {
            this.mainView = mainView;

            viewContext = SynchronizationContext.Current;

            this.eventRaisingObject = eventRaisingObject;
            this.eventRaisingObject.MyEvent +=
                (sender, s) => viewContext.Post(d => OnMyEvent(s), null);
        }

        private void OnMyEvent(string e)
        {
            // do some work

            mainView.ControlProperty = e;

            // do some work
        }
    }
}

This solution uses a SynchronizationContext object, initialised in the constructor with the context of the UI.

Solution 2: Using TaskFactory

using System.Threading.Tasks;

namespace Tester1
{
    class MainPresenter
    {
        private readonly IMainView mainView;
        private readonly EventRaisingObject eventRaisingObject;

        private readonly TaskFactory viewTaskFactory;

        public MainPresenter(IMainView mainView, EventRaisingObject eventRaisingObject)
        {
            this.mainView = mainView;

            viewTaskFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());

            this.eventRaisingObject = eventRaisingObject;
            this.eventRaisingObject.MyEvent +=
                (sender, s) => viewTaskFactory.StartNew(() => OnMyEvent(s));
        }

        private void OnMyEvent(string e)
        {
            // do some work

            mainView.ControlProperty = e;

            // do some work
        }
    }
}

This solution uses a TaskFactory object, initialised in the constructor with the TaskScheduler of the UI. When the event is raised a new task is started by using the TaskFactory object.