使用大量项目填充组合框

Posted

技术标签:

【中文标题】使用大量项目填充组合框【英文标题】:Populating Combobox with a large amount of items 【发布时间】:2021-06-28 20:15:14 【问题描述】:

我希望能从 *** 上的各位好人那里得到一些帮助/指导。我正在将所有内容从 vba 宏项目转移到 C# word interop add in。在大多数情况下,转换都很好,如果有点冗长,因为我想知道如何在 c# 中实现什么是vba中的1或2衬里。我的 vba 项目的一部分有一些形式,其中一些具有从数据库服务器加载数据的组合框(目前有 14k+ 计数)。该数据通过以下代码填充:

Private Sub IndividualsLoad(Optional vCompany As Variant)
'load the individuals
'if the company is provided, list the individuals in that company
  
  
  Dim sSql As String
  Dim sCompany As String
  Dim rsData As Recordset
  Dim sOldValue As String
  
  If (IsMissing(vCompany)) Then
    sSql = "SELECT DISTINCT LastName,FirstName " & _
           "FROM tblIndividuals " & _
           "ORDER BY LastName, FirstName;"
  Else
    sCompany = SQLSanatize(CStr(vCompany))
    
    sSql = "SELECT DISTINCT LastName, FirstName " & _
           "FROM tblIndividuals " & _
           "WHERE CompanyName = '" + sCompany + "' " & _
           "ORDER BY LastName, FirstName"

  End If
 
  Set rsData = New ADODB.Recordset
  rsData.Open sSql, gdb, adOpenForwardOnly
  
  sOldValue = cboIndividuals
  
  cboIndividuals.Clear
  
  Do Until rsData.EOF
    cboIndividuals.AddItem rsData("LastName") & ", " & rsData("FirstName")
    rsData.MoveNext
  Loop
  
  rsData.Close
  Set rsData = Nothing
  
  cboIndividuals = sOldValue
  
End Sub

该函数在表单加载时调用,当从 Word 调用时,表单加载速度非常快(不到 2 秒)。

在 C# winforms 中做同样的事情要慢得多(快到大约 10 秒 [密西西比州],只要一分半钟,我是否将填充个人功能保留在 Form_Load或 Form_Shown 事件)。

我尝试了各种方法来加快梳理其他人的堆栈溢出问题的答案。我尝试直接从 SQL 服务器从数据适配器填充数据集,并将其作为组合框的数据源:

        public void PopulateIndividualList()
        
            /*start test to see if we can speed up form loading a bit*/
            DataSet ds = new DataSet();

            using (SqlConnection conn = new SqlConnection(Globals.ThisAddIn.GenDBConnectionString()))
            
                string qry = GenFileNumbersLoadQry();
                SqlDataAdapter da = new SqlDataAdapter(qry, conn);
                conn.Open();
                
                da.Fill(ds, "Individuals");
            
        cboIndividuals.DataSource = ds;
        

我尝试过异步填充绑定列表:

        public static async Task<List<Individual>> IndividualList(string sSQL)
        
            var list = new List<Individual>();

            using (var cn = new SqlConnection  ConnectionString = Globals.ThisAddIn.GenDBConnectionString() )
            
                using (var cmd = new SqlCommand  Connection = cn )
                
                    cmd.CommandText = sSQL;

                    await cn.OpenAsync();

                    var reader = cmd.ExecuteReader();

                    while (reader.Read())
                    
                        list.Add(new Individual()  FirstName = reader.GetString(1), LastName = reader.GetString(0) );
                    
                
            

            return list;
        

BindingList<Individual> individuals = new BindingList<Individual>(await ServerOps.IndividualList(qry));
cboIndividuals.DataSource = individuals;

我尝试在创建表单之前填充绑定列表,并在构造函数中将其传递给表单并将列表分配为 Form_Load 或 Form_Shown 事件中的数据源。

我已经尝试像上面的 vba 代码那样在 while reader.Read() 循环中添加项目:

        private void IndividualsLoad()
        
            string qry = GenIndividualsLoadQry();

            string connString = Globals.ThisAddIn.GenDBConnectionString();

            using (SqlConnection connection = new SqlConnection(connString))
            
                using (SqlCommand command = connection.CreateCommand())
                

                    command.CommandText = qry;

                   connection.Open();

                    using (SqlDataReader reader = command.ExecuteReader())
                    
                        while (reader.Read())
                        
                            cboIndividuals.Items.Add(reader["FullName"]);
                        
                    
                
            
        

我尝试通过 Combobox.AddRange() 添加项目:

cboIndividuals.Items.AddRange(individuals.ToArray());

我尝试使用 user32.dll 中的 SendMessage(根据this question 上的答案之一,与 additem() 相比,它的速度提高了 60%:

        private const long CB_ERR = -1;
        private const uint CB_ADDSTRING = 0x143;
        private const uint CB_RESETCONTENT = 0x14B;
        private const long CB_SETITEMDATA = 0x151;

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern IntPtr SendMessage(
            IntPtr hWnd,
            uint Msg,
            UIntPtr wParam,
            string lParam
            );

        private void AddItem(ComboBox cmb, string txt)
        
            SendMessage(cmb.Handle, CB_ADDSTRING, UIntPtr.Zero, txt);
        

我的头发早就掉了,但我很惊讶 vba 表单在同一件事上的速度这么快

根据我的研究,将项目填充到组合框中似乎肯定是开销(而不是从 SQL 服务器中拉下项目),这让我假设 c# 组合框的无数高级功能与vba 组合框是造成差异的原因。我只是设置了一个自定义的自动完成源:

            cboIndividuals.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            cboIndividuals.AutoCompleteSource = AutoCompleteSource.CustomSource;
            AutoCompleteStringCollection combData = new AutoCompleteStringCollection();
            getData(combData);
            cboIndividuals.AutoCompleteCustomSource = combData;

如果我这样做,至少最终用户可以输入他们正在搜索的人,并且表单会自动建议/完成输入,最重要的是打开速度与 vba 版本一样快,但我知道这种变化对于我的最终用户来说,这将是超级难以接受的,如果我要以牺牲加载速度为代价来保留下拉功能,我将很难接受“让我们使用这个较慢的版本”的管理.

有没有人有任何想法,我没有尝试用大量项目填充组合框,或者可能是一个简单的 vba-ish 组合框而没有 c# 的开销。或者,完全是另一个想法,因为我现在离这个太近了。任何帮助/想法/建议将不胜感激。提前感谢您的宝贵时间。

【问题讨论】:

- 大多数 UI 控件在大约 20 个项目后变得笨拙甚至无法使用。提供搜索/替代 UI 此时计数 14k+ - 对于组合框来说太荒谬了 你在 14k 时失去了我。即使它是高性能的,它也不能被人使用。 你不能在用户至少输入后查询你的数据库,例如。 3 个字符来避免 14k+ 条目?带有计时器,以及适合您需求的某种搜索逻辑。因此它不会在每次按键时触发查询,并且会缩小您的列表。 相信我,我看到每个人都因为试图在组合框中显然有超过 10 个项目而让人们大发雷霆。不过,让我对这些“项目太多”的 cmets 感到困扰的是 vba 版本可以处理它-很好-。显然,这项技术并不过分。撇开哲学不谈,组合框的使用有 2% 的时间是用户找不到他们正在寻找的人,因为他们记错了他们的名字的一部分等。使用上面我的自定义自动完成解决方案,他们失去了通过下拉菜单进行狩猎的功能。 【参考方案1】:

如果您需要让用户浏览所有 14k 项,那么这不是一个好的解决方案,但我提到的是这样的。 (尚未正确阐述,但可能是一个好的开始)

using System;
using System.Windows.Forms;

namespace ComboBox

    public partial class Form1 : Form
    
        private Timer _timer;
        public Form1()
        
            InitializeComponent();
            InitTimer();
        

        private void InitTimer()
        
            _timer = new();
            _timer.Interval = 700;
            _timer.Tick += OnTypeTimerTicked;
        

        private void OnTypeTimerTicked(object sender, EventArgs e)
        
            _timer.Stop();

            //BeginUpdate-EndUpdate helps to gain some speed
            comboBox1.BeginUpdate();
    
            //here you can reach out to your repo and probably use AddRange instead of Add
            //here you have to filter the results according to your search logic
            //and populate the combobox
            comboBox1.Items.Add("Item 1");
            comboBox1.Items.Add("Item 2");
            comboBox1.Items.Add("Item 3");
            comboBox1.Items.Add("Item 4");
            comboBox1.Items.Add("Item 5");
            comboBox1.Items.Add("Item 6");
            comboBox1.Items.Add("Item 7");
            comboBox1.Items.Add("Item 8");
            comboBox1.Items.Add("Item 9");
            comboBox1.Items.Add("Item 10");

            comboBox1.EndUpdate();
        

        private void comboBox1_TextChanged(object sender, EventArgs e)
        
            _timer.Start();
        

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        
            try
            
                //your code here
            
            finally
            
                //maybe it's not the right place for your needs but it must be done somewhere to prevent memory leaks
                _timer.Tick -= OnTypeTimerTicked;
            
        
    

关于组合框的更多细节:它有一个AutocompleteMode 属性。您必须将其设置为 None 以外的值 - 在设计器或代码中。

【讨论】:

以上是关于使用大量项目填充组合框的主要内容,如果未能解决你的问题,请参考以下文章

使用工具提示 WPF 在数据网格中填充组合框

使用组合列访问数据库填充组合框

C#:用 CSV 中的单列填充组合框

如何从 Java 的组合框中删除特定项目?

使用 php 从组合框插入

使用组合框过滤记录并填充第二个组合框