Sin embargo, ¿qué pasa si el criterio que queremos utilizar para seleccionar los textos a sugerir es otro? Por ejemplo si queremos implementar un control con sugerencias al estilo de las búsquedas de Google.
En este artículo, y los siguientes, voy a mostrar cómo podemos crear un control TextBox que nos permita elegir el algoritmo a utilizar para mostrar los textos sugeridos.
En este artículo crearé un control que reproduzca el mecanismo de sugerencias de un control TextBox estándar, y en los siguientes mostraré cómo podemos dar al desarrollador la posibilidad de personalizar el algoritmo para seleccionar los textos sugeridos.
Para empezar voy a crear un nuevo proyecto del tipo Biblioteca de Clases llamado SuggestionTextBox.
A continuación le he añadido referencias a los ensamblados System.Drawing y System.Windows.Forms que necesitaré en el proyecto.
Para introducir el código del control he eliminado la clase Class1 que Visual Studio crea por defecto y he añadido una nueva clase TextSuggestion que hereda de TextBox.
Lo primero que haremos será crear dos propiedades para configurar las sugerencias:
- MaxNumOfSuggestions: que determinará el número máximo de resultados a mostrar
- SuggestDataSource: que establecerá la colección de cadenas que utilizará el control para las sugerencias
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace SuggestionTextBox { public class TextSuggestion: TextBox { #region Propiedades públicas [DefaultValue(10)] public int MaxNumOfSuggestions { get; set; } public IEnumerable<string> SuggestDataSource { get; set; } #endregion } }
Imports System.ComponentModel Imports System.Windows.Forms Imports System.Drawing Public Class TextSuggestion Inherits TextBox #Region "Propiedades públicas" <DefaultValue(10)> _ Public Property MaxNumOfSuggestions As Integer Public Property SuggestDataSource As IEnumerable(Of String) #End Region End Class
Para mostrar los textos sugeridos voy a utilizar un control ListBox. Así que voy a crear dos variables privadas, una para contener el control ListBox y otra que indicará si el control ya ha sido inicializado.
Además crearé 3 métodos para controlar el funcionamiento del ListBox:
- ShowListBox: que se encargará de inicializar el control ListBox si todavía no lo ha sido y lo muestra.
- HideListBox: que se encargará de ocultar el control ListBox.
- UpdateListBox: que actualizará el contenido del ListBox a partir del texto introducido en el TextBox, seleccionando las cadenas de SuggestDataSource que comiencen con el texto introducido.
Por último voy a crear un controlador para el evento Click del ListBox que se encargará de establecer el texto del TextBox con el contenido del elemento seleccionado y ocultará el control ListBox.
El constructor del control se encargará de inicializar la propiedad MaxNumOfSuggestions a su valor por defecto, iniciarlizar la variable _listBox y de asociar el controlador del evento Click al control ListBox.
#region Variables privadas private ListBox _listBox; private bool _listBoxAddedToForm = false; #endregion #region Constructor public TextSuggestion(): base() { MaxNumOfSuggestions = 10; _listBox = new ListBox(); _listBox.Click += listBox_Click; } #endregion #region ListBox private void ShowListBox() { if (!_listBoxAddedToForm) { this.TopLevelControl.Controls.Add(_listBox); Point controlLocation = this.TopLevelControl.PointToClient(this.Parent.PointToScreen(this.Location)); _listBox.Left = controlLocation.X; _listBox.Top = controlLocation.Y + this.Height; _listBox.Font = this.Font; _listBox.Width = this.Width; _listBox.MinimumSize = new Size(this.Width, _listBox.MinimumSize.Height); _listBox.Height = _listBox.ItemHeight * (MaxNumOfSuggestions + 1); _listBoxAddedToForm = true; } _listBox.Visible = true; _listBox.BringToFront(); } private void HideListBox() { _listBox.Visible = false; } private void UpdateListBox() { if (SuggestDataSource != null && !string.IsNullOrEmpty(this.Text)) { IEnumerable<string> result = SuggestDataSource .Where(s => s.StartsWith(this.Text, StringComparison.OrdinalIgnoreCase) && !s.Equals(this.Text, StringComparison.OrdinalIgnoreCase)) .OrderBy(s => s) .Take(MaxNumOfSuggestions); if (result.Count() > 0) { _listBox.DataSource = result.ToList(); ShowListBox(); } else HideListBox(); } else HideListBox(); } private void listBox_Click(object sender, EventArgs e) { if (_listBox.SelectedIndex >= 0) Text = _listBox.SelectedItem.ToString(); HideListBox(); } #endregion
#Region "Variables privadas" Private _listBox As ListBox Private _listBoxAddedToForm As Boolean = False #End Region #Region "Constructor" Public Sub New() MaxNumOfSuggestions = 10 _listBox = New ListBox() AddHandler _listBox.Click, AddressOf listBox_Click End Sub #End Region #Region "ListBox" Private Sub ShowListBox() If Not _listBoxAddedToForm Then Me.TopLevelControl.Controls.Add(_listBox) Dim controlLocation As Point _ = Me.TopLevelControl.PointToClient(Me.Parent.PointToScreen(Me.Location)) _listBox.Left = controlLocation.X _listBox.Top = controlLocation.Y + Me.Height _listBox.Font = Me.Font _listBox.Width = Me.Width _listBox.MinimumSize = New Size(Me.Width, _listBox.MinimumSize.Height) _listBox.Height = _listBox.ItemHeight * (MaxNumOfSuggestions + 1) _listBoxAddedToForm = True End If _listBox.Visible = True _listBox.BringToFront() End Sub Private Sub HideListBox() _listBox.Visible = False End Sub Private Sub UpdateListBox() If SuggestDataSource IsNot Nothing AndAlso Not String.IsNullOrEmpty(Me.Text) Then Dim result As IEnumerable(Of String) = SuggestDataSource _ .Where(Function(s) s.StartsWith(Me.Text, StringComparison.OrdinalIgnoreCase) _ AndAlso Not s.Equals(Me.Text, StringComparison.OrdinalIgnoreCase)) _ .OrderBy(Function(s) s) _ .Take(MaxNumOfSuggestions) If result.Count() > 0 Then _listBox.DataSource = result.ToList() ShowListBox() Else HideListBox() End If Else HideListBox() End If End Sub Private Sub listBox_Click(sender As Object, e As EventArgs) If _listBox.SelectedIndex >= 0 Then Text = _listBox.SelectedItem.ToString() End If HideListBox() End Sub #End Region
La parte más interesante de este código se encuentra en la inicialización del control ListBox. Lo que hago es añadir el ListBox al control especificado por la propiedad TopLevelControl (generalmente el formulario), de esta forma al mostrarse el ListBox no se verá limitado por el control contenedor del TextBox. A continuación establezco la posición del ListBox en la esquina inferior izquierda del TextBox y su ancho igual al ancho del TextBox. Cuando se muestra el ListBox llamo al método BringToFront para que se muestre por encima del resto de controles.
A continuación voy a añadir dos controladores para los eventos de teclado:
- En el evento KeyDown controlo la pulsación de las teclas "Arriba" y "Abajo" para modificar el elemento seleccionado en el ListBox. También controla la pulsación de la tecla "Enter" para que, si existe un elemento del ListBox seleccionado lo establezca como texto del control.
- En el evento KeyUp control si se ha modificado el texto y, si es así, actualizo los textos sugeridos a través del método UpdateListBox.
#region Entrada de teclado private void this_KeyDown(object sender, KeyEventArgs e) { switch (e.KeyCode) { case Keys.Down: { if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1)) _listBox.SelectedIndex++; e.SuppressKeyPress = true; break; } case Keys.Up: { if (_listBox.Visible && _listBox.SelectedIndex >= 0) _listBox.SelectedIndex--; e.SuppressKeyPress = true; break; } case Keys.Enter: { if (_listBox.Visible) { if (_listBox.SelectedIndex >= 0) { Text = _listBox.SelectedItem.ToString(); SelectAll(); } HideListBox(); e.SuppressKeyPress = true; } break; } } } string _lastText; private void this_KeyUp(object sender, KeyEventArgs e) { if (this.Text != _lastText) { UpdateListBox(); _lastText = this.Text; } } #endregion
#Region "Entrada de teclado" Private Sub this_KeyDown(sender As Object, e As KeyEventArgs) Select Case e.KeyCode Case Keys.Down If _listBox.Visible AndAlso _listBox.SelectedIndex < _listBox.Items.Count - 1 Then _listBox.SelectedIndex += 1 End If e.SuppressKeyPress = True Case Keys.Up If _listBox.Visible AndAlso _listBox.SelectedIndex >= 0 Then _listBox.SelectedIndex -= 1 End If e.SuppressKeyPress = True Case Keys.Enter If _listBox.Visible Then If _listBox.SelectedIndex >= 0 Then Text = _listBox.SelectedItem.ToString() SelectAll() End If HideListBox() e.SuppressKeyPress = True End If End Select End Sub Dim _lastText As String Private Sub this_KeyUp(sender As Object, e As KeyEventArgs) If Me.Text <> _lastText Then UpdateListBox() _lastText = Me.Text End If End Sub #End Region
Por último voy a controlar el evento LostFocus para que no se dispare cada vez que el foco pase al control ListBox, y para ocultar el ListBox cuando efectivamente se produzca. También modificaré el constructor para asociar los controladores de eventos recién creados.
#region Constructor public TextSuggestion(): base() { MaxNumOfSuggestions = 10; _listBox = new ListBox(); KeyDown += this_KeyDown; KeyUp += this_KeyUp; LostFocus += this_LostFocus; _listBox.Click += listBox_Click; } #endregion #region LostFocus protected override void OnLostFocus(EventArgs e) { if (!_listBox.ContainsFocus) base.OnLostFocus(e); } private void this_LostFocus(object sender, EventArgs e) { HideListBox(); } #endregion
#Region "Constructor" Public Sub New() MaxNumOfSuggestions = 10 _listBox = New ListBox() AddHandler KeyDown, AddressOf this_KeyDown AddHandler KeyUp, AddressOf this_KeyUp AddHandler LostFocus, AddressOf this_LostFocus AddHandler _listBox.Click, AddressOf listBox_Click End Sub #End Region #Region "LostFocus" Protected Overrides Sub OnLostFocus(e As EventArgs) If Not _listBox.ContainsFocus Then MyBase.OnLostFocus(e) End If End Sub Private Sub this_LostFocus(sender As Object, e As EventArgs) HideListBox() End Sub #End Region
Ya tenemos la funcionalidad básica del control. En las próximas entradas crearemos un proyecto sobre el que poder probar el control y le añadiremos la posibilidad de poder personalizar el método de selección de sugerencias:
- Windows Forms. TextBox con sugerencias (II) - Creando el proyecto de pruebas
- Windows Forms. TextBox con sugerencias (III) - Personalizar las sugerencias
El código completo de este artículo y los dos siguientes, tanto en C# como en Visual Basic, está disponible en:
TextBox con sugerencias tipo Google. Ejemplos MSDN.
Asier, muchas gracias por tu aporte.
ResponderEliminarTengo una pregunta: ¿cómo puedo extraer el id del registro seleccionado?
Juan Carlos
Me imagino que ya paso mucho tiempo, pero en cuanto lo solucione yo, que tambien estoy trabajando en eso, publicare la respuesta
Eliminar