Häufig möchte man rechenintensive Aufgaben in Threads auslagern und dennoch deren Forschritt in einem kleinen Fenster anzeigen. Im folgenden Beispiel wird dazu eine Fensterklasse mit Fortschrittsbalken erzeugt und ein zugehöriges Threadobjekt angelegt.
- Fortschrittsfenster mit einem Label – lblWait, Progressbar – pbWait und einem Abbrechen-Button – btnCancel
- alle Properties werden Threadsafe ausgelegt, damit der Workerthread ohne Probleme seine Ausgaben machen kann
using System.Windows.Forms;
public partial class frmWait : Form
{
/// <summary>
/// Dialognachricht
/// </summary>
public string WaitMessage
{
get { return lblWait.Text; }
set
{
if (lblWait.InvokeRequired)
{
BeginInvoke((MethodInvoker)delegate() { lblWait.Text = value; });
}
else
{
lblWait.Text = value;
}
}
}
/// <summary>
/// Fortschrittsanzeige in %
/// </summary>
public int Progress
{
get { return pbWait.Value; }
set
{
int iValue = 0;
if (value ->= pbWait.Minimum && value <= pbWait.Maximum)
{
iValue = value;
}
else
{
if (value < pbWait.Minimum)
{
iValue = pbWait.Minimum;
}
else
if (value -> pbWait.Maximum)
{
iValue = pbWait.Maximum;
}
}
if (pbWait.InvokeRequired)
{
BeginInvoke((MethodInvoker)delegate() { pbWait.Value = iValue; });
}
else
{
pbWait.Value = iValue;
}
}
}
/// <summary>
/// Caption des Dialoges
/// </summary>
public string Title
{
get { return this.Text; }
set
{
if (this.InvokeRequired)
{
BeginInvoke((MethodInvoker)delegate() { this.Text = value; });
}
else
{
this.Text = value;
}
}
}
/// <summary>
/// Konstruktor
/// </summary>
public frmWait()
{
InitializeComponent();
// Abbrechen-Button vorinitialisieren
btnCancel.DialogResult = DialogResult.Cancel;
btnCancel.TabStop = false;
}
}
- Thread-Objekt zur Kapselung des Berechnungsthreads
using System.Threading;
using System;
using System.Windows.Forms;
/// <summary>
/// Eventargument wenn ein Thread beendet wird
/// </summary>
public class ThreadEndingEventArgs : EventArgs
{
private string _msg;
public string Message
{
get { return _msg; }
}
public ThreadEndingEventArgs(string Message)
{
_msg = Message;
}
}
// die Klasse zum Kapseln der Berechnung in einem Thread
public class CWorker
{
// Referenz auf den Fortschrittsdialog
private frmWait _frmWaitDlg = null;
// aktuell ausgeführter Thread
private Thread _Thread = null;
private int _iMin = 0;
private int _iMax = 10000000;
/// <summary->
/// Event, wird ausgelöst, wenn ein Thread beendet wird
/// </summary->
public event EventHandler<ThreadEndingEventArgs-> ThreadEnding;
public CWorker(int iMin, int iMax, frmWait frmWaitDlg)
{
_iMin = iMin;
_iMax = iMax;
_frmWaitDlg = frmWaitDlg;
}
// Startfunktion
public Thread StartCalculation()
{
_Thread = new Thread(WorkFunktion);
_Thread.IsBackground = true;
_Thread.Priority = ThreadPriority.BelowNormal; // Priorität des Threads senken um der GUI Zeit zu lassen
_Thread.Start();
return _Thread;
}
// Abbruchfunktion
public void AbortCalculation()
{
if (_Thread != null && _Thread.IsAlive)
{
_Thread.Abort();
}
}
// die eigentlich Threadfunktion
private void WorkFunktion(object oObj)
{
try
{
if (_frmWaitDlg != null) _frmWaitDlg.Title = "Bitte warten ...";
// etwas berechnen
for (int iCurrent = _iMin; iCurrent < _iMax; iCurrent++)
{
int iProgress = 100 * ((iCurrent - _iMin) / _iMax - _iMin);
if (_frmWaitDlg != null)
{
_frmWaitDlg.WaitMessage = "Arbeite (" + iProgress.ToString() + "%)";
_frmWaitDlg.Progress = iProgress;
}
}
if (_frmWaitDlg != null) _frmWaitDlg.DialogResult = System.Windows.Forms.DialogResult.OK;
// Event "ThreadEnding" aufrufen, um das übergeordnete Obj zu informieren
if (this.ThreadEnding != null) ThreadEnding(this, new ThreadEndingEventArgs("Berechnung fertig!"));
}
// ThreadAbort abfangen
catch (ThreadAbortException)
{
if (_frmWaitDlg != null) _frmWaitDlg.DialogResult = System.Windows.Forms.DialogResult.Cancel;
}
// alle anderen Exceptions ausgeben
catch (Exception e)
{
if (_frmWaitDlg != null) _frmWaitDlg.DialogResult = System.Windows.Forms.DialogResult.Cancel;
MessageBox.Show("Fehler" + Environment.NewLine + Environment.NewLine + e.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
// hier noch evtl. aufräumen
}
}
}
- Aufrufbeispiel
public bool CalcFct()
{
bool bRetVal = false;
// Wartedialog erzeugen
frmWait frmWaitDlg = new frmWait();
// Worker-Objekt mit Thread erzeugen
CWorker oWorker = new CWorker(0, 1000000, frmWaitDlg);
// Event für pos. Beenden des Threads festlegen
oWorker.ThreadEnding += new EventHandler<ThreadEndingEventArgs->(oWorker_ThreadEnding);
// Thread starten
Thread trdCalc = oWorker.StartCalculation();
// auf Resultat des Wartedialoges warten (Cancel oder OK)
DialogResult result = frmWaitDlg.ShowDialog();
// wenn im Dialog "Abbruch" geklickt wurde
if (trdCalc != null && result != DialogResult.OK)
{
// ThreadAbort-Exception wird im Thread abgefangen und still behandelt
oWorker.AbortCalculation();
// dafür hier einen Dialog anzeigen
MessageBox.Show("Berechnung wurde abgebrochen.", "Nachricht", MessageBoxButtons.OK, MessageBoxIcon.Information);
bRetVal = false;
}
else
{
// alles ok
bRetVal = true;
}
return bRetVal;
}
// Event-Funktion, für positives Worker-Thread-Ende
private void oWorker_ThreadEnding(object sender, ThreadEndingEventArgs e)
{
MessageBox.Show(e.Message, "Nachricht", MessageBoxButtons.OK, MessageBoxIcon.Information);
}