views:

604

answers:

2

Hello,

I'm playing around with the (MVVM) Model View View Model design pattern but I ran into a problem with the pattern. In my scenario I'm using a DataTable as the View Model. This View Model is set as the DataSource of a DataGridView. Normally, when the Presenter adds a new row to the View Model, a new row is added to the DataGridView however, if the Presenter updates the View Model from within a thread other than the display thread, it doesn't properly update the DataGridView. There are several workarounds, in fact I have a couple in my example, but they seem to bring too much inferred knowledge about the UI to the Presenter layer. I don't need to know why this happens, instead, I'm looking for some feedback on a best practice approach with dealing with this issue.

Thanks,

// 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();
        }
    }
}
A: 

Deleted wrong answer

adrianm
A: 

You might want to look at the decorator pattern. The decorator pattern allows you to separate the concerns of caring about being on the UI thread from the concerns of whatever it is your class is supposed to do.

If you had an interface IFoo like this:

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

The decorator for this class might look like this:

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

Now you have a decorator, you can write a concrete implementation for your IFoo that does whatever you need it to do. Pass an instance to the decorator like so:

IFoo foo = new FooDecorator( someConcreteFooInstance );

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

Wikipedia

Dan