申请说明:
在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);
}
}
答案 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