2010-01-04 6 views
0

Je ne trouve pas de méthode directe pour implémenter le filtrage de l'entrée de texte dans une liste d'éléments dans une liste déroulante WPF.
En définissant IsTextSearchEnabled sur true, la liste déroulante comboBox passera à la valeur du premier élément correspondant. Ce dont j'ai besoin, c'est que la liste soit filtrée pour correspondre à la chaîne de texte (par exemple, si je me concentre sur ma liste déroulante et que je tape 'abc', j'aimerais voir tous les éléments de la collection ItemsSource qui commencent par) 'abc' en tant que membres de la liste déroulante).Filtre dynamique de WPF combobox basé sur l'entrée de texte

Je doute que cela fait une différence, mais mon élément d'affichage est templated à une propriété d'un type complexe:

<ComboBox x:Name="DiagnosisComboBox" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3" 
      ItemsSource="{Binding Path = ApacheDxList, 
           UpdateSourceTrigger=PropertyChanged, 
           Mode=OneWay}" 
      IsTextSearchEnabled="True" 
      ItemTemplate="{StaticResource DxDescriptionTemplate}" 
      SelectedValue="{Binding Path = SelectedEncounterDetails.Diagnosis, 
            Mode=TwoWay, 
            UpdateSourceTrigger=PropertyChanged}"/> 

Merci.

Répondre

-1

Il semble que ce que vous recherchez soit quelque chose de similaire à une zone de texte auto-complétée, qui fournit des suggestions d'achèvement dans une fenêtre contextuelle similaire à une fenêtre contextuelle.

Vous pourriez trouver cet article CodeProject utile:

A Reusable WPF Autocomplete TextBox

+0

Cela semble assez cool, peut-être que je ne l'implémente pas encore correctement. J'ai besoin que toute la liste soit disponible s'ils ne veulent pas utiliser la fonctionnalité de recherche. Comment sélectionner une liste complète? Aussi, bien que je puisse lier l'ItemsSource à votre contrôle (en utilisant la liaison dans mon exemple ci-dessus), je ne peux pas réellement sélectionner quelque chose dans la liste ... la zone de texte est toujours vide. Enfin, ce contrôle est central à un groupe de contrôles. J'ai besoin de SelectedItem dans les contrôles adjacents, comme dans: SelectedValue = "{Binding SelectedItem.SomeOtherProperty, ElementName = DiagnosisComboBox ...}" /> – Bob

+1

Vérifiez à nouveau l'article, il fournit tout ce dont vous avez besoin. Si vous souhaitez rendre toute la liste disponible, effacez la propriété 'MaxCompletions' et demandez à votre prédicat de filtre de renvoyer true. Pour activer réellement la sélection d'un élément de la liste, vous devez définir la propriété 'Binding' sur l'une des propriétés de vos objets de données dans la liste. –

3

Je viens de faire ce quelques jours à l'aide il y a une version modifiée du code de ce site: Credit where credit is due

Mon code complet ci-dessous:

using System.Collections; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Input; 

    namespace MyControls 
    { 
     public class FilteredComboBox : ComboBox 
     { 
      private string oldFilter = string.Empty; 

      private string currentFilter = string.Empty; 

      protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox; 


      protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) 
      { 
       if (newValue != null) 
       { 
        var view = CollectionViewSource.GetDefaultView(newValue); 
        view.Filter += FilterItem; 
       } 

       if (oldValue != null) 
       { 
        var view = CollectionViewSource.GetDefaultView(oldValue); 
        if (view != null) view.Filter -= FilterItem; 
       } 

       base.OnItemsSourceChanged(oldValue, newValue); 
      } 

      protected override void OnPreviewKeyDown(KeyEventArgs e) 
      { 
       switch (e.Key) 
       { 
        case Key.Tab: 
        case Key.Enter: 
         IsDropDownOpen = false; 
         break; 
        case Key.Escape: 
         IsDropDownOpen = false; 
         SelectedIndex = -1; 
         Text = currentFilter; 
         break; 
        default: 
         if (e.Key == Key.Down) IsDropDownOpen = true; 

         base.OnPreviewKeyDown(e); 
         break; 
       } 

       // Cache text 
       oldFilter = Text; 
      } 

      protected override void OnKeyUp(KeyEventArgs e) 
      { 
       switch (e.Key) 
       { 
        case Key.Up: 
        case Key.Down: 
         break; 
        case Key.Tab: 
        case Key.Enter: 

         ClearFilter(); 
         break; 
        default: 
         if (Text != oldFilter) 
         { 
          RefreshFilter(); 
          IsDropDownOpen = true; 

          EditableTextBox.SelectionStart = int.MaxValue; 
         } 

         base.OnKeyUp(e); 
         currentFilter = Text; 
         break; 
       } 
      } 

      protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) 
      { 
       ClearFilter(); 
       var temp = SelectedIndex; 
       SelectedIndex = -1; 
       Text = string.Empty; 
       SelectedIndex = temp; 
       base.OnPreviewLostKeyboardFocus(e); 
      } 

      private void RefreshFilter() 
      { 
       if (ItemsSource == null) return; 

       var view = CollectionViewSource.GetDefaultView(ItemsSource); 
       view.Refresh(); 
      } 

      private void ClearFilter() 
      { 
       currentFilter = string.Empty; 
       RefreshFilter(); 
      } 

      private bool FilterItem(object value) 
      { 
       if (value == null) return false; 
       if (Text.Length == 0) return true; 

       return value.ToString().ToLower().Contains(Text.ToLower()); 
      } 
     } 
    } 

Et le WPF devrait être quelque chose comme ceci:

<MyControls:FilteredComboBox ItemsSource="{Binding MyItemsSource}" 
    SelectedItem="{Binding MySelectedItem}" 
    DisplayMemberPath="Name" 
    IsEditable="True" 
    IsTextSearchEnabled="False" 
    StaysOpenOnEdit="True"> 

    <MyControls:FilteredComboBox.ItemsPanel> 
     <ItemsPanelTemplate> 
      <VirtualizingStackPanel VirtualizationMode="Recycling" /> 
     </ItemsPanelTemplate> 
    </MyControls:FilteredComboBox.ItemsPanel> 
</MyControls:FilteredComboBox> 

Quelques points à noter ici. Vous remarquerez que l'implémentation de FilterItem effectue un ToString() sur l'objet. Cela signifie que la propriété de l'objet que vous souhaitez afficher doit être renvoyée dans votre implémentation object.ToString(). (Ou être une chaîne déjà) En d'autres termes quelque chose comme ceci:

public class Customer 
{ 
    public string Name { get; set; } 
    public string Address { get; set; } 
    public string PhoneNumber { get; set; } 

    public override string ToString() 
    { 
     return Name; 
    } 
} 

Si cela ne fonctionne pas à vos besoins, je suppose que vous pourriez obtenir la valeur de DisplayMemberPath et de réflexion d'utilisation pour obtenir la propriété de l'utiliser, mais ce serait plus lent, donc je ne recommanderais pas de le faire à moins que nécessaire.

Cette implémentation n'empêche PAS l'utilisateur de taper ce qu'il veut dans la partie TextBox du ComboBox. S'ils tapent quelque chose de stupide, le SelectedItem reviendra à NULL, alors préparez-vous à gérer cela dans votre code.

Aussi, si vous avez beaucoup d'articles que je recommande fortement d'utiliser VirtualizingStackPanel comme mon exemple ci-dessus comme il fait une différence dans le temps de chargement

0

réponse de Kelly est grande. Cependant, il existe un petit bogue qui si vous sélectionnez un élément dans la liste (en surbrillance le texte d'entrée) puis appuyez sur BackSpace, le texte d'entrée reviendra à l'élément sélectionné et la propriété SelectedItem du ComboBox est toujours l'élément que vous avez sélectionné précédemment.

Vous trouverez ci-dessous le code permettant de corriger le bogue et d'ajouter la possibilité de sélectionner automatiquement l'élément lorsque le texte saisi correspond.

using System.Collections; 
using System.Diagnostics; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Input; 

namespace MyControls 
{ 
    public class FilteredComboBox : ComboBox 
    { 
     private string oldFilter = string.Empty; 

     private string currentFilter = string.Empty; 

     protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox; 


     protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) 
     { 
      if (newValue != null) 
      { 
       var view = CollectionViewSource.GetDefaultView(newValue); 
       view.Filter += FilterItem; 
      } 

      if (oldValue != null) 
      { 
       var view = CollectionViewSource.GetDefaultView(oldValue); 
       if (view != null) view.Filter -= FilterItem; 
      } 

      base.OnItemsSourceChanged(oldValue, newValue); 
     } 

     protected override void OnPreviewKeyDown(KeyEventArgs e) 
     { 
      switch (e.Key) 
      { 
       case Key.Tab: 
       case Key.Enter: 
        IsDropDownOpen = false; 
        break; 
       case Key.Escape: 
        IsDropDownOpen = false; 
        SelectedIndex = -1; 
        Text = currentFilter; 
        break; 
       default: 
        if (e.Key == Key.Down) IsDropDownOpen = true; 

        base.OnPreviewKeyDown(e); 
        break; 
      } 

      // Cache text 
      oldFilter = Text; 
     } 

     protected override void OnKeyUp(KeyEventArgs e) 
     { 
      switch (e.Key) 
      { 
       case Key.Up: 
       case Key.Down: 
        break; 
       case Key.Tab: 
       case Key.Enter: 

        ClearFilter(); 
        break; 
       default:           
        if (Text != oldFilter) 
        { 
         var temp = Text; 
         RefreshFilter(); //RefreshFilter will change Text property 
         Text = temp; 

         if (SelectedIndex != -1 && Text != Items[SelectedIndex].ToString()) 
         { 
          SelectedIndex = -1; //Clear selection. This line will also clear Text property 
          Text = temp; 
         } 


         IsDropDownOpen = true; 

         EditableTextBox.SelectionStart = int.MaxValue; 
        } 

        //automatically select the item when the input text matches it 
        for (int i = 0; i < Items.Count; i++) 
        { 
         if (Text == Items[i].ToString()) 
          SelectedIndex = i; 
        } 

        base.OnKeyUp(e);      
        currentFilter = Text;      
        break; 
      } 
     } 

     protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) 
     { 
      ClearFilter(); 
      var temp = SelectedIndex; 
      SelectedIndex = -1; 
      Text = string.Empty; 
      SelectedIndex = temp; 
      base.OnPreviewLostKeyboardFocus(e); 
     } 

     private void RefreshFilter() 
     { 
      if (ItemsSource == null) return; 

      var view = CollectionViewSource.GetDefaultView(ItemsSource); 
      view.Refresh(); 
     } 

     private void ClearFilter() 
     { 
      currentFilter = string.Empty; 
      RefreshFilter(); 
     } 

     private bool FilterItem(object value) 
     { 
      if (value == null) return false; 
      if (Text.Length == 0) return true; 

      return value.ToString().ToLower().Contains(Text.ToLower()); 
     } 
    } 
} 
Questions connexes