Introduction
If your Windows Form application is doing some long running tasks in the background, then the UI becomes unresponsive. To inform the user that some long running task is operating in the background, you need to display an indicator. The indicator can be as simple as an animated image (GIF image). This article describes how to do the same in Windows Forms.
The example provided in this article displays the number of vowels, consonants, numbers and special characters present in a text file. It reads the input from a text file line by line which is provided by the user and displays the output in a grid.
Design the Form
Create a new Windows Form project in Visual Studio.
Drag and drop the Label
, Button
, ComboBox
and DataGridView
controls in the Windows Form to create a form similar to the displayed below:
Add a PictureBox
control to the centre of the form and then set the "Image
" and "InitialImage
" properties to the loading image. You can import the image from the Visual Studio Properties Window. To do the same, click the "..." button displayed next to the "Initial Image" value. It will open the "Select Resource" window. Choose the "Project resource file" option and then click the "Import..." button. Choose the loading image and then click "Open" and "Ok" buttons. Repeat the same process to set the "Image
" property of the PictureBox
control. See the below images for reference.
Core Functionality
When the user clicks the "Find" button, we want to display a loading indicator to the user so the user can know that the operation is happening in the background. The loading indicator is displayed as follows:
After the background operation is completed, the loading image is hidden and the data are loaded to the grid. Below is a screenshot of the form where data are loaded to the grid.
Using the Code
Display Dialog
When the user clicks the "Browse.." button, we want to display a dialog from where the text file will be selected. The below code is used to display the file open dialog with the customization. Note the "Title
" and "Filter
" added to the file open dialog.
var dialog = new OpenFileDialog();
dialog.Title = "Browse Text Files";
dialog.DefaultExt = "txt";
dialog.CheckFileExists = true;
dialog.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
dialog.Multiselect = false;
dialog.ShowDialog();
var fileName = dialog.FileName;
txtFilePath.Text = fileName;
Display Loading Indicator
private void SetLoading(bool displayLoader)
{
if (displayLoader)
{
this.Invoke((MethodInvoker)delegate
{
picLoader.Visible = true;
this.Cursor = System.Windows.Forms.Cursors.WaitCursor;
});
}
else
{
this.Invoke((MethodInvoker)delegate
{
picLoader.Visible = false;
this.Cursor = System.Windows.Forms.Cursors.Default;
});
}
}
The above code is used for displaying the loading image which is kept inside a PictureBox
control. Note that the code is kept inside this.Invoke();
When the loading image is displayed, the cursor is changed to "wait
" cursor.
Releasing the UI Thread
To keep the UI responsive, the whole operation takes place in a new thread.
private void btnFind_Click(object sender, EventArgs e)
{
try
{
Thread threadInput = new Thread(DisplayData);
threadInput.Start();
}
catch (Exception ex)
{
DisplayError(ex);
}
}
The DisplayData()
method performs all operations like calling a method to show/hide loading image, read from the file, find characters and bind the grid.
private void DisplayData()
{
SetLoading(true);
SetLoading(false);
}
The first line displays the loading indicator. The other operation takes place next and finally, the loading indicator is hidden.
Reading Data from Controls
We cannot read the data from the controls directly because the whole operation takes place in a separate thread. If you will try to read the content from the control directly, then it will throw an error.
If you will try to read the ComboBox
value directly in the DisplayData()
method, then it will throw invalid cross-thread operation error.
var charType = cmbCharacterType.Text;
InvalidOperationException:
Cross-thread operation not valid: Control 'cmbCharacterType' accessed from a thread other than the thread it was created on.
The data is read from the controls as follows:
this.Invoke((MethodInvoker)delegate
{
charType = cmbCharacterType.Text;
path = txtFilePath.Text.Trim();
});
History
- 7th December, 2017: Initial version