Implementing Non-Blocking and Modal Wait Indicators in WinForms

When developing applications with user interfaces (UIs) that perform background operations, managing the interaction between the UI thread and worker threads is crucial. Common scenarios involve controlling background tasks (start, pause, stop) and displaying their progress on the UI. A typical issue arises when lengthy operations, like database access, are performed on the UI thread, leading to unresponsive applications.

This article categorizes UI feedback during background tasks into two main types:

**1. Modal Waiting:**In this approach, the UI remains completely blocked until the background operation concludes. No user interaction is permitted on the main form.

Diagram illustrating modal waiting and UI enteraction.

**2. Non-Modal Waiting:**This method allows the UI to remain interactive while a background task runs. A separate, non-blocking dialog box typicaly displays the progress.

Diagram illustrating non-modal waiting and UI interaction.

The following code examples demonstrate implementations for both modal and non-modal waiting forms.

Modal Waiting Implementation

The frmWait form handles modal background operations. It displays a progress bar and updates a label with the current status. The DoWait method initiates the background task and then blocks the UI thread by calling ShowDialog().

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

public partial class frmWait : Form
{
    private bool keepRunning = true;
    private List<string> processedItems = new List<string>();
    private ProgressBar progressBar;
    private Label statusLabel;
    private Button cancelButton;

    public frmWait()
    {
        InitializeComponent(); // Assume this sets up progressBar, statusLabel, cancelButton
        this.cancelButton.Click += new EventHandler(CancelButton_Click);
    }

    public object PerformBackgroundWork(object parameter)
    {
        int workCount = (int)parameter;
        progressBar.Maximum = workCount;

        // Execute the time-consuming operation asynchronously
        Func<List<string>> backgroundTask = () =>
        {
            Thread.Sleep(1000); // Initial delay
            for (int i = 0; i < workCount; i++)
            {
                if (!keepRunning)
                {
                    break;
                }

                string timestamp = DateTime.Now.ToLongTimeString();
                processedItems.Add(timestamp);

                // Update UI elements safely using Invoke
                this.Invoke((Action)delegate
                {
                    if (!this.IsDisposed)
                    {
                        progressBar.Value = i + 1; // +1 because loop starts at 0
                        statusLabel.Text = $"Processing item: \"{timestamp}\"";
                    }
                });

                Thread.Sleep(500); // Simulate work
            }
            return processedItems;
        };

        backgroundTask.BeginInvoke(new AsyncCallback(OnWorkCompleted), null);

        // Block the UI thread until the dialog is closed
        ShowDialog();
        return processedItems;
    }

    private void OnWorkCompleted(IAsyncResult ar)
    {
        // If the operation wasn't cancelled, close the dialog gracefully
        if (keepRunning)
        {
            this.Invoke((Action)delegate { DialogResult = DialogResult.OK; });
        }
    }

    private void CancelButton_Click(object sender, EventArgs e)
    {
        keepRunning = false; // Signal the background thread to stop
        DialogResult = DialogResult.Cancel; // Close the modal dialog
    }

    // Load event handler (can be empty or used for initialization)
    private void frmWait_Load(object sender, EventArgs e) { }
}

To use this modal form:

using (frmWait modalWaiter = new frmWait())
{
    // Execute background work and display the modal form
    List<string> results = modalWaiter.PerformBackgroundWork(50) as List<string>;
    MessageBox.Show($"Processed {results.Count} items.");
    // UI thread continues execution after the modal form is closed
}

Non-Modal Waiting Implementation

The frmNoWait form implements non-modal background operations. It displays progress but allows the main UI to remain active. Multiple instances of this form can be shown simultaneously.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

public partial class frmNoWait : Form
{
    private bool isRunning = true;
    private List<string> gatheredData = new List<string>();
    private ProgressBar progressIndicator;
    private Label messageLabel;
    private Button closeButton;

    public frmNoWait()
    {
        InitializeComponent(); // Assume this sets up progressIndicator, messageLabel, closeButton
        this.closeButton.Click += new EventHandler(CloseButton_Click);
    }

    private void BackgroundTaskCallback(IAsyncResult ar)
    {
        // ar.AsyncState could contain results if passed during BeginInvoke
        if (isRunning)
        {
            // Close the form after the background task completes successfully
            this.Invoke((Action)delegate
            {
                if (!this.IsDisposed)
                {
                    Close();
                }
            });
        }
    }

    public void InitiateNonModalWork(int itemCount)
    {
        progressIndicator.Maximum = itemCount;

        // Define and start the background task
        Action workerAction = () =>
        {
            try
            {
                Thread.Sleep(1000); // Initial delay
                for (int i = 0; i < itemCount; i++)
                {
                    if (!isRunning)
                    {
                        break;
                    }

                    string currentTimestamp = DateTime.Now.ToLongTimeString();
                    gatheredData.Add(currentTimestamp);

                    // Update UI safely
                    this.Invoke((Action)delegate
                    {
                        if (!this.IsDisposed)
                        {
                            progressIndicator.Value = i + 1;
                            messageLabel.Text = $"Loading string \"{currentTimestamp}\"";
                        }
                    });

                    Thread.Sleep(500); // Simulate task duration
                }
            }
            catch (Exception ex)
            {
                // Handle exceptions appropriately
                Console.WriteLine($"Error during background work: {ex.Message}");
            }
        };

        workerAction.BeginInvoke(new AsyncCallback(BackgroundTaskCallback), gatheredData); // Pass data as state if needed

        // Display the form without blocking the UI thread
        Show();
    }

    private void frmNoWait_Load(object sender, EventArgs e)
    {
        // Example of adding unique identifiers to multiple instances
        this.Text += $" Instance {Form1.InstanceCounter++}";
    }

    private void CloseButton_Click(object sender, EventArgs e)
    {
        Close(); // User initiated closure
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        base.OnFormClosing(e);
        isRunning = false; // Ensure background thread knows the form is closing
    }
}

// Assuming Form1 has a static counter for unique form instances
public static class Form1
{
    public static int InstanceCounter = 1;
}

To use this non-modal form:

frmNoWait nonModalWaiter = new frmNoWait();
nonModalWaiter.InitiateNonModalWork(50);
// The UI thread continues execution immediately

The OnAsync (or BackgroundTaskCallback in the refactored example) method is responsible for signaling the completion of the background task and potentially closing the non-modal form.



Tags: WinForms threading Asynchronous Programming UI Design

Posted on Fri, 04 Sep 2026 16:02:08 +0000 by tecktalkcm0391