martes, 23 de febrero de 2010

El Nuevo Maestro Detalle de Visual Basic Express 2005

Introducción
Como arte de magia se podría definir el nuevo Maestro-Detalle de Visual Basic 2005, pues si retrocedemos un poco en el tiempo en sus versiones anteriores ninguna de estas pudo superar todos aquellos problemas que se generaban a la hora de intentar enviar un registro en el extremo del detalle, dicho repositorio era representado por una tabla contenida en un control de tipo Grid. El único remedio era el hacer trucos de refrescamiento en el control para que este admitiera el valor del campo relacionado, y de esta forma en el momento de grabarlo en la base de datos este fuera aceptado y no generara ningún error en tiempo de ejecución. Otro problema muy frecuente en versiones anteriores era que los controles de tipo Grid no proporcionaban grandes funciones como el uso de controles de tipo Combo y otros más que de alguna forma eran necesarios. Recuerdo que en este mismo sitio ya hace muchos años se presento un truco innovador en la versión 5.0 de Visual Basic que consistía en utilizar controles de lista el cual se podían fusionar de alguna manera para que estos en tiempo de ejecución adquirieran la forma de un combo box dentro de un grid al estilo del famoso Microsoft Access.
Todo estos problemas en el diseño y construcción de los formularios de tipo Maestro-Detalle se han acabado con la salida de Visual Basic 2005, pues esta herramienta tan sofisticada permite el diseño de estos formularios sin escribir una sola línea de código, únicamente arrastrando con el Mouse los campos que se requieren en el formulario desde la ventana del Data Source, un nuevo elemento presente en la conectividad a orígenes de datos, el cual facilita las conexiones mediante un asistente y al mismo tiempo simplifica los pasos de conexión en uno solo, produciendo un nuevo elemento denominado TableAdapter.
Este artículo esta basado en el uso de los mecanismos para desarrollar formularios Maestros-Detalles tan fáciles como arrastrar y pegar archivos en el entorno de Windows Explorer de Microsoft.
Primeros pasos
En el diseñador, aparece un el menú de Data la opción agregar un nuevo Data Source. Se ejecuta esta opción y a continuación se visualiza un asistente el cual solicita el tipo de origen de datos. A continuación muestro el menú para ejecutar la opción, junto con la primera pantalla del asistente :
La figura 1.0 Muestra el Menú Data
La figura 2.0 Muestra la primera pantalla del asistente de selección de datos
Una vez ejecutado el asistente, de las tres opciones mostradas en la primera pantalla, seleccionar la que se denomina Database, luego hacer clic al siguiente paso. A continuación muestro el segundo paso para proseguir la configuración :
La figura 3.0 Muestra la información de conexión que utilizara la aplicación
El mecanismo que permite crear conexiones a una base de datos en una aplicación es muy sencillo, solo basta con hacer clic en el botón con el título New Connection. A continuación muestro el formulario que permite crear o cambiar la información de conexión :
La figura 4.0 Muestra las opciones de conexión
En el formulario anterior, se encuentran algunas opciones tales como el cambiar el origen de datos, examinar el sitio del archivo de la base de datos, la forma de autenticación, prueba de conexión etc. Todos estos tienen uso intuitivo el cual no mencionare por su facilidad de uso y parecido en otros ambientes de conectividad a bases de datos.
Una vez creada la conexión a la base de datos, será necesario dos pasos finales, el primero seleccionar las tablas u otros elementos requeridos de la base de datos dentro de la aplicación y para finalizar guardar la información de conexión. A continuación muestro los dos pasos finales:
La figura 5.0 Muestra los objetos disponibles de la base de datos
La figura 6.0 Muestra el archivo de configuración para ser guardado
Una vez creada la conexión de la base de datos se realizarán algunas pequeñas modificaciones al Dataset, las cuales tienen que ver con propiedades en los campos de las tablas que serán utilizadas en el formulario. A continuación muestro el explorador de proyectos :
La figura 7.0 Muestra el contenido del proyecto
Para realizar las modificaciones al Dataset será necesario hacer doble clic sobre el archivo con extensión XSD. A continuación se muestra el siguiente diagrama con las tabla de la base de datos:
La figura 8.0 Muestra el diseño relacional de las tablas usadas en el dataset
Cada campo contenido dentro de estas tablas contienen propiedades que pueden ser modificadas, tal caso es por ejemplo los títulos de los campos, si permiten valores nulos o no y un sin número de propiedades que pueden ser de mucha utilidad, el mecanismos para hacer estas modificaciones es primeramente seleccionando el campo y luego haciendo clic en la ventana propiedades o haciendo clic con el botón derecho. A continuación muestro un ejemplo :
La figura 9.0 Muestra la propiedad Caption modificada
Como se puede apreciar la lista de propiedades permite personalizar aun más los campos dentro de las tablas, esta es una características ideal en el momento de arrastrar y pegar los campos dentro de un formulario, ya que con esta operación es posible que se cree de forma automática un control de tipo Label que permita reproducir el Caption de un campo.
Posteriormente a la modificación de la propiedad Caption en algunas de las tablas presentadas en el Dataset, viene lo mejor, arrastrar el campo al formulario e irlos ubicando de acuerdo a las necesidades del diseño. A continuación muestra una figura visualizando los campos de cada una de las tablas que pueden ser utilizadas en el proyecto :
La figura 10.0 Muestra el Data Source con las tablas y campos respectivos
Como se puede observar el nombre del Dataset northwndDaset expone las tablas y a su vez estas los campos que pueden ser utilizados en el proyecto, como se puede observar también el campo Picture muestra un icono el cual indica que no puede ser usado, pero es resuelto haciendo clic sobre el mismo y luego haciendo clic con el botón derecho del Mouse y seleccionar el tipo de objeto que es. A continuación muestro una figura con el ejemplo :
La figura 11.0 Muestra los tipos de objetos disponibles
Cuando se arrastra un campo al formulario se crea un objeto que permite la navegación entre registros, el cual se define como un nuevo control para Visual Basic 2005, dicho control tiene el nombre de BindingNavegator, el cual puede ser personalizado, insertándole más botones y otros elementos de mucha utilidad según sean las necesidades. A continuación muestro BindingNavegator editándolo al momento de insertar una opción para cerrar el formulario, simplemente haciendo clic con el botón derecho del Mouse se presentara el siguiente menú contextual y en el cual se puede seleccionar el objeto requerido:
La figura 12.0 Muestra la barra de opciones personalizada
El siguiente formulario es producido luego de todos los pasos que se han ido explicando a lo largo del artículo. Pero hay algo que se requiere mencionar que en todo este proceso de diseño no fue necesidad escribir código a excepción de programar el botón cerrar del formulario. Toda la codificación que el asistente inteligente generó se encuentra en archivos que no están a la vista pero que con hacer un clic en el botón (Mostrar todo), opción del explorador de proyectos, se podrán visualizar dichos archivos.
Finalmente posterior a todo lo antes expuesto y explicado el producto resultante es un formulario elegante, intuitivo de utilizar, fácil de mantener y ligero de ejecutar. A continuación muestro el producto final:
La figura 13.0 Muestra el producto final
Para finalizar, el nuevo y mejorado control Grid, posee elementos sofisticados en la utilización de objetos dentro del mismo, como se puede observar en la figura anterior el campo de Proveedor contiene un tipo ComboBox  que permite mostrar la descripción del proveedor en vez del código, para que el Grid pueda utilizar dichos elementos será necesario en tiempo de diseño seleccionarlo y haciendo clic en el botón derecho del Mouse y luego seleccionado la opción editar columnas se dispondrá posteriormente de un asistente que permite configurar y a la vez personalizar las columnas del Grid, por ejemplo el ancho, el tipo de letra, color, tipo de objeto utilizado dentro de la cuadricula etc.
Para conseguir el efecto Maestro-Detalle será necesario arrastrar la tabla Products al formulario al momento de arrastrarla, este efecto producirá de forma automática la sincronización entre la tabla principal y la tabla secundaria, permitiendo de esta manera la relación Maestro-Detalle. Una vez realizado esto, automáticamente se generará el control Grid. Se recuerda que todos los elementos presente en el formulario fueron arrastrado en dirección al formulario.
Para cambiar la información de conexión de la base de datos en la aplicación solo basta hacer doble clic en el archivo app.config, el cual se encuentra en el explorador de proyectos. Dicho archivo es un script XML, el cual almacena entre otras cosas la información de conexión de la base de datos, esta es una implementación nueva en Visual Basic.
En el caso de este ejemplo fue necesario editar la columna que contiene el campo de proveedor, cambiando el tipo de objeto a visualizar (ComboBox) sobre el campo, y enlazándolo a la tabla que contiene la descripción al campo llave. A continuación muestro el formulario que permite realizar estas operaciones:
La figura 14.0 Muestra el editor de columnas de un objeto DataGrid
En la categoría Data se definen elementos como por ejemplo, el campo a utilizar para mostrar los datos, el campo principal que permitirá pasar el valor que viene del campo llave al campo secundario. En la categoría Design se puede observar el tipo de columna definida, así como el nombre de esta.
Son un sin número de elementos que pueden ser modificados dentro del asistente para editar columnas, pero que este artículo no los abarca ya que su objetivo esta centrado en el diseño de un formulario Maestro-Detalle y elementos básicos utilizados para su creación. Quizás en un futuro en algún otro artículo toquemos temas mas específicos en el uso de algunos controles de esta nueva generación de controles para Visual Basic
2005.
Quiero finalizar agradeciendo una vez más a todas y aquellas personas que me han escrito consultándome información en el uso o solución en algunas situaciones reales que se presentan en el momento de desarrollar e implementar software en sus trabajos. Espero que este primer artículo del 2006 y todo aquellos escritos en 2005 puedan seguir sirviendo de una u otra forma de ideas y soporte para dar soluciones reales.

Leer/Grabar Archivos .ini usando VB.NET (Tip)


Código:
Imports System.Runtime.InteropServices

Public Class
CIniClass

    Private m_Ini As String
   
Private Declare Function GetPrivateProfileStringKey Lib "kernel32" Alias
_
    "GetPrivateProfileStringA" (ByVal lpApplicationName As
String, ByVal _
    lpKeyName As String, ByVal lpDefault As
String, ByVal lpReturnedString _
    As String, ByVal nSize As
Integer, ByVal lpFileName As String) As Integer

    Private
Declare Function WritePrivateProfileString Lib "kernel32" Alias _
 
  "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal
_
    lpKeyName As String, ByVal lpString As String, ByVal
lpFileName As String) As Integer
    Property Archivo() As
String
        Get
       
    Archivo = m_Ini
        End Get
 
      Set(ByVal value As String)
       
    m_Ini = value
        End Set
 
  End Property

    'Leer una llave de un archivo
.ini
    Public Function LeeIni(ByVal Seccion As String, ByVal
Llave As String) As String
        Dim lret As
Long
        Dim ret As String

   
    ret = New String(CChar(" "), 255)

     
  lret = GetPrivateProfileStringKey(Seccion, Llave, "", ret, Len(ret),
m_Ini)

        If InStr(ret, Chr(0)) Then
 
          ret = Left$(ret, Len(ret) - 1)
 
      End If

        LeeIni =
ret
    End Function

    'grabar una llave a un
archivo ini
    Public Sub GrabaIni(ByVal Seccion As String,
ByVal Llave As String, ByVal Valor As String)

     
  Dim lret As Long

        lret =
WritePrivateProfileString(Seccion, Llave, Valor, m_Ini)
   
End Sub
End Class
Y despues un sencillo formulario con un boton rasquiento para leer y grabar un valor :
Código:
Public Class Form1

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

        Dim Ini As
CIniClass

        Ini = New CIniClass
 
      Ini.Archivo = "win.ini"
     
  MsgBox(Ini.LeeIni("Hex Workshop", "Path"))
     
  Call Ini.GrabaIni("prueba_net", "mensaje", "hola mundo")
 
  End Sub
End Class

Transacciones con .NET

Introducción
Concepto de transacción: Es una secuencia de operaciones realizadas como una sola unidad de
trabajo.
Las propiedades de las transacciones se las conoce como ACID: Atomicidad, Coherencia, Aislamiento, Durabilidad.
Atomicidad: Una transacción debe ser una unidad atómica de trabajo, o se hace todo o no se hace nada.
Coherencia: Debe dejar los datos en un estado coherente luego de realizada la transacción.
Aislamiento: Las modificaciones realizadas por transacciones son tratadas en forma independiente, como si fueran un solo y único usuario de la base de datos.
Durabilidad: Una vez concluida la transacción sus efectos son permanentes y no hay forma de deshacerlos.
Ahora, que tiene que ver esto con mis datos?? Pues bien, todas las instrucciones que normalmente escribo en los procedimientos son update, insert, delete de una o más tablas y si no uso procedimientos almacenados uso comandos desde mi programa; pues bien, entonces sería algo así

Update Persona set Sueldo=sueldo * 1.5 
 Update Grupos set estado=1 where estado=0  
 
Pero nos encontramos con un problema, que las dos sentencias deben hacerse siempre unidas, como si fueran una sola pues perteneces a una actualización de sueldos. Pero que pasa si se realiza la primera operación y no la segunda. ¡Huy! Que rabia, o que pasa si solo se realiza parte de la primera, más ¡Huy! Pues no se sabe hasta que punto se hizo o no se hizo nada, pues nada hay que me garantice esto. Aquí surgen las transacciones. 

 Begin Tran 
  Update Persona set Sueldo=sueldo * 1.5 
  Update Grupos set estado=1 where estado=0 
 Commit Tran 
 
Al encerrar en una transacción decimos que se realice todo o no se realice nada (atomicidad) pues si surge algún error se deshace todo lo realizado anteriormente en la transacción.

Ahora desde vb.NET
(Comandos independientes)
Teniendo una base de datos llamada Ejemplo y una tabla de nombre persona que tiene los siguientes campos

Codigo
varchar(10)
Nombres varchar(50)
Sueldo int
Estado
varchar(1)
En un formulario que posee un botón escribimos lo siguiente en el evento clic del botón, sin olvidarse hacer antes el imports a system.data.sqlClient:

Dim Conn As  SqlConnection = New  SqlConnection("Data Source=INFORM77;Initial Catalog=EJEMPLO;User Id=sa") Conn.Open() 
Try 
 Dim  Comando As New  SqlClient.SqlCommand("INSERT INTO PERSONA (codigo, nombres, sueldo, estado) Values('JG1','JOSE',200,'A')", Conn ) 
Comando.ExecuteNonQuery() 
Comando = New  SqlClient.SqlCommand("INSERT INTO PERSONA (codigo, nombres, sueldo, estado) Values('JG2','LUIS',180,'B')", Conn) 
Comando.ExecuteNonQuery() 
Comando = New  SqlClient.SqlCommand("INSERT INTO PERSONA (codigo, nombres, sueldo, estado) Values('JG3','PEDRO',400,'A')", Conn) 
Comando.ExecuteNonQuery() 
Catch  ex As  Exception 
MsgBox(ex.Message) 
End Try 
Conn.Close() 
MsgBox("Datos Ingresados")
Como vemos añadiremos en la tabla persona 3 registro, pero si quisiéramos convertirle a transacción para que se ejecuten todos o ninguno si encuentra un error deberíamos hacer lo siguiente.
Fíjese en los datos para el campo código pues ahora son “tr1”, “tr2”, “tr3”.

Dim Conn As        SqlConnection = New        SqlConnection("Data Source=INFORM77;Initial Catalog=EJEMPLO;User Id=sa") Conn.Open() 
Dim  myTrans As  SqlTransaction 
Dim  Comando As  SqlClient.SqlCommand 
myTrans = Conn.BeginTransaction() 
Try 
Comando = New  SqlClient.SqlCommand("INSERT INTO PERSONA (codigo, nombres, sueldo, estado) Values('tr1','JOSE',200,'A')", Conn) 
Comando.Transaction = myTrans 
Comando.ExecuteNonQuery() 
Comando = New  SqlClient.SqlCommand("INSERT INTO PERSONA (codigo, nombres, sueldo, estado) Values('tr2','LUIS',180,'B')", Conn) 
Comando.Transaction = myTrans 
Comando.ExecuteNonQuery() 
Comando = New  SqlClient.SqlCommand("INSERT INTO PERSONA (codigo, nombres, sueldo, estado) Values('tr3','PEDRO',400,'A')", Conn) 
Comando.Transaction = myTrans 
Comando.ExecuteNonQuery() 
myTrans.Commit() 
MsgBox("Datos Ingresados") 
Catch  ex As  Exception 
myTrans.Rollback() 
MsgBox(ex.Message) 
End Try 
Conn.Close()
Ahora al ejecutar insertará los nuevos 3 registros sin novedad, pero lo hará usando transacciones para lo cual iniciamos la transacción con myTrans = Conn.BeginTransaction() y finalizamos con myTrans.Commit() y en caso de algún error ejecutamos myTrans.Rollback() para cancelar todo lo realizado en la transacción. 
Para comprobar que la transacción se cancela al encontrar un error cambiamos los valores del campo código por tr1 por ab1 y tr2 por ab2, dejando tr3 en el tercer registro pues así dará un error de clave duplicada porque ya existe un registro con esta clave previamente grabada.
Al ejecutar nos saldrá un mensaje que indica que existió una violación de primary key. Si verificamos los datos, no habrá ningún registro añadido pues como está en una transacción o se agregan todos o no se agrega ninguno.

Ahora con DataSets
En un formulario tenemos 2 botones Grabar y LeerDatos además de un DataGrid asi:
Ahora vamos a crear una clase llamada persona que ejecute las dos acciones de los botones, aquí está el código:
Imports        System.Data.SqlClient Public Class  Persona 
Public Function  Recuperar() As  DataSet 
   'definimos la coneccion 
   Dim Conn As  SqlConnection = New  SqlConnection("Data Source=INFORM77;Initial Catalog=EJEMPLO;User Id=sa") 
   'creamos el data adapter con la instruccion select a recuperar 
   Dim  adapter As New  SqlDataAdapter("Select * from PERSONA", Conn ) 
   'abrimos la coneccion 
Conn.Open() 
   'creamos el dataset donde recuperaremos los datos de la base de datos 
   Dim  ds As  DataSet = New  DataSet 
   'recuperamos los datos a travez del adapter 
adapter.Fill(ds, "PERSONA") 
   'retornamos los datos 
   Return  ds 
End Function 
  
Public Sub  Grabar( ByVal  ds As  DataSet) 
   'definimos la coneccion 
   Dim Conn As  SqlConnection = New  SqlConnection("Data Source=INFORM77;Initial Catalog=EJEMPLO;User Id=sa") 
   'abrimos la coneccion 
Conn.Open() 
   'creamos el data adapter con la instruccion select a recuperar 
   Dim  adapter As New  SqlDataAdapter("Select * from PERSONA", Conn ) 
   'Creamos e inicimos la transaccion 
   Dim  Tran As  SqlTransaction = Conn.BeginTransaction 
   'asignamos la transaccion al comando Select del adapter 
adapter.SelectCommand.Transaction = Tran 
   'construimos los demas comandos del adapter (DELETE, INSERT, UPDATE) 
   Dim  X As New  SqlCommandBuilder(adapter) 
   Try 
      'Actualizamos el DataSet 
adapter.Update(ds, "PERSONA") 
      'Confirmamos la transaccion 
Tran.Commit() 
'mensaje final 
MsgBox("Datos grabados con éxito") 
Catch  Ex As  SqlException 
      'variable para el mensaje 
Dim  men As String 
'configuracion del mensaje de acuerdo al numero de error devuelto por la MRDB 
If  ex.Number = 8152 Then 
men = "Existen datos demasiados extensos, corrija el problema y vuelva a intentar" 
ElseIf  ex.Number = 2627 Then 
If  ex.Message.IndexOf("PRIMARY") <> -1 Then 
men = "Error por intentar grabar valores duplicados en campos clave, corrija el problema y vuelva a intentar"

   ElseIf  ex.Message.IndexOf("UNIQUE") <> -1 Then 
men = "Error por intentar grabar valores duplicados en campos de valores únicos, corrija el problema y vuelva a intentar" 
Else 
men = "Error general en la base de datos" 
End If 
ElseIf  ex.Number = 515 Then 
men = "Algunos datos no han sido ingresados y son necesario para completar la operación, corrija el problema y vuelva a intentar" 
Else 
men = "Error general en la base de datos" 
      End If 
'cancelamos la transaccion 
Tran.Rollback() 
'Indicamos el mensaje 
Throw New  Exception(men) 
Catch  Ex As  DBConcurrencyException 
'cancelamos la transaccion 
Tran.Rollback() 
'Indicamos el mensaje 
Throw New  Exception("Lo siento, los datos fueron actualizados por otro usuario") 
Catch  Ex As  Exception 
'Indicamos el mensaje 
Throw New  Exception("Error: " & EX.Message) 
End Try 
End Sub
 
Luego en el formulario creado con los botones y el grid escribimos el siguiente código 

Botón Leer
     Dim        dt As New        Persona Grid.DataSource = dt.Recuperar 
Botón Grabar
               Dim        dt As New   Persona Try 
      dt.Grabar(Grid.DataSource) 
      Grid.DataSource = dt.Recuperar 
   Catch  ex As  Exception 
      MsgBox(ex.Message, MsgBoxStyle.Critical) 
   End Try 
Load_Form (Carga del formulario)
               Dim        dt As New   Persona    Grid.DataSource = dt.Recuperar 

De esta forma podemos grabar y leer los datos del grid en forma transaccional, es decir que si por algún motivo existiese un error se cancelarán todas las actualizaciones. Además esto nos es bien recomendable cuando existe concurrencia de varios usuarios sobre el mismo grupo de registros pues graba solo los registros actualizados del primer usuario que ejecuta el grabar y para los demás usuarios no graba ningún registro, que si no se lo hace en forma transaccional grabaría una parte de los registros y otros no.
Para esto hay que tomar en cuenta que el manejo de transacciones debe hacerse en capa de reglas de negocio (nunca en la capa UI o en la de Datos).

Usando D.T.C.
Hasta aquí hemos formado transacciones a nivel de base de datos y desde vb.NET su manejo cuando usamos comandos o cuando usamos Datasets. Ahora vamos un paso más allá, usaremos DTC (Coordinador de Transacciones Distribuidas), aprovechando el uso de COM+ y aprenderemos a
interactuar desde .NET.
Veamos un escenario. En un banco yo tengo un Depósito configurado como una transacción pues al depositar yo actualizo y creo registros en alrededor de 6 tablas. Así mismo yo tengo un Retiro configurado como otra transacción y también actualizo y creo registros en 6 tablas más. Pero que pasa cuando deseo hacer otro objeto llamado Traspaso de Dinero en donde está involucrado un retiro y un depósito. Pues, se complica la cosa porque son dos transacciones diferentes y una transacción no puede estar embebida en otra transacción y pero aún si complicamos el escenario cuando el depósito se realiza en SQL y el retiro el Oracle. ¡Huy! Como hago una transacción que al hacer Rollback deje en su esto inicial, pues esto si que está difícil, pues cuando se hacer una transacción que encierra mas transacciones lo visto hasta ahora casi casi no podemos emplear al menos que seamos muy bueno para el uso de bandera de estado y para interoperar entre sistemas.
Para esto hay que crear una clase especial que cumpla ciertas características:

  • Debe tener nombre seguro (Strong Name)
  • Debe poseer dentro del AssemblyInfo las etiquetas
  < Assembly : ApplicationName("Ejemplo_COM_NET")>
    < Assembly : AssemblyKeyFile("c:\key\demo.snk")>  
  • Debe heredar de ServicedComponent
  • Antes del nombre de la clase debe escribirse
  <Transaction(TransactionOption.Required)> Public class Persona
Nota: Todos los métodos de la clase se vuelven transaccionales, para ello hay que escribir <AutoComplete()> antes de la declaración de cada uno. Esto significa que cuando el método termina sin novedad envía un mensaje de terminación y cuando termina por algún error éste envía automáticamente al DTC un mensaje de error para que cancele las operaciones.
Luego de todo hay que registrar la clase en COM+ usando Regsvcs.exe , o al usar la primera vez lo hace automáticamente pero si se tiene permisos de administrador.
Pasemos a transformar nuestra código de actualización del DataSet en una clase COM+.
Nos creamos un proyecto nuevo dentro de la solución que tenemos.
Luego nos creamos un par de llaves (pública y privada para hacer de nuestro nuevo assembly uno con nombre seguro), para esto salimos al símbolo del sistema de visual studio y nos creamos un directorio en la raiz de C:\ (solo por facilidad)
  Md key    ‘crea un directorio llamado key
  Cd key    ‘cambia al Nuevo directorio
  Sn –k demo.snk   ‘crea el archive que contiene las klaves
 
Creamos primero una referencia a: System.EnterpriseServices
Luego Ingresamos al archivo AssemblyInfo de nuestro nuevo proyecto y escribimos el imports a la referencia anterior como primera línea.
 Imports System.EnterpriseServices 
Luego en las etiquetas de los atributos del ensamblado hay que escribir los 2 atributos exigidos para lo que necesitamos:
 < Assembly : ApplicationName("Ejemplo_COM_NET")>
 < Assembly : AssemblyKeyFile("c:\key\demo.snk")>

 
El ApplicationName es el nombre con el que se registrará en el DTC
El AssemblyKeyFile indica el nombre del archivo generado con las claves pública y privada usadas para crear un assembly con nombre seguro.
La clase quedaría tal como esta en el siguiente código:
Imports        System.EnterpriseServices Imports  System.Data.SqlClient 
  
<Transaction(TransactionOption.Required)> Public Class GrabandoCom 
Inherits  ServicedComponent 
<AutoComplete()> Public Sub Grabando( ByVal  ds As  DataSet) 
 'definimos la coneccion 
Dim Conn As  SqlConnection = New  SqlConnection("Data Source=INFORM77;Initial Catalog=EJEMPLO;User Id=sa;Password=''") 
 'abrimos la coneccion 
 Conn.Open() 
'creamos el data adapter con la instruccion select a recuperar 
 Dim  adapter As New SqlDataAdapter("Select * from PERSONA", Conn ) 
 'construimos los demás comandos del adapter (DELETE, INSERT, UPDATE) 
 Dim  X As New SqlCommandBuilder(adapter) 
Try 
  'Actualizamos el DataSet 
  adapter.Update(ds, "PERSONA") 
 Catch  Ex As  SqlException 
'variable para el mensaje 
  Dim  men As String 
'configuracion del mensaje de acuerdo al numero de error devuelto por la MRDB    
  If  ex.Number = 8152 Then 
   men = "Existen datos demasiados extensos, corrija el problema y vuelva a intentar" 
  ElseIf  ex.Number = 2627 Then 
   If  ex.Message.IndexOf("PRIMARY") <> -1 Then 
    men = "Error por intentar grabar valores duplicados en campos clave, corrija el problema y vuelva a intentar" 
 

ElseIf  ex.Message.IndexOf("UNIQUE") <> -1 Then 
    men = "Error por intentar grabar valores duplicados en campos de valores únicos, corrija el problema y vuelva a intentar" 
Else 
    men = "Error general en la base de datos" 
   End If 
  ElseIf  ex.Number = 515 Then 
   men = "Algunos datos no han sido ingresados y son necesario para completar la operación, corrija el problema y vuelva a intentar" 
Else 
   men = "Error general en la base de datos" 
  End If 
 Throw New Exception(men) 
  Catch  Ex As  DBConcurrencyException 
 Throw New Exception("Lo siento, los datos fueron actualizados por otro usuario") 
  Catch  Ex As  Exception 
 Throw New Exception("Error: " & EX.Message) 
End Try 
End Sub 
End Class 
Como se puede observar, no existe movimiento ni código de transacciones pues esto se encargará automáticamente del DTC.
Luego en el proyecto de anterior donde teníamos la clase persona cambiamos la programación, creando antes una referencia al nuevo proyecto que acabamos de escribir.
El código del método grabar se reemplazo por este:
 Public Sub        Grabar( ByVal        ds As        DataSet)  Dim  dtc As New Datos.GrabandoCom 
Try 
  dtc.Grabando(ds) 
 Catch  ex As  Exception 
  Throw New Exception(ex.Message) 
 End Try 
 End Sub 
Aquí se crea una variable de tipo Datos.GrabandoCom que es el nombre del proyecto seguido del método GrabandoCom.
Eso es todo, ahora las transacciones son automáticas y quien es encargado es el DTC. Puede estar las conexiones a varias bases de datos de diferente o igual tipo más todo se hará Commit o Rollback, todo como una sola y única transacción con todas las prestaciones de COM+.
Luego de ejecutar la pantalla que contiene el Grid se puede comprobar que las transacciones si se ejecutan y es más, al ver el monitor de COM+ comprobamos que todo está Ok.
Para ver que se ha registrado nuestra dll entraremos por:
  • Inicio, Panel de Control, Herramientas Administrativas, Servicios de
    Componentes
  • Raiz de Consola, Servicios de Componentes, Equipos, Mi PC, Aplicaciones
    COM+

    • Alli debe estar nuestra aplicación registrada

Y más abajo en estadísticas de transacciones se puede ver el registro de cuantas transacciones se han registrado, tanto las concluidas bien como las no concluidas.

lunes, 22 de febrero de 2010

Formulario de Login en VS 2005

Introducción:

Esto es algo que mucha gente pregunta, pero que no tenía por aquí publicado, así que ya era hora.
El tema es que tenemos una aplicación de tipo "desktop" (Windows.Forms) en la que necesitamos que el usuario introduzca una clave para poder usarla. El problema con el que se encuentran algunos de los que intentan hacer esto es que escriben el código en el formulario principal y si la clave no es correcta, lo cierran, pero debido a como maneja Visual Studio .NET el tema de los formularios, al cerrar ese formulario principal, se cierra toda la aplicación... ¡incluso si la clave es correcta!
La solución es crear un formulario para comprobar esa clave, a ese formulario lo llamamos justo al iniciarse la aplicación, de forma que si la clave introducida es correcta, se muestre el formulario principal, y si no es correcta, cerramos la aplicación.

Crear el proyecto de ejemplo:

Los pasos que tenemos que dar para crear la aplicación de ejemplo son los siguientes:
  1. Crea un nuevo proyecto del tipo Windows.Forms (Aplicación para Windows).
  2. Tendremos un formulario llamado Form1, por simplicidad vamos a dejarle ese mismo nombre (Form1).
  3. Añade a ese formulario una etiqueta en la debes escribir lo que quieras, simplemente es para que sepas que es el formulario principal de tu aplicación. Por supuesto en este formulario es donde tendrás que hacer todo lo que tu aplicación deba hacer.
  4. Añade un segundo formulario al que le darás el nombre FormAcceso, inicialmente se llamará Form2, pero si le cambias el nombre justo al añadirlo, será más cómodo.
  5. En ese formulario de acceso, que será el que usemos para pedir la clave, añade una etiqueta, una caja de textos y dos botones.
  6. Modifica la propiedad FormBorderStyle para que tenga el valor FixedDialog.
  7. Asigna un valor False a las propiedades ControlBox, MaximizeBox y MInimizeBox.
  8. A la caja de textos dale el nombre txtClave y si no quieres que se vea la clave mientras se escribe, asigna un * a la propiedad PasswordChar.
  9. A los botones, le asignas los nombres btnAceptar y btnCancelar, y los textos Aceptar y Cancelar
    respectivamente.
  10. Sitúa esos controles donde más te guste, por ejemplo, puede tener un aspecto como el mostrado en la figura 1:
Figura 1. El formulario para la clave de acceso
Figura 1. El formulario para la clave de acceso
  1. Haz dobleclick en el botón de Cancelar y sustitúyelo por el siguiente código, en el que asignamos el valor Cancel al valor devuelto por la propiedad DialogResult que es el valor que devuelve el método
    ShowDialog de ese formulario (más abajo verás cómo se usa), en lugar de cerrar el formulario, lo ocultamos para que todo funcione mejor.
Private Sub btnCancelar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                Handles btnCancelar.Click
    Me.DialogResult = DialogResult.Cancel
    Hide()
End Sub
  1. Haz dobleclick en el botón Aceptar y escribe el código de más abajo, en el que hacemos la comprobación de si la clave escrita es la correcta, en este caso 123456, si es así, asignamos un valor OK y si no lo es, asignamos cualquier otro valor, ya que en nuestro código de validación, daremos como que la clave es correcta si tiene el valor OK. Aquí también ocultamos el formulario de las claves con Hide para que continúe la ejecución del programa y vuelva al código desde el que se ha mostrado este formulario.
Private Sub btnAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                Handles btnAceptar.Click
    If txtClave.Text = "123456" Then
        Me.DialogResult = DialogResult.OK
    Else
        Me.DialogResult = DialogResult.No
    End If
    Hide()
End Sub
  1. En ambos casos, se supone que los nombres de los controles son como te he comentado, si no es así, pues deberás cambiar los nombres de los controles, así que, te recomiendo que para este ejemplo uses los nombres que te indico.
  2. Una vez hecho esto, ya tenemos el código del formulario que pide la clave de acceso.
  3. Tal como está el código solo tendrás un intento para escribir correctamente la clave, después veremos cómo cambiar el código para que tengas el número de intentos que quieras.
  4. Ahora vamos a modificar el código del formulario principal, aunque lo que tienes que escribir puede estar en cualquier otro formulario, lo recomendable es que esté en el que haga de formulario principal.
  5. Añade este código al formulario principal (Form1):
Public Class Form1
    Public Shared Sub Main()
        Application.EnableVisualStyles()
 
        Dim fAcceso As New FormAcceso
        If fAcceso.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
            fAcceso.Close()
            Application.Run(New Form1)
        End If
    End Sub
 
End Class
  1. Con esto lo que hacemos es cargar el formulario que pide la clave, y si escribes la clave correcta, se cargará el formulario principal (Form1), si no es correcta, el valor devuelto por ShowDialog será distinto de OK, por tanto no se carga en memoria el formulario principal y se acabará la aplicación.
    Para cargar el formulario principal usamos Application.Run al que le pasamos el formulario que queremos que se utilice como inicio, es decir, ese formulario será el que mantenga la aplicación abierta, en cuanto se cierre el formulario principal (Form1) la aplicación finalizará.
  2. Lo siguiente que debemos hacer es decirle al Visual Basic que queremos que nuestra aplicación empiece por el método este que acabamos de escribir.
  3. En el menú Proyecto, seleccionamos Propiedades... (en realidad te mostrará Propiedades de y el nombre que le has dado al proyecto), esa misma opción la puedes conseguir al pulsar con el botón derecho del ratón en el proyecto del Explorador de soluciones, además puedes pulsar en el icono My Project que está también en el Explorador de soluciones. Una vez seleccionadas las opciones, te mostrará una ficha como la de la figura 2. Y tendrás que desmarcar la opción Habilitar marco de trabajo de la aplicación, de esa forma, en la lista desplegable que hay debajo de Objeto inicial, podrás seleccionar Sub Main, tal como te muestro en la figura 2:
Figura 2. En las propiedades del proyecto le indicamos que empiece con el código de Sub Main
Figura 2. En las propiedades del proyecto le indicamos que empiece con el código de Sub Main
  1. El problema que tenemos ahora es que los estilos de Windows XP ya no estarán habilitados por defecto, por eso en el código del Sub Main que te mostré antes está lo de Application.EnableVisualStyles, para que se usen los estilos de XP con la aplicación.
  2. Y una vez que has pulsado en Aceptar, puede probar a ver si todo funciona bien, para ello, pulsa en F5 y te mostrará el formulario de acceso, escribe 123456 y pulsa en Aceptar, verás que te muestra el
    formulario principal (Form1).
  3. Si pulsas en Cancelar o escribes mal la clave, se cerrará el programa.

Mejoras al código

Ahora vamos a mejorar un poco el código.
Lo primero que vamos a hacer es asignar a los botones del formulario que pide la clave la funcionalidad que normalmente suelen tener, aunque esto lo dejo a tu gusto, esa funcionalidad es que al pulsar Intro sea como si pulsáramos en el botón Aceptar, de esta forma, el usuario escribe la clave y pulsa Intro en tener que buscar el botón y hacer click, aunque también puede hacerlo y todo funcionará igual. La otra característica es que el botón Cancelar se asocie con la tecla ESC, de forma que si el usuario pulsa esa tecla sea lo mismo que si hubiera pulsando en Cancelar.
Para conseguir esto, debes hacer lo siguiente:
  1. Muestra el formulario de la clave de acceso y pulsa una vez en cualquier parte del formulario, pero no en ninguno de los controles.
  2. Ahora en la ventana de propiedades (si no la ves, pulsa F4), selecciona la propiedad AccepButton y de la lista desplegable (ver la figura 3) selecciona btnAceptar.
Figura 3. Asignar el botón predeterminado
Figura 3. Asignar el botón predeterminado
  1. Una vez hecho eso, verás que el botón Aceptar tiene un borde más oscuro, eso indica que es el botón predeterminado, por tanto será el que reciba la pulsación de la tecla Intro.
  2. Ahora selecciona la propiedad CancelButton y de la lista selecciona btnCancelar.
  3. Ya tienes esa funcionalidad, que puedes probar si inicias la aplicación (F5) y después de escribir la clave pulsas INTRO, verás que no tienes que hacer click en el botón Aceptar.
  4. Lo mismo ocurre si pulsas la tecla ESC, verás que se cierra la aplicación aunque la clave sea correcta, ya que en realidad es como si hubieras pulsado en el botón Cancelar.
Sigamos con las mejoras:

Permitir varios intentos fallidos antes de cerrar la aplicación

Como es posible que el usuario se equivoque al escribir o se olvide de la clave, vamos a darle algunas oportunidades más, por ejemplo tres.
Para hacer esto, tenemos que modificar el código del formulario de acceso, por tanto muestra el panel del código de ese formulario (FormAcceso) y antes del código que añadimos al principio, escribe esto:
Private veces As Integer = 0
Private Const NumeroIntentos As Integer = 3
La constante NumeroIntentos tendrá el valor de las veces que vamos a permitir que escriba la clave antes de darlo como cosa perdida y cerrar la aplicación.
Ahora escribe esto en el método del botón Aceptar, de forma que esto sea el nuevo código:
Private Sub btnAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                Handles btnAceptar.Click
    If txtClave.Text = "123456" Then
        Me.DialogResult = DialogResult.OK
    Else
        ' Permitir varios intentos
        veces = veces + 1
        If veces < NumeroIntentos Then
            Label1.Text = "Quedan " & (NumeroIntentos - veces) & " intentos"
            Exit Sub
        End If
        Me.DialogResult = DialogResult.No
    End If
    Hide()
End Sub
Lo que hacemos es incrementar el contenido de la variable veces cada vez que se pulse en Aceptar, pero cuando la clave que escribimos no es la correcta, ya que si es correcta no hay que dar más intentos, je, je. Si el valor de esa variable en menor que el número máximo de intentos, modificamos el contenido de la etiqueta que hay junto a la caja de textos (Label1) para que muestre los intentos que quedan. Si superamos ese número se volverá al otro código y se acabará la aplicación.

Y esto es todo, espero que te haya resultado sencillo de comprender, y si ya tenías más experiencia, espero que sepas perdonar que lo haya explicado tan "paso a paso".
¡Que lo disfrutes!

Implementar drivers de impresora en Visual Basic 2005

Muchas veces en nuestras aplicaciones tenemos la necesidad de utilizar impresoras no estándar como por ejemplo, impresoras de tickets o controladores fiscales. Estos simpáticos aparatos frecuentemente son incompatibles entre sí, lo cual para nosotros significa escribir código que maneje cada modelo individualmente. En sistemas comerciales, donde el soporte para distintos modelos de una misma entidad (en este caso la impresora) es un imperativo, la situación se nos complica un poco.
Una solución posible sería guardar en una variable el tipo de impresora que el usuario ha configurado, y en el momento de imprimir dibujar un gran Select Case, que según la impresora, invoque a una u otra rutina cuyo código implementará la lógica particular de cada periférico. Esta solución, además de ser horrible, tiene un problema muy concreto: no es escalable. Cuando el sistema crezca, probablemente la lista de impresoras soportadas también se extienda y nuestro Select Case, junto con las rutinas a las que llama, se convertirán en monstruos inmantenibles. Algo así, por ejemplo:
Private Sub ImprimirALaViejaUsanza(ByVal Texto As String)
        Select Case mTipoImpresora
            Case "Impresora1"
                ImprimirEnImpresora1(Texto)
            Case "Impresora2"
                ImprimirEnImpresora2(Texto)
            Case "Impresora3"
                ImprimirEnImpresora3(Texto)
            Case "Impresora4"
                ImprimirEnImpresora4(Texto)
            Case "Impresora5"
                ImprimirEnImpresora5(Texto)
            Case "Impresora6"
                ImprimirEnImpresora6(Texto)
            Case "Impresora7"
                ImprimirEnImpresora7(Texto)
            Case "Impresora8"
                ImprimirEnImpresora8(Texto)
            Case "Impresora9"
                ImprimirEnImpresora9(Texto)
        End Select
    End Sub
En los tiempos de la programación estructurada ésta era la única opción para manejar distintas periféricos, pero hoy existen formas mucho más elegantes, que pueden prevenir la esquizofrenia por lectura de código spaghetti. Por eso, para los iniciados en las artes de la programación orientada a objetos mostraremos otra solución utilizando polimorfismo por herencia.

A trabajar

Para mostrar el ejemplo, construiremos una pequeña aplicación tipo Notepad, que solamente tendrá la funcionalidad de imprimir y seleccionar impresora. Para que todo quede prolijito pondremos la interfaz de usuario en un proyecto tipo “WinForms” al cual llamaremos DriversFront, y la lógica de manejo de impresoras en otro de tipo “librería de clases” que se titulará DriversBack.
Una vez que tenemos el esqueleto de la solución, comenzaremos a trabajar sobre la arquitectura. La idea es diseñar un objeto impresora genérico que tenga la funcionalidad de base que requeriremos a nuestros drivers. En nuestro caso, implementaremos tres métodos: Abrir(), Cerrar() e Imprimir(Texto), este último requerirá como parámetro el texto a ser impreso. Para nuestro ejemplo, el código sería así:
Public MustInherit Class ImpresoraBase
    ''' <summary>
    ''' Abre e inicializa los recursos que utilizará la impresora
    ''' </summary>
    Public MustOverride Sub Abrir()
    ''' <summary>
    ''' Imprime el texto enviado como parámetro
    ''' </summary>
    Public MustOverride Sub Imprimir(ByVal Texto As String)
    ''' <summary>
    ''' Cierra y libera los recursos de la impresora
    ''' </summary>
    Public MustOverride Sub Cerrar()
End Class
¿Simple no? Esta es nuestra clase base, su modificador MustInherit determina que no puede ser instanciada directamente sino que deberá ser extendida. A expensas de simplificar el ejemplo la redujimos a sólo tres métodos, una clase del mundo real probablemente ostentaría algunas propiedades, campos e incluso podríamos escribir métodos para que sean “vistos” solamente por sus clases hijas (protected). Como decía, de esta clase derivaremos otras que se ocuparán del manejo de cada impresora particular. Entonces, para una impresora de tipo X corresponderá una clase ImpresoraX que heredará de ImpresoraBase y que obligatoriamente (gracias al MustOverride) deberá escribir código para los métodos Abrir, Imprimir y Cerrar. Veamos la realización de una impresora virtual que escribirá la salida en un archivo de texto:
Public Class ImpresoraArchivo
    Inherits ImpresoraBase
    'este campo es el archivo que utilizaremos para imprimir
    Dim strArchivo As IO.StreamWriter
    'nombre del archivo de salida
    Const NOMBREARCHIVOSALIDA As String = "ImpresionArchivo.txt"
 
    Public Overrides Sub Abrir()
        'abrimos el archivo
        strArchivo = New IO.StreamWriter(String.Format("{0}{1}", ObtenerDirectorioDeEjecucion, NOMBREARCHIVOSALIDA), True)
    End Sub
 
    Public Overrides Sub Cerrar()
        strArchivo.WriteLine()
        strArchivo.WriteLine("__________________")
        strArchivo.Close()
    End Sub
 
    Public Overrides Sub Imprimir(ByVal Texto As String)
        strArchivo.Write(Texto)
        strArchivo.Flush()
    End Sub
 
    Private Function ObtenerDirectorioDeEjecucion() As String
        Dim DirEjecucion As String = My.Application.Info.DirectoryPath
        'si no termina en contrabarra, la agregamos
        If DirEjecucion.EndsWith("\") Then
            Return DirEjecucion
        Else
            Return String.Format("{0}\", DirEjecucion)
        End If
    End Function
End Class
Lo que vemos aquí, señoras y señores, es el fruto de la herencia. La clase ImpresoraArchivo es un vástago de ImpresoraBase, que para no ser desheredado ha debido implementar los métodos Abrir(), Cerrar() e Imprimir(Texto), pero con derecho a utilizar su propia y particular manera. En este caso, la impresora en realidad abre un archivo en el disco y escribe la salida en él. En una aplicación real quizás este driver no serviría de mucho, (salvo para hacer pruebas de impresión sin gastar papel) pero ahora veremos que lo realmente valioso del ejemplo es la arquitectura usada, que permite a cada hijo manejarse de manera independiente, utilizando su propia lógica -que puede ser muy distinta a la de sus “hermanos”- para realizar la tarea. Observen que la clase hija puede también incorporar funcionalidad adicional, por ejemplo ImpresoraArchivo tiene un método privado adicional –ObtenerDirectorioDeEjecucion()- por el cual dilucida el path de ejecución del sistema. Las clases hijas tienen la obligación de implementar ciertos caracteres que mandan sus padres, pero nada les impide desarrollar nuevos
métodos, campos y propiedades, incluso pueden ampliar la interfaz con métodos y propiedades públicas (aunque, como veremos más adelante, esto no nos servirá de mucho en este tipo de arquitectura). Volviendo al ejemplo, hemos desarrollado tres drivers: una impresora virtual a archivo, otra impresora virtual a pantalla (el cual abre un WinForm y escribe en él la salida de la
impresora, inútil pero didáctico) y un tercero que sería el hermano vago ya que no hace nada. En realidad este último se comporta como impresora nula, y lo utilizaremos para el caso en que el usuario decida trabajar sin impresora.
Diagrama de clases

Aquí vemos la estructura de herencia de nuestros drivers. Las tres clases derivadas implementan Abrir, Cerrar e Imprimir, aunque también pueden contener otros métodos, campos y propiedades.

Como vemos, la implementación de cada driver puede manejarse fácilmente, sólo es cuestión de heredar del padre correcto (evitemos la comparación con la vida real), y escribir la funcionalidad deseada en la plantilla. La cuestión a resolver ahora es cómo imprimiremos en nuestra impresora. Aquí echaremos manos a la magia de los objetos. El truco es utilizar Polimorfismo, de esta manera nuestra aplicación declarará un objeto de tipo ImpresoraBase, pero en el momento de ser instanciado su lugar será tomado por uno de sus hijos. Esto lo veremos en un momento, ahora déjenme mostrarles cómo usaremos nuestra impresora:
Private mImpresoraSeleccionada As DriversBack.ImpresoraBase
 
    Private Sub mnuImprimir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuImprimir.Click
        If mImpresoraSeleccionada IsNot Nothing Then
            Try
                mImpresoraSeleccionada.Abrir()
                mImpresoraSeleccionada.Imprimir(TextBox1.Text)
            Catch ex As Exception
                MessageBox.Show(String.Format("Se produjo un error al imprimir: {0}", ex.Message), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Finally
                mImpresoraSeleccionada.Cerrar()
            End Try
        Else
            MessageBox.Show("Para imprimir primero seleccione una impresora.", "Atención", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
        End If
    End Sub
Simplemente, llamamos al método Abrir() de nuestro objeto ImpresoraBase declarado en la primera línea, imprimimos el texto con Imprimir(TextBox1.Text) y la cerramos en el Finally con Cerrar(). S-I-M-P-L-E. La belleza de este método es que, sin importar si el sistema soporta una impresora o un centenar, la rutina se mantiene inmutable, ya que generalmente los cambios se harán en la implementación de cada driver. Probablemente, el interior del método Abrir() para una impresora determinada es un infierno; puede ser necesario abrir puertos, chequear configuración, si está activa, tiene papel o está en error. Para imprimir, algunas impresoras de tickets usan lenguajes propietarios; entonces en el método Imprimir(Texto) de esas impresoras deberemos traducir el texto al lenguaje de cada engendro particular. No obstante, desde el punto de vista del consumidor de la clase, el llamador sabe que lo único que tiene que hacer para imprimir un texto es invocar el método que tan responsablemente los hijos de ImpresoraBase se han comprometido a resolver, de una manera o de otra.

El patrón

Lo único que nos queda por resolver es la forma de instanciar la clase. Es decir, cómo haremos para que, en tiempo de ejecución se pueda setear una impresora u otra de acuerdo a las necesidades de cada usuario. Para ello utilizaremos una clase adicional llamada, no caprichosamente, ImpresoraFactory. ¿Porqué le pusimos este nombre? Porque en realidad, lo que estamos haciendo aquí corresponde a un patrón de arquitectura, más específicamente, el apodado Simple Factory (ver recuadro). En el contexto de este patrón, la responsabilidad de la clase Factory es proveer servicios para instanciar la clase correspondiente en tiempo de ejecución. Esta clase puede publicar algún tipo de matriz o colección de claves cuyos elementos se usarán en el método de creación para instanciar el objeto. En nuestro ejemplo, así quedaría nuestra clase ImpresoraFactory.
Public Class ImpresoraFactory
    Private Shared mTiposDeImpresora As String() = {"Impresora Virtual a archivo", "Impresora Virtual a pantalla", "Sin impresora"}
 
    Public Shared ReadOnly Property TiposDeImpresora()
        Get
            Return mTiposDeImpresora
        End Get
    End Property
 
    Public Function InstanciarImpresora(ByVal TipoDeImpresora As String) As ImpresoraBase
        Select Case TipoDeImpresora
            Case mTiposDeImpresora(0)
                Return New ImpresoraArchivo
            Case mTiposDeImpresora(1)
                Return New ImpresoraPantalla
            Case Else
                'no se ha configurado ninguna impresora
                Return New ImpresoraNula
        End Select
    End Function
End Class
Como decíamos, la propiedad TiposDeImpresora nos devuelve una matriz que representa todas las impresoras disponibles. Con el método InstanciarImpresora(TipoDeImpresora) obtenemos a cambio de uno de los elementos del array, un objeto hijo de ImpresoraBase, que como vimos, tiene todo lo necesario para que podamos imprimir. Desde el consumidor de la clase el código para crear el objeto se vería así:
Private Sub mnuConfig_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuConfig.Click
        Dim fconf As New frmSeleccionarImpresora
        fconf.ShowDialog()
 
        Dim ImprFactory As New DriversBack.ImpresoraFactory
        mImpresoraSeleccionada = ImprFactory.InstanciarImpresora(fconf.ImpresoraSeleccionada)
    End Sub
En el momento de llamar a InstanciarImpresora(TipoDeImpresora), le pasaremos como parámetro el String correspondiente a la impresora que ha seleccionado el usuario y este nos devolverá la impresora indicada. Esta será la única clase que tendremos que modificar en el caso de agregar más impresoras -además de obviamente construir la clase hija que la manejará-
poniendo más valores en el arreglo de strings, y reconociendo el tipo de impresora pedido para la instanciación. Con el tiempo, el código de InstanciarImpresora(TipoDeImpresora) podría crecer, pero es el único lugar donde tendríamos que manejarnos con delicadeza. Si lo comparamos con el escenario inicial, donde para cada tipo de impresora debíamos escribir funciones distintas, creo que hemos dado un salto cualitativo.

Para terminar

En este artículo hemos visto una forma de utilizar la herencia para hacer más claro y manejable el código de manejo de impresoras en nuestras aplicaciones. No obstante, esta arquitectura puede extenderse a otro tipo de periféricos. Por ejemplo, podría adaptarse para manejar controladoras fiscales. En este caso la clase base debería ser un poco más compleja ya que se necesitarían AbrirTicketFiscal, AgregarItem, AgregarPago, etc; sin embargo, el patrón sería el mismo (y justamente ahí está la razón de ser de los patrones). Otros destinos podrían ser: servicios de validación de transacciones, autorizaciones de tarjetas de crédito on-line o internas, salidas a displays de caracteres, etc. Siempre es deseable que este tipo de funcionalidad sea implementada con drivers, ya que aunque en el presente nuestro sistema trabaje con un solo tipo de aparato, nunca sabemos cuando podría requerir soporte otras marcas o modelos y lo mejor es, como un buen boy-scout, estar siempre listo ;-)