'Crossthread databinding in NotifyPropertyChanged for DataGridView Control
When trying to update a displayTimer (duration for how long a specific orderline is taking), I get a Crossthread error in NotifyPropertyChanged for my DisplayTimer Property. The Control it mentions is a datagridview inside a tableLayoutPanel that I generate in code in a form.
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string DisplayTimer
{
get
{
return _displayTimer;
}
set
{
_displayTimer = value;
NotifyPropertyChanged("DisplayTimer");
}
}
I set this in an static class with a System.timers.timer elapsed event. Code looks like this:
public static BindingList<OrderModel> Orders = new();
private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
timer.Stop();
BindingList<OrderModel> newOrders = DBQueries.GetOrders();
foreach (OrderModel order in newOrders)
{
Orders.Add(order);
}
DBQueries.OrderUpdates();
foreach (OrderModel om in KitchenComHandler.Orders)
{
for (int i = 0; i < om.Orderlines.Count; i++)
{
OrderlineModel ol = om.Orderlines[i];
TimeSpan ts = DateTime.Now - ol.ReceivedTime;
ol.DisplayTimer = ts.ToString(@"hh\:mm\:ss"); <-- the Culprit
}
}
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
timer.Start();
}
}
Additional information: I've tried switching the DataGridView.DataBinding to BindingSource as I read that this should fix certain crossthreading issues. The OrderModel contains:
public BindingList<OrderlineModel> Orderlines
{
get
{
if (_orderlines == null)
{
_orderlines = new();
_orderlines.ListChanged += Orderlines_ListChanged;
}
return _orderlines;
}
}
private void Orderlines_ListChanged(object sender, ListChangedEventArgs e)
{
NotifyPropertyChanged("Orderlines");
}
Note: I managed to get this error for a no-name control as well. I can update all the other properties in another class as well, however it doesn't make any sense to have the updated displaytimer here as the following method only occures when there is an update to get ->
if (fullUpdate)
{
orderline.OrderID = reader.GetInt32(0);
orderline.ID = reader.GetInt32(1);
orderline.Description = reader.GetString(3);
orderline.Status = OrderAndOrderlineStatus.Received; //We don't want to touch the status for an existing orderline.
orderline.StatusChanged = true;
}
orderline.CountOf = reader.GetDecimal(2);
Byte[] b = (Byte[])reader.GetValue(5);
orderline.RowVersion = b;
if (!string.IsNullOrWhiteSpace(reader.GetString(4)))
{
orderline.Alternatives = new();
string[] separatingString = { Environment.NewLine };
string[] altArray = reader.GetString(4).Split(separatingString, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < altArray.Length; i++)
{
orderline.Alternatives.Add(altArray[i]);
}
}
Solution 1:[1]
I solved this now by Finding any active form and executing the update of the DisplayTimer property there. Code:
if (System.Windows.Forms.Form.ActiveForm != null &&
System.Windows.Forms.Form.ActiveForm.InvokeRequired)
{
TimeSpan ts = DateTime.Now - ol.ReceivedTime;
System.Windows.Forms.Form.ActiveForm.Invoke(new Action(
() => ol.DisplayTimer = ts.ToString(@"hh\:mm\:ss"))
);
}
EDIT:
I went and updated the code some more and started using SyncronizationContext instead. As I could not instantiate this context from the ApplicationContext I had to find the first form that launched and start my static class containing the update methods. Code:
UIThread = SynchronizationContext.Current;
TimeSpan totatElapsedTime = (DateTime.Now - Orders[i].Orderlines[j].ReceivedTime);
UIThread.Send((object stat) => {
ol.DisplayTimer = totatElapsedTime.ToString(@"mm\:ss"); //.ToString(@"hh\:mm\:ss");
}, null);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |