Fecha: 24~27/Dic/2000
Publicado: 28/Dic/2000
Algunos cambios con respecto a Visual Basic 6.0 (y/o anteriores)
Ahora que tengo la Beta1 del Visual Studio .NET, he convertido algunos proyectos de VB6, para ver que tal quedan en la nueva versión.
Para convertir un proyecto, simplemente hay que abrirlo en con el VS.NET y automáticamente se ejecutará el asistente.
El resultado de la conversión no siempre es "funcional", de hecho creo que pocos proyectos serán funcionales al 100%.
Veamos algunas de las peculiaridades con las que me he encontrado.Nota: Si has hecho pruebas con el SDK pre-beta, verás que los eventos no funcionan igual que con el VS.NET, eso es debido a que hay que declarar los controles usando Dim WithEvents en lugar de un simple Private o Dim.
- Acceder a un fichero con datos de longitud fija (usando un tipo definido)
- Manejar colección de datos usando colecciones propias y acceso a ficheros secuenciales.
Al final encontrarás unos links para acceder al código de VB6 y el de VB.NET.
Cuando me refiero a VB6, no sólo es para esa versión, sino también para las versiones anteriores, al menos para la 5 y posiblemente la 4, todo es cuestión de que pruebes...
Acceder a un fichero con datos de longitud fija
(Acceso aleatorio y tipo definido por el usuario):Este primer proyecto accede a un fichero de acceso aleatorio en el cual se almacenan datos de un tipo definido con cadenas de longitud fija, también se muestra cómo trabajar con un control ListBox y un control de diálogos comunes.
En la versión de VB6 se usa Drag & Drop para leer los datos del fichero soltado, aunque el VB7 no permite esta acción... así que... no hay Drag & Drop en esta primera prueba.Veamos los "problemillas" encontrados y las soluciones.
El tipo definido tiene esta estructura:
Private Type tColega Nombre As String * 50 Email As String * 128 FechaNac As String * 11 End TypeEn VB6 para leer los datos, se haría así:
' Abrir el fichero como acceso aleatorio Open sFic For Random As nFic Len = Len(tmpColega) ' El número de registros del fichero n = LOF(nFic) \ Len(tmpColega) ' Recorrer todo el contenido del fichero For i = 1 To n Get #nFic, i, tmpColega ' Asignarlo al ListBox List1.AddItem Trim$(tmpColega.Nombre) & vbTab & Trim$(tmpColega.Email) & vbTab & Trim$(tmpColega.FechaNac) Next Close nFicPero en VB.NET no se permiten cadenas de longitud fija, por tanto todo el código generado por el asistente es prácticamente inoperativo, aunque no del todo desechable...
Este es el código generado por el asistente:
Private Structure tColega
Dim Nombre As String
Dim Email As String
Dim FechaNac As String
End Structure
Dim tmpColega As tColega
'...
' Abrir el fichero para grabar
VB6.Open(nFic, sFic, VB6.OpenMode.Random, , , SizeOf(tmpColega))Pero esto no funciona, ya que el SizeOf devuelve el tamaño de la estructura y el caso es que la estructura contiene tres campos del tipo cadena y como en VB.NET no se permiten cadenas de longitud fija, no nos sirve... ya que el valor devuelto por SizeOf(tmpColega) siempre es 12, independientemente del contenido de cada una de las cadenas de la estructura.
¿Cómo lo he solucionado?
- Para empezar he aprovechado la característica que tiene "Structure" de poder definir propiedades y métodos tal y como se hacen con las Clases. Ahora lo veremos en detalle.
- He cambiado la orden Open para que en lugar de abrir el fichero en modo Random lo haga en modo Binary , de esta forma no es necesario indicar el tamaño (SizeOf) de lo que se va a guardar o leer.
- He añadido varios métodos o propiedades a la estructura tColega para que:
Devuelva el tamaño de los datos almacenados (Len)
Devuelva una cadena con los datos a guardar, de forma que cada campo tenga la longitud que queremos que tenga, 50 para Nombre, 128 para Email y 11 para FechaNac (Put)
Pasar una cadena leída del fichero y asignarla a cada uno de los campos (Get)El código del tipo definido quedaría así:
Private Structure tColega ' Variables privadas para almacenar los valores de las propiedades Private _Nombre As String Private Const LenNombre As Integer = 50 Private _Email As String Private Const LenEmail As Integer = 128 Private _FechaNac As String Private Const LenFechaNac As Integer = 11 ' ' Esta función devolverá una cadena con el tamaño indicado Private Function ProperLen(ByVal sElement As String, ByVal nLen As Integer) As String Return VB.Left(sElement & Space(nLen), nLen) End Function ' ' Propiedades públicas, ' se devuelve y asigna el tamaño que queramos que tengan Property Nombre() As String Get Return ProperLen(_Nombre, LenNombre) End Get Set _Nombre = ProperLen(Value, LenNombre) End Set End Property Property Email() As String Get Return ProperLen(_Email, LenEmail) End Get Set _Email = ProperLen(Value, LenEmail) End Set End Property Property FechaNac() As String Get Return ProperLen(_FechaNac, LenFechaNac) End Get Set _FechaNac = ProperLen(Value, LenFechaNac) End Set End Property ' Len devolverá el tamaño total que queremos que tenga Function Len() As Integer Return LenNombre + LenEmail + LenFechaNac End Function ' Put devolverá el contenido de la estructura ' con el tamaño adecuado para cada uno de los elementos ' de la estructura. Function Put() As String Return Nombre & Email & FechaNac End Function ' Esta propiedad recibirá una cadena ' y la desglosará en cada uno de los campos ' El Get hay que ponerlo entre corchetes para indicarle ' que es un nombre de propiedad. WriteOnly Property [Get]() As String Set Dim s As String s = ProperLen(Value, Me.Len()) Nombre = VB.Left(s, LenNombre) Email = Mid(s, LenNombre + 1, LenEmail) FechaNac = VB.Right(s, LenFechaNac) End Set End Property End StructureComo ves, he creado unas variables y constantes privadas, para almacenar los valores de las 3 propiedades (o campos) y para indicar el tamaño de cada una de ellas; lo del tamaño es necesario ya que en el fichero hay que guardar la cantidad de caracteres que queremos que tenga cada uno de los campos, por eso mismo, las propiedades devuelven y o asignan esa cantidad de caracteres, para que al "leer" todo el contenido de nuestra estructura (con Put), se asignen los caracteres que hemos indicado... todo este follón se evitaría si el vb.NET admitiera cadenas de longitud fija... pero como no es ese el caso... ¡hay que buscarse la vida!
Verás también que hay algunas cosas extrañas, por ejemplo para obtener el elemento "i" de la lista, el "asistente" hace algo como esto:
s = VB6.GetItemString(List1, i) & ControlChars.Tab,
pero puedes cambiarlo por esto otro, que es más intuitivo...
s = List1.Items(i) & ControlChars.Tab
También me he topado con este código, que la verdad no se a cuento de qué lo ha puesto así:
d = VB6.GetDefaultProperty(VB.Left(s, j - 1))
Cuando es algo tan simple como esto otro:
d = VB.Left(s, j - 1)
Fíjate que tanto al Left como al Right, el asistente le añade VB., en cambio, al Mid no le añade nada...Estos serían los procedimientos para leer y guardar los datos en el fichero.
Private Sub Guardar(ByVal sFic As String) ' Guardar el contenido del ListBox en el fichero indicado Dim j, i, n As Integer Dim tmpColega As tColega Dim nFic As Integer Dim s As String Dim d As String ' Si el nombre del fichero no es una cadena vacia If CBool(Len(sFic)) Then On Error Resume Next ' ' Si existe el fichero, borrarlo If CBool(Len(Dir(sFic))) Then VB.Kill(sFic) End If ' Err.Number = 0 ' ' Obtener un canal libre nFic = VB6.FreeFile ' ' Abrir el fichero para grabar VB6.Open(nFic, sFic, VB6.OpenMode.Binary) If Err.Number = 0 Then ' Recorrer todos los elementos del ListBox For i = 0 To List1.Items.Count - 1 ' Cada uno de los elementos, ' se añade un vbTab para asegurarnos de que tenga uno al final s = List1.Items(i) & ControlChars.Tab ' son tres los datos en cada elemento del ListBox For n = 1 To 3 ' Buscar el primer vbTab j = InStr(s, ControlChars.Tab) d = "" ' Si hay alguno... If CBool(j) Then d = VB.Left(s, j - 1) s = Mid(s, j + 1) End If ' Asignar el dato correspondiente If n = 1 Then tmpColega.Nombre = d ElseIf n = 2 Then tmpColega.Email = d Else tmpColega.FechaNac = d End If Next VB6.FilePut(nFic, tmpColega.Put()) Next VB6.Close(nFic) Else Err.Clear() End If End If End SubPrivate Sub Leer(ByVal sFic As String) ' Leer el contenido del ListBox en el fichero indicado Dim i As Integer Dim n As Integer Dim tmpColega As tColega Dim nFic As Integer Dim s As String ' ' Si el nombre del fichero no es una cadena vacia If CBool(Len(sFic)) Then ' Borrar el contenido del ListBox List1.Items.Clear() ' Obtener un canal libre nFic = VB6.FreeFile ' On Error Resume Next ' Abrir el fichero como acceso aleatorio VB6.Open(nFic, sFic, VB6.OpenMode.Binary) If Err.Number = 0 Then ' Asignar el número de registros del fichero n = VB6.LOF(nFic) \ tmpColega.Len() ' Recorrer todo el contenido del fichero s = Space(tmpColega.Len()) For i = 1 To n VB6.FileGet(nFic, s) tmpColega.Get = s ' Asignarlo al ListBox List1.Items.Add(Trim(tmpColega.Nombre) & ControlChars.Tab & Trim(tmpColega.Email) & ControlChars.Tab & Trim(tmpColega.FechaNac)) Next VB6.Close(nFic) Else Err.Clear() End If End If End SubComo puedes comprobar, es poco lo que hay que modificar, además de que hay que tener en cuenta de que es una versión BETA1, con lo cual esperamos que la cosa mejore... no sólo en el asistente, sino en el propio lenguaje.
Sobre Drag & Drop
El asistente no ha convertido el código para aceptar ficheros soltados en el formulario o en el listbox.
En la documentación que acompaña a esta versión Beta no informa cómo implementar Drag & Drop, así que, habrá que esperar a una nueva versión o bien "indagar" en los grupos de noticias... En cuanto tenga información lo indicaré.Array de controles y controles Line:
En VB.NET no existen los arrays de controles y tampoco los controles del tipo Shape, pero el asistente los migra bien... es decir de forma transparente para nosotros, por tanto no debemos preocuparnos demasiado si nuestro proyecto tiene array de controles.Otra cosa son los controles Line, en este caso, los convierte en Labels y la verdad sea dicha, salvo que hay que cambiar poca cosa, el efecto es el mismo.
Para que tenga la apariencia de línea 3D de separación, hay que hacer que la línea Blanca tenga un ancho 2 y esté debajo de la línea Gris, la cual tendrá un ancho 1.Para manejar los arrays, el asistente añade un nuevo "objeto" para cada uno de los arrays que tenemos, por ejemplo para el caso del array Text1, hace esto:
Me.Text1 = New Microsoft.VisualBasic.Compatibility.VB6.TextBoxArray()Cada uno de los controles que tiene el formulario los crea con nombres independientes:
Me.Text1_0 = New System.WinForms.TextBox()Después cada uno de los elementos los va añadiendo de esta forma:
Text1.SetIndex(Text1_0, 0%)Con lo cual podemos acceder a cualquiera de los elementos del array de la forma habitual:
Este sería el código del evento Text1_GotFocus, que en VB.NET sería Text1_Enter:Private Sub Text1_Enter(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Dim Index As Short Index = Text1.GetIndex(eventSender) ' Seleccionar el texto al entrar en el control With Text1.Item(Index) .SelectionStart = 0 .SelectionLength = Len(.Text) End With End SubY para asignar un texto al control con índice n:
Text1.Item(n).Text = d
Los índices siguen empezando por cero. Salvo que se haya indicado lo contrario al asignar el control con SetIndex.El código mostrado, funciona tal y como está porque Option Strict está Off, es decir, que no se comprueba de "forma severa" las asignaciones entre distintos tipos de datos...
Por ejemplo, para asignar a un String el contenido de un elemento del ListBox, con Option Strict Off podemos hacerlo de esta forma:
s = List1.Items(i)
Pero con Option Strict On habría que hacerlo de esta otra:
s = List1.Items(i).ToString
En el caso de averiguar el número de datos que hay guardados en el fichero, tendriamos que hacer esto otro:
n = VB6.LOF(nFic).ToInt16 \ tmpColega.Len()De todas formas, el IDE del Visual Studio .NET nos avisa, mostrándolas en la ventana "Task List", de las cosas que no "cuadran".
En algunas ocasiones será más fácil la conversión que en otras... por ejemplo este error necesitaría un poco de "casting", es decir, especificar o convertir el dato dado al esperado:
Este código es del evento cmdGuardar_Click, que es un array de controles del tipo Button y está "convertido" por el asistente, por tanto usa el método GetIndex para saber que "número de índice" le corresponde a este botón en cuestión, pero ese método espera un objeto del tipo System.WinForms.Button y sin embargo el parámetro es del tipo System.Object, por tanto hay que hacer una conversión o mejor dicho, hay que "pasar" como parámetro el interface correspondiente (que es lo que me imagino que hace CType).
Por tanto este código, que es bastante simple y claro:
Index = cmdGuardar.GetIndex(eventSender)
quedaría de esta forma algo más enrevesada:
Index = cmdGuardar.GetIndex(CType(eventSender, System.WinForms.Button))Personalmente, al igual que en las versiones anteriores usaba el Option Explicit, voy a usar el Option Strict On, (valor por defecto, salvo que se indique lo contrario con: Option Strict Off), y en los casos en los que no haya más remedio, porque tenga que usar "Late Bound", habrá que "desconectar" esa opción, pero en el resto de los casos... a sufrir un poco haciendole las cosas más fáciles al VB.NET... Y por supuesto, seguiré suando Option Explicit.
Con estos links puedes ver el código de las dos versiones, la de VB6 y la de VB.NET Beta1
Manejar colecciones propias y acceso a ficheros secuenciales.
En esta segunda prueba, vamos a ver cómo crear clases del tipo colección, es decir una clase que actúa como colección de otra clase.
También veremos cómo grabar o leer de un fichero secuencial.
Además de usar una clase para "suplantar" al cuadro de diálogos comunes, al menos en las opciones de Abrir (ShowOpen) y Guardar (ShowSave).
Empecemos por las colecciones.
El VB.NET no permite propiedades por defecto, salvo que éstas tengan parámetros no opcionales. Además de que, a diferencia del VB normal, no permite que esa "propiedad" sea un método o función, es decir: las propiedades por defecto deben ser definidas como: Property.
Esto nos impide a tener una clase y hacer una asignación directa a la propiedad por defecto. Por supuesto, la razón de esa "negación" es porque en VB.NET para asignar un objeto a otro ya no es necesario usar SET, sino que se asignan como si de una variable normal se tratara y con ello se evita tener que "decidir" que es lo que queremos hacer: asignar un objeto o asignar el valor a la propiedad por defecto...
En el caso de las propiedades que permiten parámetros es otro cantar... porque se supone que no existe esa "dualidad"; y digo se supone, porque si ese objeto es parte de un array... ¿cómo saber que estoy asignando a un objeto a un elemento del array? (esto último no lo he probado, así que por ahora no te daré la respuesta)Lo dicho al principio: empecemos por las colecciones y después veremos el tema de las propiedades predeterminadas (o por defecto).
Para que una clase se comporte como una colección, al menos en el caso de lo que a los bucles For Each ... se refiere, hay que Importar la librería de Colecciones, ya que en esa librería está declarada la interface IEnumerator que es el tipo devuelto por un método de la colección.
Lo mismo que en el VB6 usamos NewEnum que devuelve un objeto del tipo IUnknown, en vb.NET hay que declarar una función llamada GetEnumerator que devuelva un objeto del tipo IEnumerator, veamos los ejemplos del VB6 y del VB.NET:' Para VB5 y VB6 (en VB4 esto no se puede hacer) Public Function NewEnum() As IUnknown ' Debe ser un miembro oculto y ' el id del procedimiento debe ser -4 ' Set NewEnum = m_col.[_NewEnum] End Function ' Para vb.NET Public Overridable Function GetEnumerator() As IEnumerator Return m_col.GetEnumerator End FunctionComo puedes apreciar, en ambas versiones se devuelve un "elemento" de la colección que tiene el mismo nombre que la función.
En cuanto al tema de las propiedades por defecto, lo único que tendrás que hacer es buscar cada uno de los "avisos" de error que te mostrará el Visual Studio y añadir el nombre de la propiedad en cuestión, salvo que sea una propiedad que reciba como mínimo un parámetro. Como por ejemplo la propiedad Item de la colección, que suele recibir como parámetro el índice al que queremos acceder:
' Default Public ReadOnly Property Item(ByVal sIndex As String) As cMensaje Get ' Método predeterminado Dim tMensaje As cMensaje ' Iniciamos la detección de errores On Error Resume Next Item = CType(m_col.Item(sIndex), cMensaje) If CBool(Err.Number) Then ' no existe ese elemento Err.Clear() ' Creamos un nuevo objeto tMensaje = New cMensaje() tMensaje.ID = sIndex ' lo añadimos a la colección m_col.add(tMensaje, tMensaje.ID) Item = tMensaje ' Eliminamos el objeto tMensaje = Nothing End If End Get End PropertyEl CType que hay en la asignación de Item es porque tengo puesto Option Strict en On y hay que "decirle" que tipo de datos debe devolver de la colección, ya que los objetos almacenados en las colecciones son del tipo Object.
Sí, ya se que si además de que hay que repasar lo que VB.NET convierte encima hay que repasar el código para que sea más estricto es una verdadera lata, pero... se supone que al no haber compilación tardía (late bound), el código irá más rápido... De todas formas, no tienes porqué indicar el On en Option Strict, puedes seguir dejándolo en Off, ya que funciona igualmente bien y como en las versiones betas eso de hacer test de velocidad no tiene sentido, además de que lo "prohíbe" el acuerdo, pues... en fin...Cómo acceder a un fichero de acceso secuencial.
Ahora vamos a ver el código para leer el contenido de un fichero y asignarlo a la colección de Mensajes y a continuación asignarlo al ListBox, no te asustes si ves que podía haberlo hecho sin necesidad de usar la colección, pero es que, precisamente, esta prueba es para ver cómo trabajar con colecciones, además de acceder a ficheros secuenciales...Primero mostraré el código de VB6 y a continuación el de VB.NET.
' El código de VB6 Private Sub Leer(ByVal sFic As String) ' Leer el contenido de la colección Dim nFic As Long Dim tMensaje As cMensaje Dim s As String Dim j As Long ' On Error Resume Next nFic = FreeFile Open sFic For Input As nFic If Err Then Err = 0 MsgBox "Se ha producido el error: " & vbCrLf & Err.Description Exit Sub End If ' Leer el primer dato para comprobar si es un fichero de mensajes Line Input #nFic, s If s <> "cMensajes" Then MsgBox "No es un fichero con datos de Mensajes" Close nFic Exit Sub End If ' El número de elementos Line Input #nFic, s j = Val(s) ' Eliminar los mensajes de la colección m_Mensajes.Clear ' Leer cada uno de los mensajes Do While Not EOF(nFic) Line Input #nFic, s s = Trim$(s) If Len(s) Then Set tMensaje = New cMensaje tMensaje.ID = s Line Input #nFic, s tMensaje.Contenido = s m_Mensajes.Add tMensaje Else ' Si el ID era una cadena vacía, leer el siguiente dato Line Input #nFic, s End If Loop Close nFic ' Añadir los mensajes al ListBox With List1 .Clear For Each tMensaje In m_Mensajes .AddItem tMensaje.ID & vbTab & tMensaje.Contenido Next End With If j <> m_Mensajes.Count Then MsgBox "Se esperaban " & CStr(j) & " mensajes y se han leido: " & CStr(m_Mensajes.Count) End If End Sub ' El código de VB.NET Private Sub Leer(ByVal sFic As String) ' Leer el contenido de la colección Dim nFic As Integer Dim tMensaje As cMensaje Dim s As String Dim j As Integer ' On Error Resume Next nFic = VB6.FreeFile VB6.Open(nFic, sFic, VB6.OpenMode.Input) If CBool(Err.Number) Then Err.Clear() MsgBox("Se ha producido el error: " & ControlChars.CrLf & Err.Description) Exit Sub End If ' Leer el primer dato para comprobar si es un fichero de mensajes s = VB6.LineInput(nFic) If s <> "cMensajes" Then MsgBox("No es un fichero con datos de Mensajes") VB6.Close(nFic) Exit Sub End If ' El número de elementos s = VB6.LineInput(nFic) j = CInt(Val(s)) ' Eliminar los mensajes de la colección m_Mensajes.Clear() ' Leer cada uno de los mensajes Do While Not VB6.EOF(nFic) s = VB6.LineInput(nFic) s = Trim(s) If CBool(Len(s)) Then tMensaje = New cMensaje() tMensaje.ID = s s = VB6.LineInput(nFic) tMensaje.Contenido = s m_Mensajes.Add(tMensaje) Else ' Si el ID era una cadena vacía, leer el siguiente dato s = VB6.LineInput(nFic) End If Loop VB6.Close(nFic) ' Añadir los mensajes al ListBox With List1 .Items.Clear() For Each tMensaje In m_Mensajes .Items.Add(tMensaje.ID & ControlChars.Tab & tMensaje.Contenido) Next tMensaje End With If j <> m_Mensajes.Count Then MsgBox("Se esperaban " & CStr(j) & " mensajes y se han leido: " & CStr(m_Mensajes.Count)) End If End SubY con esto y un bizcocho... hasta mañana a las ocho... o casi... en fin...
La cuestión es que la cosa queda aquí, al menos por hoy... a ver cuando lo publico, espero que mañana día 28... si no me da problemas la lenta conexión que tengo...Si quieres ver el código completo del formulario, tanto para VB6 como para VB.NET, pulsa en estos links.
Nos vemos.
Guillermo