views:

1508

answers:

4

Is there a way to "stream" a set of results (eg. a DataTable) from a BackgroundWorker to a DataGridView. What I want to do is to query data, and fill the results in a DataGridView as they come (like query grid results in SQL Server Management Studio). My first thought was to use a BackgroundWorker (to avoid the UI freeze effect), but there would still be a perceivable "lag" as the BackgroundWorker is loading the results.

What would be the best way to go about this?

+1  A: 

You could:

Bind the DataGridView to an initially empty DataTable.

Then, in your worker thread, use a thread-safe collection (a synchronized queue for example) and calls to Control.BeginInvoke to pass record info over to the UI thread.

In the UI thread, you'd pull items out of the queue and add corresponding rows to the DataTable. Through the magic of data binding, these would be added to the gridview.

However, by employing multithreading you immediately make your program much more likely to be broken! I haven't tried this specific scheme, and don't know if the GridView would be rendered effectively unusable while items are being added to it. I have populated a treeview using multithreading, and it is indeed a cool effect. However, I ended up disabling the functionality, as it introduced bugs due to not being a fully correct implementation dealing with all the possible user interactions.

mackenir
+1  A: 

I have tried, and done this. The application and architecture was done before my coming to the company, and what I did was made it "paged" and async.

They had a stored procedure that had paging in it already... so what I did was, upon the first request, sent back the "size" of the total results (along with page1's data).

On the client end, I added blank rows to a DataTable... (blank, except for the "RowNumber" field).

Upon further pages of data (which was recieved async), I would get rows[X], set it's "ItemArray" to the new array, and update my grid. Example:

myDataGridView.Rows[rowNumber].SetValues(valuesFromNewPage);
Timothy Khouri
A: 

If the process is taking 2 seconds or less, then I would show a "busy" cursor and do the update inline. There are 2 reasons for this:

  • Somebody who starts an operation that is only going to take just a couple of seconds will still be in a "focused" mode of thinking by the time the operation has completed. Subconsciously he is still waiting for the results of his action and hasn’t yet switched his conscious brain out of that particular focus (as long as the app shows the "busy" signal).

  • Given the above, I don't think the cognitive overhead of multi-threading, with all of its attendant dangers, is worth incurring.

If the process is taking up to 5 seconds, then I would first concentrate on reducing it down to 2 seconds or less. Again, it's usually much easier to concentrate on improving performance (for example, by paging in the SQL) than on introducing multi-threading. Even with the BackgroundWorker type, the non-sequential interactions involved in multi-threading are still difficult to analyse and make safe.

If you find there is no way of reducing the delay to 2 seconds or less, then you can employ something like the technique recommended by confuzatron. But even then, you might still want to show a progress dialog to the end-user because he is going to have a context switch after more than 2 seconds of waiting. The progress dialog gives him easy-to-absorb information about when he can switch back to focusing on your context.

RoadWarrior
A: 

This works a lot better if you only have one thread that can modify the underlying collection. I have a read-only DGV, such that the only way to add/delete rows is to manipulate the underlying BindingSource. I have a synchronization thread that adds/removes items from the BindingSource at regular intervals.

I did have to do one "tricky" thing -- if the item being updated is the selected item, you can't just say

myBindingSource[n] = newItem;

but rather you have to deep-copy the values from the new item into the existing one. Otherwise, you'll trigger a "changed" event, which will redraw anything else that's bound to your DataSource. I got a noticeable flicker when I did the reference copy, and switching to deep-copy (only for the Current item) fixed it.

Of course, if you're letting the user tinker with data directly from the form, rather than using it as a read-only view, you're opening a whole new (ugly!) can of worms.

Coderer