views:

606

answers:

5

I'm writing a simple data UI using standard .Net databinding to a typed DataSet from SQL Server.

I have a reload button which calls Fill on all of the DataAdapters to get new data from the database (in case another user changed the data).

This takes some time, during which the UI is frozen. It must be run on the UI thread or the databinding event handlers throw cross-thread exceptions.

I'd like to show a modal "Please Wait" dialog on a background thread (so that it can be animated) while the UI thread connects to the database.

How can I show a modal dialog box on the non-UI thread?


EDIT: I'm aware that best practice is to run the operation in the background, but I can't do that because of the databinding events.

+6  A: 

You should do the opposite. Run your long-running process on a background thread and leave the UI thread free to respond to the user actions.

If you want to block any user actions while it is processing you have a number of options, including modal dialogs. Once the background thread completes processing you can inform the main thread about the outcome

mfeingold
As I explained in the question, I can't. I'm well aware that this is best practice.
SLaks
I think you still can. It just means that the background thread should not update the data directly. Retrieve them, package them and delegate to the main thread to update the UI
mfeingold
I understand your dilemma, but I still think what I am suggesting is better (less trouble) than the alternative
mfeingold
Is there any simple way to do this and still use standard databinding with update events? I'm trying to finish this project quickly.
SLaks
A: 
using( var frmDialog = new MyPleasWaitDialog() ) {
    // data loading is started after the form is shown
    frmDialog.Load += (_sender, _e) {
        // load data in separate thread
        ThreadPool.QueueWorkItem( (_state)=> {
            myAdapter.Fill( myDataSet );
            // refresh UI components in correct (UI) thread
            frmDialog.Invoke( (Action)myDataControl.Refresh );
            // close dialog
            frmDialog.Invoke( (Action)frmDialog.Close() );
        }
    }

    // shows dialog
    frmDialog.ShowDialog( this );
}
TcKs
You need load data in different DataSet, which is not bound to datagrid, and then rebind datagrid to the new dataset.And/or try use "BeginLoadData()" and "EndLoadData" on the dataset/datatable.
TcKs
As I tried to explain in the question, `myAdapter.Fill` throws an InvalidOperationException in the grid's databinding event handlers when called on a background thread. Calling `BeginLoadData` on the table doesn't help.
SLaks
So, you need fill the another dataset and then rebind the datagrid to "new" (filled) dataset instance.
TcKs
By the way, when adding anonymous event handlers, you can write `EventName += delegate { code };` (note the lack of parentheses), which will implicitly declare the parameters.
SLaks
+2  A: 

The code running in the databinding events need to be decoupled from the UI, probably using some kind of data transfer object.

Then you can run the query operation in a separate thread or a BackgroundWorker, and leave the UI thread as it was.

Edit: The really quick way to fix this is to get the events to run in their own delegate using InvokeRequired and .Invoke. That will give the methods UI context. My co-worker does this like it's going out of style and it annoys me to no end because it's rarely a good idea to do it this way... but if you want a fast solution this will work. (I'm not at work so I don't have a sample with me; I'll try to come up with something.)

Edit 2: I'm not sure what you're asking for is possible. I made a sample app that created a modal dialog in another thread, and it ends up being modeless. Instead of using a modal dialog, could you use some other control or set of controls to indicate progress change, most likely directly on the same form?

Jon Seigel
Is there any simple way to do this and still use standard databinding with update events? I'm trying to finish this project quickly.
SLaks
Re. Edit: I can't put `Invoke` calls inside the DataGrid.
SLaks
Re: Edit 2: You may very well be right. If it was a simple question, I wouldn't have asked it here. As for putting controls on the form, that's even worse - it is _completely_ impossible to have some controls on a form owned by a different thread.
SLaks
Right, but if the actual processing is going on in a different thread, then those controls are just normal controls on the form, in the same thread as the form.
Jon Seigel
If the actual processing is on a different thread, I can just call `ShowDialog` on the UI thread and make a modal form.
SLaks
You're right... sorry, this problem has me thinking backwards. Note to self: don't try to solve technical problems on the weekend ;)
Jon Seigel
A: 

Here is an example of using BackgroundWorker to do the loading of data and running a user friendly form to show 'Loading records' or similar...

    public void Run()
    {
        bgWorkrFillDS = new BackgroundWorker();
        bgWorkrFillDS.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorkrFillDS_RunWorkerCompleted);
        bgWorkrFillDS.DoWork += new DoWorkEventHandler(bgWorkrFillDS_DoWork);
        bgWorkrFillDS.RunWorkerAsync();
    }

    void bgWorkrFillDS_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker bgWrkrFillDS = (BackgroundWorker)sender as BackgroundWorker;
        if (bgWrkrFillDS != null)
        {
            // Load up the form that shows a 'Loading....'
            // Here we fill in the DS
            // someDataSetAdapter.Fill(myDataSet);
        }
    }


    void bgWorkrFillDS_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // Hide or unload the form when the work is done
    }

Hope this helps... Take care, Tom.

tommieb75
Please read the question. Calling `someDataSetAdapter.Fill` on the background thread will throw an InvalidOperationException.
SLaks
A: 

I solved this problem by creating a new DataSet, loading in in the background, then calling DataSet.Merge on the UI thread. Thanks everyone for your advice, which led to this solution.

As an added bonus, this runs much faster than it used to (calling Fill in the background, which only worked with no grids open). Does anyone know why?

SLaks
Why was this downvoted?
SLaks