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