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 :)

jueves, 12 de enero de 2012

Copiar los valores de las celdas seleccionadas de un DataGridView a otros controles

Tengo el siguiente DataGridView enlazado a un DataTable:



Cuya propiedad "SelectionMode" es igual a "FullRowSelect". Lo que quiero hacer es que cuando selecciones una fila, los valores de sus celdas se copien a otros controles.

Para ello, en el evento "SelectionChanged" pongo el siguiente código para C# (he llamado a mi DataGridView "dGrid):

if (dGrid.DataSource != null && dGrid.Rows.Count > 0 && dGrid.SelectedRows.Count > 0)
            {
                textBox1.Text = dGrid.SelectedRows[0].Cells[0].Value.ToString();
                comboBox.Text = dGrid.SelectedRows[0].Cells[0].Value.ToString();  
                DateTimePick.Value = Convert.ToDateTime(dGrid.SelectedRows[0].Cells[1].Value);
                textBox2.Text = dGrid.SelectedRows[0].Cells[2].Value.ToString();               
            }

En el código tengo dos TextBoxes, un ComboBox y un DateTimePicker. Los índices de las celdas empiezan desde cero. Al asignarle un valor tipo fecha al DateTimePicker hay que tener cuidado que el valor en la celda del DataGridView tenga el formato correcto o tirará una excepción.

miércoles, 4 de enero de 2012

Transacciones a una base de datos en Access

Tenía que escribir un programita que accediera (y algunas veces modificara) a los datos en una base de datos local en Access. Esta base de datos también era leída y modificada desde otra aplicación, y era muy probable que ambos programas accedieran a la base de datos al mismo tiempo, e intentaran modificarla.
Tenía que evitar que una de las aplicaciones corrompiera la lectura/escritura de la otra, así que les hice la pregunta a la gente de StackOverflow ¿cuál es la mejor manera de atacar este problema?
Me respondieron que usara Transacciones.

Mi programa tenía que leer data de la base de datos y guardarla en un DataTable. Buscando tutoriales para usar Transacciones con un DataAdapter me topé con la Web del Guille.
El problema era que Guille escribió su tutorial pensando en SQL Server, no para Access. Cuando quise usar su código usando los objetos OleDbConnection, OleDbDataAdapter, OleDbCommandBuilder y OleDbCommand me tiraba varias excepciones.

Leyendo un poco la documentación de MSDN y, mediante prueba y error, pude finalmente crear un método para usar un Select a una base de datos en Access con Transacciones y que devolviera un DataTable:

private DataTable miDatatable(string sqlQuery)
        {
            string connString = String.Concat("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=", rutaAmiBaseDeDatos);

            using (OleDbConnection connDB = new OleDbConnection(connString))
            {
                OleDbDataAdapter dAdapter;
                OleDbCommandBuilder cBuilder;
                OleDbCommand command = new OleDbCommand();
                DataTable dTable = new DataTable();
                OleDbTransaction trans = null;

                try
                {
                    connDB.Open();
                    // no acepta IsolationLevel.Serializable, Snapshot o RepeatableRead
                    trans = connDB.BeginTransaction(IsolationLevel.ReadCommitted);

                    command.Connection = connDB;
                    command.Transaction = trans;
                    command.CommandText = sqlQuery;

                    dAdapter = new OleDbDataAdapter(sqlQuery, connDB);
                    cBuilder = new OleDbCommandBuilder(dAdapter);

                    dAdapter.SelectCommand.Transaction = trans;
                    dAdapter.Fill(dTable);
                    trans.Commit();
                }

                catch
                {
                    try
                    {
                        trans.Rollback();
                    }
                    catch { } // transacción inactiva
                }

                return dTable;
            }
        }


La variable sqlQuery puede tener cualquier sentencia SQL que sea un Select, como por ejemplo "Select * from miTabla" e incluir "Where", "Join", "Distinct" etc.

miércoles, 21 de diciembre de 2011

Mi Repositorio de Códigos Fuente

Son códigos que ya he publicado anteriormente, pero ahora en un sólo lugar, en mi cuenta en mygnet para que todo el mundo los descargue, los copie, los modifique, los analice, los traduzca a otros lenguajes, y haga todo lo que les plazca con ellos :D

jueves, 17 de noviembre de 2011

La Api ShellExecute devuelve código de error 42

El error 42 se produce cuando se intenta abrir un ejecutable con la api ShellExecute, la cual sirve para abrir archivos o carpetas. En el caso de VB6, si se desea ejecutar otra aplicación se deberá usar la función Shell.

En VB6 la api ShellExecute se declara como:

Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal HWnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long


Si deseo abrir un archivo con el programa que lo abre por defecto, deberé escribir (Me es el formulario):
Dim ret As Long
ret = ShellExecute(Me.HWnd, "Open", "C:\text.txt", "", "", 1)

Esto abrirá el archivo de texto con el block de notas.

Si deseo abrir un archivo con otro programa, escribiré:
Dim ret As Long
ret = ShellExecute(Me.HWnd, "Open", "Wordpad.exe", "C:\text.txt", "", 1)

Lo cual abrirá el archivo de texto con el Wordpad de Windows.

La función Shell es propia de VB6. Si quiero ejecutar una aplicación deberé escribir:
Shell "C:\MiApp.exe", vbNormalFocus

Update Abril 2013:

Sí se pueden ejecutar archivos ejecutables con la api ShellExecute, sólo hay que pasarle los parámetros correctos:

Dim ret As Long
ret = ShellExecute(Me.hwnd, vbNullString, _ 
"D:\unaCarpeta\Otra Carpeta\mIEjecutable.exe", vbNullString, vbNullString, 1)

Si no se va a evaluar el valor devuelto por ShellExecute no es necesario declarar la variable ret. Por ejemplo esto ejecuta el block de notas:
ShellExecute Me.hwnd, vbNullString, "notepad.exe", vbNullString, vbNullString, 1

Y esto ejecuta la calculadora de windows:
ShellExecute Me.hwnd, vbNullString, "calc.exe", vbNullString, vbNullString, 1

lunes, 24 de octubre de 2011

Error al actualizar un Recordset: "El Campo no es Actualizable"

Intentaba modificar un registro de una base de datos en Access desde VB6, cuando, al hacer miRecordset.Update, me saltaba el siguiente error:


En general, el mensaje de error es: "No se puede actualizar 'nombre del campo'; el campo no es actualizable".

Esto se debe a que ese campo específico tiene alguna característica que hace que no se puede reescribir. En mi base d edatos, el campo "Id" es llave primaria y es auto incrementable. 

Aunque los permisos de la base de datos permitan hacer modificaciones a sus datos, un campo del recordset no permite cambiar su contenido si es auto incrementable. Antes de actualizar mi recordset, necesitaba saber qué campos son autoincrementables para no tocarlos cuando quiera modificar su contenido.

Después de mucho buscar por internet, di con este foro:
http://www.experts-exchange.com/Programming/Languages/Visual_Basic/Q_20248270.html

Son rescatables estos posts:

Expert Comment

Posted by rspahitz on 21/12/01 at 8:20 AM
I don't know the answer, but since (I think) Properties is a collection, you should be able to cycle through and find the valid choices:

for i = 0 to rs.Fields(x).Properties.count - 1
   debug.print rs.Fields(x).Properties(i).name
next i



Author Comment

Posted by andyknott on 21/12/01 at 8:28 AM
Hi rspahitz,

Thanks that helped a bit, the properties listed are

BASECATALOGNAME
BASECOLUMNNAME
BASESCHEMANAME
BASETABLENAME
COLLATINGSEQUENCE
COMPUTEMODE
DATETIMEPRECISION
ISAUTOINCREMENT
ISCASESENSITIVE
ISSEARCHABLE
OCTETLENGTH
KEYCOLUMN
COMPFLAGS
SORTID
BASETABLEINSTANCE
TDSCOLLATION



Para saber, por ejemplo, si un campo con índice x es auto incrementable, se hace:
If miRecordset(x).Properties("IsAutoIncrement").Value Then _
Debug.Print "Es Auto Incrementable"

En mi caso, al modificar mi recordset, hago:
For i = 0 To miRecordset.Fields.count - 1
    If Not miRecordset(i).Properties("IsAutoIncrement").Value Then
        miRecordset(i) = nuevoValor
    End If
Next

miRecordset.Update

Con esto elimino el error de Campo no Actualizable.

sábado, 8 de octubre de 2011

Desocultando Carpetas Ocultadas por Virus con Linq

Actualización de http://electronicaamartillazos.co.nr/ (sección C#) donde muestro cómo usar Linq para crear un programita que desoculta todas las carpetas ocultas (y declaradas como carpetas de sistema) en los directorios raíz de discos duros y memorias extraíbles. Los virus que ocultan carpetas también las declaran como carpetas de sistema, de modo que no se pueden desocultar manualmente.
Este programita sirve como base para hacer programas más complejos que administren los atributos de archivos, carpetas y subcarpetas.

viernes, 9 de septiembre de 2011

Error en Crystal Reports: "Esta sección de Grupo no se puede imprimir..."

El mensaje completo es:
"Esta Seccion de grupo no se puede imprimir porque su campo de condicion no existe o no es valido. Dar formato a la seccion para elegir otro campo de condicion". Este mensaje de error aparece al querer ir a la pestaña "Vista Previa". Al presionar el botón Aceptar, aparece una página en blanco en lugar del reporte.


Busqué en Internet y no hallé cómo eliminar este error. Tampoco hallé nada útil en la Ayuda de Crystal Reports. Pero, después de probar un poco, dí yo misma con la solución.

Todos los reportes de Crystal Reports van conectados a una Base de Datos. Los grupos del reporte (por ejemplo un encabezado), están vinculadas a un campo específico de la Base de Datos. Esto es para que Crystal Reports sepa en qué orden mostrar los reportes con la data jalada desde la Base de Datos.
Este error se genera al cambiar la conexión a otra Base de Datos, eliminarla, cambiar a otra tabla, y cualquier situación en que el reporte no tenga cómo hallar el campo al que estaba vinculado.

Para solucionar esto, se debe ir al asistente de Base de Datos y configurar o crear la conexión, esto es necesario en caso de haberla eliminado:



Luego, en la pertaña Diseño, le hacemos click derecho al grupo que da problemas y elegimos la opción "Cambiar el Grupo":


Se nos abrirà la ventana que nos permitirá asignar un nuevo campo de condición:


Con esto, ya podemos ir a la Vista Previa de nuestro reporte :D

lunes, 1 de agosto de 2011

Leyendo data numérica desde un archivo de texto con C++

Tengo un archivo de texto que contiene una lista de números con el siguiente formato:


He llamado al archivo "18.txt" por ninguna razón en especial. Lo que quiero hacer es leer los números en el archivo y realizar algunas operaciones matemáticas con ellos. Como están en formato de texto, primero debo convertirlos a números decimales.
Para ello uso la función "atof", pero hay un problema: mis números usan coma decimal, y la función "atof" considera punto decimal. Debo leer los números, línea por línea, y  ponerlos en el formato con el que puede trabajar "atof", así que declaro un aray de caracteres llamado "palabra". Ninguno de los números guardados en "18.txt" ocupa más de cinco caracteres, así que "palabra" es declarada como: char palabra[6];
Luego recorro cada caracter dentro de "palabra" y si encuentro una coma, la reemplazo por un punto.

El array "beta", separa espacio en memoria para 150 números decimales ya que sólo quiero operar con 150 números en total.

El código fuente es el siguiente (el ejecutable compilado está en la misma carpeta que "18.txt"):

#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#pragma hdrstop
#pragma argsused

void main(int argc, char* argv[])
{    FILE *fp;
     int i;
     double *beta, c, promedio, rms, desviacion;
     char palabra[6];

     beta = (double *)malloc(sizeof(double)*151);
     i=0;
     fp = fopen("18.txt", "r");

if (fp==NULL)
 cout<<"no existe el archivo \n";

else {

    do
    {
       c = fscanf(fp, "%s", palabra);

       for(int j=0; j<6; j++)
       if (palabra[j]== ',')
            { palabra[j]='.' break; }
       
        beta[i] = atof(palabra);
        cout<<beta[i];
        cout<<"\n";
        i++;
    }
  while (c != EOF && i != 151);

//valores estadísticos:
promedio=0; desviacion=0; rms=0;

//Promedio
     for (int j= 0; j<=150; j++)
       promedio= promedio+ beta[j];
     
       promedio = promedio/151;

    cout<<"Promedio "; cout<<promedio;
    cout<<"\n";

//Desviación Estándar
      for (int j= 0; j<=150; j++)
       desviacion= desviacion + pow((beta[j]-promedio),2);
       
       desviacion=pow(desviacion,0.5)/pow(151,0.5);
       cout<<"la desviacion estandar es: "; cout<<desviacion;
       cout<<"\n";

//Valor Cuadrático Medio   
      for (int j= 0; j<=150; j++)
       rms= rms + pow(beta[j],2);
       
       rms=pow(rms,0.5)/pow(151,0.5);
       cout<<"El valor RMS: "; cout<<rms;
       cout<<"\n";

       fclose(fp);
    }
       system("PAUSE");
  }

Pero si quiero operar con todos los números en un archivo de texto sin importar cuántos sean, usaré este código:

#include <iostream.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#pragma hdrstop
#pragma argsused

void main(int argc, char* argv[])
{    FILE *fp;
     double i;
     double beta, c, promedio, rms, desviacion;
     char palabra[6];
     //valores estadísticos:
     promedio=0; desviacion=0; rms=0;

     i=0;
     fp = fopen("18.txt", "r");

if (fp==NULL)
 cout<<"no existe el archivo \n";

else {

    do
    {
       c = fscanf(fp, "%s", palabra);

       for(int j=0; j<6; j++)
       if (palabra[j]== ',')
       { palabra[j]='.'break; }
       
        beta = atof(palabra);
        cout<<beta;
        cout<<"\n";
        i++;

        promedio = promedio + beta;       
        rms = rms + pow(beta,2);

    }
  while (c != EOF);
     
    promedio = promedio/i;
    rms=pow(rms,0.5)/pow(i,0.5);

    i=0;
    rewind(fp);   

    do
    {
       c = fscanf(fp, "%s", palabra);

       for(int j=0; j<6; j++)
       if (palabra[j]== ',')
       { palabra[j]='.';  break; }
       
        beta = atof(palabra);
        i++;

        desviacion= desviacion + pow((beta-promedio),2);
    }
  while (c != EOF);

    desviacion=pow(desviacion,0.5)/pow(i,0.5);

    cout<<"Promedio "; cout<<promedio;
    cout<<"\n";
    cout<<"la desviacion estandar es: "; cout<<desviacion;
    cout<<"\n";
    cout<<"El valor RMS: "; cout<<rms;
    cout<<"\n";  

       fclose(fp);
    }
    system("PAUSE");
  }

Ambos códigos han sido probados con el compilador gratuito de Borland.
Cómo usar el compilador, lo explico aquí.

En este link hay información sobre "fscanf" y "rewind". Y aquí hay un tutorial sobre lectura y escritura de archivos con C++.

viernes, 15 de julio de 2011

Calculando la potencia de un número usando recursividad en C#

Primero, el código fuente:

    class Potencia
    {
        static void Main(string[] args)
        {
            Potencia pot = new Potencia();
            double r = pot.potenciaR(2, 1, 3, 0);
            Console.WriteLine(r.ToString());
            Console.Read();
        }

        private double potenciaR(double base0, double p, int exponente, int i)
        {
            return i >= exponente ? p : potenciaR(base0, p * base0, exponente, i + 1);
        }
    }

Los parámetros de la función "potenciaR" son: base, resultado, exponente, condición de parada.
El parámetro "p" debe ser 1 al inicio porque es el "acumulador" dodne se irá multiplicando la base tantas veces como indique el exponente. La función "potenciaR" deja de llamarse a sí misma cuando el parámetro "i" es igual al valor "exponente". El valor inicial de "i" es cero.

Y a continuación, lo mismo, pero con una expresión lambda recursiva:

class Potencia
    {
        static void Main(string[] args)
        {
            Func<double, double, double,double, double> potenciaF = null;
            potenciaF = (base0, p, exponente, i) =>
                    i >= exponente ? p : potenciaF(base0, p * base0, exponente, i + 1);

            Console.WriteLine(potenciaF(2, 1, 3, 0).ToString());
            Console.Read();
        }      
    }

Releyendo, noté que podemos suprimir el parámetro "i" y usar el mismo parámetro "exponente" como condición de parada. Para la expresión lambda recursiva queda así:

class Potencia
    {
        static void Main(string[] args)
        {
            Func<double, double, double, double> potenciaF = null;
            potenciaF = (base0, p, exponente) =>
                    exponente <= 0 ? p : potenciaF(base0, p * base0, exponente - 1);

            Console.WriteLine(potenciaF(2, 1, 3).ToString());
            Console.Read();
        }
    }

En todos los casos, el parámetro "p" es siempre 1.

Esta es la web donde aprendí a hacer expresiones lambda recursivas:
http://commanet.blogspot.com/2008/02/recursividad-annima.html

viernes, 3 de junio de 2011

VB6 Code Snippet: Mantener la proporción de alto y ancho de una imagen

Tengo una imagen cargada en un control Image en Visual Basic 6. Quiero que esa imagen mantenga su proporción Ancho/Alto cuando el control Image tiene su propiedad Stretch en True.  El código para hacerlo es el siguiente (He llamado al control Image imgProporcion):

        Dim i As Double, j As Double
        Dim proporcion As Double
       
        i = imgProporcion.Width
        j = imgProporcion.Height
   
        imgProporcion.Visible = False
        imgProporcion.Stretch = False
               
        i = (i / imgProporcion.Width)
        j = (j / imgProporcion.Height)
       
        If j > i Then proporcion = j Else proporcion = i
       
        imgProporcion.Stretch = True
        imgProporcion.Width = CInt(proporcion * imgProporcion.Width)
        imgProporcion.Height = CInt(proporcion * imgProporcion.Height)
        imgProporcion.Visible = True