You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在应用加载/数据库操作时显示加载表单及多窗体调用实现

多窗体按需调用加载弹窗的实现方案

需求:应用启动或从数据库获取大量数据时显示加载表单,实现数据库操作时的加载逻辑。已编写基于BackgroundWorker的代码,需要指导如何在多个窗体中按需调用showLoading和closeLoading方法。

现有代码

Private bgw As New BackgroundWorker
Public Sub showLoading()
    bgw.WorkerSupportsCancellation = True
    
    AddHandler bgw.DoWork, AddressOf bgw_doWork
    AddHandler bgw.RunWorkerCompleted, AddressOf bgw_Complete
    
    If Not bgw.IsBusy = True Then
        bgw.RunWorkerAsync()
    End If
End Sub

Public Sub closeLoading()
    If bgw.WorkerSupportsCancellation = True Then
        bgw.CancelAsync()
    End If
End Sub

Private Sub bgw_doWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
    Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    Dim loadingScreen As New frmLoading
    
    
    loadingScreen.Show()
    
    While True
        If Not bgw.CancellationPending Then
            Threading.Thread.Sleep(50)
        Else
            e.Cancel = True
            Exit While
        End If
        End While
    End Sub
    
    Private Sub bgw_Complete(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
        Dim lstFrm As New List(Of frmLoading)
        lstFrm = Application.OpenForms.OfType(Of frmLoading)()
        
        If lstFrm.Count > 0 Then
            For Each frm As frmLoading In lstFrm
                frm.Close()
            Next
        End If
    End Sub

解决方案

一、修复原代码的核心问题

  1. 跨线程UI操作错误:bgw_doWork运行在后台线程,直接调用loadingScreen.Show()会触发跨线程异常,必须通过UI线程调度窗体显示。
  2. 重复绑定事件:每次调用showLoading()都会重复添加事件处理,导致回调逻辑多次执行,应在初始化时仅绑定一次。
  3. 实例复用冲突:单个BackgroundWorker实例重复调用可能出现状态混乱,建议每次显示加载时创建新实例或严格校验状态。

二、封装公共加载管理器

将加载逻辑封装为独立公共类,让所有窗体可直接调用,避免重复代码:

Imports System.ComponentModel
Imports System.Threading

Public Class LoadingManager
    Private Shared bgw As BackgroundWorker

    Public Shared Sub ShowLoading()
        If bgw IsNot Nothing AndAlso bgw.IsBusy Then
            Return ' 避免重复显示加载窗体
        End If

        bgw = New BackgroundWorker()
        bgw.WorkerSupportsCancellation = True

        ' 仅绑定一次事件
        AddHandler bgw.DoWork, AddressOf Bgw_DoWork
        AddHandler bgw.RunWorkerCompleted, AddressOf Bgw_RunWorkerCompleted

        bgw.RunWorkerAsync()
    End Sub

    Public Shared Sub CloseLoading()
        If bgw IsNot Nothing AndAlso bgw.WorkerSupportsCancellation AndAlso bgw.IsBusy Then
            bgw.CancelAsync()
        End If
    End Sub

    Private Shared Sub Bgw_DoWork(sender As Object, e As DoWorkEventArgs)
        Dim worker = DirectCast(sender, BackgroundWorker)
        Dim loadingForm As frmLoading = Nothing

        ' 通过UI线程创建并显示加载窗体
        Application.OpenForms(0).Invoke(Sub()
                                            loadingForm = New frmLoading()
                                            loadingForm.Show()
                                        End Sub)

        ' 循环等待取消信号
        While Not worker.CancellationPending
            Thread.Sleep(50)
        End While

        e.Cancel = True
    End Sub

    Private Shared Sub Bgw_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
        ' 关闭所有加载窗体并释放资源
        Dim loadingForms = Application.OpenForms.OfType(Of frmLoading).ToList()
        For Each frm In loadingForms
            If Not frm.IsDisposed Then
                frm.Close()
                frm.Dispose()
            End If
        Next

        ' 清理事件绑定与实例
        RemoveHandler bgw.DoWork, AddressOf Bgw_DoWork
        RemoveHandler bgw.RunWorkerCompleted, AddressOf Bgw_RunWorkerCompleted
        bgw = Nothing
    End Sub
End Class

三、多窗体调用示例

示例1:窗体启动时加载数据

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    ' 显示加载窗体
    LoadingManager.ShowLoading()

    ' 后台执行数据库初始化逻辑
    Task.Run(Sub()
                 ' 替换为实际数据库操作
                 Thread.Sleep(3000)

                 ' 操作完成后关闭加载
                 LoadingManager.CloseLoading()

                 ' 回到UI线程更新界面
                 Me.Invoke(Sub()
                               Me.lblStatus.Text = "数据初始化完成"
                           End Sub)
             End Sub)
End Sub

示例2:按钮触发大数据查询

Private Sub btnQuery_Click(sender As Object, e As EventArgs) Handles btnQuery.Click
    LoadingManager.ShowLoading()

    Task.Run(Sub()
                 ' 执行大量数据库查询
                 Dim data = FetchLargeDataFromDB()

                 ' 查询完成关闭加载
                 LoadingManager.CloseLoading()

                 ' 更新DataGridView(UI线程操作)
                 Me.Invoke(Sub()
                               dgvData.DataSource = data
                           End Sub)
             End Sub)
End Sub

' 模拟数据库查询方法
Private Function FetchLargeDataFromDB() As DataTable
    Thread.Sleep(2000)
    Dim dt As New DataTable()
    dt.Columns.Add("ID")
    dt.Columns.Add("Name")
    For i = 1 To 1000
        dt.Rows.Add(i, $"数据项 {i}")
    Next
    Return dt
End Function

四、关键注意事项

  • UI线程安全:所有操作UI的代码必须通过Invoke/BeginInvoke回到主线程,避免跨线程异常。
  • 防止重复调用:调用ShowLoading()前检查是否已有加载窗体显示,避免重复创建。
  • 资源清理:关闭加载窗体后调用Dispose()释放资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Nikunj Chovatiya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 12:05:16