seleccionar - ¿Cómo puedo cambiar dinámicamente las entradas de autocompletar en un cuadro combinado de C#o en un cuadro de texto?
lista desplegable excel autocompletar (13)
Tengo un combobox en C # y quiero usar sugerencias de autocompletar con él, sin embargo, quiero poder cambiar las entradas de autocompletar a medida que el usuario escribe, porque las posibles entradas válidas son demasiado numerosas para completar AutoCompleteStringCollection
al inicio.
Como ejemplo, supongamos que estoy dejando que el usuario escriba un nombre. Tengo una lista de posibles nombres ("Joe", "John") y una lista de apellidos ("Bloggs", "Smith"), pero si tengo mil de cada uno, entonces serían un millón de cadenas posibles. demasiados para poner en las entradas de autocompletar. Así que inicialmente quiero tener solo los primeros nombres como sugerencias ("Joe", "John"), y luego, una vez que el usuario ha escrito el primer nombre, ("Joe"), quiero eliminar las entradas de autocompletar existentes y reemplazarlas ellos con un nuevo conjunto que consiste en el nombre elegido seguido de los posibles apellidos ("Joe Bloggs", "Joe Smith"). Para hacer esto, probé el siguiente código:
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 );
}
Sin embargo, esto no funciona correctamente. Parece que la llamada a Clear () hace que el mecanismo de autocompletar se "apague" hasta que aparezca el siguiente carácter en el cuadro combinado, pero por supuesto cuando aparece el siguiente carácter, el código anterior vuelve a llamar a Clear (), por lo que el usuario nunca realmente ve la funcionalidad de autocompletar. También hace que se seleccione todo el contenido del cuadro combinado, por lo que entre cada pulsación de tecla debe deseleccionar el texto existente, lo que lo hace inutilizable. Si elimino la llamada a Borrar (), el autocompletar funciona, pero parece que la llamada AddRange()
no tiene ningún efecto, porque las nuevas sugerencias que agrego no aparecen en el menú desplegable Autocompletar.
He estado buscando una solución para esto y he visto varias cosas sugeridas, pero no puedo hacer que ninguna funcione: la funcionalidad de autocompletar aparece deshabilitada o no aparecen nuevas cadenas. Aquí hay una lista de cosas que he intentado:
- Llamando a
BeginUpdate()
antes de cambiar las cadenas yEndUpdate()
después. - Llamar a
Remove()
en todas las cadenas existentes en lugar de Clear (). - Borrando el texto del cuadro combinado mientras actualizo las cadenas y lo vuelvo a agregar después.
- Estableciendo
AutoCompleteMode
en "Ninguno" mientras cambio las cadenas, y configurándolo de nuevo en "SuggestAppend" después. -
TextUpdate
eventoTextUpdate
oKeyPress
lugar deTextChanged
. - Reemplazar el
AutoCompleteCustomSource
existente con un nuevoAutoCompleteStringCollection
cada vez.
Ninguno de estos ayudó, incluso en varias combinaciones. Spence sugirió que intente sobrescribir la función ComboBox
que obtiene la lista de cadenas para usar en autocompletar. Utilizando un reflector encontré un par de métodos en la clase ComboBox
que parecen prometedores: GetStringsForAutoComplete()
y SetAutoComplete()
, pero ambos son privados, por lo que no puedo acceder a ellos desde una clase derivada. No podría llevar eso más allá.
Traté de reemplazar el ComboBox
con un TextBox
, porque la interfaz de autocompletar es la misma, y encontré que el comportamiento es ligeramente diferente. Con el TextBox
parece funcionar mejor, en el sentido de que la parte de Agregar del autocompletado funciona correctamente, pero la parte de sugerir no: el cuadro de sugerencias se ilumina brevemente, pero luego desaparece de inmediato.
Así que pensé: "De acuerdo, viviré sin la funcionalidad Suggest y simplemente usar Append", sin embargo, cuando configuro AutoCompleteMode
para anexar, recibo una excepción de violación de acceso. Lo mismo sucede con Suggest: el único modo que no arroja excepciones es SuggestAppend
, aunque la parte Suggest no se comporta correctamente.
Pensé que se suponía que era imposible obtener excepciones de violación de acceso cuando usaba el código administrado C #. Avram sugirió que use "bloqueo" para solucionar esto, pero no sé lo que debo bloquear: lo único que tiene un miembro de SyncRoot es AutoCompleteStringCollection
, y el bloqueo no impide las excepciones de violación de acceso. También traté de bloquear el ComboBox
o el TextBox
, pero eso tampoco ayudó. Según entiendo, el bloqueo solo previene otros bloqueos, por lo que si el código subyacente no está usando el bloqueo, mi uso no hará ninguna diferencia.
El resultado de todo esto es que actualmente no puedo usar un TextBox
o un ComboBox
con autocompletado dinámico. ¿Alguien tiene alguna idea de cómo podría lograr esto?
Actualizar:
Todavía no funcionó, pero descubrí algo más. Quizás algo de esto inspire a otra persona a encontrar una solución.
Traté de reemplazar el ComboBox
con un TextBox
, porque la interfaz de autocompletar es la misma, y encontré que el comportamiento es ligeramente diferente. Con el TextBox
parece funcionar mejor, en el sentido de que la parte de Agregar del autocompletado funciona correctamente, pero la parte de sugerir no: el cuadro de sugerencias se ilumina brevemente, pero luego desaparece de inmediato.
Así que pensé "De acuerdo, viviré sin la funcionalidad Suggest y simplemente usar Append", sin embargo, cuando configuro AutoCompleteMode
para anexar, recibo una excepción de violación de acceso. Lo mismo sucede con Suggest: el único modo que no arroja excepciones es SuggestAppend
, aunque la parte Suggest no se comporta correctamente.
Pensé que se suponía que era imposible obtener excepciones de violación de acceso cuando usé el código administrado C #, pero de todos modos, el resultado es que actualmente no puedo usar un TextBox
o un ComboBox
con ningún tipo de autocompletado dinámico. ¿Alguien tiene alguna idea de cómo podría lograr esto?
Actualización 2:
Después de probar varias otras cosas, como cambiar el autocompletado en un subproceso de trabajo y usar BeginInvoke()
para simular el comportamiento de tipo de PostMessage (), finalmente me BeginInvoke()
y acabo de implementar mi propio menú desplegable autocompletar utilizando un cuadro de lista. Es mucho más receptivo que el que viene incorporado, y pasé menos tiempo haciendo eso que tratando de hacer que el incorporado funcione, por lo que la lección para cualquier otra persona que quiera este comportamiento es: probablemente estés mejor. implementándolo usted mismo.
Creo que es posible que desee sacar el reflector y ver anular el comportamiento de autocompletar en el cuadro combinado. Estoy seguro de que la autocompletación llamaría a una función que accede a la lista de autocompletar. Si puede encontrar esta función y anularla, puede usar cualquier comportamiento que desee.
Vea qué documentación puede encontrar en la misma clase de combobox.
Este es un problema muy antiguo que conozco, pero todavía existe en la actualidad. Mi solución fue establecer el modo Autocompletar y las propiedades de origen en ''ninguno'' y actualizar manualmente los elementos en el evento KeyUp.
Estoy seguro de que es raro pero funciona perfectamente para mí sin problemas por bastante tiempo, independientemente de la velocidad con la que se ingresen los datos, con la ventaja añadida de que mi cabello comienza a crecer nuevamente.
También puede elegir si desea sugerir, sugerir o anexar. Espero que pueda ayudar a alguien.
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;
}
Esto funcionó para mí, no addRange
a la misma AutoCompleteStringCollection
, sino que crea una nueva cada vez.
form.fileComboBox.TextChanged += (sender, e) => {
var autoComplete = new AutoCompleteStringCollection();
string[] items = CustomUtil.GetFileNames();
autoComplete.AddRange(items);
form.fileComboBox.AutoCompleteCustomSource = autoComplete;
};
La mejor solución para esto es usar los controladores de eventos de combobox. Al usar textUpdate KeyDown DropDown y ChangeCommit , puede imitar el modo autocompletar y puede personalizar qué buscar y qué aparecer en el menú desplegable.
Encontré útil this respuesta, pero está codificada en Visual C ++ y es una caja de herramientas, pero el concepto es idéntico . De todos modos, existe una gran similitud entre c # y c ++ en .net y no debería ser un problema para entender la solución.
No he probado esto, pero puede valer la pena intentarlo.
En lugar de borrar AutoCompleteCustomSource, doble buffer manteniendo dos instancias. Cuando el texto cambie, llame a GetNameSuggestions () y cree las cadenas para la que no se está utilizando actualmente, luego configure ComboName.AutoCompleteCustomSource con el que acaba de configurar.
Creo que debería verse algo como esto.
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;
}
No intentes esto, pero para tu caso específico podrías codificar algo como:
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 );
}
}
Para mí, el secreto fue usar el evento TextChanged y ninguno de los KeyDown / Up / Press, etc.
Actualización: después de tener otros problemas con el cambio dinámico de AutoCompleteCustomSource, finalmente abandoné el uso de la función Autocompletar incorporada e implementé la mía en mucho menos tiempo de lo que había desperdiciado originalmente. Parece que hay algunos problemas en el código no administrado que implementa el control ComboBox. Específicamente, estaba teniendo problemas con el controlador de evento TextChanged disparando cuando debería. Decidí usar solo los controladores OnKeyDown / Press / Up en mi implementación personalizada y que parecían ser más confiables.
Sam, tienes esto resuelto? Estoy corriendo en la misma situación. Clear () parece causar la excepción. Eliminé la llamada para borrar y recibí el evento de sugerencias correctas aunque la colección sigue creciendo ...
Además, con respecto a los miembros privados: puede acceder a ellos mediante la reflexión:
PropertyInfo[] props = [object].GetType().GetProperties({flags go here});
props[0].SetValue(this, new object[] { 0 });
Tuve el mismo problema y encontré una solución extremadamente simple. Como todos los demás aquí, no pude encontrar ningún medio para controlar el comportamiento del componente, así que tuve que aceptarlo.
El comportamiento natural es: no puede completar dinámicamente la lista cada vez que el usuario escribe en el cuadro de texto. Debe llenarlo una vez, y luego el mecanismo Autocompletar toma el control. La conclusión es: debe rellenar AutoCompleteCustomSource con cada entrada posible en su base de datos para que funcione como queramos.
Por supuesto, esto no es viable si tiene millones de registros para completar la lista. Los problemas de rendimiento en la transferencia de datos y el mecanismo Autocompletar en sí mismo no le permitirán hacer eso.
La solución de compromiso que encontré fue: rellenar dinámicamente el AutoCompleteCustomSource cada vez que la longitud del texto alcanza exactamente N caracteres (3 en mi caso). Esto funcionó porque la complejidad se redujo drásticamente. La cantidad de registros que se extraen de la base de datos que coinciden con estos 3 caracteres iniciales fue lo suficientemente pequeña como para evitar problemas de rendimiento.
El principal inconveniente es que los usuarios no recibirán la lista Autocompletar hasta que escriban el N-ésimo carácter. Pero parece que los usuarios no esperan realmente una lista completa de Autocompletar antes de escribir 3 caracteres.
Espero que esto ayude.
Vine aquí buscando una solución, pero ahora encontré la mía.
El truco consiste en no llamar a Clear () en AutoCompleteCustomSource sino eliminar todos los elementos en un ciclo for y luego reconstruir la lista con los datos nuevos. En mi caso (una aplicación de colección de libros) estoy recuperando nombres de autores de una base de datos con una letra de inicio específica, en lugar de todo el lote. Tenga en cuenta que esto solo funcionará si la parte de la caja de texto del cuadro combinado está o está vacía.
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);
}
}
actualización: razón principal para poner el candado en este lugar es
funciona :) la mayor parte de la "misteriosa excepción" que alguna vez tuve, después de que este truco desapareciera
- el bloqueo como en este código, puede ayudar con su excepción
- como mencionas antes, hay menos problemas con el uso de textbox
- en este código, SuggestAppend funciona 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;
}
}
}
usa este código
private void dataGridView1_EditingControlShowing(object sender,DataGridViewEditingControlShowingEventArgs e)
{
if (e.Control is DataGridViewComboBoxEditingControl)
{
((ComboBox)e.Control).DropDownStyle = ComboBoxStyle.DropDown;
((ComboBox)e.Control).AutoCompleteSource = AutoCompleteSource.ListItems;
((ComboBox)e.Control).AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
}
}
if(!textBox3.AutoCompleteCustomSource.Contains(textBox3.Text))
textBox3.AutoCompleteCustomSource.Add(textBox3.Text);