Hello,
I've spoken with Infragistics Support about this issue (support request CAS-08857-GUP6P0 if Infragistics engineers (for instance Mike :) ) would like to take a look at it). Unfortunately, the answer was pretty much depressing. Our current implementation looks like this:
We have a form with a grid on it. There is a System.Data.DataSet bound to the grid. To be more specific, let’s assume that the user enters some article ids (one in each row). After the user is done with ids, he presses a button which starts some long running calculation (computation of optimal prices). Currently we do the following:
1. We call BeginUpdate() and SuspendRowSynchronization() on the grid. 2. We start the thread which changes the dataset 3. At the very end this method (which changes the dataset) calls UpdateGrid()
4. private void UpdateGrid()
{
if(InvokeRequired)
Invoke(new MethodInvoker(UpdateGrid));
}
else
ultraGrid1.ResumeRowSynchronization();
ultraGrid1.EndUpdate();
So, we do everything in our power to prevent access to UI controls (grid) from a non UI thread.
According to Infragistics Support the described implementation still doesn’t guarantee that the grid won’t “try to query the datasource for changes. If a property is set on the grid, or an action is taken, such as moving the mouse, then the grid will try to talk to its data source to retrieve the values and this is where the problems occur”.
So, my question is whether there is some solution for this more than usual problem (without unbinding the grid completely from the datasource like grid.DataSource=null). Would setting AllowUpdate=false help? Would grid.Enabled = false help?
I’m more than aware that I need to “synchronize worker threads with the UI”. My task is to keep the UltraGridRows and Layout (therefore I don’t want to unbind the DataGrid from the DataSet) AND perform some long running operation which changes the dataset on a separate thread. I would very appreciate an algorithm in pseudo code.
There's really no way to do this, since you cannot control when the grid might try to access the data.
What you would have to do is have your data source on the second thread deal with some other object on the UI Thread which is then bound to the grid. This data source would have to be something over which you have complete control, so you could make sure it deals with the other thread properly at all times.
The code you have here is actually not bad. Assuming that this is the only place you are using the second thread, this might work. But... if you are starting the thread in step 2, and you are calling the UpdateGrid method "at the very end of this method", what ensures that the thread has finished when you are calling ResumeRowSynchronization on the grid? Unless you left out some pretty big steps here, you appear to be assuming that your thread is working synchronously and is therefore complete and soon as the next line of code is executed.
Hello Mike,
Code says more than thousand words :)
--------------------------------
private void Form1_Load(object sender, EventArgs e) { ultraGrid1.SetDataBinding(CreateHierarchicalDataSet(100, 100), "parent"); }
private void ultraButton1_Click(object sender, EventArgs e) { ultraGrid1.BeginUpdate(); ultraGrid1.SuspendRowSynchronization(); //Problem with BeginUpdate(). Uncomment this line an trying moving this new window //on the screen while ultraGrid is between BeginUpdate() and EndUpdate() //The ultragrid visual appearance is then broken (it's better to see it) //Commenting out Begin/EndUpdate() makes datagrid to react to changes in the datasource again //Why is SuspendRowSynchronization() not enough? //new Form().Show(); ThreadPool.QueueUserWorkItem(ChangeDataSet); } private void ChangeDataSet(object state) { int x = 100; while (x > 0) { foreach (DataRow row in dataSet.Tables[0].Rows) { row["DateTime"] = ((DateTime)row["DateTime"]).AddDays(1); } x--; } UpdateGrid(); } private void UpdateGrid() { if(InvokeRequired) { Invoke(new MethodInvoker(UpdateGrid)); } else { ultraGrid1.ResumeRowSynchronization(); ultraGrid1.EndUpdate(); MessageBox.Show("Changing data set done"); } }
So, as you can see, we call ResumeRowSynchronization() and EndUpdate() from the worker thread (when operations on the dataset are really done) AND make necessary synchronization with the UI thread (with Invoke()). I also attach a sample if you would like to try it out.
Is this approach safe? Or it is still not allowed to use a dataset as a datasource and we need to replace it with our own object which must synchronize the access and prevent the grid to touch the datasource when it is being changed from a worker thread?