Hosting multiple instance of app in a tabcontrol/ queuing - c#

So I have This app with a MainForm that has a few buttons on it. and the buttons would do time consuming tasks that includes working with MainForm UI. Because some times we need to run multiple instances of this app at the sametime I decided to Create a MainFormHost where it is a form with a tab control which under each tab I create an instance of my MainForm and host it there. And so far everything was ok. The problem is when I click on a button on MAinForm1 it starts working fine but as soon as I click on a button on MainForm2 the process of MainForm1 button gets queued behind the MainForm2 process.
MainForm GetMainFrom(TabPage tabPage)
{
tabPage.Invoke(new Action(() =>
{
mainForm = new MainForm();
mainForm.TopLevel = false;
mainForm.FormBorderStyle = FormBorderStyle.None;
mainForm.Dock = DockStyle.Fill;
_mainForms.Add(mainForm);
tabPage.Controls.Add(mainForm);
mainForm.Show();
}));
}
And then call the method:
var mainFormThread = new Thread(() =>
{
mainForm = GetMainFrom(tabPage);
});
mainFormThread.SetApartmentState(ApartmentState.STA);
mainFormThread.Start();
I cannot change the code inside the MainForm to Invoke things when they want to work with the UI because it is going to be too much of work but I can easily make each button click to be called from a thread/task or...
TIA

In short, you can only have one 'main thread' that can interact with the UI controls. Ever. Period. There is no way to get around this (in standard code, you can draw to your form from a different process, but I won't go into that)
So, what you need to do is arrange your code in a set pattern.
Methods that do background work - this can be done on a separate thread(s)
Methods that Update the UI - MUST be done on the UI/Main Thread
The idea being simple, background work takes a long time, and updating the UI shouldn't.
Because you have 1 or 2 long running actions running ON THE MAIN UI THREAD, this 'blocks the UI' and causes the behavior your are experiencing.
By blocks the UI, what you actually have is the Windows Message pump can't pump. So every single command to resize a window or update a control on a form is a message in the pump. That must run on the main thread. If you do 'work' on that thread, such as connecting to a database, download a file etc., then the pump can't continue, so you get the 'this application is not responding' message.
So, you must download the file or whatever on a thread, and when it's finished, transition to the UI thread, and update the UI, e.g. say finished in a text box.
The way to transition between the two threads (background to main) is to use begininvoke, and you know if this is needed by using 'invoke required' .
From MSDN
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
This is a great article and will get you where you need to be:
MSDN about thread safe code and invoke required

Related

Display form without blocking execution of program

I'm trying to show a simple form (that just displays a label) from within a function of my DLL. However, I need this form to be displayed without blocking the execution of this function (e.g. show form and continue execution of function.)
I've tried using just:
form.ShowDialog();
However this is obviously a modal dialog, so blocks the execution until the user interacts with it. I've also tried using:
form.Show();
But this causes the form to load behind all other windows, and the controls don't load properly (just show blank boxes where they should be.)
To try and combat these problems, I've tried creating and showing the form on a new thread both like this:
Thread thread = new Thread(ShowXForm);
thread.Start();
and like this:
new Thread(delegate ()
{
ShowXForm();
}).Start();
where:
private void ShowXForm()
{
frmX XForm = new frmX();
XForm.ShowDialog();
}
Again, these lead to almost the same behavior as simply using Show without creating a new thread.
An added difficulty with it running on a different thread is that I still need to be able to update the label on the form from the main thread (could be achieved using delegates?)
I am aware that it is good practice to have the main thread as the UI thread, however with this being a DLL rather than a Windows form app, it feels like the UI should perhaps not be running on the main thread.
The call to Application.Run() needs to be INSIDE a new thread.
Something like...
private void ShowXForm()
{
new Thread(delegate ()
{
frmX XForm = new frmX();
Application.Run(XForm);
}).Start();
}
If you want to have it update, though, you'll have to keep that form reference out at class level and give it a mechanism to pass updates and have that form correctly marshal that call to itself with a delegate/Invoke().

multithread : new form close too fast

I want to show a new form with a progress bar marquee. This form should be on an other thread, since the action that is called is too big and the UI doesn't update the progress bar if it's on the same thread.
I call a new thread, create my form, but as soon as it appears, the form close...
Here is my code from my main :
frmProgress f = new frmProgress();
Thread t = new Thread(new ThreadStart(delegate() { f.Show(); }));
t.Start();
documentManager.Compile(); //This is my big action
t.Abort();
Here is the code of form :
public frmProgress()
{
InitializeComponent();
}
I would like to know what I did wrong.
Thanks!
You have several problems:
You're showing a form in a non-UI thread. This will only cause problems; it needs to be in the UI thread.
You're performing a long running task in the UI thread; this is blocking UI updates.
You need to be showing the forum in the UI thread, and running the long running task in a background thread.
A great tool for the job here is to use a BackgroundWorker. It will do much of the work for you to ensure the proper code is running in the UI/non-UI threads as it should be. Create a new BackgroundWorker and put the long running task in the DoWork event. Make any simple UI updates that you need to (showing the form) before you start the BGW, and do any cleanup in the Completed event (which will run in the UI). The MSDN page on BackgroundWorker has some nice examples.
Another advantage of using a BackgroundWorker is that you can easily report progress (if you have a good way of knowing your % complete) rather than just having a marquee bar. Just add a handler to the ProgressReported event handler to update the progress bar, and call the ReportProgress method periodically when doing work. See the linked examples for exact code snippets.

Form Visiblity Problem

Form1.button_Click(...) {
// Show a dialog form, which runs a method <CheckBalance()> on it's OnLoad Event.
var modemDialog = new ModemDialog("COM25");
modemDialog.ShowDialog();
// the user can't see this dialog form until the method <CheckBalance()> terminates.
}
Is it possible to show first the dialog then run the specified method?
THanks.
That is correct and expected. Winforms UI is inherently single-threaded. Having a function call like "CheckBalance" in the form load event will prevent the form from showing until the form load event completes. Depending on the duration of the task, you have a number of options available to you:
If it's a fast task, compute it ahead of time before showing the form
If it's something the user may want to initiate, move it to a button on the new form, so it's only calculated on the request of the user
If it's a long running task that takes some time, you'll need to move it off in to another thread. Using a BackgroundWorker is recommended.
OnLoad occurs before the form is shown to allow you to initialise the form and variables and what not, which means it is synchronous. The form will not show until you return from that function.
If you want to asynchronously run the CheckBalance() method, then you can use a few techniques, such as utilising the Threading, ThreadPool or Tasks API to shift that work to a background thread, and returning immediately so that the form is shown.
Here is an example of using a Task to perform the same action, but asynchronously so that the form immediately shows:
Action<object> action = () => { CheckBalance(); };
new Task(action).Start();
Please note that if you access the UI thread, you'll need to beware of thread-safety and invocation.
The simple way to make sure your form is visible before CheckBalance is run is to use this code in the form load handler:
this.BeginInvoke((Action)(() => this.CheckBalance()));
This will push the execution of the CheckBalance method onto the UI thread message pump so will execute after all preceding UI code is complete.
Others are correct though that the UI will still be blocked as CheckBalance executes. You probably want to run it on a background thread to prevent this.

Winforms: How to display a "loading" form?

I have a grid and when a row is double clicked a form is loaded. However a lot of data must be loaded, so I'd like to display a simple form with the text 'loading, please wait..'. And when all loading is finished, the form must disappear.
This is what I have right now, but it doesn't work:
Code that invokes the form with lots of data:
FormWithLotData form = new FormWithLotData();
form.ShowDialog(this);
Constructor of FormWithLotData:
// Show load form
FormIsLoading frm = new FormIsLoading();
_CloseLoadForm closeForm = new _CloseLoadForm(frm.Close);
System.Threading.Thread thread = new System.Threading.Thread(frm.Show);
thread.Start();
InitializeComponent();
this.Visible = false;
LoadAllData();
this.Visible = true;
// Close load form
Invoke(closeForm);
Hope you can help me out.
EDIT:
I'd like to show an animated gif on the loading form.
SOLUTION:
I've created a background worker. The DoWork event handles all the loading and by using the invoke() method I add nodes to the treeview. Now, the GUI doesn't hang and the user don't have the idea that the application is hanging.
You need to reverse your code.
The constructor of FormWithLotData is running in the UI thread. This is the thread that must show your FormIsLoading form. So instead of trying to display this form using the new Thread, do your data loading with it.
The DoEvents way others have suggested is the easiest to implement and (possibly? never done it myself) may work well.
The better pattern to use is to do your data loading on a worker thread. Before you show your FormWithLotData, Begin loading data on a background thread and show your Loading dialog. The method that loads the data should have a callback method into the Loading dialog to signal when it should Close(). Once it closes you can then construct a new FWLD, pass it the already loaded data, and Show it.
Trying to load your data after the form has already been invoked mixes your UI with your data operations, forcing your form to not only be in charge of the UI but also be in charge of data retrieval. Bad for KISS and Single Responsibility, imho.
After your update, it seems like DoEvents is going to be the only real answer to your question, but with some caveats.
You will not be able to show another form MODALLY while you construct your tree. You will still have to do your heavy work within your form's constructor. You will still have to hide your main form and Show() (not ShowDialog) your loading form. You will also have to call DoEvents at every single possible moment while constructing your tree. Its not exactly an elegant solution, but it will probably be your best bet at this point.
how about ...
FormIsLoading frm = new FormIsLoading();
frm.Show();
Application.DoEvents();
// ... load data ...
frm.Close();
In the Form_Load event, add the following:
this.Visible = true;
Application.DoEvents();
before any other processing occurs. The Application.DoEvents caused the UI to show the form at the current state, where normally the UI thread is locked while you other processing is taking place.
Don't do your LoadAllData() directly in a UI thread, instead start up a background thread to do it. The in your Form_Loaded event handler, use an AutoResetEvent and wait till it becomes signalled by the background data retrieving thread. Once it is signalled you can then continue to do whatever you need to do with the data, like bind it into the UI.
This method is still a little clunky for various reasons, but it will get you started.
Edit: i was being lazy with my answer above... a better option is to pass a delegate (callback) to the background thread, when the delegate is invoked (upon completion of the data retrieval) it marshals itself back on to the UI thread, and starts doing the required work with the data.
Inside your form, you can use a Timer control to simulate loading
using ProgressBar when it reaches 100 it unloads the form or any
kind of animation.
for progress-bar code just add the control from the toolbox and then
write the following code:
ProgressBar1.Value = ProgressBar1.Value+1;
if(ProgressBar1.Value == 100)
{
timer1.Enabled = false; this.Hide();
MessageBox.Show("Complete Loading...");
}

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?

Categories

Resources