简体   繁体   English

C#窗口显示从数据库更新表单GUI时正在加载GIF

[英]C# windows show loading GIF while updating the form GUI from database

Application Description: 应用说明:
Developed a small windows MDI application in C# where multiple users update and scan product bar codes before dispatching. 在C#中开发了一个小型Windows MDI应用程序,多个用户在分派之前更新和扫描产品条形码。 We are using SQL as a database. 我们正在使用SQL作为数据库。

Activity: 活动:
The supervisors have form named dashboard in the application where they can check multiple count details like scanning done by each user, daily scan count, hourly scan count, product scan count etc. 主管在应用程序中具有名为仪表板的表格,他们可以在其中检查多个计数详细信息,例如每个用户执行的扫描,每日扫描计数,每小时扫描计数,产品扫描计数等。

Done: 完成:
I have used multiple list-views and labels to display this details successfully. 我使用了多个列表视图和标签来成功显示此详细信息。

Requirement: 需求:
The database contains millions of scanning records. 该数据库包含数百万个扫描记录。 I am using multiple stored procedures to update the list view and labels. 我正在使用多个存储过程来更新列表视图和标签。 Loading the same takes time. 加载相同内容需要时间。 I want to show an animated GIF in picture box while the loading is being done in the background. 我想在后台执行加载时在图片框中显示动画GIF。

I tried using threading and background worker but updating multiple controls like list view and labels on the master GUI doesn't look feasible [cross-threading]. 我尝试使用线程和后台工作程序,但是在主GUI上更新多个控件(如列表视图和标签)看起来并不可行[跨线程]。 Is this the only way to achieve this, Please direct me in the right direction. 这是实现此目标的唯一方法,请引导我朝正确的方向前进。

        string queryString = "select scanby,count(distinct refno),count(1) from SecRec where scan='Y' and convert(varchar(10),ScanTime,111) like '" + comboDate + "' group by scanby order by 3,2 desc";
        lstVUser.Clear();

        lstVUser.Columns.Add("User", 105);
        lstVUser.Columns.Add("Cust", 60);
        lstVUser.Columns.Add("Imp", 60);

        using (SqlConnection conn = new SqlConnection(connectString))
        {
            SqlCommand cmd = new SqlCommand(queryString, conn);
            try
            {
                conn.Open();
                SqlDataAdapter adp = new SqlDataAdapter(queryString, conn);
                DataTable dt = new DataTable();
                adp.Fill(dt);
                for (int i = 0; i < dt.Rows.Count; i++)
                {
                    DataRow dr = dt.Rows[i];
                    ListViewItem listitem = new ListViewItem(dr[0].ToString());
                    listitem.SubItems.Add(dr[1].ToString().PadLeft(3));
                    listitem.SubItems.Add(dr[2].ToString().PadLeft(3));
                    lstVUser.Items.Add(listitem);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

It sounds like you're coming from Delphi / FoxPro background. 听起来您来自Delphi / FoxPro背景。 In .NET, grids are not virtual by default, so data binding millions of rows is always a terrible idea. 在.NET中,默认情况下网格不是虚拟的,因此数据绑定数百万行始终是一个糟糕的主意。

This requires a bit of thought. 这需要一些思考。

First, figure out how much data you really need to get at a time - maybe loading everything at once (but separately from the data binding) is a good option, maybe you want to read data on-demand. 首先,弄清楚一次真正需要获取多少数据-也许一次加载所有内容(但数据绑定分开 )是一个不错的选择,也许您想按需读取数据。

Second, figure out how to present the data. 其次,弄清楚如何呈现数据。 For a few hundred rows, binding it directly to the grid is fine. 对于几百行,将其直接绑定到网格就可以了。 For anything more, you really need some way of partitioning the data to avoid always presenting everything. 除此之外,您确实需要某种方式对数据进行分区,以避免始终显示所有内容。 The default winforms DataGridView is in fact designed to deal with this - you just have to enable virtual mode, and write a bit of code. 实际上,默认的winforms DataGridView旨在解决此问题-您只需要启用虚拟模式并编写一些代码即可。 See https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.virtualmode(v=vs.110).aspx for more information. 有关更多信息,请参见https://msdn.microsoft.com/zh-cn/library/system.windows.forms.datagridview.virtualmode(v=vs.110).aspx

In most cases, you don't really want to work with a grid with millions of rows, virtual or not. 在大多数情况下,您实际上并不想使用具有数百万行的网格(无论是否虚拟)。 You might want to consider adding some filters that align well with the requirements of your customers. 您可能要考虑添加一些符合客户需求的过滤器。 It might very well be fine to only show the last hundred rows without filters, for example, and only giving access to the rest with a filter. 例如,仅显示最后一百行而不使用过滤器,而仅使用过滤器访问其余的行,可能会很好。

Applying those ideas, you should be able to get away with having to use a loading animation at all. 应用这些想法,您应该完全不必使用加载动画。 If the loading still does take some perceptible amount of time (eg more than 100-200ms), you can add the animation, but make sure you separate the data loading, and the data binding - this allows you to load the data in the background (using asynchronous database requests or a background worker), and only block the GUI while doing the actual binding (unavoidable, but painless when using virtual grids). 如果加载仍然需要一定的时间(例如,超过100-200ms),则可以添加动画,但请确保分开数据加载和数据绑定-这允许您在后台加载数据(使用异步数据库请求或后台工作程序),并且仅在进行实际绑定时阻止GUI(这是不可避免的,但在使用虚拟网格时则很轻松)。

EDIT: 编辑:

Working with your code, you'd want to do something like this: 使用代码,您需要执行以下操作:

async Task<DataTable> LoadData()
{
  // Setup the command, connection etc. as usual
  using (var reader = await command.ExecuteReaderAsync())
  {
    var results = new DataTable();
    results.Load(reader);

    return results;
  }
}

async void btnDoStuff_Click(object sender, EventArgs e)
{
  try
  {
    loadingAnimation.Show();

    var dataTable = await LoadData();

    // Use the data table to bind the ListView's data as usual
  }
  finally
  {
    loadingAnimation.Hide();
  }
}

Well, if you want to access a control that is created in another thread then you will always face a [cross-threading] problem. 好吧,如果您要访问在另一个线程中创建的控件,那么您将始终会遇到[跨线程]问题。 so you will need to use Control.Invoke and the patterns associated with it. 因此,您将需要使用Control.Invoke及其相关的模式。

Here is a sample: 这是一个示例:

private delegate void SetListProperties(DataTable myData);

private void UpdateListView(DataTable myData)
{
    if (lstVUser.InvokeRequired)
    {
        SetListProperties d = new SetListProperties(UpdateListView);
        lstVUser.BeginInvoke(d, myData);
    }
    else
    {
       for (int i = 0; i < dt.Rows.Count; i++)
       {
           DataRow dr = dt.Rows[i];
           ListViewItem listitem = new ListViewItem(dr[0].ToString());
           listitem.SubItems.Add(dr[1].ToString().PadLeft(3));
           listitem.SubItems.Add(dr[2].ToString().PadLeft(3));
           lstVUser.Items.Add(listitem);
       }
    }
}

Concerning the splash screen, you will need to be absolutely sure that all initialization logic happens outside the GUI thread. 关于启动屏幕,您需要绝对确保所有初始化逻辑都在GUI线程之外发生。

Here is a nice solution Splash_Screen_Sample 这是一个不错的解决方案Splash_Screen_Sample

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM