当用户在文本框中输入时,提供即时搜索功能的最佳方式是什么?

本文关键字:功能 搜索 最佳 是什么 方式 文本 用户 输入 | 更新日期: 2023-09-27 18:11:03

我希望用户通过姓名或员工编号搜索员工。我提供了一个文本框。因此,当用户在文本框中输入时,我处理_TextChanged事件并使用员工列表更新dataGridview,其中员工名称或员工编号包含用户在文本框中输入的文本。

我遇到的问题是,这减慢了输入和datagridview更新,因为每次文本框中的文本变化,我的搜索查询命中数据库。这使得表单在某种程度上缺乏响应性。

有人知道更好的方法吗?

当用户在文本框中输入时,提供即时搜索功能的最佳方式是什么?

  • Simple:延迟查询db半秒或一秒,以确保用户已经停止输入,记住一些文本的最后一次更改。
  • 长期更好:如果db查询需要很长时间(超过一秒),那么您可以将查询db外包给另一个线程(任务或后台工作线程)。你甚至可以填满DataGrid在自己的Task中,如果数据太多,需要很长时间来绘制。你也可以实现一些取消机制,你就有了你的主GUI元素保持响应。

我想到了下面的democode:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication19
{
    public partial class Form1 : Form
    {
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        public Form1()
        {
            InitializeComponent();
        }
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            // cancel old query and datagrid update
            tokenSource.Cancel();
            tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;
            Task.Factory.StartNew((s) =>
                {
                    var q = Task.Factory.StartNew<IEnumerable<DemoData>>(() => LongLastingDataQuery(textBox1.Text, token), token);
                    if (!token.IsCancellationRequested)
                        Task.Factory.StartNew(() => BindData(q.Result));
                }, token);
        }
        private IEnumerable<DemoData> LongLastingDataQuery(string search, CancellationToken token)
        {
            List<DemoData> l = new List<DemoData>();
            for (int i = 0; i < 10000 * search.Length; i++)
            {
                if (token.IsCancellationRequested)
                    return l;
                l.Add(new DemoData { ID = i, Text = search + i, Text1 = search + i + i, Text2 = search + i + i + i, Text3 = search + i + i + i + i });
            }
            Thread.Sleep(1000);
            return l;
        }
        private void BindData(IEnumerable<DemoData> enumerable)
        {
            if (dataGridView1.InvokeRequired)
                dataGridView1.Invoke(new MethodInvoker(() => BindData(enumerable)));
            else
            {
                demoDataBindingSource.DataSource = null;
                demoDataBindingSource.DataSource = enumerable;
            }
        }
        public class DemoData
        {
            public string Text { get; set; }
            public string Text1 { get; set; }
            public string Text2 { get; set; }
            public string Text3 { get; set; }
            public int ID { get; set; }
        }
    }
}

我有两个建议:

  1. 处理"TextBox enter KeyPressdown"事件而不是TextChange事件,因此该事件仅在您键入查询后按下"Return"键时触发。示例代码如下:

    private void searchTextBox_KeyPress(object sender, KeyPressEventArgs e)
    {
        if (e.KeyChar == (char)Keys.Return)
        {
            // DoQueryAndRebindEmployees(searchTextBox.Text);
        }
    }
    
  2. 如果你坚持处理TextChange事件,把数据库搜索代码放在另一个线程中,比如在一个BackgroundWorker线程中,在TextChange事件处理程序中:

     BackgroundWorker employeeQueryWorker = new BackgroundWorker();
        employeeQueryWorker.DoWork += new DoWorkEventHandler(employeeQueryWorker_DoWork);
        employeeQueryWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(employeeQueryWorker_RunWorkerCompleted);
        employeeQueryWorker.RunWorkerAsync(searchTextBox.Text);
    

为了在后台线程和UI线程之间通信,您可以定义类的成员变量来表示员工查询结果,如

 private IList<Employee>  m_employeeQueryResult = null;

然后在employeeQueryWorker_DoWork(object sender, DoWorkEventArgs e)线程方法中,执行耗时的数据库查询,并将查询结果存储到m_employeeQueryResult中。

最后,在employeeQueryWorker_RunWorkerCompleted()方法中,将m_employeeQueryResult与DataGridView控件绑定。