Modal Dialog and Threads

G

Guest

Greetings,

I have an application that needs to use a modal form to display a progress
bar.

In order to get it working fine I used a background worker that is always
updating the info into the form, when the work was done I just send a true
variable to close the form...

This all works if I call the form like progress.Show().

But this way the form is not modal...

If I try progress.ShowDialog() the form isn't updated. I remains on the
screen as it is initialized...

How can I do to make it work like a modal form with the Show() method?
or
Is any way to work around this?
 
G

Guest

How are you spawning the worker thread and how are you updating the UI. You
should be using the ThreadPool and calling the Invoke method of the
form/progress bar to change the controls properties.
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi,

Can you post some code, especially when you create your thread and how the
thread communicate with the form.

In the meantime, how the thread gets a hold of one of the form's control?
( to use Invoke() )

What if the thread is started from the modal form? (apparentely you run it
from the calling form)
 
G

Guest

I'm calling a background worker that calls a form...

example (this is a little like pseudocode):
longTimeMethod()
{
progressWorker.RunWorkerAsync();
while (counter < total)
{
progressValue = total/counter
counter++
}
bDone = true;
}

progressWorker_DoWork()
{
ProgressForm progress = new progressForm();
progress.ShowDialog();
while(!bDone)
{progress.percent = progressValue;}
progress.Close();
bDone = false;
}

can you give me an example on how can I use the invoke as you refered
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi,


Diogo Alves said:
I'm calling a background worker that calls a form...

example (this is a little like pseudocode):
longTimeMethod()
{
progressWorker.RunWorkerAsync();
while (counter < total)
{
progressValue = total/counter
counter++
}
bDone = true;
}


The above code is wrong, you cannot access the UI from a worker thread. You
have to use Control.Invoke
See my other post in this thread
 
G

Guest

In events class
private void DeleteEvents()
{
int totalevents = dgEvents.SelectedRows.Count;
ProgressWorker.RunWorkerAsync("Please Wait, Removing Events.");
for (int j = 0; j < totalevents; j++)
{
progressValue = (int)((double)((double)(j)/(double)(totalevents))*100);
if (dgEvents[2, dgEvents.SelectedRows[j].Index].Value.ToString() != "")
{
OdbcConnection oConn = new
OdbcConnection(NFive.TimeFive.Common.Global.sConnString);
Processing proc = new Processing();
if ((dgEvents[2, dgEvents.SelectedRows[j].Index].Value) != DBNull.Value)
{
proc.DeleteEvent((int)dgEvents[2, dgEvents.SelectedRows[j].Index].Value);
OdbcCommand oCmd = new OdbcCommand("DELETE FROM tb_S57_Events WHERE
eventid = " + dgEvents[2, dgEvents.SelectedRows[j].Index].Value.ToString(),
oConn);
oCmd.Connection.Open();
oCmd.ExecuteNonQuery();
oCmd.Connection.Close();
}
}
}
bDone = true;
}

private void ProgressWorker_DoWork(object sender, DoWorkEventArgs e)
{
ProgressForm progress = new ProgressForm();
progress.MainHandler = thisHandle;
progress.Show();
while (!bDone)
{
Application.DoEvents();
if (progressValue > -1)
{
progress.Status = e.Argument.ToString() + " " + progressValue.ToString()
+ "%";
progress.Progress = progressValue;
}
else
{
progress.Status = e.Argument.ToString();
}

}
progress.Close();
bDone = false;
progressValue = 0;
}

in ProgressForm class
public partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
this.TopLevel = true;

}

public string Status
{
get
{
return lblStatus.Text;
}
set
{
if (value != lblStatus.Text)
{
lblStatus.Text = value;
lblStatus.Update();
lblStatus.Invalidate();
}
}
}


public int Progress
{
get
{
return progressBar1.Value;
}
set
{
if (!((value == -1) && (progressBar1.Style == ProgressBarStyle.Marquee)))
{
if (value == -1)
{
progressBar1.MarqueeAnimationSpeed = 50;
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.Update();
progressBar1.Invalidate();

}
else if (value != progressBar1.Value)
{
progressBar1.Style = ProgressBarStyle.Blocks;
progressBar1.Value = value;
progressBar1.Update();
progressBar1.Invalidate();

}
}
}
}
}


I did thinks this way because I need a simple way to put the progress bar...
the code had no thread or something.. so I tryed to adjust this way without
touching the old code too much
 
G

Guest

You're doing it the wrong way around. You need to do the work in the DoWork
handler and the UI stuff outside of DoWord. More like:

BeginWork()
{
this.progress = new progressForm();
progress.ShowDialog();
progressWorker.RunWorkerAsync();
}

progressWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (counter < total)
{
worker.ReportProgress(total/counter);
counter++
}
}

progressWorker_RunWorkerCompleted()
{
progress.Close();
progress.Dispose();
progress = null;
}

progressWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progress.percent = e.ProgressPercentage;
}


Of course, this requires the following before the call to
progressWorker.RunWorkerAsync():
progressWorker.WorkerReportsProgress = true;

The way you're doing it means the UI thread is hogged doing the work (and
can't update) while the DoWork method tries to update the UI and will
generate cross-thread exceptions.
 
P

Peter Duniho

Your code is wrong, but not for the reason Ignacio says.

I'm replying to this post, because in spite of the more detailed post you
offered, it seems your pseudocode is pretty close to what you're actually
doing. And it's broken. Here's why...

I'm calling a background worker that calls a form...

example (this is a little like pseudocode):
longTimeMethod()
{
progressWorker.RunWorkerAsync();
while (counter < total)
{
progressValue = total/counter
counter++
}
bDone = true;
}

So, you run a progress dialog on a BackgroundWorker, while doing all of
the actual work on the original thread. This is exactly backwards from
the intended use of BackgroundWorker.
progressWorker_DoWork()
{
ProgressForm progress = new progressForm();
progress.ShowDialog();
while(!bDone)
{progress.percent = progressValue;}
progress.Close();
bDone = false;
}

The above is the main problem. ShowDialog() will block until the form is
closed. So the loop where you pool "bDone" doesn't get executed until the
dialog form is closed.

However, since you're misusing BackgroundWorker anyway, it would be more
fruitful to show you how to correctly use it. Again in pseudocode:

ProgressForm pf = new ProgressForm();

void initiator()
{
BackgroundWorker bw = new BackgroundWorker();

bw.DoWork += worker;
bw.ProgressChanged += progress;
bw.RunWorkerCompleted += done;

bw.RunWorkerAsync();
pf.ShowDialog();
}

void worker()
{
// Here you do all the stuff you have in your "DeleteEvents" method
// Call bw.ReportProgress when you want the progress dialog to be
updated
// e.g. once per loop iteration
}

progress()
{
// update your ProgressForm here. E.g. ProgressForm.Progress =
e.ProgressPercentage
// or similar as appropriate
}

done()
{
// close your ProgressForm here. When you do that, this will
cause the
// call to ShowDialog() found in the initiator() method to return,
and
// your code execution will proceed from there.
}

Note that with BackgroundWorker there is no need to use Invoke(), as it
runs the ProgressChanged and RunWorkerCompleted events on the original
thread. Because of this, and because they can't be run until you get back
into a message pump, even if the worker finishes before you get to show
your progress dialog, I don't think there should be any trouble closing
the dialog from the done() method, since you shouldn't be able to get in
there until the form has been shown and a message pump loop has been
entered again (implicit in the call to ShowDialog()).

Pete
 
P

Peter Duniho

BeginWork()
{
this.progress = new progressForm();
progress.ShowDialog();
progressWorker.RunWorkerAsync();
}

You need to call RunWorkerAsync() first, otherwise it won't start until
the dialog form has been closed.
[...]
The way you're doing it means the UI thread is hogged doing the work (and
can't update) while the DoWork method tries to update the UI and will
generate cross-thread exceptions.

He doesn't get cross-thread exceptions, because he creates the form on the
same thread in which he uses it. It's not exactly good form, but it's not
technically incorrect either (at least not for that reason...there are
plenty of other things wrong with the code as written :) ).

Pete
 
G

Guest

Peter Duniho said:
You need to call RunWorkerAsync() first, otherwise it won't start until
the dialog form has been closed.
.... yeah, pseudo code
He doesn't get cross-thread exceptions, because he creates the form on the
same thread in which he uses it. It's not exactly good form, but it's not
technically incorrect either (at least not for that reason...there are
plenty of other things wrong with the code as written :) ).

he'll get cross-thread exceptions if he tries to show the form with the
other form as the parent (which I assume he will eventually want to do,
otherwise the progress form could be hidden behind the main form...).

While there's nothing wrong with creating and showing Forms on a thread
other than the main thread, you need a message pump on the thread that
creates the form, e.g. : Application.Run.(...) I don't recall what happens
when you don't do that. Another problem is that Forms require STA threads
(i.e. Forms on MTA threads re not supported), ThreadPool threads are MTA.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top