Berechnungsfortschritt einer Threadfunktion in einem Fenster anzeigen

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