通过键盘“预先输入”搜索在WPF列表框中选择项目

时间:2010-11-17 22:23:08

标签: wpf listbox selection typeahead

我有一个WPF Listbox控件,我想允许用户使用预先输入更改所选项目。我正在寻找的行为与Windows资源管理器完全一样。当您继续键入文件夹名称的文本时,列表将继续选择更正确的项目。

例如假设这个文件夹结构:

OtherFolderName
MyFirstFolder
MyFirstFileFolder
MyFirstList

如果您使用鼠标选择OtherFolderName,则开始输入MyFirstF将选择项MyFirstFolder,但如果您继续键入MyFirstFiMyFirstFileFolder将被选中。

我的WPF列表框没有表现出这种行为,我希望我可以轻松启用它,因为旧的WinForms列表框就是这样做的。

2 个答案:

答案 0 :(得分:7)

查看TextSearch类,特别是TextSearch.TextPath附加属性:

<ListBox TextSearch.TextPath="FolderName" ... />

TextSearch.TextPath属性启用文本搜索,并指定如何从每个项目中提取搜索文本。在这种情况下,我假设每个Folder对象都有一个名为“FolderName”的属性。

如果这不能满足您的所有需求,您可能必须实施自己的搜索,因为TextSearch功能不是特别可调。要做到这一点:

  1. 处理TextInput事件
  2. 比较当前TextInput与先前TextInput的时间。如果足够接近,则附加到前缀字符串,否则将其设置为键入的单个字符。
  3. 搜索所有项目以获取给定的前缀&amp;如果找到set SelectedItem。
  4. 我会使用附加属性将其构建为一个单独的类,类似于内置的TextSearch类。

答案 1 :(得分:0)

我使用一个隐藏的TextBox,在人输入时会短暂显示,并在几秒后重置并清除,以便在计时器到期后不会尝试匹配其内容。此人将键入ListBox,并且由于KeyUp上的绑定,其SearchText事件将填充TextBox。填充SearchText时,会触发MyFilteredItems()以执行该文本与ListBox之间的匹配。然后,如果此人按Enter键,则选择将进入另一个TextBox(未在XAML中列出,但在代码中注释掉)并从lstPickList中清除。然后清除TextBox并重置计时器。

XAML:

<TextBox Name="txtPicker" IsReadOnly="True" Foreground="LightGreen" FontFamily="Consolas" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"></TextBox>

<ListBox Name="lstPickList" Grid.Row="1" ItemsSource="{Binding MyFilteredItems}" KeyUp="lstPickList_KeyUp"></ListBox>

然后这是相关的代码隐藏:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private Timer t = new Timer();    
    public System.Windows.Threading.DispatcherTimer tCleanup =
         new System.Windows.Threading.DispatcherTimer();

    private string _searchText; 
    public string SearchText
    {
        get { return _searchText; }
        set
        {
            _searchText = value;

            OnPropertyChanged("SearchText");
            OnPropertyChanged("MyFilteredItems");
        }
    }

    public List<string> MyItems { get; set; }        

    public IEnumerable<string> MyFilteredItems
    {
        get
        {
            if (SearchText == null) return MyItems;

            return MyItems.Where(x => x.ToUpper().StartsWith(SearchText.ToUpper()));
        }            
    }


    public MainWindow()
    {
        InitializeComponent();

        MyItems = new List<string>() { "ABC", "DEF", "GHI" };                      
        this.DataContext = this;

        t.Interval = 1000;
        t.Elapsed += new ElapsedEventHandler(timerCounter);
        tCleanup.Interval = new TimeSpan(0,0,1);
        tCleanup.Tick += new EventHandler(cleanupCounter_Tick);        
        txtPicker.Visibility = Visibility.Collapsed;
        tCleanup.Start();
    }
    private static int counter = 0;
    protected void timerCounter(object sender, ElaspedEventArgs e)
    {
        counter++;   
    }

   protected void cleanupCounter_Tick(object sender, EventArgs e)
   {
        if (counter > 2 && txtPicker.Visibility == Visibility.Visible)
            txtPicker.Visibility = Visibility.Collapsed;   
   }

   private void lstPickList_KeyUp(object sender, KeyEventArgs e)
   {
       ListBox lst = (ListBox)sender;
       string strg = Convert.ToString(e.Key.ToString().Replace("D",""));
       if (counter < 2)
       {
           txtPicker.Visibility = Visibility.Visible;
           t.Start();
           if (strg == "Return")
           {
                txtPicker.Text += "{Enter}";
                SearchText += "{Enter}";
           }
           else
           {
               txtPicker.Text += strg;
               SearchText += strg;
           }
      }
      else
      {
          SearchText = strg;
          txtPicker.Text = strg;
          t.Stop();
          counter = 0;
          t.Start();
       }

       if (strg == "Return")
       {
           // This next line would be if you had a "selected items" ListBox to store the item
           // lstSelectedList.Items.Add(lstPickList.SelectedItem);
           lstPickList.Items.Remove(lstPickList.SelectedItem);
           t.Stop();
           txtPicker.Visibility = Visibility.Collapsed;
           counter = 0;
           txtPicker.Text = String.Empty;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}