首页 > 解决方案 > 从 Winforms MVP 中的模型事件更新 UI(被动视图)

问题描述

在使用 MVP(被动视图)模式时必须更新 UI 的模型触发的事件(不是 UI 事件,如按钮单击、窗口调整大小等)处理的最佳方法是什么?

有很多类似的问题询问如何从后台线程、事件等更新 UI。但是,所有答案似乎都建议使用该Control.Invoke方法,这对于简单的 Winforms 应用程序来说很好,但不能在以下情况下使用使用 MVP 模式。

最简单的方法是在演示者中使用SynchronizationContext对象吗?然后使用该SynchronizationContext.Post方法在 UI 线程上运行事件代码。

我正在使用 .NET Framework 4.8 版

标签: c#multithreadingwinformsuser-interfacemvp

解决方案


这些是我提出的不使用Control.Invoke的解决方案。

IMainViewMainView和的代码Program在两种解决方案中都是相同的:

主视图

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

主视图

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;
        }
    }
}

程序

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);
        }
    }
}

解决方案 1:使用SynchronizationContext

主讲者

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
        }
    }
}

此解决方案使用一个SynchronizationContext对象,在构造函数中使用 UI 的上下文进行初始化。

解决方案 2:使用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
        }
    }
}

此解决方案使用一个对象,在构造函数中使用UI 进行TaskFactory初始化。TaskScheduler引发事件时,使用该TaskFactory对象启动新任务。


推荐阅读