jueves, 2 de julio de 2020

Listbox con elementos de apariencia diferente

El control Listbox de Windows Forms es bastante útil en determinadas situaciones y muy utilizado también. Una característica que puede ser poco conocida para algunos desarrolladores es la posibilidad de definir las características de cada línea (elemento) mostrada en este control de forma independiente.
Este resultado se puede lograr de un modo bastante sencillo realizando 3 simples pasos:
  - En primer lugar debemos establecer la propiedad DrawMode al valor OwnerDrawFixed o el valor OwnerDrawVariable.
  - En segundo lugar, hay que definir el manejador del evento DrawItem. Aquí podremos dibujar lo que queremos mostrar en cada línea del Listbox de forma individual para cada elemento. Es posible definir el color, tamaño y familia de la fuente, por ejemplo.
  - En último lugar, y sólo en caso de haber seleccionado la opción DrawMode = OwnerDrawVariable, debemos definir el manejador del evento MeasureItem que nos permitirá variar las dimensiones de la línea que ocupa cada elemento de la fuente de datos del ListBox.

A continuación se muestra un poco de código con un ejemplo de lo arriba descrito. En el código se puede ver una clase auxiliar que se utiliza para definir cada uno de los elementos que muestra el control Listbox. En esta clase se incluye la propiedad Fuente que nos permite definir la fuente del texto mostrado en cada línea del control de forma independiente.

Public Class frmFuentes
    Private Const FUENTE_MIN As Integer = 8
    Private Const FUENTE_MAX As Integer = 14
    Dim listaFuentes As List(Of ItemCboFuentes)

    Private Sub frmFuentes_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim fuentesInstaladas As New InstalledFontCollection
        Dim strFamiliasFuentes As New StringBuilder("")
        Dim itemCombo As ItemCboFuentes
        listaFuentes = New List(Of ItemCboFuentes)
        Dim valor As Integer = 0
        For Each familia In fuentesInstaladas.Families
            If valor Mod 2 = 0 Then
                itemCombo = New ItemCboFuentes(familia.Name, valor.ToString())
            Else
                itemCombo = New ItemCboFuentes(familia.Name, valor.ToString(), FUENTE_MAX)
            End If
            listaFuentes.Add(itemCombo)
            valor = valor + 1
        Next
        cboFuente.ValueMember = "Valor"
        cboFuente.DisplayMember = "Texto"
        cboFuente.DataSource = listaFuentes
        lstFuentes.DrawMode = DrawMode.OwnerDrawVariable
        lstFuentes.ValueMember = "Valor"
        lstFuentes.DisplayMember = "Texto"
        lstFuentes.DataSource = listaFuentes
    End Sub

    Private Sub cboFuente_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboFuente.SelectedIndexChanged
        MessageBox.Show("Text: " & cboFuente.SelectedItem.ToString() & " -- " & cboFuente.SelectedValue)
    End Sub

    Private Sub ListBox1_DrawItem(sender As Object, e As DrawItemEventArgs) Handles lstFuentes.DrawItem
        Dim indice As Integer = e.Index
        Dim rectF As RectangleF
        rectF = RectangleF.op_Implicit(e.Bounds)
        e.DrawBackground()
        e.Graphics.DrawString(listaFuentes.Item(indice).Texto, listaFuentes.Item(indice).Fuente, Brushes.Black, RectangleF.op_Implicit(e.Bounds))
        e.DrawFocusRectangle()
    End Sub

    Private Sub lstFuentes_MeasureItem(sender As Object, e As MeasureItemEventArgs) Handles lstFuentes.MeasureItem
        If e.Index Mod 2 <> 0 Then
            e.ItemHeight = e.ItemHeight * 2
        End If
    End Sub

    'Clase auxiliar para definir los elementos que vamos a mostrar en el control Listbox.
    'Esta clase incluye una propiedad Fuente que nos permite definir la fuente de cada br/>     'elemento de forma independiente.
    Private Class ItemCboFuentes
        Private _texto As String
        Public Property Texto() As String
            Get
                Return _texto
            End Get
            Set(ByVal value As String)
                _texto = value
            End Set
        End Property
        Private _valor As String
        Public Property Valor() As String
            Get
                Return _valor
            End Get
            Set(ByVal value As String)
                _valor = value
            End Set
        End Property
        Private _fuente As Font
        Public Property Fuente() As Font
            Get
                Return _fuente
            End Get
            Set(ByVal value As Font)
                _fuente = value
            End Set
        End Property
        Sub New()
            Texto = ""
            Valor = ""
            Fuente = New Font(FontFamily.GenericSansSerif, FUENTE_MIN)
        End Sub

        Sub New(ByVal texto As String, ByVal value As String)
            _texto = texto
            _valor = value
            _fuente = New Font(texto, FUENTE_MIN)
        End Sub

        Sub New(ByVal texto As String, ByVal value As String, ByVal tam As Integer)
            _texto = texto
            _valor = value
            _fuente = New Font(texto, tam)
        End Sub
    End Class

End Class

El código mostrado permite obtener el resultado que se observa en la siguiente imagen.


miércoles, 1 de julio de 2020

Captura de eventos de teclado

Es habitual el desarrollo de aplicaciones que, en algún momento, tengamos que controlar los eventos de teclado. Lo más habitual es tener que controlar la pulsación de alguna tecla para realizar alguna tarea, o simplemente para actuar como atajos de teclado para poder realizar acciones comunes de un modo más ágil.
Para detectar las pulsaciones realizadas dentro de un determinado control debemos definir las operaciones a realizar dentro del manejador del evento correspondiente (keydown, keyup, keypress, etc.) para ese control.

Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress
        If e.KeyChar >= ChrW(48) And e.KeyChar <= ChrW(57) Then
            MessageBox.Show(("TextBox.KeyPress: '" +
                e.KeyChar.ToString() + "' pulsado."))
            Select Case e.KeyChar
                Case ChrW(49), ChrW(57)
                    MessageBox.Show(("TextBox.KeyPress: '" +
                        e.KeyChar.ToString() + "' absorbido."))
                    e.Handled = True
            End Select
        End If
    End Sub

Sin embargo, si queremos detectar la pulsación de las teclas en el formulario debemos hacer algunas modificaciones. En primer lugar, debemos poner a True la propiedad KeyPreview del formulario. Además, debemos definir el código de las acciones a realizar en el manejador de eventos del formulario. De este modo, las teclas pulsadas serán detectadas primero por el formulario y después se pasarán a los distintos controles, salvo que dentro del manejador del evento correspondiente se defina la propiedad e.Handled = true.

Sub Form1_KeyPress(ByVal sender As Object,  ByVal e As KeyPressEventArgs) Handles Me.KeyPress
...
End Sub

Incluso haciendo estos ajustes existen casos en los que las teclas no alfanuméricas pueden no ser detectadas. Para estos casos es recomendable sobreescribir la función ProcessCmdKey. En este caso no es necesario establecer la propiedad del formulario KeyPreview a True. Si esta función devuelve True el evento detectado será consumido por esta función y no será recibido por el control que tenga el foco en ese momento.

Protected Overrides Function ProcessCmdKey(ByRef msg As Message, ByVal keyData As Keys) As Boolean
    If keyData = Keys.Up Then
        MessageBox.Show("Flecha arriba pulsada")
        Return True
    End If
    If keyData = Keys.Down Then
        MessageBox.Show("Flecha abajo pulsada")
        Return True
    End If

    Return MyBase.ProcessCmdKey(msg, keyData)
End Function