BackgroundWorker to fill a DataGridView - c#

EDIT: Solved using this: http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/
In my project (.net/windows forms) I'm filling a DataGridView with a large DataTable. Filling can take up to 20 seconds, so I'd like an animated loading window. This animation freezes if the thread is busy, so I'll have to use a new thread either for the window or for filling the DataGridView.
I've tried using the BackgroundWorker to show the form, but it'll be a blank white window in the correct shape.
I've also tried using the BackgroundWorker to fill the DataGridView, but it'll throw an error saying the DataGridView is being accessed by a different thread than the one it has been created for. Since the DataGridView is being created in the designer class, I can't just create it in the new thread - plus that solution doesn't sound very elegant.
What's the best way for me to show a working animated form while filling the DataGridView?
Edit: The answer didn't solve the issue for me, so I've tried to break the code down to something that can be presented here. I didn't do it before because it didn't really seem relevant enough to work through some 1k lines of code. Might be something missing or some remnants from previous experiments in the code presented here. Please ignore bad naming of function, that's a legacy thing I'll fix once I got it working. Parts of the code are ancient.
I've made it run without errors, but the frmLoading still isn't animated (it is if I keep it alive while the working thread isn't busy, though).
namespace a
{
public partial class frmMain : DockContent, IPlugin
{
//...
private delegate void SafeCallDelegate(DataTable dt);
private Thread thread1 = null;
private frmLoading frmLoading = new frmLoading();
public frmMain()
{
//...
}
//...
private void FillDataGrid(DataTable dt)
{
if(this.InvokeRequired)
{
var d = new SafeCallDelegate(FillDataGrid);
Invoke(d, new object[] { dt });
}
else
{
//...
DataGridFiller(dt);
}
}
private void DataGridFiller(DataTable dt)
{
BindingSource dataSource = new BindingSource(dt, null);
//...
dgvData.DataSource = dataSource;
//...
frmLoading.Hide();
}
private void btnGetData_Click(object sender, EventArgs e)
{
DataTable dt = [...];
// Wenn Daten vorhanden sind, dann anzeigen
if (dt != null)
{
//...
frmLoading.Show();
thread1 = new Thread(() => FillDataGrid(dt));
thread1.Start();
}
}
}
}

The second approach is the correct one: use a BackgroundWorker to do any work that will freeze the UI if done in the main thread. About the exception you're getting, that's because the call you're making isn't thread-safe (Because the control was created on a different thread that the one whos calling its methods). Plase take a look at this link to MSDN to understand how to make this kind of call between threads, using backgroundWorker's event-driven model.
From the link:
There are two ways to safely call a Windows Forms control from a
thread that didn't create that control. You can use the
System.Windows.Forms.Control.Invoke method to call a delegate created
in the main thread, which in turn calls the control. Or, you can
implement a System.ComponentModel.BackgroundWorker, which uses an
event-driven model to separate work done in the background thread from
reporting on the results.
Take a look at the second example which demonstrates this technique using the backgroundWorker
EDIT
From your comments I get that the real problem here is size. The problem is that the thread that owns the DataGridView control, is the thread that is displaying it, so no matter how, if you load all the data at once it will freeze for the time it takes this thread to draw all that data on the screen. Fortunately you're not the first that had this problem, and microsoft got you covered this time. This is, I think, a great example of what the XY problem is, the real problem is that you want to load a huge dataset (X) and the answer on how to do that is here, but instead you asked how to display a loading icon while filling your datagrid whithout the UI freezing (Y), which was your attempted solution.
This was from a technical perspective but, from an UI/UX perspective I would like you to think about the actuall use of the form. Do you really need all that data loaded every time you visit this section? If the answer is no, maybe you could think about implementing some pagination (example here). Also 200 colums means horizontal scroll even for an ultrawide monitor. I can't really figure out the user case for you to need all that information on a list/table view at once. I think that maybe implemeting a Master-Detail kind of interface could be more useful.
EDIT 2.0
I think that you can try to create a whole new Form window in another thread, place it over your datagrid and paint the loading animation there. Meanwhile in the main window you can draw the grid without its painting making the loading animation freeze (the other thread is taking care of the drawing). You may want to hold a reference to the thread and kill it, or maybe better try to hold a reference to the From and close it more gracefully.
I've never done this but, I think I recall from some legacy application doing something like that, and it was ugly.

Use an asynchronous task that displays the pop-up loading window until the DataGridView is filled up.
Here's a link to a write-up Microsoft made for async programming: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

Related

C# completely loading a WinForm without showing it (VS2010)

Working with DevExpress 2012 vol 2.10
C# on top of VS 2010
First question seems to have been unclear...
So lets clear it a bit (or try to at least)!
We are building a MainForm with a Ribbon containing many buttons. Every button in the Ribbon is disabled until their respective state is "ready to enable".
"ready to enable" depends on one thing : The WinForm_Popup associated with the button has been completely built, including data retrieval and DevExpress.ExpressApp.ListView construction.
Retrieving data from database takes less than 0.1 second
Calling the WinForm_Popup.Show() takles over 15 seconds
We tried to put this in a Thread or a Task, with no success: It crashes on WinForm_Popup.Show() with an exception related to the DragDrop Event.
What I know by now, is Show() method takes long, but I don't have a clue what happens in this method, but constructing the DevExpress.ExpressApp.ListView, which should be taken away from Show (or do it in a Task or Thred maybe).
Or, in other words, having the WinForm_Popup UI completely built as when it's shown but doing this asynchronously (like in a separate Task, for example).
Any idea, advice, help, link, suggestion, tip... Any "thing" ?
Make a new form and make it empty. In the program.cs file change your main form to the new form. Then make the new form constructor be like this:
public newForm()
{
this.Hide();
Thread backTh = new Thread(() =>
{
MainForm mf = new MainForm();
mf.Show();
});
backTh.Start();
}
I would go for the opposite approach, one that is usually used in slow loading systems like a web browser. Why don't you load the form fast, then use a thread to populate your slow loading grid view?
That way, you can have like a spinning hourglass (or something less 1995) that will tell your users that the data is loading.

Can I freeze my UI rendering while my form loads?

Is there any way I can pause all UI Update commands in Winforms?
Or I have a slight feeling I'm trying to go about this the completely wrong way, so is there another way around my problem: I basically load a saved state of a control, which loads new controls all over it. However I do some of this in the UI thread, and some of the data loading from another thread, which then fills the UI.
So the effect I have when it is loading is that the user can see a few of the controls appearing in one place, then moving to another place on the form, changing values, etc.
I'd like to get a loading screen instead of this and load the controls in the background. It's quite a large application and its not THAT important so redesigning my code isn't really an option.
Can I simply stop all Update() commands on a control while a method is executing?
You can use the SuspendLayout and ResumeLayout methods to wrap the setup of UI in one operation (without the update of the rendering).
Basically (assuming SomeMethod is in the form class):
private void SomeMethod()
{
this.SuspendLayout();
// all UI setup
this.ResumeLayout();
}
it really depends on your form logic, in general you should not overload the Load or Show method with too much things so that the form can be shown and drawn quickly and always look responsive.
in some cases it could help to use the SuspendLayout and ResumeLayout methods, see here:
Control.SuspendLayout Method

C# Background Worker UI Update

I am trying to use a background worker in order to retrieve a large amount of data from the database without stalling the main thread. This seems to work well, except that when it comes to update the UI, the update freezes the screen. Relevant code as follows:
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lvwTest.BeginUpdate();
lvwTest.Items.Clear();
// Populate the UI
foreach (TestItem ti in testData)
{
ListViewItem lvi = lvwTest.Items.Add(ti.Value1);
lvi.SubItems.Add(ti.Value2);
}
lvwTest.EndUpdate();
}
The update takes around 2 - 3 seconds, for which time the screen is locked. I realise that only the main thread can update the screen, but is it possible to load this data into memory in some way (in a background thread or another instance of a listview or something) and then just display it? All I want to happen is for the program to simply refresh the data without taking up time in the main thread.
I recommend loading the data into memory and using a virtual mode ListView. That way, you only create the ListViewItem objects as they are needed.
If you have to load really huge amount of data into UI it will require time and it will block our app. The option is smart scrolling or pagination. You load all data but you put it piece by piece upon user request.
Since most of the above are good advice, but don't really solve your immediate problem, here is another approach:
This will update your GUI and keep it responsive. Assuming you are in WinForm App?
Application.DoEvents();
this.Refresh();
Nevertheless, this does not mean that maybe you should not listen to the ideas from above :-)
In addition to virtualization, I would recommend breaking the items into batches of, say, 100 and adding each batch in its own message. That way, the UI has a change to process other messages whilst the batches are being added to the ListView.
In other words, all the RunWorkerCompleted handler does is queue the first batch for adding in a separate message. The adding method will then add the items and then queue the next batch. This will continue until there are not more items left to add. At that point, you would re-enable the relevant portion of your UI (the ListView).

Run a modal dialog on a non-UI thread

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.
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
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?
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 );
}
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.
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?

Delay loading of combobox when form loads

I've got a Windows Forms (C#) project with multiple comboboxes/listboxes etc that are populated when the form loads.
The problem is that the loading of the comboboxes/listboxes is slow, and since the loading is done when the form is trying to display the entire form isn't shown until all the controls have been populated. This can in some circumstances be 20+ seconds.
Had there been a Form_finished_loaded type of event I could have put my code in there, but I can't find an event that is fired after the form is done drawing the basic controls.
I have one requirement though - the loading has to be done in the main thread (since I get the items from a non-threading friendly COM-application).
I have found one potential solution, but perhaps there is a better way?
I can create a System.Timer.Timer when creating the form, and have the first Tick be called about 1 second later, and then populate the lists from that tick. That gives the form enough time to be displayed before it starts filling the lists.
Does anyone have any other tips on how to delay the loading of the controls?
There is the Shown event that "occurs whenever the form is first displayed.". Also you may want to use the BeginUpdate and EndUpdate functions to make the populating of your combobox faster.
It has that certain smell of workaround, but this approach should fulfil your needs:
private bool _hasInitialized = false;
private void Form1_Shown(object sender, EventArgs e)
{
if (!_hasInitialized)
{
ThreadPool.QueueUserWorkItem(state =>
{
Thread.Sleep(200); // brief sleep to allow the main thread
// to paint the form nicely
this.Invoke((Action)delegate { LoadData(); });
});
}
}
private void LoadData()
{
// do the data loading
_hasInitialized = true;
}
What it does is that it reacts when the form is shown, checks if it has already been initialized before, and if not it spawns a thread that will wait for a brief moment before calling the LoadData method on the main thread. This will allow for the form to get painted properly. The samething could perhaps be achieve by simply calling this.Refresh() but I like the idea of letting the system decide how to do the work.
I would still try to push the data loading onto a worker thread, invoking back on the main thread for populating the UI (if it is at all possible with the COM component).
Can you get your data from a web service that calls the COM component?
That way, you can display empty controls on a Locked form at the start, make Asynchronous calls to get the data, and on return populate the respective combos, and once all of them are loaded, you can unlock the form for the user to use.
You could listen for the VisibleChanged event and the first time it's value is true you put your initialization code.
Isn't FormShown the event you're looking for?
When you say that you cannot use a background thread because of COM what do you mean? I am using many COM components within my apps and running them on background threads.
If you create a new thread as an STAThread you can probably load the ComboBox/ListBox on a Non-UI thread. IIRC the ThreadPool allocates worker threads as MTAThread so you'll need to actually create a thread manually instead of using ThreadPool.QueueUserWorkItem.

Categories

Resources