1. 在窗体上添加一个 ProgressBar
控件
在您的窗体中添加一个 ProgressBar
控件,并设置其属性为 Marquee
或 Continuous
。这个控件用来展示连接测试的进度。
2. 初始化 BackgroundWorker
在窗体的构造函数中,初始化并配置 BackgroundWorker
。假设您的窗体类名为 Form1
:
BackgroundWorker backgroundWorker;
public Form1() // 构造函数
{
InitializeComponent()
// 具体原因看问题点1
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
// 初始化 BackgroundWorker
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += BackgroundWorker_DoWork;
backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
}
3. 开始 BackgroundWorker
在 btnTestLink_Click
方法中启动 BackgroundWorker
,并显示进度条:
private void btnTestLink_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
{
progressBar.Visible = true; // 显示进度条
progressBar.Value = 0; // 重置进度条
backgroundWorker.RunWorkerAsync(); // 启动异步操作
}
}
4. 执行连接操作和报告进度
在 BackgroundWorker_DoWork
方法中实现您的连接逻辑。当每个项目完成连接测试后,通过 ReportProgress
方法更新进度:
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
int totalItems = lvDevice.Items.Count;
int completedItems = 0;
foreach (ListViewItem item in lvDevice.Items)
{
if (item.Checked)
{
cp.IP = item.SubItems[3].Text;
cp.IPPort = Int32.Parse(item.SubItems[4].Text);
cp.CommStyle = 1;
cp.ClockID = Int32.Parse(item.SubItems[0].Text);
bool isConnected = OpenPort(ref cp);
// 创建一个更新对象
var updateInfo = new
{
Item = item,
Success = isConnected
};
// 报告进度
backgroundWorker.ReportProgress(0, updateInfo);
ClosePort(ref cp);
completedItems++;
// 更新进度条的值
backgroundWorker.ReportProgress((completedItems * 100) / totalItems);
}
}
}
5. 更新 UI 元素
在 ProgressChanged
方法中根据连接结果更新 UI 控件:
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var updateInfo = (dynamic)e.UserState;
// 更新 ListView 项的状态
if (updateInfo.Success)
{
updateInfo.Item.BackColor = Color.GreenYellow; // 成功则设置为绿色
updateInfo.Item.SubItems[5].Text = "连接成功"; // 显示连接状态
updateInfo.Item.ForeColor = Color.Black; // 设置文本颜色为黑色
updateInfo.Item.SubItems[5].ForeColor = Color.Green; // 状态文本颜色为绿色
}
else
{
updateInfo.Item.BackColor = Color.Red; // 失败则设置为红色
updateInfo.Item.SubItems[5].Text = "连接失败"; // 显示连接状态
updateInfo.Item.ForeColor = Color.Black; // 设置文本颜色为黑色
updateInfo.Item.SubItems[5].ForeColor = Color.Red; // 状态文本颜色为红色
}
}
6. 处理完成操作
在 RunWorkerCompleted
方法中隐藏进度条,并提示用户所有操作已完成:private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar.Visible = false; // 隐藏进度条
MessageBox.Show("所有连接测试完成!");
}
问题点:
1.System.InvalidOperationException:“线程间操作无效: 从不是创建控件“lvDevice”的线程访问它
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
目的
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls
是一个属性,设置为 false
后,允许从非创建控件的线程访问和修改控件。然而,尽管这样做可以消除异常,但这并不是一个好的做法,原因如下:
- 不安全性:直接从其他线程访问 UI 控件可能引发线程间竞争条件,导致应用程序崩溃或 UI 状态出错。
- 不推荐的做法:这种做法破坏了 Windows Forms 本身的线程模型,可能会导致难以调试的错误。
- 可维护性差:其他开发人员在看到此设置时,可能不明白代码为什么会这样处理,增加了理解和维护的复杂性。
正确的做法
正确的方式是使用 Control.Invoke
或 Control.BeginInvoke
方法将 UI 更新的代码封装进一个委托,并在主线程上执行。这样做不仅安全而且符合线程模型。