web-dev-qa-db-fra.com

Comment modifier dynamiquement des entrées à saisie automatique dans une liste déroulante ou une zone de texte C #?

J'ai une liste déroulante en C # et je souhaite utiliser les suggestions de saisie automatique, mais je souhaite pouvoir modifier les entrées de saisie automatique au fur et à mesure que l'utilisateur tape, car les entrées valides possibles sont beaucoup trop nombreuses pour renseigner la AutoCompleteStringCollection au démarrage.

Par exemple, supposons que je laisse l'utilisateur taper un nom. J'ai une liste de prénoms possibles ("Joe", "John") et une liste de noms de famille ("Bloggs", "Smith"), mais si j'en ai mille, alors ce serait un million de chaînes possibles - trop pour mettre dans les entrées de complétion automatique. Donc, au départ, je veux avoir uniquement les prénoms sous forme de suggestions ("Joe", "John"), puis une fois que l'utilisateur a saisi le prénom ("Joe"), je souhaite supprimer les entrées de saisie automatique existantes et les remplacer. les avec un nouvel ensemble composé du prénom choisi suivi des noms de famille possibles ("Joe Bloggs", "Joe Smith"). Pour ce faire, j'ai essayé le code suivant:

void InitializeComboBox()
{
    ComboName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    ComboName.AutoCompleteSource = AutoCompleteSource.CustomSource;
    ComboName.AutoCompleteCustomSource = new AutoCompleteStringCollection();
    ComboName.TextChanged += new EventHandler( ComboName_TextChanged );
}

void ComboName_TextChanged( object sender, EventArgs e )
{
    string text = this.ComboName.Text;
    string[] suggestions = GetNameSuggestions( text );

    this.ComboQuery.AutoCompleteCustomSource.Clear();
    this.ComboQuery.AutoCompleteCustomSource.AddRange( suggestions );
}

Cependant, cela ne fonctionne pas correctement. Il semble que l’appel de Clear () provoque la "désactivation" du mécanisme de complétion automatique jusqu’à ce que le caractère suivant apparaisse dans la liste déroulante, mais bien sûr, lorsque le caractère suivant apparaît, le code ci-dessus appelle à nouveau Clear (), de sorte que l’utilisateur voit réellement la fonctionnalité de complétion automatique. Cela entraîne également la sélection de tout le contenu de la liste déroulante. Vous devez donc désélectionner le texte existant entre chaque pression de touche, ce qui le rend inutilisable. Si je supprime l'appel de Clear (), l'auto-complétion fonctionne, mais il semble alors que l'appel AddRange() n'a pas d'effet, car les nouvelles suggestions que j'ai ajoutées n'apparaissent pas dans le menu déroulant de l'auto-complétion.

Je cherchais une solution à ce problème et j'ai vu diverses choses suggérées, mais je ne parviens pas à en faire fonctionner l'une ou l'autre - la fonctionnalité de saisie automatique semble désactivée ou de nouvelles chaînes n'apparaissent pas. Voici une liste de choses que j'ai essayées:

  • Appelez BeginUpdate() avant de changer les chaînes et EndUpdate() après.
  • Appel de Remove() sur toutes les chaînes existantes au lieu de Clear ().
  • Effacer le texte de la liste déroulante pendant la mise à jour des chaînes et le rajouter par la suite.
  • Régler la AutoCompleteMode sur "Aucune" pendant que je change les chaînes, puis la remettre sur "SuggestAppend" par la suite.
  • Déclenchement de l'événement TextUpdate ou KeyPress au lieu de TextChanged.
  • Remplacer la AutoCompleteCustomSource existante par une nouvelle AutoCompleteStringCollection à chaque fois.

Aucun de ceux-ci n'a aidé, même dans diverses combinaisons. Spence m'a suggéré d'essayer de remplacer la fonction ComboBox qui permet d'obtenir la liste des chaînes à utiliser pour la complétion automatique. En utilisant un réflecteur, j'ai trouvé quelques méthodes dans la classe ComboBox qui semblent prometteuses - GetStringsForAutoComplete() et SetAutoComplete(), mais elles sont toutes deux privées et ne peuvent donc pas y accéder à partir d'une classe dérivée. Je ne pouvais pas aller plus loin.

J'ai essayé de remplacer la ComboBox par une TextBox, parce que l'interface de complétion automatique est la même et j'ai constaté que le comportement était légèrement différent. Avec la variable TextBox, cela semble mieux fonctionner, dans la mesure où la partie Ajout de la saisie semi-automatique fonctionne correctement, mais pas la partie Suggérer, la boîte à suggestions clignote brièvement, puis disparaît immédiatement.

Je me suis donc dit: "OK, je vais vivre sans la fonctionnalité Suggérer et utiliser simplement Ajouter à la place", mais lorsque je règle la variable AutoCompleteMode sur Ajouter, une exception de violation d'accès est générée. La même chose se produit avec Suggest - le seul mode qui ne lève pas d'exceptions est SuggestAppend, même si la partie Suggérer ne se comporte pas alors correctement.

Je pensais qu'il était supposé impossible d'obtenir des exceptions de violation d'accès lors de l'utilisation de code géré C #. Avram m'a suggéré d'utiliser "lock" pour résoudre ce problème, mais je ne sais pas ce que je devrais verrouiller - la seule chose qui a un membre SyncRoot est la AutoCompleteStringCollection, et un verrouillage qui n'empêche pas les exceptions de violation d'accès. J'ai également essayé de verrouiller la ComboBox ou TextBox, mais cela n'a pas aidé non plus. Si j'ai bien compris, le verrouillage empêche uniquement les autres verrous. Ainsi, si le code sous-jacent n'utilise pas le verrouillage, mon utilisation ne fera aucune différence.

Le résultat de tout cela est que je ne peux pas utiliser actuellement une TextBox ou une ComboBox avec la complétion dynamique. Quelqu'un at-il une idée de la façon dont je pourrais y parvenir?

Mettre à jour:

Cela ne fonctionne toujours pas, mais j'en ai découvert d'autres. Peut-être qu'une partie de cela inspirera quelqu'un d'autre à proposer une solution.

J'ai essayé de remplacer la ComboBox par une TextBox, parce que l'interface de complétion automatique est la même et j'ai constaté que le comportement était légèrement différent. Avec la variable TextBox, cela semble mieux fonctionner, dans la mesure où la partie Ajout de la saisie semi-automatique fonctionne correctement, mais pas la partie Suggérer, la boîte à suggestions clignote brièvement, puis disparaît immédiatement.

Je me suis donc dit: "OK, je vais vivre sans la fonctionnalité Suggérer et utiliser simplement Append à la place", mais lorsque je règle la variable AutoCompleteMode sur Append, une exception de violation d'accès est générée. La même chose se produit avec Suggest - le seul mode qui ne lève pas d'exceptions est SuggestAppend, même si la partie Suggérer ne se comporte pas alors correctement.

Je pensais qu'il était censé être impossible d'obtenir des exceptions de violation d'accès lors de l'utilisation de code géré C #, mais de toute façon, le résultat est que je ne peux pas utiliser actuellement une TextBox ou une ComboBox avec une sorte de complétion dynamique. Quelqu'un at-il une idée de la façon dont je pourrais y parvenir?

Mise à jour 2:

Après avoir essayé diverses choses, telles que la modification de la saisie semi-automatique dans un thread de travail et l'utilisation de BeginInvoke() pour simuler le comportement de type PostMessage (), j'ai finalement abandonné et mis en place ma propre liste déroulante de saisie semi-automatique à l'aide d'une liste déroulante. C'est beaucoup plus réactif que celui intégré, et j'ai passé moins de temps à le faire qu'à essayer de le faire fonctionner, alors la leçon à tirer pour quiconque souhaite ce comportement est que vous êtes probablement mieux loti. en le mettant en œuvre.

46
Sam Hopkins

J'ai eu le même problème et j'ai trouvé une solution de contournement extrêmement simple. Comme tout le monde ici, je ne pouvais trouver aucun moyen de contrôler le comportement du composant, je devais donc l'accepter.

Le comportement naturel est le suivant: vous ne pouvez pas renseigner dynamiquement la liste chaque fois que l'utilisateur tape dans la zone de texte. Vous devez le renseigner une fois, puis le mécanisme de saisie semi-automatique prend le contrôle. La conclusion est la suivante: vous devez renseigner AutoCompleteCustomSource avec toutes les entrées possibles dans votre base de données pour le faire fonctionner à notre guise.

Bien sûr, cela n’est pas viable si vous avez des millions d’enregistrements à remplir la liste. Les problèmes de performances liés au transfert de données et au mécanisme de saisie semi-automatique ne vous permettront pas de le faire.

La solution de compromis que j'ai trouvée était la suivante: remplir dynamiquement AutoCompleteCustomSource chaque fois que la longueur du texte atteint exactement N caractères (3 dans mon cas). Cela a fonctionné parce que la complexité a été considérablement réduite. Le nombre d'enregistrements extraits de la base de données et correspondant à ces 3 caractères initiaux était suffisamment petit pour éviter tout problème de performances.

L'inconvénient majeur est que la liste de saisie semi-automatique ne sera pas présentée aux utilisateurs tant qu'ils n'auront pas saisi le N-ème caractère. Mais il semble que les utilisateurs ne s'attendent pas vraiment à une liste de saisie semi-automatique significative avant la saisie de 3 caractères.

J'espère que cela t'aides.

13
Alexandre Mafra

Cela a fonctionné pour moi, vous ne addRange pas à la même AutoCompleteStringCollection, mais en créez une nouvelle à chaque fois.

form.fileComboBox.TextChanged += (sender, e) => {
    var autoComplete = new AutoCompleteStringCollection();
    string[] items = CustomUtil.GetFileNames();
    autoComplete.AddRange(items);
    form.fileComboBox.AutoCompleteCustomSource = autoComplete;
};
2
Jaanus

Je n'ai pas testé cela, mais ça peut valoir le coup.

Au lieu d'effacer AutoCompleteCustomSource, doublez la mémoire tampon en conservant deux instances. Lorsque le texte change, appelez GetNameSuggestions () et créez les chaînes de celles qui ne sont pas utilisées, puis définissez ComboName.AutoCompleteCustomSource sur celle que vous venez de configurer.

Je pense que cela devrait ressembler à quelque chose comme ça.

AutoCompleteCustomSource accs_a;
AutoCompleteCustomSource accs_b;
bool accs_check = true; //true for accs_a, false for accs_b
void InitializeComboBox()
{
    ComboName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    ComboName.AutoCompleteSource = AutoCompleteSource.CustomSource;

    accs_a = new AutoCompleteStringCollection();
    accs_b = new AutoCompleteStringCollection();

    ComboName.AutoCompleteCustomSource = accs_a;
    ComboName.TextChanged += new EventHandler( ComboName_TextChanged );
}

void ComboName_TextChanged( object sender, EventArgs e )
{
    string text = this.ComboName.Text;

    if(accs_check)
    {
       accs_b.Clear();
       accs_b.AddRange(GetNameSuggestions( text ));
       accs_check = false;
    }
    else
    {
       accs_a.Clear();
       accs_a.AddRange(GetNameSuggestions( text ));
       accs_check = true;
    }

    this.ComboQuery.AutoCompleteCustomSource = accs_check? accs_a : accs_b;
}
1
Adam Haile

Je pense que vous voudrez peut-être sortir le réflecteur et envisager de remplacer le comportement de saisie semi-automatique dans la liste déroulante elle-même. Je suis certain que l'auto-complétion appellerait une fonction qui accède à la liste d'auto-complétion. Si vous pouvez trouver cette fonction et la remplacer, vous pouvez utiliser le comportement de votre choix.

Voyez quelle documentation vous pouvez trouver sur la classe combobox elle-même.

1
Spence

Après avoir essayé toutes les solutions qui ont été proposées ici (sans succès), j'ai trouvé quelque chose qui fonctionne pour moi:

private void CellBox_TextChanged(object sender, EventArgs e)
{
    ((TextBox)sender).TextChanged -= CellBox_TextChanged;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteMode = AutoCompleteMode.None;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteCustomSource = null;                
    aCSC.Clear();
    foreach (string value in Autocompletevalues())
    {
        aCSC.Add(value);
    }
    ((TextBox)dataGridView1.EditingControl).AutoCompleteCustomSource = aCSC;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteMode = AutoCompleteMode.Suggest;
    ((TextBox)sender).TextChanged += CellBox_TextChanged;
}

Pas:

  • Désactiver le gestionnaire d'événements
  • Désactiver le mode de saisie semi-automatique
  • Définir la source sur null
  • Mettre à jour AutoCompleteStringCollection (aCSC)
  • Définir la source sur AutoCompleteStringCollection mise à jour
  • Activer le mode de saisie semi-automatique
  • activer le gestionnaire d'événements

J'espère que ça aide quelqu'un ..

0
steloe

Sam, tu as compris ça? Je suis dans la même situation. Clear () semble provoquer l'exception. J'ai supprimé l'appel pour effacer et je reçois le message de suggestions correct même si la collection continue de croître ...

De plus, concernant les membres privés: vous pouvez y accéder en utilisant la réflexion:

PropertyInfo[] props = [object].GetType().GetProperties({flags go here});
props[0].SetValue(this, new object[] { 0 });
0
fixitchris

C’est un très vieux problème que je connais, mais c’est un problème qui existe encore aujourd’hui …… Ma solution de contournement consistait à définir le mode de saisie semi-automatique et les propriétés source sur «none» et à mettre à jour manuellement les éléments de l'événement KeyUp.

Je suis sûr que c'est hacky, mais cela fonctionne parfaitement pour moi sans problème pendant un bon bout de temps, quelle que soit la vitesse à laquelle les données sont entrées, avec en prime l'avantage que mes cheveux commencent à repousser.

Vous pouvez également choisir si vous souhaitez simplement suggérer, ou suggérer et ajouter… J'espère que cela pourra aider quelqu'un.

private void comboBox1_KeyUp(object sender, KeyEventArgs e)
    {

        if (string.IsNullOrWhiteSpace(comboBox1.Text))
        {
            e.Handled = true;
            return;
        }
        if (comboBox1.Text.Length < 3)
        {
            e.Handled = true;
            return;
        }

        if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
        {
            e.Handled = true;
            return;
        }
        else if (e.KeyCode == Keys.Back)
        {
            e.Handled = true;
            return;
        }

        string text = comboBox1.Text;

        if (e.KeyCode == Keys.Enter)
        {
            comboBox1.DroppedDown = false;
            comboBox1.SelectionStart = text.Length;
            e.Handled = true;
            return;
        }

        List<string> LS = Suggestions(comboBox1.Text);

        comboBox1.Items.Clear();
        comboBox1.Items.AddRange(LS.ToArray());

        //If you do not want to Suggest and Append
        //comment the following line to only Suggest
        comboBox1.Focus();

        comboBox1.DroppedDown = true;
        comboBox1.SelectionStart = text.Length;

        //Prevent cursor from getting hidden
        Cursor.Current = Cursors.Default;
        e.Handled = true;
    }
0
TEDSON

mise à jour: raison principale pour mettre le verrou sur cet endroit est

son fonctionnement :) la plupart des "exception mystérieuse" que j'ai jamais, après cette astuce disparaître


  1. le verrou comme dans ce code, peut aider avec votre exception
  2. comme vous l'avez mentionné précédemment, l'utilisation de textbox pose moins de problèmes
  3. dans ce code, SuggestAppend fonctionne bien


    private void Form1_Load(object sender, EventArgs e)
    {
        textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
        textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

        textBox1.TextChanged+=new EventHandler(textBox1_TextChanged);

        col1.AddRange(new string[] { "avi avi", "avram avram" });
        col2.AddRange(new string[] { "boria boria", "boris boris" });

        textBox1.AutoCompleteCustomSource = col1;
        textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    }
    AutoCompleteStringCollection col1 = new AutoCompleteStringCollection();
    AutoCompleteStringCollection col2 = new AutoCompleteStringCollection();

    object locker = new object();
    private void textBox1_TextChanged(object sender, EventArgs e)
    {
        lock (locker)
        {
            if (textBox1.Text.StartsWith("a") && textBox1.AutoCompleteCustomSource != col1)
            {
                textBox1.AutoCompleteCustomSource = col1;
            }
            if (textBox1.Text.StartsWith("b") && textBox1.AutoCompleteCustomSource != col2)
            {
                textBox1.AutoCompleteCustomSource = col2;
            }
        }
    }
0
Avram

Pour moi, le secret utilisait l'événement TextChanged et aucun des événements KeyDown/Up/Press, etc.

Mise à jour: Après avoir eu d'autres problèmes avec la modification dynamique de la fonctionnalité de saisie semi-automatique, j'ai finalement abandonné l'utilisation de la fonctionnalité de saisie semi-automatique intégrée et mis en œuvre le mien dans un temps beaucoup plus court que celui que j'avais perdu à l'origine. Il semble y avoir des problèmes dans le code non géré qui implémente le contrôle ComboBox. Plus précisément, j’ai eu des problèmes avec le déclenchement du gestionnaire d’événements TextChanged quand il le fallait. J'ai décidé d'utiliser uniquement les gestionnaires OnKeyDown/Press/Up dans mon implémentation personnalisée et cela semblait être plus fiable.

0
Boog
if(!textBox3.AutoCompleteCustomSource.Contains(textBox3.Text))
   textBox3.AutoCompleteCustomSource.Add(textBox3.Text);
0
Randy

Je n'ai pas essayé cela, mais dans votre cas particulier, vous pourriez coder quelque chose comme:

    private void txtAutoComplete_KeyUp(object sender, KeyEventArgs e)
    {

        String text = txtAutoComplete.Text;

        if (text.EndsWith(" "))
        {

            string[] suggestions = GetNameSuggestions( text ); //put [text + " "] at the begin of each array element
            txtAutoComplete.AutoCompleteCustomSource.Clear();
            txtAutoComplete.AutoCompleteCustomSource.AddRange( suggestions );

        }

    }
0
Black Shell

Je suis d'abord venu ici pour chercher une solution, mais j'ai maintenant trouvé la mienne. 

L'astuce consiste à ne pas appeler Clear () sur AutoCompleteCustomSource, mais à supprimer tous les éléments d'une boucle for, puis à reconstruire la liste avec les nouvelles données. Dans mon cas (une application de collection de livres), je récupère les noms d’auteurs d’une base de données avec une lettre de départ spécifique, au lieu du tout. Notez que cela ne fonctionnera que si la zone de texte faisant partie de la liste déroulante est ou est devenue vide. 

    private void cboAuthor_KeyDown(object sender, KeyEventArgs e)
    {
        if (cboAuthor.Text.Length == 0)
        {
            // Next two lines simple load data from the database in the
            // into a collection (var gateway), base on first letter in
            // the combobox. This is specific to my app.
            var gateway = new AuthorTableGateway();
            gateway.LoadByFirstLetter(Char.ConvertFromUtf32(e.KeyValue)[0]);

            // Clear current source without calling Clear()
            for (int i = 0; i < authorsAutoComplete.Count; i++)
                authorsAutoComplete.RemoveAt(0);

            // Rebuild with new data
            foreach (var author in gateway)
                authorsAutoComplete.Add(author.AuthorName);
        }
    }
0
Jim Cramer

La meilleure solution consiste à utiliser les gestionnaires d’événements de combobox. En utilisant textUpdate KeyDown DropDown et ChangeCommit, vous pouvez imiter le mode autocompletemode et personnaliser ce que vous souhaitez rechercher et ce qui apparaît dans la liste déroulante.

J'ai trouvé this answer utile mais il est codé dans Visual C++ et c'est toolstripcombobox mais le concept est identique. Quoi qu'il en soit, il existe une énorme similitude entre c # et c ++ en .net et cela ne devrait pas poser de problème pour comprendre la solution.

Recherche automatique personnalisée de ToolStripCombobox dans Visual C++

0
catzilla