C#windows显示从数据库

时间:2015-07-13 07:55:34

标签: c# sql multithreading loading

申请说明:
在C#中开发了一个小型Windows MDI应用程序,其中多个用户在分派之前更新并扫描产品条形码。我们使用SQL作为数据库。

的活动:
主管在应用程序中有一个名为仪表板的表单,可以检查多个计数详细信息,例如每个用户执行的扫描,每日扫描计数,每小时扫描计数,产品扫描计数等。

完成


我使用了多个列表视图和标签来成功显示此详细信息。

要求:
该数据库包含数百万个扫描记录。我正在使用多个存储过程来更新列表视图和标签。加载相同需要时间。我想在后台完成加载时在图片框中显示动画GIF。

我尝试使用线程和后台工作程序,但更新多个控件(如列表视图和主GUI上的标签)看起来不可行[交叉线程]。这是实现这一目标的唯一途径,请指导我正确的方向。

        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);
            }
        }

2 个答案:

答案 0 :(得分:1)

听起来你是来自Delphi / FoxPro背景。在.NET中,默认情况下网格不是虚拟的,因此绑定数百万行的数据总是是一个糟糕的主意。

这需要一点思考。

首先,弄清楚你真正一次需要获取多少数据 - 可能一次加载所有内容(但单独与数据绑定相关)是一个不错的选择,也许你想按需阅读数据。

其次,弄清楚如何呈现数据。对于几百行,将其直接绑定到网格上就可以了。对于更多的东西,你真的需要一些分区数据的方法,以避免总是呈现一切。默认的winforms DataGridView实际上是为了解决这个问题而设计的 - 你只需要启用虚拟模式,然后编写一些代码。有关详细信息,请参阅https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.virtualmode(v=vs.110).aspx

在大多数情况下,确实想要使用具有数百万行(无论是否为虚拟)的网格。您可能需要考虑添加一些与客户要求完全一致的过滤器。例如,仅显示没有过滤器的最后一百行可能很好,并且只允许使用过滤器访问其余行。

应用这些想法,您应该能够完全使用加载动画。如果加载仍然需要一些可察觉的时间(例如超过100-200ms),您可以添加动画,但要确保分离数据加载和数据绑定 - 这允许您在后台加载数据(使用异步数据库请求或后台工作程序),并且只在执行实际绑定时阻止GUI(不可避免,但在使用虚拟网格时无痛)。

修改

使用您的代码,您需要执行以下操作:

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();
  }
}

答案 1 :(得分:1)

好吧,如果你想访问在另一个线程中创建的控件,那么你将始终面临[交叉线程]问题。 所以你需要使用Control.Invoke以及与之相关的模式。

以下是一个示例:

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);
       }
    }
}

关于启动画面,您需要绝对确保所有初始化逻辑都发生在GUI线程之外。

这是一个很好的解决方案Splash_Screen_Sample