(MVVM)模型视图视图模型和线程

时间:2009-10-15 20:52:03

标签: .net winforms data-binding mvvm multithreading

我正在使用(MVVM)模型视图模型设计模式,但我遇到了模式的问题。在我的场景中,我使用DataTable作为视图模型。此视图模型设置为DataGridView的DataSource。通常,当Presenter向View Model添加新行时,会向DataGridView添加一个新行,但是,如果Presenter从显示线程以外的线程中更新View Model,则它不会正确更新DataGridView。有几种解决方法,实际上我在我的示例中有一对,但它们似乎为Presenter层带来了太多关于UI的推断知识。我不需要知道为什么会发生这种情况,相反,我正在寻找一些关于处理这个问题的最佳实践方法的反馈。

谢谢,

// Implements View
namespace WinApp
{
    using System.Data;
    using System.Windows.Forms;
    using MVVC;

    class View : Form, IView
    {
        [System.STAThread]
        static void Main()
        {
            Application.Run(new View());
        }

        private Presenter _presenter;
        private int _topOffset;

        public View()
        {
            _presenter = new Presenter(this);
            AddDataGridView();
            AddButton(OperationTypes.PreferredApproach);
            AddButton(OperationTypes.ControlInvoke);
            AddButton(OperationTypes.SynchronizationContextSend);
        }

        void AddDataGridView()
        {
            DataGridView c = new DataGridView() { Top = _topOffset, Width = this.Width - 10, Height = 150 };
            c.DataSource = this.ViewModel;
            _topOffset += c.Height + 5;
            this.Controls.Add(c);
        }

        void AddButton(OperationTypes operationTypes)
        {
            Button c = new Button() { Text = operationTypes.ToString(), Top = _topOffset, Width = this.Width - 10 };
            c.Click += delegate
            {
                this.ViewModel.Clear();
                _presenter.LoadProgressBars(operationTypes);
            };
            _topOffset += c.Height + 5;
            this.Controls.Add(c);
        }

        #region IView Members

        public void Send(SendCallback sendCallback)
        {
            // If calling thread is not the display thread then we must use the invoke method.
            if (InvokeRequired)
            {
                Invoke(new MethodInvoker(delegate
                {
                    sendCallback();
                }));
            }
            else
            {
                sendCallback();
            }
        }

        public DataTable ViewModel { get; set; }

        #endregion
    }
}

// Doesn't have a notion of UI implementation (System.Windows.Forms)
namespace MVVC
{
    using System.Collections;
    using System.Data;
    using System.Threading;

    public delegate void SendCallback();

    public interface IView
    {
        DataTable ViewModel { get; set; }

        void Send(SendCallback sendCallback);
    }

    public enum OperationTypes
    {
        PreferredApproach,
        ControlInvoke,
        SynchronizationContextSend
    }

    public class Presenter
    {
        private IView _view;
        private Thread _thread;

        public Presenter(IView view)
        {
            _view = view;
            _view.ViewModel = new DataTable("TridTable");
            _view.ViewModel.Columns.Add("Column1", typeof(int));
        }

        public void LoadProgressBars(OperationTypes operationType)
        {
            SynchronizationContext context = SynchronizationContext.Current;
            if (_thread != null)
            {
                _thread.Abort();
            }
            _thread = new Thread(delegate()
            {
                string[] batch = new string[10];
                for (int i = 0; i < batch.Length; i++)
                {
                    // Emulate long running process. (e.g. scanning large file, creating images, figuring out the meaning of life ...)
                    Thread.Sleep(500);

                    switch (operationType)
                    {
                        case OperationTypes.PreferredApproach:
                            // Doesn't Work
                            // Different thread so the bindings won't get notified.
                            _view.ViewModel.Rows.Add(i);
                            break;
                        case OperationTypes.ControlInvoke:
                            // Does Work
                            // Send back to view to delegate work
                            _view.Send(delegate
                            {
                                _view.ViewModel.Rows.Add(i);
                            });
                            break;
                        case OperationTypes.SynchronizationContextSend:
                            // Does Work
                            // Dispatch a synchronous message to the Synchronization Context of the display thread
                            context.Send(delegate
                            {
                                _view.ViewModel.Rows.Add(i);
                            }, null);
                            break;
                    }
                }
            });
            _thread.Start();
        }
    }
}

1 个答案:

答案 0 :(得分:0)

您可能想要查看装饰器模式。装饰器模式允许您将关心UI线程的关注点与您的类应该做的任何问题分开。

如果您有这样的接口IFoo:

public interface IFoo
{
  void Bar(string str);
}

此类的装饰器可能如下所示:

public class FooDecorator : IFoo
{
  private IFoo _impl;

  public FooDecorator(IFoo impl)
  {
    _impl = impl;
  }

  public void Bar(string str)
  {
    InvokeOnUIThread( _impl.Bar(x));  //use whatever UI invocation you need
  }
}

现在你有了一个装饰器,你可以为你的IFoo编写一个具体的实现,它可以完成你需要做的任何事情。将实例传递给装饰器,如下所示:

IFoo foo = new FooDecorator( someConcreteFooInstance );  

http://www.dofactory.com/Patterns/PatternDecorator.aspx

Wikipedia