在开发Windows Forms应用程序时,经常会遇到需要在后台执行长时间运行的任务。这些任务可能会阻塞UI线程,导致用户界面变得无响应。为了通知用户后台任务正在进行,需要显示一个加载指示器。这个指示器可以是一个简单的动画图像(GIF图像)。本文将介绍如何在Windows Forms中实现这一功能。
首先,在Visual Studio中创建一个新的Windows Forms项目。然后,通过拖放Label、Button、ComboBox和DataGridView控件到Windows表单上,创建一个类似于下图的表单。
在表单的中心添加一个PictureBox控件,并将"Image"和"InitialImage"属性设置为加载图像。可以通过Visual Studio属性窗口导入图像。具体操作是,点击"Initial Image"值旁边的"..."按钮,打开"Select Resource"窗口。选择"Project resource file"选项,然后点击"Import..."按钮。选择加载图像,然后点击"Open"和"Ok"按钮。重复相同的过程来设置PictureBox控件的"Image"属性。
当用户点击"Find"按钮时,希望显示一个加载指示器,以便用户知道后台操作正在进行。加载指示器的显示如下:
后台操作完成后,加载图像被隐藏,数据被加载到网格中。以下是数据加载到网格的表单截图。
当用户点击"Browse..."按钮时,希望显示一个对话框,从中可以选择文本文件。以下是用于显示文件打开对话框的代码,并进行了自定义。注意添加到文件打开对话框的"Title"和"Filter"。
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;
以下是用于显示加载图像的代码,该图像位于PictureBox控件内。注意代码被放在this.Invoke()内。
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;
});
}
}
上述代码用于显示加载图像,当显示加载图像时,光标会更改为"wait"光标。
为了保持UI的响应性,整个操作在新线程中进行。
private void btnFind_Click(object sender, EventArgs e)
{
try
{
Thread threadInput = new Thread(DisplayData);
threadInput.Start();
}
catch (Exception ex)
{
DisplayError(ex);
}
}
DisplayData()方法执行所有操作,如调用显示/隐藏加载图像的方法,从文件中读取数据,查找字符并绑定到网格。
private void DisplayData()
{
SetLoading(true);
// Do other operations...
SetLoading(false);
}
第一行显示加载指示器。接下来的操作紧随其后,最后隐藏加载指示器。
不能直接从控件中读取数据,因为整个操作在单独的线程中进行。如果尝试直接在DisplayData()方法中读取ComboBox的值,它将抛出无效的跨线程操作错误。
var charType = cmbCharacterType.Text;
InvalidOperationException: Cross-thread operation not valid: Control 'cmbCharacterType' accessed from a thread other than the thread it was created on.
this.Invoke((MethodInvoker)delegate
{
charType = cmbCharacterType.Text;
path = txtFilePath.Text.Trim();
});