Having been working on JavaScript and Angular for the last year, I am currently working on a project in Winforms. I understand the cross thread issue with UI controls (InvokeRequired), but I haven't worked with BindingList before so to experiment I have a basic app that has a Form, Controller, and POCO that implements INotifyPropertyChanged. The Form gets injected with the controller, which has a BindingList. To simulate an outside thread manipulating the data, I have a Start method inside the Controller that creates a thread and runs a loop and updates the stock list. Obviously, this will raise the property changed event, which notifies the Grid and its datasource, however I get the cross thread exception. What I want to know what is the common practice for this? My past experience has been threads started from the GUI (background worker) and so the code is usually already in the Form and has access to 'this' to call InvokeRequired on. However when using BindingList from a Controller I don't have that.
Form1Controller
public class Form1Controller
{
public bool IsRunning { get; private set; }
private readonly Random _Random = new Random();
public Form1Controller()
{
Stocks = new BindingList<Stock>();
Stocks.Add(new Stock()
{
Name = "Microsoft",
Ticker = "MSFT",
Price = 25,
Volume = 100000,
LastTraded = DateTime.Now
});
Stocks.Add(new Stock()
{
Name = "Google",
Ticker = "GOOG",
Price = 450,
Volume = 100000,
LastTraded = DateTime.Now
});
Stocks.Add(new Stock()
{
Name = "Apple",
Ticker = "AAPL",
Price = 750,
Volume = 100000,
LastTraded = DateTime.Now
});
Stocks.Add(new Stock()
{
Name = "Oracle",
Ticker = "ORCL",
Price = 80,
Volume = 100000,
LastTraded = DateTime.Now
});
}
public BindingList<Stock> Stocks { get; set; }
public void Start()
{
IsRunning = true;
new Thread(() =>
{
while (IsRunning)
{
var index = _Random.Next(0, Stocks.Count);
Debug.WriteLine(index);
var stock = Stocks[index];
stock.Price += 1;
stock.Volume += 500;
stock.LastTraded = DateTime.Now;
Thread.Sleep(100);
}
}).Start();
}
public void Stop()
{
IsRunning = false;
}
}
Stock
public class Stock : DependencyObject
{
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, () => Name); }
}
private string ticker;
public string Ticker
{
get { return ticker; }
set { SetField(ref ticker, value, () => Ticker); }
}
private int price;
public int Price
{
get { return price; }
set { SetField(ref price, value, () => Price); }
}
private long volume;
public long Volume
{
get { return volume; }
set { SetField(ref volume, value, () => Volume); }
}
private DateTime lastTraded;
public DateTime LastTraded
{
get { return lastTraded; }
set { SetField(ref lastTraded, value, () => LastTraded); }
}
}
DependencyObject
public class DependencyObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
}
Form1
public partial class Form1 : Form
{
private readonly Form1Controller _Controller = new Form1Controller();
public Form1(Form1Controller controller)
{
InitializeComponent();
_Controller = controller;
_MainGrid.DataSource = _Controller.Stocks;
}
private void _StartButton_Click(object sender, EventArgs e)
{
_Controller.Start();
}
private void _StopButton_Click(object sender, EventArgs e)
{
_Controller.Stop();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_Controller.Stop();
}
}
How do I marshal the property changed back onto the UI thread from the BindingList (assuming that is what I should be asking for)?
Aucun commentaire:
Enregistrer un commentaire