J'essaie d'ajouter une fonctionnalité de complétion automatique à une zone de texte, les résultats proviennent d'une base de données. Ils viennent dans le format de
[001] Dernier, premier milieu
Actuellement, vous devez taper [001] ... pour que les entrées s'affichent. Le problème est donc que je veux que ce soit terminé même si je tape le prénom en premier . Donc, si une entrée était
[001] Smith, John D
si j'ai commencé à taper John, cette entrée devrait apparaître dans les résultats de la saisie automatique.
Actuellement, le code ressemble à quelque chose comme
AutoCompleteStringCollection acsc = new AutoCompleteStringCollection();
txtBox1.AutoCompleteCustomSource = acsc;
txtBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
txtBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
....
if (results.Rows.Count > 0)
for (int i = 0; i < results.Rows.Count && i < 10; i++)
{
row = results.Rows[i];
acsc.Add(row["Details"].ToString());
}
}
results est un ensemble de données contenant les résultats de la requête
La requête est une requête de recherche simple utilisant l'instruction like. Les résultats corrects sont renvoyés si nous n'utilisons pas la saisie semi-automatique et si nous jetons simplement les résultats dans un tableau.
Aucun conseil?
MODIFIER:
Voici la requête qui renvoie les résultats
SELECT Name from view_customers where Details LIKE '{0}'
Avec {0} étant l'espace réservé pour la chaîne recherchée.
La fonctionnalité de saisie semi-automatique existante ne prend en charge que la recherche par préfixe. Il ne semble pas y avoir de moyen décent de passer outre au comportement.
Certaines personnes ont implémenté leurs propres fonctions de saisie semi-automatique en remplaçant l'événement OnTextChanged
. C'est probablement votre meilleur pari.
Par exemple, vous pouvez ajouter une ListBox
juste en dessous de la TextBox
et définir sa visibilité par défaut sur false. Vous pouvez ensuite utiliser les événements OnTextChanged
de TextBox
et SelectedIndexChanged
de ListBox
pour afficher et sélectionner des éléments.
Cela semble bien fonctionner comme exemple rudimentaire:
public Form1()
{
InitializeComponent();
acsc = new AutoCompleteStringCollection();
textBox1.AutoCompleteCustomSource = acsc;
textBox1.AutoCompleteMode = AutoCompleteMode.None;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
private void button1_Click(object sender, EventArgs e)
{
acsc.Add("[001] some kind of item");
acsc.Add("[002] some other item");
acsc.Add("[003] an orange");
acsc.Add("[004] i like pickles");
}
void textBox1_TextChanged(object sender, System.EventArgs e)
{
listBox1.Items.Clear();
if (textBox1.Text.Length == 0)
{
hideResults();
return;
}
foreach (String s in textBox1.AutoCompleteCustomSource)
{
if (s.Contains(textBox1.Text))
{
Console.WriteLine("Found text in: " + s);
listBox1.Items.Add(s);
listBox1.Visible = true;
}
}
}
void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
textBox1.Text = listBox1.Items[listBox1.SelectedIndex].ToString();
hideResults();
}
void listBox1_LostFocus(object sender, System.EventArgs e)
{
hideResults();
}
void hideResults()
{
listBox1.Visible = false;
}
Vous pouvez faire beaucoup plus sans trop d'efforts: ajouter du texte à la zone de texte, capturer des commandes de clavier supplémentaires, etc.
Si vous décidez d'utiliser une requête basée sur les entrées de l'utilisateur, veillez à utiliser SqlParameters pour éviter les attaques par injection SQL.
SqlCommand sqlCommand = new SqlCommand();
sqlCommand.CommandText = "SELECT Name from view_customers where Details LIKE '%" + @SearchParam + "%'";
sqlCommand.Parameters.AddWithValue("@SearchParam", searchParam);
Voici une implémentation qui hérite de la classe de contrôle ComboBox
, plutôt que de remplacer la liste déroulante entière par un nouveau contrôle. Il affiche son propre menu déroulant lorsque vous tapez dans la zone de texte, mais le fait de cliquer pour afficher la liste déroulante est traité comme auparavant (c'est-à-dire pas avec ce code). En tant que tel, vous obtenez le contrôle et le look natifs appropriés.
S'il vous plaît utilisez-le, modifiez-le et modifiez la réponse si vous souhaitez l'améliorer!
class ComboListMatcher : ComboBox, IMessageFilter
{
private Control ComboParentForm; // Or use type "Form"
private ListBox listBoxChild;
private int IgnoreTextChange;
private bool MsgFilterActive = false;
public ComboListMatcher()
{
// Set up all the events we need to handle
TextChanged += ComboListMatcher_TextChanged;
SelectionChangeCommitted += ComboListMatcher_SelectionChangeCommitted;
LostFocus += ComboListMatcher_LostFocus;
MouseDown += ComboListMatcher_MouseDown;
HandleDestroyed += ComboListMatcher_HandleDestroyed;
}
void ComboListMatcher_HandleDestroyed(object sender, EventArgs e)
{
if (MsgFilterActive)
Application.RemoveMessageFilter(this);
}
~ComboListMatcher()
{
}
private void ComboListMatcher_MouseDown(object sender, MouseEventArgs e)
{
HideTheList();
}
void ComboListMatcher_LostFocus(object sender, EventArgs e)
{
if (listBoxChild != null && !listBoxChild.Focused)
HideTheList();
}
void ComboListMatcher_SelectionChangeCommitted(object sender, EventArgs e)
{
IgnoreTextChange++;
}
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
listBoxChild.Items.Add(Item);
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
/// <summary>
/// Copy the selection from the list-box into the combo box
/// </summary>
private void CopySelection()
{
if (listBoxChild.SelectedItem != null)
{
this.SelectedItem = listBoxChild.SelectedItem;
HideTheList();
this.SelectAll();
}
}
private void listBox1_Click(object sender, EventArgs e)
{
var ThisList = sender as ListBox;
if (ThisList != null)
{
// Copy selection to the combo box
CopySelection();
}
}
private void HideTheList()
{
if (listBoxChild != null)
listBoxChild.Hide();
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == 0x201) // Mouse click: WM_LBUTTONDOWN
{
var Pos = new Point((int)(m.LParam.ToInt32() & 0xFFFF), (int)(m.LParam.ToInt32() >> 16));
var Ctrl = Control.FromHandle(m.HWnd);
if (Ctrl != null)
{
// Convert the point into our parent control's coordinates ...
Pos = ComboParentForm.PointToClient(Ctrl.PointToScreen(Pos));
// ... because we need to hide the list if user clicks on something other than the list-box
if (ComboParentForm != null)
{
if (listBoxChild != null &&
(Pos.X < listBoxChild.Left || Pos.X > listBoxChild.Right || Pos.Y < listBoxChild.Top || Pos.Y > listBoxChild.Bottom))
{
this.HideTheList();
}
}
}
}
else if (m.Msg == 0x100) // WM_KEYDOWN
{
if (listBoxChild != null && listBoxChild.Visible)
{
switch (m.WParam.ToInt32())
{
case 0x1B: // Escape key
this.HideTheList();
return true;
case 0x26: // up key
case 0x28: // right key
// Change selection
int NewIx = listBoxChild.SelectedIndex + ((m.WParam.ToInt32() == 0x26) ? -1 : 1);
// Keep the index valid!
if (NewIx >= 0 && NewIx < listBoxChild.Items.Count)
listBoxChild.SelectedIndex = NewIx;
return true;
case 0x0D: // return (use the currently selected item)
CopySelection();
return true;
}
}
}
return false;
}
}
Si vous exécutez cette requête (avec {0}
remplacé par la chaîne entrée), vous aurez peut-être besoin des éléments suivants:
SELECT Name from view_customers where Details LIKE '%{0}%'
LIKE
a toujours besoin du caractère %
... Et oui, vous devriez utiliser des paramètres plutôt que de faire confiance aux entrées de l'utilisateur :)
En outre, vous semblez renvoyer la colonne Name
, mais interroger sur la colonne Details
. Donc, si quelqu'un tape "John Smith", si ce n'est pas dans la colonne Details
, vous n'obtiendrez pas ce que vous voulez.
CELA VOUS DONNERA LE COMPORTEMENT AUTOCOMPLETE QUE VOUS RECHERCHEZ.
L'exemple ci-joint est un formulaire complet, Besoin uniquement de votre source de données et des noms de colonne liés.
using System;
using System.Data;
using System.Windows.Forms;
public partial class frmTestAutocomplete : Form
{
private DataTable maoCompleteList;
private const string MC_DISPLAY_COL = "name";
private const string MC_ID_COL = "id";
public frmTestAutocomplete()
{
InitializeComponent();
}
private void frmTestAutocomplete_Load(object sender, EventArgs e)
{
using (clsDataAccess oData = new clsDataAccess())
{
maoCompleteList = oData.PurificationRuns;
maoCompleteList.CaseSensitive = false; //turn off case sensitivity for searching
testCombo.DisplayMember = MC_DISPLAY_COL;
testCombo.ValueMember = MC_ID_COL;
testCombo.DataSource = GetDataTableFromDatabase();
testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;
testCombo.KeyUp += testCombo_KeyUp;
}
}
private void testCombo_KeyUp(object sender, KeyEventArgs e)
{
//use keyUp event, as text changed traps too many other evengts.
ComboBox oBox = (ComboBox)sender;
string sBoxText = oBox.Text;
DataRow[] oFilteredRows = maoCompleteList.Select(MC_DISPLAY_COL + " Like '%" + sBoxText + "%'");
DataTable oFilteredDT = oFilteredRows.Length > 0
? oFilteredRows.CopyToDataTable()
: maoCompleteList;
//NOW THAT WE HAVE OUR FILTERED LIST, WE NEED TO RE-BIND IT WIHOUT CHANGING THE TEXT IN THE ComboBox.
//1).UNREGISTER THE SELECTED EVENT BEFORE RE-BINDING, b/c IT TRIGGERS ON BIND.
testCombo.SelectedIndexChanged -= testCombo_SelectedIndexChanged; //don't select on typing.
oBox.DataSource = oFilteredDT; //2).rebind to filtered list.
testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;
//3).show the user the new filtered list.
oBox.DroppedDown = true; //do this before repainting the text, as it changes the dropdown text.
//4).binding data source erases text, so now we need to put the user's text back,
oBox.Text = sBoxText;
oBox.SelectionStart = sBoxText.Length; //5). need to put the user's cursor back where it was.
}
private void testCombo_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox oBox = (ComboBox)sender;
if (oBox.SelectedValue != null)
{
MessageBox.Show(string.Format(@"Item #{0} was selected.", oBox.SelectedValue));
}
}
}
//=====================================================================================================
// code from frmTestAutocomplete.Designer.cs
//=====================================================================================================
partial class frmTestAutocomplete
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.testCombo = new System.Windows.Forms.ComboBox();
this.SuspendLayout();
//
// testCombo
//
this.testCombo.FormattingEnabled = true;
this.testCombo.Location = new System.Drawing.Point(27, 51);
this.testCombo.Name = "testCombo";
this.testCombo.Size = new System.Drawing.Size(224, 21);
this.testCombo.TabIndex = 0;
//
// frmTestAutocomplete
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.testCombo);
this.Name = "frmTestAutocomplete";
this.Text = "frmTestAutocomplete";
this.Load += new System.EventHandler(this.frmTestAutocomplete_Load);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ComboBox testCombo;
}