Acerca de:

Este blog contiene los códigos, ejemplos y bases de datos que he usado cuando aprendía acerca de algún tema específico. En lugar de borrarlos (una vez dominado ya el tema), he decidido publicarlos :)

martes, 28 de diciembre de 2010

Código fuente en VB6 para llenar un TreeView con las tablas, y otro con las vistas, de una Base de Datos en SQL Server 2005

El código fuente es muy parecido para hacer lo mismo en Oracle 10g 
De todos modos voy a poner el código fuente completo con la explicación:

En TreeView1 irán las tablas y en TreeView2 irán las vistas. "db" es un objeto tipo Connection, "nodx" es un objeto tipo Node, "rs" y "rs1" son Recordset, "count" es una variable tipo Integer. El estilo (propiedad Style) de los TreeViews es el número 7.


db.Open miConnectionString 'cadena de conexión a la BD en SQL Server

TreeView1.Nodes.Clear
Set rs = db.OpenSchema(adSchemaTables, Array(Empty, Empty, Empty, "TABLE"))
count = 0
Set nodx = TreeView1.Nodes.Add(, , "Root", "El título que le quiero poner")

Do Until rs.EOF
    count = count + 1
    Set nodx = TreeView1.Nodes.Add(1, tvwChild, "a" & str(count), rs!TABLE_NAME)
    rs.MoveNext
Loop
rs.Close

TreeView2.Nodes.Clear
rs1 = db.OpenSchema(adSchemaTables, Array(Empty, Empty, Empty, "VIEW"))  ' <<== Acá
count = 0
Set nodx = TreeView2.Nodes.Add(, , "Root", "El título que le quiero poner")

Do Until rs1.EOF
    count = count + 1
    Set nodx = TreeView2.Nodes.Add(1, tvwChild, "a" & str(count), rs1!TABLE_NAME)
    rs1.MoveNext
Loop
rs1.Close

Si se intenta abrir el recordset rs1 con db.OpenSchema(adSchemaViews) nos dará error, y si se le coloca un On Error Resume Next, el bucle que recorre rs1 será infinito, pues como rs1 nunca se abrió, su propiedad EOF nunca será verdadera.

lunes, 6 de diciembre de 2010

VB6: usando InStrRev para separar el nombre de un archivo de la ruta donde está almacenado

Imaginemos que tengo este string que contiene la ruta a una rchivo:

unaRuta = "C:\unaCarpeta\otraCarpeta\unArchivo.txt"

Y quisiera separalo en dos strings, con la ruta del archivo y el nombre del archivo por separado:

miRuta =  "C:\unaCarpeta\otraCarpeta\"
miArchivo = "unArchivo.txt"

Para hacerlo uso la función de VB6 InStrRev, que busca una cadena dentro de otra pero enpezando desde el final.
El código completo es:

Dim miRuta as String
Dim miArchivo as String
Dim unaRuta as String
Dim unaLen as Long

unaRuta = "C:\unaCarpeta\otraCarpeta\unArchivo.txt"

miRuta = Left$(unaRuta, InStrRev(unaRuta, "\"))

unaLen = Len(unaRuta) - InStrRev(unaRuta, "\")
miArchivo = Right$(unaRuta, unaLen)

Con este código chiquito ya tenemos separados el nombre de un archivo de la ruta donde está almacenado :D

sábado, 23 de octubre de 2010

Código fuente en VB6 para llenar un TreeView con las tablas, y otro con las vistas, de una Base de Datos en Oracle

En TreeView1 irán las tablas y en TreeView2 irán las vistas. "db" es un objeto tipo Connection, "nodx" es un objeto tipo Node, "rs" y "rs1" son Recordset, "count" es una variable tipo Integer. El estilo (propiedad Style) de los TreeViews es el número 7.

db.Open miConnectionString 'cadena de conexión a la BD en Oracle

TreeView1.Nodes.Clear
Set rs = db.OpenSchema(adSchemaTables, Array(Empty, Empty, Empty, "TABLE"))
count = 0
Set nodx = TreeView1.Nodes.Add(, , "Root", "El título que le quiero poner")

Do Until rs.EOF
    count = count + 1
    Set nodx = TreeView1.Nodes.Add(1, tvwChild, "a" & str(count), rs!TABLE_NAME)
    rs.MoveNext
Loop
rs.Close

TreeView2.Nodes.Clear
Set rs1 = db.OpenSchema(adSchemaViews)
count = 0
Set nodx = TreeView2.Nodes.Add(, , "Root", "El título que le quiero poner")

Do Until rs1.EOF
    count = count + 1
    Set nodx = TreeView2.Nodes.Add(1, tvwChild, "a" & str(count), rs1!TABLE_NAME)
    rs1.MoveNext
Loop
rs1.Close

martes, 14 de septiembre de 2010

Cómo pasar parámetros desde una App en VB6 a una App escrita en C#

En general, para ejecutar otra aplicación desde VB6 y pasarle un parámetro se usa el siguiente código:

Dim LInstr As Long
Dim RutaExe As String
Dim sExeName As String

' para este caso rutaExe = vbNullString

sExeName = RutaExe & "notepad.exe" & _
        Chr$(32) & Chr$(34) & "D:\archivo_1.txt" & Chr$(32) & Chr$(34)
       
' Se ejecuta
LInstr = Shell(sExeName, vbNormalFocus) 

Esto abrirá el archivo "archivo_1.txt" (en caso de existir) en el block de notas.

El número 32 equivale al código Ascii del espacio en blanco, 34 es el código Ascii de las comillas.

Lo bueno del C# es que todas las aplicaciones escritas en este lenguaje arrancan desde un método Main con la siguiente forma:
static void Main(string[] args)

El array "args" del tipo string recibe todos los parámetros que le pasamos al ejecutable. Si es una aplicación de consola llamada "miExe" se escribiría:


Donde "1", "2" y "3" son strings y corresponden a las variables args[0], args[1] y args[2] en la aplicación en C# respectivamente. Si se desea operar con ellas, se deberá convertir de string al tipo numérico deseado, pues "Main" siempre recibe los parámetros como string. Los elementos del array args se emplean como cualquier variable de .Net tipo string.

Si queremos llamar a miExe y pasarle parámetros desde una aplicación en VB6, el código fuente será:

Dim LInstr as Long
Dim RutaExe as String
Dim sExeName as String
Dim arg1 as String, arg2 as String, arg3 as String

... coding...  coding... coding.....
' rutaExe contiene la ruta a la carpeta donde está miExe.exe

sExeName = RutaExe & "\miExe.exe" & _
        Chr$(32) & Chr$(34) & arg0 & Chr$(32) & Chr$(34) & _
        Chr$(32) & Chr$(34) & arg1 & Chr$(32) & Chr$(34) & _
        Chr$(32) & Chr$(34) & arg2 & Chr$(34) 

' Se ejecuta miExe
LInstr = Shell(sExeName, vbMinimizedNoFocus ' el segundo parámetro es opcional

La aplicación en C# separará los argumentos de la forma correcta, y los asignará a los elementos del array args, sólo si se los pasamos con espacios y comillas en el orden correcto. La aplicación en C# usa los espacios y las comillas para saber qué partes del string sExeName son el nombre del ejecutable y cada uno de los argumentos que está recibiendo.

lunes, 23 de agosto de 2010

Cómo averiguar los tipos de datos almacenados en cada campo de una base de datos

El código está en VB6:

' rs es el recorset ya enlazado a una base de datos

Select Case rs.Fields("un_campo").Type

Case adBigInt, adBinary, adDecimal, adDouble, adNumeric, adInteger, adLongVarBinary, adSingle, _
adSmallInt, adTinyInt, adUnsignedBigInt, adUnsignedInt, adUnsignedSmallInt, adUnsignedTinyInt, _
adVarNumeric, adVarBinary, adBoolean
        ' Hacer cosas con datos de tipo numérico
   
Case adBSTR, adChar, adLongVarChar, adLongVarWChar, adVarChar, adVarWChar, adWChar, _
adVariant
        ' Hacer cosas con datos de tipo texto o caracteres
   
Case adDate, adDBDate, adDBTime, adDBTimeStamp
        If IsDate(lo que hay en el campo) Then
               ' Esto funciona sólo para Access y SQL Server
        End If
        ' Para Oracle se debe usar la función TO_DATE(lo que hay en el campo, formato de fecha)
        ' el formato de fecha es DD/MM/YY por defecto.
Case Else
       ' El formato de imágenes para estas tres bases de datos es en VB6 el tipo AdLongVarBinary, 
       ' el cual tiene el valor de 205.
End Select

viernes, 2 de julio de 2010

Reutilización de componentes en desarrollo de software

Una respuesta que di cuando alguien me preguntó sobre la reutilización de componentes en desarrollo de software:

Las librerías de la plataforma .Net agrupan clases que sirven para hacer todo: mandar y recibir datos de la consola, controlar el puerto serial, los trabajos de impresión, contienen controles tales como formularios para crear ventanas, botones, cajas de texto, etc.

.Net se basa en la reutilización de pedazos de software (las librerías) y la POO. En .Net todo son clases (las librerías, también llamadas Namespaces agrupan a las clases) y objetos que referencian a estas clases y pueden "heredar" o hacer uso de sus métodos, dentro de éstos están los algoritmos.

En el generador de laberintos se usan mucho las clases y objetos que proporciona el namespace System.Drawings. Yo no he creado ningún componente, todo el laberinto se crea usando este namespace. El objeto de tipo Graphics hereda los métodos necesarios para dibujar el laberinto.

Una explicación más detallada de cómo lo programé está en mi web:
http://yelinna.comoj.com/eam/maze_generator.html
http://yelinna.comoj.com/eam/maze_generator_hex.html

El equivalente de .Net para Java es la JVM o la máquina virtual. Para el Visual C++ existe además el MFC (Microsoft Foundation Clases) que hacen un poco más fácil trabajar directamente con las funciones mismas del sistema operativo (las Apis), que son funciones que se pueden usar para hacer las mismas cosas que con el .Net o la JVM, pero dependen mucho del tipo y versión del sistema operativo (los virus para Windows son básicamente muchas llamadas a las Apis, con ellas se puede ver la libreta de direcciones de Outlook, controlar el envío por red, copair en carpetas específicas del disco duro, ver el usuario actual del sistema, etc).
Como verás, las apis son otro componente reutilizable.

viernes, 21 de mayo de 2010

Retornando un valor nulo en lugar de un mapa de bits

Tengo un método, dentro de una clase cualquiera programada en C#, que procesa un par de mapas de bits y además genera un tercer mapa de bits que es el valor de retorno:

public Bitmap Foo (string r1, string r2)
{
            Bitmap bmap = new Bitmap(r1);
            Bitmap bmap2 = new Bitmap(r2);
            Bitmap bmapR = new Bitmap(bmap.Width, bmap.Height, bmap.PixelFormat);

// hacer cosas, muchas muchas cosas con bmap y bmap2, y la imagen resultante va a bmapR
            bmap.Dispose();
            bmap2.Dispose();
            return bmapR;
}

El problema es que un mapa de bits consume una cantidad considerable de memoria, además todas esas muchas cosas sólo quiero hacerlas si bmap y bmap2 tienen las mismas dimensiones, además que bmapR sólo se crearía para este caso. Se podría programar un método intermedio que llamaría al método Foo sólo si se cumple la condición, pero eso implicaría crear bmap y bmap2 en este método.

Esta manera de solucionarlo ya no me estaba convenciendo.

Entonces recordé que en .Net todo es un objeto, y como tal todo puede tener un valor nulo. ¿Y si retorno un valor nulo (null) en lugar de un bitmap si la condición de las dimensiones iguales no se cumple? Luego podría evaluar si se devolvío un valor nulo o no.

Mi método modificado resultó ser:

public Bitmap Foo (string r1, string r2)
{
            Bitmap bmap = new Bitmap(r1);
            Bitmap bmap2 = new Bitmap(r2);

            if (bmap.Width != bmap2.Width || bmap.Height != bmap2.Height)
            {
                bmap.Dispose();
                bmap2.Dispose();
                return null;
            }

            Bitmap bmapR = new Bitmap(bmap.Width, bmap.Height, bmap.PixelFormat);
  // hacer cosas, muchas muchas cosas con bmap y bmap2, y la imagen resultante va a bmapR
            bmap.Dispose();
            bmap2.Dispose();
            return bmapR;
}


Y la forma de llamarla es:

private void Metodo() 
{
            UnaClase M = new UnaClase();
            Bitmap bt = M.Foo(ruta1, ruta2);

            if (bt == null)
                MessageBox.Show("Las imágenes deben tener las mismas dimensiones");
            else
                pictureBox.Image = bt;

         //   bt.Dispose();  Esta lìnea es mejor omitirla pues da problemas si está en el evento Click de un botón
 }

¡Funciona! Y me evito crear tres mapas de bits cuando no los necesito.

:D

viernes, 14 de mayo de 2010

Fixed o no Fixed

Tengo el siguiente código:

using System.Drawing;
namespace SomeNamespace {
unsafe class SomeClass{

      private void One() {
            Point[] esq = new Point[3];
            Point* p = &esq[0];
            Two(p);
            }
     private void Two(Point *p) {
            // Write your ad here :)
            }
      }
}

Y pareciera que no hubiera error, pues he declarado la clase como "insegura" y estoy pasando un puntero a un dato tipo "Point" como parámetro a otra función. Y aún así me sale este error:

"Error  1 Sólo se puede adquirir la dirección de una expresión de tipo unfixed de un inicializador de instrucción fixed"

C# no deja usar punteros no "fijos" (es decir, cuyo valor, o dirección a la que apuntan, se puedan modificar). Esto es debido al colector de basura, que está siempre monitoreando la memoria, desasignando espacios de memoria, borrando objetos o moviéndolos a otras ubicaciones. Un puntero "fixed" evita que su valor apuntado sea movido a otra ubicación en la memoria, y así poder asegurarse que su valor no será reemplazado por otro sin que nos demos cuenta.

El código "fixed" sería:

using System.Drawing;
namespace SomeNamespace {
unsafe class SomeClass{
      private void One()  {
            Point[] esq = new Point[3];
         
            fixed( Point* p = &esq[0])
                   {
                        Two(p);
                   }
             }
     private void Two(Point *p)  {
            // Write your ad here :)
            }
     }
}



Según la documentación de Microsoft, es posible evitar usar "fixed" si se separa espacio en la pila para nuestros punteros. Esto se hacce con "stackalloc". La memoria se libera cuando se sale del método donde se separó el espacio en la pila:

using System.Drawing;
namespace SomeNamespace {
unsafe class SomeClass{
      private void One() {
            Point[] esq = stackalloc Point[3];
            Two(&esq[0]);    // también es válido: Two(esq);
            }
     private void Two(Point *p) {
            // Write your ad here :) 
            }
      }
}

lunes, 29 de marzo de 2010

Un dato muy útil para VB6

Por qué no se debe poner SetFocus en el evento Load de un formulario:

Don't use SetFocus on Invisible Controls

Esto se debe a que el Visual basic llama al método SetFocus antes de que los controles del formulario se hayan cargado.