Mon problème est similaire à celui-ci: Comment puis-je modifier dynamiquement des entrées à saisie automatique dans une liste déroulante C # ou une zone de texte? Mais je ne trouve toujours pas de solution.
Le problème brièvement:
J'ai une ComboBox
et un grand nombre d'enregistrements à afficher. Lorsque l'utilisateur commence à taper, je veux charger les enregistrements commençant par le texte saisi et le proposer à l'utilisateur pour la saisie semi-automatique. Comme décrit dans la rubrique ci-dessus, je ne peux pas les charger sur сomboBox_TextChanged
car je écris toujours les résultats précédents et ne les vois jamais.
Puis-je implémenter ceci en utilisant seulement ComboBox
? (pas TextBox
ou ListBox
)
J'utilise ces paramètres:
сomboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
сomboBox.AutoCompleteSource = AutoCompleteSource.CustomSource;
Voici ma solution finale. Cela fonctionne bien avec une grande quantité de données. J'utilise Timer
pour m'assurer que l'utilisateur veut trouver la valeur actuelle. Cela semble complexe, mais ça ne… .. Merci à Max Lambertini pour l’idée.
private bool _canUpdate = true;
private bool _needUpdate = false;
//If text has been changed then start timer
//If the user doesn't change text while the timer runs then start search
private void combobox1_TextChanged(object sender, EventArgs e)
{
if (_needUpdate)
{
if (_canUpdate)
{
_canUpdate = false;
UpdateData();
}
else
{
RestartTimer();
}
}
}
private void UpdateData()
{
if (combobox1.Text.Length > 1)
{
List<string> searchData = Search.GetData(combobox1.Text);
HandleTextChanged(searchData);
}
}
//If an item was selected don't start new search
private void combobox1_SelectedIndexChanged(object sender, EventArgs e)
{
_needUpdate = false;
}
//Update data only when the user (not program) change something
private void combobox1_TextUpdate(object sender, EventArgs e)
{
_needUpdate = true;
}
//While timer is running don't start search
//timer1.Interval = 1500;
private void RestartTimer()
{
timer1.Stop();
_canUpdate = false;
timer1.Start();
}
//Update data when timer stops
private void timer1_Tick(object sender, EventArgs e)
{
_canUpdate = true;
timer1.Stop();
UpdateData();
}
//Update combobox with new data
private void HandleTextChanged(List<string> dataSource)
{
var text = combobox1.Text;
if (dataSource.Count() > 0)
{
combobox1.DataSource = dataSource;
var sText = combobox1.Items[0].ToString();
combobox1.SelectionStart = text.Length;
combobox1.SelectionLength = sText.Length - text.Length;
combobox1.DroppedDown = true;
return;
}
else
{
combobox1.DroppedDown = false;
combobox1.SelectionStart = text.Length;
}
}
Cette solution n'est pas très cool. Donc, si quelqu'un a une autre solution, partagez-la avec moi.
Oui, vous le pouvez sûrement ... mais il faut un peu de travail pour le faire fonctionner de manière transparente. Ceci est un code que je suis venu avec. N'oubliez pas qu'il n'utilise pas utilise les fonctions de saisie semi-automatique de combobox et qu'il risque d'être assez lent si vous l'utilisez pour passer au crible un grand nombre d'éléments ...
string[] data = new string[] {
"Absecon","Abstracta","Abundantia","Academia","Acadiau","Acamas",
"Ackerman","Ackley","Ackworth","Acomita","Aconcagua","Acton","Acushnet",
"Acworth","Ada","Ada","Adair","Adairs","Adair","Adak","Adalberta","Adamkrafft",
"Adams"
};
public Form1()
{
InitializeComponent();
}
private void comboBox1_TextChanged(object sender, EventArgs e)
{
HandleTextChanged();
}
private void HandleTextChanged()
{
var txt = comboBox1.Text;
var list = from d in data
where d.ToUpper().StartsWith(comboBox1.Text.ToUpper())
select d;
if (list.Count() > 0)
{
comboBox1.DataSource = list.ToList();
//comboBox1.SelectedIndex = 0;
var sText = comboBox1.Items[0].ToString();
comboBox1.SelectionStart = txt.Length;
comboBox1.SelectionLength = sText.Length - txt.Length;
comboBox1.DroppedDown = true;
return;
}
else
{
comboBox1.DroppedDown = false;
comboBox1.SelectionStart = txt.Length;
}
}
private void comboBox1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Back)
{
int sStart = comboBox1.SelectionStart;
if (sStart > 0)
{
sStart--;
if (sStart == 0)
{
comboBox1.Text = "";
}
else
{
comboBox1.Text = comboBox1.Text.Substring(0, sStart);
}
}
e.Handled = true;
}
}
J'ai écrit quelque chose comme ça ....
private void frmMain_Load(object sender, EventArgs e)
{
cboFromCurrency.Items.Clear();
cboComboBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
cboComboBox1.AutoCompleteSource = AutoCompleteSource.ListItems;
// Load data in comboBox => cboComboBox1.DataSource = .....
// Other things
}
private void cboComboBox1_KeyPress(object sender, KeyPressEventArgs e)
{
cboComboBox1.DroppedDown = false;
}
C'est tout (Y)
J'ai trouvé la réponse de Max Lambertini très utile, mais j'ai modifié sa méthode HandleTextChanged en tant que telle:
//I like min length set to 3, to not give too many options
//after the first character or two the user types
public Int32 AutoCompleteMinLength {get; set;}
private void HandleTextChanged() {
var txt = comboBox.Text;
if (txt.Length < AutoCompleteMinLength)
return;
//The GetMatches method can be whatever you need to filter
//table rows or some other data source based on the typed text.
var matches = GetMatches(comboBox.Text.ToUpper());
if (matches.Count() > 0) {
//The inside of this if block has been changed to allow
//users to continue typing after the auto-complete results
//are found.
comboBox.Items.Clear();
comboBox.Items.AddRange(matches);
comboBox.DroppedDown = true;
Cursor.Current = Cursors.Default;
comboBox.Select(txt.Length, 0);
return;
}
else {
comboBox.DroppedDown = false;
comboBox.SelectionStart = txt.Length;
}
}
Dans les réponses précédentes sont des inconvénients. Propose sa propre version avec la sélection dans la liste déroulante l'élément souhaité:
private ConnectSqlForm()
{
InitializeComponent();
cmbDatabases.TextChanged += UpdateAutoCompleteComboBox;
cmbDatabases.KeyDown += AutoCompleteComboBoxKeyPress;
}
private void UpdateAutoCompleteComboBox(object sender, EventArgs e)
{
var comboBox = sender as ComboBox;
if(comboBox == null)
return;
string txt = comboBox.Text;
string foundItem = String.Empty;
foreach(string item in comboBox.Items)
if (!String.IsNullOrEmpty(txt) && item.ToLower().StartsWith(txt.ToLower()))
{
foundItem = item;
break;
}
if (!String.IsNullOrEmpty(foundItem))
{
if (String.IsNullOrEmpty(txt) || !txt.Equals(foundItem))
{
comboBox.TextChanged -= UpdateAutoCompleteComboBox;
comboBox.Text = foundItem;
comboBox.DroppedDown = true;
Cursor.Current = Cursors.Default;
comboBox.TextChanged += UpdateAutoCompleteComboBox;
}
comboBox.SelectionStart = txt.Length;
comboBox.SelectionLength = foundItem.Length - txt.Length;
}
else
comboBox.DroppedDown = false;
}
private void AutoCompleteComboBoxKeyPress(object sender, KeyEventArgs e)
{
var comboBox = sender as ComboBox;
if (comboBox != null && comboBox.DroppedDown)
{
switch (e.KeyCode)
{
case Keys.Back:
int sStart = comboBox.SelectionStart;
if (sStart > 0)
{
sStart--;
comboBox.Text = sStart == 0 ? "" : comboBox.Text.Substring(0, sStart);
}
e.SuppressKeyPress = true;
break;
}
}
}
Ce code est écrit sur votre formulaire. Il affiche toutes les visites dans la base de données lorsque l’utilisateur saisit une lettre dans une liste déroulante. Ce code suggère et ajoute automatiquement le bon choix selon les souhaits de l'utilisateur.
con.Open();
cmd = new SqlCommand("SELECT DISTINCT Tour FROM DetailsTB", con);
SqlDataReader sdr = cmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(sdr);
combo_search2.DisplayMember = "Tour";
combo_search2.DroppedDown = true;
List<string> list = new List<string>();
foreach (DataRow row in dt.Rows)
{
list.Add(row.Field<string>("Tour"));
}
this.combo_search2.Items.AddRange(list.ToArray<string>());
combo_search2.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
combo_search2.AutoCompleteSource = AutoCompleteSource.ListItems;
con.Close();
using (var client = new UserServicesClient())
{
var list = new AutoCompleteStringCollection();
list.AddRange(client.ListNames(query).ToArray());
comboBoxName.AutoCompleteCustomSource = list;
}
Ce fut une douleur majeure pour travailler. Je me suis retrouvé dans une impasse, mais le résultat final est assez simple. Espérons que cela puisse profiter à quelqu'un. Il faudra peut-être un peu de broche et de polissage, c'est tout.
Remarque: _addressFinder.CompleteAsync renvoie une liste de KeyValuePairs.
public partial class MyForm : Form
{
private readonly AddressFinder _addressFinder;
private readonly AddressSuggestionsUpdatedEventHandler _addressSuggestionsUpdated;
private delegate void AddressSuggestionsUpdatedEventHandler(object sender, AddressSuggestionsUpdatedEventArgs e);
public MyForm()
{
InitializeComponent();
_addressFinder = new AddressFinder(new AddressFinderConfigurationProvider());
_addressSuggestionsUpdated += AddressSuggestions_Updated;
MyComboBox.DropDownStyle = ComboBoxStyle.DropDown;
MyComboBox.DisplayMember = "Value";
MyComboBox.ValueMember = "Key";
}
private void MyComboBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (char.IsControl(e.KeyChar))
{
return;
}
var searchString = ThreadingHelpers.GetText(MyComboBox);
if (searchString.Length > 1)
{
Task.Run(() => GetAddressSuggestions(searchString));
}
}
private async Task GetAddressSuggestions(string searchString)
{
var addressSuggestions = await _addressFinder.CompleteAsync(searchString).ConfigureAwait(false);
if (_addressSuggestionsUpdated.IsNotNull())
{
_addressSuggestionsUpdated.Invoke(this, new AddressSuggestionsUpdatedEventArgs(addressSuggestions));
}
}
private void AddressSuggestions_Updated(object sender, AddressSuggestionsUpdatedEventArgs eventArgs)
{
try
{
ThreadingHelpers.BeginUpdate(MyComboBox);
var text = ThreadingHelpers.GetText(MyComboBox);
ThreadingHelpers.ClearItems(MyComboBox);
foreach (var addressSuggestions in eventArgs.AddressSuggestions)
{
ThreadingHelpers.AddItem(MyComboBox, addressSuggestions);
}
ThreadingHelpers.SetDroppedDown(MyComboBox, true);
ThreadingHelpers.ClearSelection(MyComboBox);
ThreadingHelpers.SetText(MyComboBox, text);
ThreadingHelpers.SetSelectionStart(MyComboBox, text.Length);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
ThreadingHelpers.EndUpdate(MyComboBox);
}
}
private class AddressSuggestionsUpdatedEventArgs : EventArgs
{
public IList<KeyValuePair<string, string>> AddressSuggestions { get; private set; }
public AddressSuggestionsUpdatedEventArgs(IList<KeyValuePair<string, string>> addressSuggestions)
{
AddressSuggestions = addressSuggestions;
}
}
}
ThreadingHelpers est juste un ensemble de méthodes statiques de la forme:
public static string GetText(ComboBox comboBox)
{
if (comboBox.InvokeRequired)
{
return (string)comboBox.Invoke(new Func<string>(() => GetText(comboBox)));
}
lock (comboBox)
{
return comboBox.Text;
}
}
public static void SetText(ComboBox comboBox, string text)
{
if (comboBox.InvokeRequired)
{
comboBox.Invoke(new Action(() => SetText(comboBox, text)));
return;
}
lock (comboBox)
{
comboBox.Text = text;
}
}