This documentation is for the old version. Go to the latest Graphics Mill docs

Creating Multithreaded GUI

Developing user interface you aim to make it easy and convenient for users. This principle is especially important for applications performing long-running calculations. There is a special technique to achieve this goal for such class of solutions. This article describes how to use this approach jointly with Microsoft Windows Forms controls and Graphics Mill for .NET.

This approach is based on the multithreading concept. Its main idea is to process long-running operations in the background and provide users with full control over the application. The present article considers two implementations of one simple image editor application. The first implementation is single-threaded and has some imperfections in the user interface. The second implementation is multithreaded; it is more sophisticated but eliminates the shortcomings of the first one.

Single-threaded Application

Assume that our editor can apply only a blur transformation on the loaded image. The application GUI may look like on this screenshot:

Single-threaded Blur Editor

It uses the following elements to interact with a user:

  • The Button to show the dialog to open an image file;
  • The BitmapViewer to display the processed image;
  • The TrackBar to specify blur radius;
  • The ProgressBar to indicate the progress of the image processing.

The code which implements this sample application is shown below:

Visual Basic
' Bitmap which stores a source image. 
Private _sourceBitmap As Aurigma.GraphicsMill.Bitmap
' Bitmap for applying blur effect. 
Private _blurBitmap As Aurigma.GraphicsMill.Bitmap = New Aurigma.GraphicsMill.Bitmap()

Private Sub _openFileBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _openFileBtn.Click
    If _openFileDialog.ShowDialog() = DialogResult.OK Then
        Try
            _sourceBitmap = New Aurigma.GraphicsMill.Bitmap(_openFileDialog.FileName)
            _bitmapViewer.Bitmap = _sourceBitmap
            _trackBar.Value = 0
        Catch Ex As System.Exception
            System.Windows.Forms.MessageBox.Show("Error arrised during loading following file:" & Environment.NewLine & _openFileDialog.FileName)
            Exit Sub
        End Try
    End If
End Sub

Private Sub _trackBar_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _trackBar.ValueChanged
    ' Reset the progress bar. 
    _progressBar.Value = 0
    ApplyBlurTransform(_trackBar.Value)
End Sub

Private Sub ApplyBlurTransform(ByVal blurRadius As Integer)
    ' Create new Blur class instance with the specified blur radius.
    Dim blur As Aurigma.GraphicsMill.Transforms.Blur = New Aurigma.GraphicsMill.Transforms.Blur(blurRadius)

    ' Add event handlers.
    AddHandler blur.Progress, AddressOf BlurProgressHandler
    AddHandler blur.Stopped, AddressOf BlurStoppedHandler

    ' Configure the transformation.
    blur.Type = Aurigma.GraphicsMill.Transforms.BlurType.Gaussian
    ' Transform the copy of the _sourceBitmap and store it into the _blurBitmap.
    blur.ApplyTransform(_sourceBitmap, _blurBitmap)
End Sub

Private Sub BlurProgressHandler(ByVal sender As Object, ByVal e As Aurigma.GraphicsMill.ProgressEventArgs)
    ' Set the current transformation progress parameters to the ProgressBar.
    _progressBar.Maximum = e.Maximum
    _progressBar.Value = e.Current
End Sub

Private Sub BlurStoppedHandler(ByVal sender As Object, ByVal e As Aurigma.GraphicsMill.ErrorEventArgs)
    ' If the transformation completes without any exceptions.
    If IsNothing(e.Exception) Then
        ' Display the _blurBitmap in the BitmapViewer.
        _bitmapViewer.Bitmap = _blurBitmap
    End If
End Sub
C#
namespace BlurEditorCS
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            this._blurBitmap = new Aurigma.GraphicsMill.Bitmap();
        }

        // Bitmap which stores a source image. 
        private Aurigma.GraphicsMill.Bitmap _sourceBitmap;
        // Bitmap for applying blur effect. 
        private Aurigma.GraphicsMill.Bitmap _blurBitmap;

        private void _openFileBtn_Click(object sender, EventArgs e)
        {
            if (_openFileDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    _sourceBitmap = new Aurigma.GraphicsMill.Bitmap(_openFileDialog.FileName);
                    _bitmapViewer.Bitmap = _sourceBitmap;
                    _trackBar.Value = 0;
                }
                catch (System.Exception)
                {
                    System.Windows.Forms.MessageBox.Show("Error arrised during loading following file:" + Environment.NewLine + _openFileDialog.FileName);
                    return;
                }
            }
        }

        private void _trackBar_ValueChanged(object sender, EventArgs e)
        {
            // Reset the progress bar. 
            _progressBar.Value = 0;
            ApplyBlurTransform(_trackBar.Value);
        }

        private void ApplyBlurTransform(int blurRadius)
        {
            // Create new Blur class instance with the specified blur radius.
            using (Aurigma.GraphicsMill.Transforms.Blur blur = new Aurigma.GraphicsMill.Transforms.Blur(blurRadius))
            {
                // Add event handlers.
                blur.Progress += new Aurigma.GraphicsMill.ProgressEventHandler(BlurProgressHandler);
                blur.Stopped += new Aurigma.GraphicsMill.StopEventHandler(BlurStoppedHandler);

                // Configure the transformation.
                blur.Type = Aurigma.GraphicsMill.Transforms.BlurType.Gaussian;
                // Transform the copy of the _sourceBitmap and store it into the _blurBitmap.
                blur.ApplyTransform(_sourceBitmap, _blurBitmap);
            }
        }

        void BlurProgressHandler(object sender, Aurigma.GraphicsMill.ProgressEventArgs e)
        {
            // Set the current transformation progress parameters to the ProgressBar.
            _progressBar.Maximum = e.Maximum;
            _progressBar.Value = e.Current;
        }

        void BlurStoppedHandler(object sender, Aurigma.GraphicsMill.ErrorEventArgs e)
        {
            // If the transformation completes without any exceptions.
            if (e.Exception == null)
            {
                // Display the _blurBitmap in the BitmapViewer.
                _bitmapViewer.Bitmap = _blurBitmap;
            }
        }  
    }
}

Since this application is single-threaded, all operations, including the image processing, are executed in the user interface thread. This approach is easy to implement but has some disadvantages. The main disadvantage is that the application does not respond to user actions when the blur transformation is running. The reason of this problem is that image processing operations, such as blur, are resource-intensive and may require some time to complete. Therefore while the user interface thread transforms the image it cannot respond to any user input. As a result, the window is not redrawn; the user cannot click any buttons in the application GUI and even close the application.

Multithreaded Application

To avoid these problems we should apply the blur transformation in the asynchronous mode. Roughly speaking, we need to run this operation in the additional worker thread concurrently with the user interface thread, i.e. make our application multithreaded. To implement this approach we will use the System.Threading.Thread class from the System.Threading namespace, which allows us to create and control additional threads. In our case, we need to associate the ApplyBlurTransform method with the worker thread and start it whenever the user moves the TrackBar. It is also necessary to check whether this thread is already running and terminate it before starting a new transformation cycle.

Now, when we perform a long-running operation in the background we make the user interface thread free to interact with the user. It would be enough for a worker thread which does not show users any results. However, it is supposed that the application needs to update ProgressBar during image processing and display the final result in BitmapViewer. According to Windows Forms documentation: any instance members are not guaranteed to be thread safe.

Fortunately, the System.Windows.Forms.Control class provides four methods which can be safely called from any thread, they are: System.Windows.Forms.Control.BeginInvoke, System.Windows.Forms.Control.Invoke, System.Windows.Forms.Control.EndInvoke, and System.Windows.Forms.Control.CreateGraphics. The BitmapViewer as a descendant class of the Control inherits these methods too. Now, to make our multithreaded application completely safe and user friendly we should create two delegates for ProgressBar and BitmapViewer updating. Then we use BeginInvoke method to call them from the BlurProgressHandler and BlurStoppedHandler respectively. The BeginInvoke method calls the delegate with specified arguments (if needed) asynchronously and returns immediately, that allows us to update a user interface from the worker thread and continue its work instantly.

Below is the code which adds multithreading to our simple application:

Visual Basic
' Bitmap which stores a source image. 
Private _sourceBitmap As Aurigma.GraphicsMill.Bitmap
' Bitmap for applying blur effect. 
Private _blurBitmap As Aurigma.GraphicsMill.Bitmap = New Aurigma.GraphicsMill.Bitmap()
' Worker thread which processes the _blurBitmap.
Private _workerThread As Thread
' Flag which indicates whether the tranformation was aborted.
Private _abortTransform As Boolean

' Delegate for the UpdateProgressBar method.
Delegate Sub UpdateProgressBarDelegate(ByVal maximum As Integer, ByVal value As Integer)
' Delegate for the UpdateBitmapViewer method.
Delegate Sub UpdateBitmapViewerDelegate()

Private Sub _openFileBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _openFileBtn.Click
    If _openFileDialog.ShowDialog() = DialogResult.OK Then
        Try
            _sourceBitmap = New Aurigma.GraphicsMill.Bitmap(_openFileDialog.FileName)
            _bitmapViewer.Bitmap = _sourceBitmap
            _trackBar.Value = 0
        Catch Ex As System.Exception
            System.Windows.Forms.MessageBox.Show("Error arrised during loading following file:" & Environment.NewLine & _openFileDialog.FileName)
            Exit Sub
        End Try
    End If
End Sub

Private Sub _trackBar_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _trackBar.ValueChanged
    ' Reset the progress bar. 
    _progressBar.Value = 0

    ' If the worker thread is running.
    If Not IsNothing(_workerThread) AndAlso _workerThread.ThreadState = Threading.ThreadState.Running Then
        ' Abort the transformation.
        _abortTransform = True
        ' Block the worker thread.
        _workerThread.Join()
        ' Begin a new transformation.
        _abortTransform = False
    End If

    ' Create a new worker thread to apply the transformation with new radius parameter value.
    _workerThread = New Thread(New ParameterizedThreadStart(AddressOf ApplyBlurTransform))
    _workerThread.Start(_trackBar.Value)
End Sub

Private Sub ApplyBlurTransform(ByVal blurRadius As Object)
    ' Create new Blur class instance with the specified blur radius.
    Dim blur As Aurigma.GraphicsMill.Transforms.Blur = New Aurigma.GraphicsMill.Transforms.Blur(Int(blurRadius))

    ' Add event handlers.
    AddHandler blur.Progress, AddressOf BlurProgressHandler
    AddHandler blur.Stopped, AddressOf BlurStoppedHandler

    ' Configure the transformation.
    blur.Type = Aurigma.GraphicsMill.Transforms.BlurType.Gaussian
    ' Pass exceptions into Stopped event.
    blur.ExceptionThrowEnabled = False
    ' Transform the copy of the _sourceBitmap and store it into the _blurBitmap.
    blur.ApplyTransform(_sourceBitmap, _blurBitmap)
End Sub

Private Sub BlurProgressHandler(ByVal sender As Object, ByVal e As Aurigma.GraphicsMill.ProgressEventArgs)
    Dim blur As New Aurigma.GraphicsMill.Transforms.Blur()
    blur = CType(sender, Aurigma.GraphicsMill.Transforms.Blur)

    ' If the transformation was aborted. 
    If _abortTransform = True Then
        blur.Abort()
    Else
        Dim params As Object() = {e.Maximum, e.Current}
        ' Invoke the UpdateProgressBar method from the worker thread.
        _progressBar.BeginInvoke(New UpdateProgressBarDelegate(AddressOf UpdateProgressBar), params)
    End If
End Sub

Private Sub UpdateProgressBar(ByVal maximum As Integer, ByVal value As Integer)
    ' Set the current transformation progress parameters to the ProgressBar.
    _progressBar.Maximum = maximum
    _progressBar.Value = value
End Sub

Private Sub BlurStoppedHandler(ByVal sender As Object, ByVal e As Aurigma.GraphicsMill.ErrorEventArgs)
    ' If the transformation completes without any exceptions.
    If IsNothing(e.Exception) Then
        ' Invoke the UpdateBitmapViewer method from the worker thread.
        _bitmapViewer.BeginInvoke(New UpdateBitmapViewerDelegate(AddressOf UpdateBitmapViewer))
    End If
End Sub

Private Sub UpdateBitmapViewer()
    ' Display the _blurBitmap in the BitmapViewer.
    _bitmapViewer.Bitmap = _blurBitmap
End Sub

C#
using System.Threading;

namespace BlurEditorMultithreadingCS
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            this._blurBitmap = new Aurigma.GraphicsMill.Bitmap();
        }
        // Bitmap which stores a source image. 
        private Aurigma.GraphicsMill.Bitmap _sourceBitmap;
        // Bitmap for applying blur effect. 
        private Aurigma.GraphicsMill.Bitmap _blurBitmap;
        // Worker thread which processes the _blurBitmap.
        private Thread _workerThread;
        // Flag which indicates whether the tranformation was aborted.
        private bool _abortTransform;

        // Delegate for the UpdateProgressBar method.
        delegate void UpdateProgressBarDelegate(int maximum, int value);
        // Delegate for the UpdateBitmapViewer method.
        delegate void UpdateBitmapViewerDelegate();

        private void openFileBtn_Click(object sender, EventArgs e)
        {
            if (_openFileDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    _sourceBitmap = new Aurigma.GraphicsMill.Bitmap(_openFileDialog.FileName);
                    _bitmapViewer.Bitmap = _sourceBitmap;
                    _trackBar.Value = 0;
                }
                catch (System.Exception)
                {
                    System.Windows.Forms.MessageBox.Show("Error arrised during loading following file:" + Environment.NewLine + _openFileDialog.FileName);
                    return;
                }
           }
        }

        private void trackBar_ValueChanged(object sender, EventArgs e)
        {
            // Reset the progress bar. 
            _progressBar.Value = 0;

            // If the worker thread is running.
             if (_workerThread != null && _workerThread.ThreadState == ThreadState.Running)
             {                    
                 // Abort the transformation.
                 _abortTransform = true;
                 // Block the worker thread.
                 _workerThread.Join();
                 // Begin a new transformation.
                 _abortTransform = false;
             }

            // Create a new worker thread to apply the transformation with new radius parameter value.
             _workerThread = new Thread(new ParameterizedThreadStart(ApplyBlurTransform));
             _workerThread.Start(_trackBar.Value);
        }

        private void ApplyBlurTransform(object blurRadius)
        {
            // Create new Blur class instance with the specified blur radius.
            using (Aurigma.GraphicsMill.Transforms.Blur blur = new Aurigma.GraphicsMill.Transforms.Blur((int)blurRadius))
            {
                // Add event handlers.
                blur.Progress += new Aurigma.GraphicsMill.ProgressEventHandler(BlurProgressHandler);
                blur.Stopped += new Aurigma.GraphicsMill.StopEventHandler(BlurStoppedHandler);

                // Configure the transformation.
                blur.Type = Aurigma.GraphicsMill.Transforms.BlurType.Gaussian;
                // Pass exceptions into Stopped event.
                blur.ExceptionThrowEnabled = false;
                // Transform the copy of the _sourceBitmap and store it into the _blurBitmap.
                blur.ApplyTransform(_sourceBitmap, _blurBitmap);
            }
        }

        void BlurProgressHandler(object sender, Aurigma.GraphicsMill.ProgressEventArgs e)
        {
            Aurigma.GraphicsMill.Transforms.Blur blur = (Aurigma.GraphicsMill.Transforms.Blur)sender;
            
            // If the transformation was aborted. 
            if (_abortTransform)
            {
                blur.Abort();
            }
            else
            {                 
                // Invoke the UpdateProgressBar method from the worker thread.
                _progressBar.BeginInvoke(new UpdateProgressBarDelegate(UpdateProgressBar), 
                    new object[] { e.Maximum, e.Current });
            }            
        }

        private void UpdateProgressBar(int maximum, int value)
        {
            // Set the current transformation progress parameters to the ProgressBar.
            _progressBar.Maximum = maximum;
            _progressBar.Value = value;
        }


        void BlurStoppedHandler(object sender, Aurigma.GraphicsMill.ErrorEventArgs e)
        {
            // If the transformation completes without any exceptions.
            if (e.Exception == null)
            {
                // Invoke the UpdateBitmapViewer method from the worker thread.
                _bitmapViewer.BeginInvoke(new UpdateBitmapViewerDelegate(UpdateBitmapViewer));
            }
        }        

        private void UpdateBitmapViewer()
        {
            // Display the _blurBitmap in the BitmapViewer.
            _bitmapViewer.Bitmap = _blurBitmap;
        }

    }
}

See Also

Reference

Manual