Question Spinning Control need to spin while main UI loads data from Database

tim8w

Well-known member
Joined
Sep 8, 2020
Messages
133
Programming Experience
10+
Hi,
I want my forever spinning form, which I brought up before a heavy Database load, to continue to spin while the main form does it's Database load. I can't calculate the time it'll take, so I am ok with my Spinning Progress form continuing to spin until the main form has completed the database load. Any ideas? I looked and looked and tried a few, but couldn't get this simple case to work.
 
The UI thread can't do two things at once. If you expect the UI thread to animate one form then it can't be loading data from a database in another form. You'll need to load the data from the database asynchronously.
 
Why are you using a non-standard spinning form instead of the Windows standard progress bar? The Windows progress bar has the mode to show a continuous repeating flow.

It's these non-standard UI which gives Windows a bad reputation for "inconsistent UI" and drives people to use Macs. Thankfully, though, some programmers who don't care about UI conventions are also now creating non-standard UI in Macs and starting to make it inconsistent as well.
 
I created a "wait dialogue" some time ago that allows you to display a modal dialogue with a ProgressBar on the UI thread while doing other work on a background thread. Here's the user code for that form:
BackgroundWorkerForm:
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
 
namespace Wunnell.Demo.BackgroundWorkerForm.CS
{
    public partial class BackgroundWorkerForm : Form
    {
#region Fields
 
        private readonly DoWorkEventHandler onDoWork;
        private readonly ProgressChangedEventHandler onProgressChanged;
        private readonly RunWorkerCompletedEventHandler onRunWorkerCompleted;
 
#endregion Fields
 
#region Constructors
 
        /// <summary>
        /// Creates a new instance of the <see cref="BackgroundWorkerForm"/> class.
        /// </summary>
        /// <remarks>
        /// Parameterless constructor is private to ensure handlers are provided for <see cref="BackgroundWorker"/>.
        /// </remarks>
        public BackgroundWorkerForm()
        {
            InitializeComponent();
        }
 
        /// <summary>
        /// Creates a new instance of the <see cref="BackgroundWorkerForm" /> class.
        /// </summary>
        /// <param name="onDoWork">
        /// Handler for the <see cref="BackgroundWorker.DoWork">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
        /// </param>
        public BackgroundWorkerForm(DoWorkEventHandler onDoWork)
            : this()
        {
            this.onDoWork = onDoWork;
 
            // AddHandler is used for local event handlers so that remote event handlers can be registered first and thus executed first.
 
            // Remote event handlers
            backgroundWorker1.DoWork += onDoWork;
 
            // Local event handlers
            backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
        }
 
        /// <summary>
        /// Creates a new instance of the <see cref="BackgroundWorkerForm" /> class.
        /// </summary>
        /// <param name="onDoWork">
        /// Handler for the <see cref="BackgroundWorker.DoWork">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
        /// </param>
        /// <param name="onProgressChanged">
        /// Handler for the <see cref="BackgroundWorker.ProgressChanged">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
        /// </param>
        public BackgroundWorkerForm(DoWorkEventHandler onDoWork, ProgressChangedEventHandler onProgressChanged)
            : this()
        {
            this.onDoWork = onDoWork;
            this.onProgressChanged = onProgressChanged;
 
            // AddHandler is used for local event handlers so that remote event handlers can be registered first and thus executed first.
 
            // Remote event handlers
            backgroundWorker1.DoWork += onDoWork;
            backgroundWorker1.ProgressChanged += onProgressChanged;
 
            // Local event handlers
            backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
            backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
 
            // A ProgressChanged handler has been provided so the ProgressBar will be updated explicitly based on the BackgroundWorker.
            backgroundWorker1.WorkerReportsProgress = true;
            progressBar1.Style = ProgressBarStyle.Continuous;
        }
 
        /// <summary>
        /// Creates a new instance of the <see cref="BackgroundWorkerForm" /> class.
        /// </summary>
        /// <param name="onDoWork">
        /// Handler for the <see cref="BackgroundWorker.DoWork">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
        /// </param>
        /// <param name="onRunWorkerCompleted">
        /// Handler for the <see cref="BackgroundWorker.RunWorkerCompleted">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
        /// </param>
        public BackgroundWorkerForm(DoWorkEventHandler onDoWork, RunWorkerCompletedEventHandler onRunWorkerCompleted)
            : this()
        {
            this.onDoWork = onDoWork;
            this.onRunWorkerCompleted = onRunWorkerCompleted;
 
            // AddHandler is used for local event handlers so that remote event handlers can be registered first and thus executed first.
 
            // Remote event handlers
            backgroundWorker1.DoWork += onDoWork;
            backgroundWorker1.RunWorkerCompleted += onRunWorkerCompleted;
 
            // Local event handlers
            backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
        }
 
        /// <summary>
        /// Creates a new instance of the <see cref="BackgroundWorkerForm" /> class.
        /// </summary>
        /// <param name="onDoWork">
        /// Handler for the <see cref="BackgroundWorker.DoWork">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
        /// </param>
        /// <param name="onProgressChanged">
        /// Handler for the <see cref="BackgroundWorker.ProgressChanged">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
        /// </param>
        /// <param name="onRunWorkerCompleted">
        /// Handler for the <see cref="BackgroundWorker.RunWorkerCompleted">RunWorkerCompleted</see> event of a <see cref="BackgroundWorker"/>.
        /// </param>
        public BackgroundWorkerForm(
            DoWorkEventHandler onDoWork,
            ProgressChangedEventHandler onProgressChanged,
            RunWorkerCompletedEventHandler onRunWorkerCompleted)
            : this()
        {
            this.onDoWork = onDoWork;
            this.onProgressChanged = onProgressChanged;
            this.onRunWorkerCompleted = onRunWorkerCompleted;
 
            // AddHandler is used for local event handlers so that remote event handlers can be registered first and thus executed first.
 
            // Remote event handlers
            backgroundWorker1.DoWork += onDoWork;
            backgroundWorker1.ProgressChanged += onProgressChanged;
            backgroundWorker1.RunWorkerCompleted += onRunWorkerCompleted;
 
            // Local event handlers
            backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
            backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
 
            // A ProgressChanged handler has been provided so the ProgressBar will be updated explicitly based on the BackgroundWorker.
            backgroundWorker1.WorkerReportsProgress = true;
            progressBar1.Style = ProgressBarStyle.Continuous;
        }
 
#endregion Constructors
 
#region Properties
 
        public bool SupportsCancellation
        {
            set
            {
                backgroundWorker1.WorkerSupportsCancellation = value;
 
                // If the worker can be cancelled, show the Cancel button and make the form big enough to see it.
                cancelWorkButton.Visible = value;
                ClientSize = new Size(284,
                                      value ? 76 : 47);
            }
        }
 
#endregion Properties
 
#region Methods
 
        private void BackgroundWorkerForm_Shown(object sender, System.EventArgs e)
        {
            // Start the background work when the form is displayed.
            backgroundWorker1.RunWorkerAsync();
        }
 
        private void cancelWorkButton_Click(object sender, System.EventArgs e)
        {
            // Disable the button to prevent another click.
            cancelWorkButton.Enabled = false;
 
            // Cancel the background work.
            backgroundWorker1.CancelAsync();
        }
 
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Update the ProgressBar.
            progressBar1.Value = e.ProgressPercentage;
        }
 
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // Close the form when the work is done.
            Close();
        }
 
        private void BackgroundWorkerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Remote event handlers.
 
            if (onDoWork != null)
            {
                backgroundWorker1.DoWork -= onDoWork;
            }
 
            if (onProgressChanged != null)
            {
                backgroundWorker1.ProgressChanged -= onProgressChanged;
            }
 
            if (onRunWorkerCompleted != null)
            {
                backgroundWorker1.RunWorkerCompleted -= onRunWorkerCompleted;
            }
 
            // Local event handlers
            backgroundWorker1.ProgressChanged -= backgroundWorker1_ProgressChanged;
            backgroundWorker1.RunWorkerCompleted -= backgroundWorker1_RunWorkerCompleted;
        }
 
#endregion Methods
    }
}
and here's a usage example:
Sample Usage:
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
 
namespace Wunnell.Demo.BackgroundWorkerForm.CS
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        // This method will be executed by the BackgroundWorker in the dialogue.
        private void BackgroundWorkerForm_DoWork(object sender, DoWorkEventArgs e)
        {
            var worker = (BackgroundWorker)sender;
 
            for (var i = 0; i <= 100; i++)
            {
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
 
                    break;
                }
 
                if (reportProgressCheckBox.Checked)
                {
                    worker.ReportProgress(i);
                }
 
                Thread.Sleep(100);
            }
        }
 
        // This method will be executed by the BackgroundWorker in the dialogue.
        private void BackgroundWorkerForm_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            statusLabel.Text = (e.ProgressPercentage / 100.0).ToString("p0");
        }
 
        // This method will be executed by the BackgroundWorker in the dialogue.
        private void BackgroundWorkerForm_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            statusLabel.Text = e.Cancelled
                                   ? "Operation cancelled"
                                   : "Operation complete";
        }
 
        private void runButton_Click(object sender, System.EventArgs e)
        {
            // Display the dialogue to initiate the background work.
            using (var waitDialogue = GetWaitDialogue())
            {
                waitDialogue.ShowDialog();
            }
        }
 
        private BackgroundWorkerForm GetWaitDialogue()
        {
            var dialogue = new BackgroundWorkerForm();
 
            // Only provide a ProgressChanged handler if the corresponding CheckBox is checked.
            if (reportProgressCheckBox.Checked)
            {
                dialogue = new BackgroundWorkerForm(BackgroundWorkerForm_DoWork,
                                                    BackgroundWorkerForm_ProgressChanged,
                                                    BackgroundWorkerForm_RunWorkerCompleted);
            }
            else
            {
                dialogue = new BackgroundWorkerForm(BackgroundWorkerForm_DoWork,
                                                    BackgroundWorkerForm_RunWorkerCompleted);
            }
 
            // Only display a Cancel button if the corresponding CheckBox is checked.
            dialogue.SupportsCancellation = allowCancellationCheckBox.Checked;
 
            return dialogue;
        }
    }
}
Without having seen what you're doing, I'm guessing that, in your case, you could query the database in the DoWork event handler and then bind the populated DataTable to your UI in the RunWorkerCompleted event handler.

Here's an idea of what the dialogue looks like in the designer:

1607991571348.png


and here's the demo form:

1607991708893.png
 
Back
Top Bottom