【问题标题】:Filtering an DataGridView that doesn't have databinding过滤没有数据绑定的 DataGridView
【发布时间】:2010-11-18 08:13:18
【问题描述】:

我有一个非数据绑定 DGV(没有数据源等;手动添加的行)。为了过滤它,我一直在循环检查并适当地设置行可见属性。这在较小的测试集上效果很好,但在较大的测试集上表现完全失败。 1k 行以 5000/秒过滤。 10k 行仅以 ~250/秒过滤。 50k 仅需 40/秒。我对正在发生的事情的假设是,每次我更改行可见性​​时,DGV 都会重建一个显示行列表,将过滤过程变成 O(n^2) 操作。

即使有 10k 行表明用户正在滥用系统;需要考虑行为不良的用户,所以我需要做一些不同的事情。有没有比我现在在不使用数据绑定的情况下更快地过滤大量行的方法,还是我需要依靠清除/重新创建所有行(对于合理数量的数据,这要慢得多)?

//伪代码。如果超过几千行,则运行缓慢。 foreach(myDGV 中的 DataGridViewRow 行) { row.Visible = CalculateFilter(row); }

【问题讨论】:

  • 为什么必须手动生成行?如果这是一个深层次的架构问题,可能没有好的或快速的答案。
  • 没有提供数据源的数据库。数据后端使用 xml 序列化将数据记录存储在“伪表”文件中,并将显示值(来自多个文件的组合)作为结构的 List 传递出去,每个结构都包含单个 DGV 行的数据。
  • 感谢您指出。我可能不会意识到这一点,直到为时已晚。

标签: c# datagridview filter


【解决方案1】:

几年前我遇到过这个问题(在我知道数据绑定之前),并在 Microsoft 发现了一个错误帖子,说这已得到确认,但问题可能不会得到解决。

但是,有几种可能解决这个问题。

  1. 向datagridview添加行,向datatable添加行并将其绑定到datagridview。

    DataTable table = new DataTable();
    table.Columns.Add("Name", typeof(String));
    table.Columns.Add("...", typeof(String));
    
    foreach (var element in list)
       table.Rows.Add(element.Name, element.Something);
    
    dataGridView1.DataSource = table1;
    table.DefaultView.RowFilter = "Name Like '...'";
    
  2. 创建一个继承自 BindingList 并实现 IBindingList 的类。然后将其绑定到您的 DataGridView。

  3. 将 DataGridView VirtualMode 设置为 true。

方法二比较复杂,因为要实现FindCore方法,需要自己添加逻辑。

你应该看看这里:http://social.msdn.microsoft.com/Forums/en-US/winformsdatacontrols/thread/68c8b93e-d273-4289-b2b0-0e9ea644623a

【讨论】:

  • Method1 看起来可能是最好的方法。不幸的是,在这个开发周期中这样做可能为时已晚。 msdn 线程 Suspend/ResumeLayout 中的一个“廉价”修复没有任何明显的性能提升。我的假设是,虽然没有解决根本问题,但 MS 还是很友好地添加了某种更改缓存布局暂停,以限制我的无知可能造成的损害。
【解决方案2】:

如果您在过滤时暂时从 dataGridView 中删除行,整体性能应该会大大提高。

  1. 创建一个 Windows 窗体应用程序
  2. 将一个 DataGridView 和四个按钮拖放到表单中
  3. 复制并粘贴此代码(不要忘记为按钮事件添加事件处理程序)

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    
        private Stopwatch watch = new Stopwatch();
        private void Form1_Load(object sender, EventArgs e)
        {
            // populate dataGridView
            for (int i = 0; i < 10000; i++)
                dataGridView1.Rows.Add("Column", i+1, 10000 - i);
    
            for (int i = 0; i < 10000; i = i + 2)
                dataGridView1.Rows[i].DefaultCellStyle.BackColor = Color.Red;
    
        }
    
        // remove filter
        private void button1_Click(object sender, EventArgs e)
        {
            watch.Reset();
            watch.Start();
    
            foreach (DataGridViewRow row in dataGridView1.Rows)
                row.Visible = true;
    
    
            watch.Stop();
            MessageBox.Show(watch.ElapsedMilliseconds.ToString());
        }
    
        // add filter (hide all odd rows)
        private void button2_Click(object sender, EventArgs e)
        {
            watch.Reset();
            watch.Start();
    
            foreach (DataGridViewRow row in dataGridView1.Rows)
            {
                if (Convert.ToInt32(row.Cells[1].Value) % 2 != 0)
                    row.Visible = false;
            }
    
            watch.Stop();
            MessageBox.Show(watch.ElapsedMilliseconds.ToString());
        }
    
        // remove filter (improved)
        private void button3_Click(object sender, EventArgs e)
        {
            watch.Reset();
            watch.Start();
    
            List<DataGridViewRow> rows = new List<DataGridViewRow>();
            foreach (DataGridViewRow row in dataGridView1.Rows)
            {
                rows.Add(row);
            }
    
            dataGridView1.Rows.Clear();
    
            foreach (DataGridViewRow row in rows)
                row.Visible = true;
    
            dataGridView1.Rows.AddRange(rows.ToArray());
    
            watch.Stop();
            MessageBox.Show(watch.ElapsedMilliseconds.ToString());
        }
    
        // add filer (improved)
        private void button4_Click(object sender, EventArgs e)
        {
            watch.Reset();
            watch.Start();
    
            List<DataGridViewRow> rows = new List<DataGridViewRow>();
            foreach (DataGridViewRow row in dataGridView1.Rows)
            {
                rows.Add(row);
            }
    
            dataGridView1.Rows.Clear();
    
            foreach (DataGridViewRow row in rows)
            {
                if (Convert.ToInt32(row.Cells[1].Value) % 2 != 0)
                {
                    row.Visible = false;
                }
            }
    
            dataGridView1.Rows.AddRange(rows.ToArray());
    
            watch.Stop();
            MessageBox.Show(watch.ElapsedMilliseconds.ToString());
        }
    }
    

【讨论】:

  • 那行得通。该方法还加快了初始网格加载过程。
  • 我很惊讶这在性能上有多大的差异:在我的机器上是 19 秒对 0.1 秒。帮了我很多。谢谢。
  • 确实,我还没有验证这一点,但是今天我假设在更改之前调用dataGridView.SuspendLayout();dataGridView.ResumeLayout(true); 可能会产生相同的效果(我喜欢包装这种代码进入x.SuspendLayout(); try { ... } finally { x.ResumeLayout(); } 块,否则如果出现故障,您的 dataGridView 将不再更新 ui。
【解决方案3】:
  1. 在代码中的某处添加这些扩展方法:

    [DllImport("user32.dll", EntryPoint = "SendMessageA", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)] 私有静态外部 int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam); 私有常量 int WM_SETREDRAW = 0xB;

    public static void SuspendDrawing(this Control target)
    {
        SendMessage(target.Handle, WM_SETREDRAW, 0, 0);
    }
    
    public static void ResumeDrawing(this Control target) { ResumeDrawing(target, true); }
    public static void ResumeDrawing(this Control target, bool redraw)
    {
        SendMessage(target.Handle, WM_SETREDRAW, 1, 0);
    
        if (redraw)
        {
            target.Refresh();
        }
    }
    
  2. 现在在循环之前添加myDGV.SuspendDrawing(),然后在循环之后添加myDGV.ResumeDrawing()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-10
    相关资源
    最近更新 更多