【问题标题】:How to fill ComboBox items when text in the same combobox changes当同一组合框中的文本更改时如何填充组合框项目
【发布时间】:2020-03-04 10:25:15
【问题描述】:

我正在尝试对组合框实施 textchanges 事件,以便项目根据在同一组合框中输入的值进行更改。

这是我的 XAML

<ComboBox Name="ComboBoxRoleNameDescEdit" IsEditable="True" TextBoxBase.TextChanged="ComboBoxRoleNameDescEdit_TextChanged"/>

这是代码隐藏:

private void ComboBoxRoleNameDescEdit_TextChanged(object sender, TextChangedEventArgs e)
{
    try
    {
        ComboBoxRoleNameDescEdit.Items.Clear();
        using (var Connect = new SqlConnection(connstr))
        {
            Connect.Open();
            using (var Command = new SqlCommand("[dbo].[spParametresRolesTb_FillRoleIdComboBox]", Connect))
            {
                Command.CommandType = CommandType.StoredProcedure;
                Command.Parameters.Add("@search", SqlDbType.VarChar).Value = ComboBoxRoleNameDescEdit.Text;
                Command.Parameters.Add("@entity_id", SqlDbType.VarChar).Value = LoggedInData.LoggedInstitutionId;
                SqlDataReader dr = Command.ExecuteReader();
                while (dr.Read())
                {
                    string classes = dr.GetString(0);
                    ComboBoxRoleNameDescEdit.Items.Add(classes);
                }
                dr.Close();
            }
            Connect.Close();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }

我遇到的问题是,因为我需要在每个输入处刷新项目列表,所以由于这段代码“ComboBoxRoleNameDescEdit.Items.Clear();”,输入本身正在被清除。实际上,只有第一个输入会被自动清除,其余输入不会。

知道如何克服这个问题吗?

【问题讨论】:

  • ComboBoxRoleNameDescEdit.DataSource = null; 而不是 clear 并最终使用 ComboBoxRoleNameDescEdit.ResetText();
  • 感谢 styx,但 DataSource 和 ResetText 在 WPF 中不起作用。我猜那一定是 Winform。

标签: c# wpf xaml combobox


【解决方案1】:

首先,不要直接对ItemsControl进行操作。对源集合即数据模型进行操作。您还需要在ComboBox 上禁用文本搜索。

ViewModel.cs

class ViewModel
{
  public ObservableCollection<string> Classes { get; set; }

  public ViewModel()
  {
    this.Classes = new ObservableCollection<string>();
  }

  public async Task FilterItemsAsync(string predicate, CancellationToken cancellationToken)
  {
    using (var connection = new SqlConnection(connstr))
    {
      connection.OpenAsync(cancellationToken);
      using (var command = new SqlCommand("[dbo].[spParametresRolesTb_FillRoleIdComboBox]", connection))
      {
        command.CommandType = CommandType.StoredProcedure;

        command.Parameters.Add("@search", SqlDbType.VarChar).Value = predicate;
        command.Parameters.Add("@entity_id", SqlDbType.VarChar).Value = LoggedInData.LoggedInstitutionId;

        using (SqlDataReader dataReader = await command.ExecuteReaderAsync(cancellationToken))
        {    
          this.Classes.Clear();
          while (await dataReader.ReadAsync(cancellationToken))
          {
            string classes = dataReader.GetString(0);
            this.Classes.Add(classes);
          }
        }
      }
    }  
  }
}

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <ComboBox IsTextSearchEnabled="False" 
            IsEditable="True" 
            ItemsSource="{Binding Classes}"
            TextBoxBase.TextChanged="ComboBox_TextChanged" />
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  private DispatcherTimer DelayTimer { get; set; }
  private CancellationTokenSource CancellationTokenSource { get; set; }
  private string FilterPredicate { get; set; }
  private bool IsFilterExecuting { get; set; }

  public MainWindow()
  {
    InitializeDelayTimer();
    this.FilterPredicate = string.Empty;
  }

  private void InitializeDelayTimer()
  {
    this.DelayTimer = new DispatcherTimer();
    this.DelayTimer.Tick += FilterItemsAsync_OnTimerElapsed;
    this.DelayTimer.Interval = TimeSpan.FromMilliseconds(800);
  }

  private void ComboBox_TextChanged(object sender, TextChangedEventArgs e)
  {
    if (!(e.OriginalSource is TextBox textBox))
    {
      return;
    }

    HandleTimer();       
    TryCancelRunningFilter();
    this.FiterPredicate = textBox.Text;
  }

  private async void FilterItemsAsync_OnTimerElapsed(object sender, EventArgs e)
  {
    this.IsFilterExecuting = true;
    var viewModel = this.DataContext as ViewModel;

    try
    {
      this.CancellationTokenSource = new CancellationTokenSource();
      await viewModel.FilterItemsAsync(this.FiterPredicate, this.CancellationTokenSource.Token);
    }
    catch (OperationCanceledException) {}
    finally
    {
      this.DelayTimer.Stop();
      this.CancellationTokenSource.Dispose();
      this.IsFilterExecuting = false;
    }
  }

  private void HandleTimer()
  {
    // Start or reset the timer interval
    this.DelayTimer.Start();
  }

  private bool TryCancelRunningFilter()
  {
    if (this.IsFilterExecuting)
    {
      this.CancellationTokenSource?.Cancel();
      return true;
    }
    return false;
  }
}

您当前的实施效率不高,并且提供了糟糕的用户体验。在每次按键时,事件处理程序可能会读取大量数据,这将冻结 UI。我推荐一种异步方法。甚至可能会增加一些延迟,以便在键入单词时并非每次按键都会触发过滤器。

【讨论】:

  • 嗨@BionicCode,感谢您的输入,但代码在viewModel.FilterItems(textBox.Text); 这一行抛出异常。异常是 NullReferenceException。关于延迟:您可能是对的。您如何看待这样的实现?当输入长度超过某个值时,我是否应该只检查长度并触发整个代码?谢谢
  • 很可能抛出异常,因为this.DataContext as ViewModel 返回null。这是因为错误的类型转换或未设置DataContext。确保设置了 DataContext,例如,就像在使用 XAML 的示例中一样。输入长度不是一个好的标准,因为您永远不会提前知道输入长度。它可能是一切> 0。
  • 我更新了答案。该示例使用DispatcherTimer。它将在第一次调用 TextChanged 处理程序时开始。间隔设置为 800 毫秒(根据您的要求进行调整)。每当再次调用处理程序并且此时计时器未经过时,计时器将被重置。一旦用户完成输入,计时器将结束并执行视图模型的FilterItemsAsync() 方法。
  • 还可以考虑在 SQL 查询中使用Count 约束来将结果限制在合理的计数范围内,以缩短执行时间(使用数据虚拟化)。我还更改了您的数据库处理(请参阅ViewModel)以使用具有取消支持的异步 API。 TextChanged 处理程序现在是完全异步的。
  • 如果你学习累了,你可以使用你的原始代码。只需将ComboBox.IsTextSearchEnabled 设置为False。但正如我之前所说,您当前的实现在接受用户输入和获取更多数据方面的表现会很差。您应该让示例运行起来,然后研究它以提高您的技能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-06
  • 1970-01-01
  • 1970-01-01
  • 2012-02-15
相关资源
最近更新 更多