14 oct 2009

Como acceder a la propiedad Text de un control NumericUpDown

Problema con NumericUpDown y la propiedad Text

Hoy me encontré con un problemita trabajando sobre un formulario que contiene varios NumericUpDown. El tema es que se podía seleccionar un valor, luego borrarlo y el sistema no hacía una validación que indicara al usuario que debía introducir un valor.

Al empezar a hacer pruebas noté que el texto queda vacío, pero el valor no cambia, o sea que si el NumericUpDown tiene un valor de 75 y le borramos el texto, al acceder a la propiedad Value del mismo, ésta nos devuelve 75.

El problema para solucionar ese bug me lo encontré cuando intenté acceder a la propiedad Text, para chequear que la misma no contenga un String vacío... Oh sorpresa, no se puede acceder a esa propiedad porque es privada!!

Para acceder a la propiedad Text aprovecharemos de la herencia de esta clase, NumericUpDown es una subclase de Control, entonces al hacer un cast podemos acceder a la propiedad Text para hacer el chequeo.
Esto nos permite varias soluciones posibles, una que me pareció bastante elegante fue asignar un event handler, o manejador de eventos al evento TextChanged, al cual también accederemos mediante un cast a Control.


Solución:


Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
For Each c As Control In Me.Controls
If TypeOf c Is NumericUpDown Then
AddHandler c.TextChanged, AddressOf NumericUpDownControls_TextChanged
End If
Next
End Sub

Private Sub NumericUpDownControls_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs)
If CType(sender, Control).Text = String.Empty Then
CType(sender, Control).Text = CType(sender, NumericUpDown).Value.ToString()
End If
End Sub



Otra posible solución, más eficiente desde el punto de vista de reusabilidad sería crear un control que herede de NumericUpDown y ya implemente de por si este comportamiento; que a mi entender es una falta de parte del equipo que desarrolló el control.

8 jun 2009

Deshabilitar el Cortar / Copiar de líneas vacías en Visual Studio

Un comportamiento muy molesto de Visual Studio es cuando por error copias o cortas una línea vacía, o sin seleccionar nada... hasta hace un tiempo pensaba que era el comportamiento normal del IDE, pero un día hurgando en la ventana de opciones me encontré con que se puede desactivar.

Evitar copiar sin seleccionar en Visual Studio

7 may 2009

Extendiendo la clase List(Of T)

Hoy necesité exportar el contenido de una Lista a un DataTable, entonces pensé:


  1. Crear una tabla con la estructura necesaria

  2. Recorrer la lista secuencialmente y agregar filas a la tabla


Pero pensando como programador, se me ocurrió que seguramente volviera a necesitar esta función y posiblemente la necesitaría con diferentes tipos de listas.

Como era lógico, pensé en Generics, entonces me creé una función que recibe una System.Collections.Generic.List(Of T) y devuelve un System.Data.DataTable con la siguiente firma:

Public Function ListToDataTable(Of T)(List As System.Collections.Generic.List(Of T)) As System.Data.DataTable

después creé toda la lógica utilizando System.Reflection para examinar las propiedades, y aprovechando los CustomAttributes para poder ocultar propiedades, entonces para hacer un Test, hice una clase con la siguiente estructura:



Public Class MiClase

    Private mId As Integer
    Private mNombre As String
    Private mCantidad As Integer

    <System.ComponentModel.Browsable(False)> _
    Public Property Id() As Integer
        Get
            Return mId
        End Get
        Set(ByVal value As Integer)
            mId = value
        End Set
    End Property

    Public Property Nombre() As String
        Get
            Return mNombre
        End Get
        Set(ByVal value As String)
            mNombre = value
        End Set
    End Property

    Public Property Cantidad() As Integer
        Get
            Return mCantidad
        End Get
        Set(ByVal value As Integer)
            mCantidad = value
        End Set
    End Property

    Public Sub New()

    End Sub

    Public Sub New(ByVal Id As Integer, ByVal Nombre As String, ByVal Cantidad As Integer)
        mId = Id
        mNombre = Nombre
        mCantidad = Cantidad
    End Sub

End Class


como ven, la propiedad Id tiene un CustomAttribute, el System.ComponentModel.BrowsableAttribute seteado con el valor False, o sea que esa propiedad estaría excluida de la exportación.

Todo muy lindo, funcionó a la perfección... pero se me ocurrió algo mejor... usar Extension Methods o métodos de extensión, esa nueva posibilidad que nos brinda el .Net Framework 3.5 de extender clases sin la necesidad de tener el código fuente de la misma.

Usando extension methods sobre la clase System.Collections.Generic.List(Of T)

Entonces creé un módulo llamado ListExtension y le asigné el mismo Namespace de la lista, System.Collections.Generic.



Imports System.Reflection
Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Namespace System.Collections.Generic
    Module ListExtension
        ''' <summary>
        ''' Gets a Datatable with all Browsable properties of T as columns containing all Items in the List.
        ''' </summary>
        ''' <typeparam name="T"></typeparam>
        ''' <param name="List">System.Collections.Generic.List(Of T)</param>
        ''' <returns>System.Data.DataTable</returns>
        ''' <remarks>http://aprendiendonet.blogspot.com</remarks>
        <Extension()> _
        Public Function ToDataTable(Of T)(ByVal List As List(Of T)) As DataTable
            Dim dt As New DataTable()

            Dim tipo As Type = GetType(T)
            Dim members As MemberInfo() = tipo.GetMembers() ' Obtenemos todos los Miembros de la clase correspondiente al tipo T

            For Each m As MemberInfo In members
                If m.MemberType = MemberTypes.Property Then ' Sólo nos interesan las propiedades
                    Dim skip As Boolean = False

                    Dim atts As Object() = m.GetCustomAttributes(GetType(BrowsableAttribute), False) ' Chequeamos si tiene BrowsableAttribute
                    If atts.Length > 0 Then
                        If CType(atts(0), BrowsableAttribute).Browsable = False Then
                            skip = True ' Seteamos un flag para no agregar la columna
                        End If
                    End If

                    If Not skip Then
                        Dim c As DataColumn = Nothing
                        Try
                            c = New DataColumn(m.Name, CType(m, PropertyInfo).PropertyType) ' Nueva columna con el nombre de la propiedad y el tipo de la misma
                        Catch ex As Exception
                            c = New DataColumn(m.Name, GetType(String)) ' En caso de error intento crearla como String
                        End Try
                        dt.Columns.Add(c)
                    End If
                End If
            Next

            For Each itm As T In List ' Recorro la lista y agrego una fila por cada item de la misma
                Dim r As DataRow = dt.NewRow()
                For Each c As DataColumn In r.Table.Columns
                    Dim aux As MemberInfo() = tipo.GetMember(c.ColumnName)
                    If aux.Length > 0 Then
                        r(c.ColumnName) = CType(aux(0), PropertyInfo).GetValue(itm, Nothing)
                    End If
                Next
                dt.Rows.Add(r)
            Next

            Return dt
        End Function

    End Module
End Namespace


Entonces ahora en toda mi solución tengo la posibilidad de exportar mis Listas a DataTable con tan solo llamar a este método de extensión:

Dim auxDT as DataTable = miLista.ToDataTable()

29 abr 2009

Como dejar un log en el Visor de Sucesos de Windows


Todos hemos visto el Visor de Sucesos de Windows, y a veces es una buena alternativa para guardar registros de nuestras aplicaciones, ya que nos evita el tener que definirnos una estructura de logs.

Suele ser muy útil cuando desarrollamos un Servicio de Windows y queremos llevar registro de excepciones o ciertas situaciones... por eso el ejemplo de hoy se trata de crear un log para nuestra aplicación utilizando la clase EventLog del Namespace System.Diagnostics.


Public Sub EscribirEventLog(ByVal Origen As String, ByVal Mensaje As String, ByVal Equipo As String, ByVal TipoDeEntrada As EventTipoDeEntrada)
If Not EventLog.SourceExists(Origen, Equipo) Then ' Si el origen no existe lo creamos
EventLog.CreateEventSource(Origen, "GNSys", Equipo)
End If

Dim eLog As New EventLog("GNSys", Equipo, Origen) ' Instanciamos la clase EventLog
eLog.WriteEntry(Mensaje, TipoDeEntrada, GetSafeShort(111), GetSafeShort(1)) ' Escribimos la entrada
End Sub




En el código anterior, (que por cierto es muy sencillo), en la línea que escribe nuestra entrada de log hay datos que deberemos definir en función de nuestras necesidades, veamos la línea y cómo está formada:

eLog.WriteEntry([Event], LogEntryType, GetSafeShort(111), GetSafeShort(1))

La firma del método WriteEntry es la siguiente:

Public Shared Sub WriteEntry(ByVal source As String, ByVal message As String, ByVal type As System.Diagnostics.EventLogEntryType, ByVal eventID As Integer, ByVal category As Short)

Viendo la siguiente imágen se puede ver qué es cada campo.

Detalles de una entrada en el Visor de Sucesos

28 abr 2009

Como leer y escribir en el Registro de Windows desde una aplicación .NET

Para trabajar sobre el registro de Windows utilizaremos las cases Registry y RegistryKey del Namespace Microsoft.Win32. Con éstas 2 clases podremos realizar todas tareas de lectura y escritura en el registro.
Este es un ejemplo muy sencillo, por lo tanto vamos a crear una clave Key, dentro crearemos un valor Value luego los eliminaremos. Después leeremos todos los Values que se encuentren en HKLM\Microsoft\Windows\CurrentVersion\Run, que son los que indican qué aplicaciones se ejecutan al iniciar Windows para todos los usuarios.

Debajo de estas líneas está el código, uno de los ejemplos más sencillos para leer y escribir en el Registro de Windows.

Trabajar con el Registro de Windows en nuestra aplicación .NET


Imports Microsoft.Win32
Public Class Form1

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
CrearKey()
End Sub

Private Sub CrearKey()
Dim KeyPath As String = "Software\Test"
Registry.CurrentUser.CreateSubKey(KeyPath)
End Sub


Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
CrearValue()
End Sub

Private Sub CrearValue()
Dim KeyPath As String = "Software\Test"
Dim ValueName As String = "TestValue"

Dim key As RegistryKey = Registry.CurrentUser.OpenSubKey(KeyPath, True) ' True indica que se abre para escritura
If key IsNot Nothing Then ' Si key es Nothing significa que no se encontró
key.SetValue(ValueName, "Esto es una prueba", RegistryValueKind.String)
Else
If MessageBox.Show(String.Format("No se encontró la clave 'HKCU\{0}'. Desea crearla?", KeyPath), "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes Then
CrearKey() ' Creamos la clave y volvemos a intentar crear el valor
CrearValue()
End If
End If
End Sub


Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
EliminarValue()
End Sub

Private Sub EliminarValue()
Dim KeyPath As String = "Software\Test"
Dim ValueName As String = "TestValue"

Dim Key As RegistryKey = Registry.CurrentUser.OpenSubKey(KeyPath, True)

If Key IsNot Nothing Then
If Key.GetValueNames().Contains(ValueName) Then ' Buscamos el nombre del valor en la lista de todos los valores de la clave
Key.DeleteValue(ValueName) ' Borramos el valor
Else
MessageBox.Show(String.Format("No se encontró el valor '{0}'.", ValueName))
End If
Else
MessageBox.Show(String.Format("No se encontró la clave 'HKCU\{0}'.", KeyPath))
End If
End Sub

Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
EliminarKey()
End Sub

Private Sub EliminarKey()
Dim KeyPath As String = "Software\Test"

Dim key As RegistryKey = Registry.CurrentUser.OpenSubKey(KeyPath)

If key IsNot Nothing Then
Registry.CurrentUser.DeleteSubKey(KeyPath) ' Borramos la sub clave
Else
MessageBox.Show(String.Format("No se encontró la clave 'HKCU\{0}'.", KeyPath))
End If
End Sub


Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
LeerRegistro()
End Sub

Private Sub LeerRegistro()
Dim KeyPath As String = "Software\Microsoft\Windows\CurrentVersion\Run"
Dim key As RegistryKey = Registry.LocalMachine.OpenSubKey(KeyPath, False) ' Abrimos para sólo lectura

If key IsNot Nothing Then
Dim sb As New System.Text.StringBuilder()

Dim values As String() = key.GetValueNames() ' Obtenemos los nombres de todos los valores en la key
For Each value As String In values
sb.AppendLine(String.Format("{0} > {1} ({2})", value, key.GetValue(value), key.GetValueKind(value).ToString()))
Next

Me.TextBox1.Text = sb.ToString() ' Mostramos el resultado en nuestra TextBox Multilínea
End If
End Sub
End Class


Registro de Windows - Aprendiendo.NET