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, 29 de marzo de 2012

Access y C#: Error de Síntaxis en la expresión "Insert Into" (otra vez)

Desde una app en C# intentaba ejecutar este query a una base de datos en Access:
string query = "INSERT INTO MiTabla (miID, Date) VALUES (1, '29/03/2012')";

Y me saltaba esta excepción:



El query correcto es:
string query = "INSERT INTO MiTabla (miID, [Date]) VALUES (1, '29/03/2012')";

Damn you, Access.

miércoles, 14 de marzo de 2012

VB6: Error '-2147217887 (80040e21)' en tiempo de ejecución

El mensaje completo de mi error es:
"Error '-2147217887 (80040e21)' en tiempo de ejecución. Error en el método 'DataSource' del objeto 'IMSHFlexGrid'"


El error saltaba en esta línea de código:
miHierarchicalFlexGrid.DataSource = unRecordset

Lo curioso es que el recordset mostraba sus datos correctamente en varias cajas de texto. Algo debía haber en el recordset que no era compatible con el Hierarchical FlexGrid al que quería enlazarlo.

La base de datos que estoy utilizando es AdventureWorks para SQL Server 2005. Ésta es una base de datos de ejemplo y se puede descargar de Internet. La tabla a la que se conecta el recordset es "Persons.Contact":


Y mirando la tabla Persons.Contact noté que uno de los campos tiene un tipo de datos que no había visto antes: XML. Decidí cambiarlo a VarChar y correr nuevamente el programa.
Ya no sale el error y el recordset sí se puede mostrar en el Hierarchical FlexGrid.

Conclusión: El Hierarchical FlexGrid de Visual Basic 6 no es compatible con tipos de datos XML.

miércoles, 7 de marzo de 2012

C#: Rutina para Eliminar FIlas Repetidas en un DataGridView

Antes ya lo había hecho para un Hierarchical FlexGrid en VB6. Éste es el mismo algoritmo, pero aplicado a un DataGridView (llamado dgView) y en C#:

int m = 0;  // Apunta a la fila actual
int n = dgView.Rows.Count - 1;  // cantidad de filas en el DataGridView
int k;
string estaFila, unaFila;

while (m < n)
{
    k = 1;
    estaFila = String.Empty;

     // Relleno la cadena con los datos de toda la fila
    for (int i = 0; i < dgView.Columns.Count; i++)
        estaFila = String.Concat(estaFila, dgView.Rows[m].Cells[i].Value.ToString());

    while (k < n)
    {
        unaFila = String.Empty;  // Fila a comparar

        for (int i = 0; i < dgView.Columns.Count; i++)
            unaFila = String.Concat(unaFila, dgView.Rows[k].Cells[i].Value.ToString());

        if (String.Compare(estaFila, unaFila) == 0 && k != m)
        {
            dgView.Rows.RemoveAt(k); // Si son iguales remuevo unaFila solamente
            n--;     // Tamaño actual del DataGridView, al remover disminuye en uno
        }
        k++;
    }
    m++;
}

sábado, 18 de febrero de 2012

Tutorial: Creando Arrays de Controles con sus Eventos en C# y usándolos al estilo VB6

Al programar en C# siempre eché en falta el no poder crear arrays de controles al estilo Visual Basic 6.  Fue después de probar un poco que descubrí que .Net, además de admitir arrays de controles, también se les puede asignar eventos de tal forma que el código se comporte de modo muy similar al viejo VB6, donde se identifican a los controles del array mediante un índice.
El tutorial ya está subido a la sección de C# de: http://electronicaamartillazos.co.nr/
Link directo Aquí.
Un Mirror: Aquí.

Incluye cómo enviar un mensaje a otra ventana (formulario) desde un control creado dentro del Array.

lunes, 13 de febrero de 2012

Añadir y remover columnas, y manipular datos de un DataTable en C#

Tengo un método que me devuelve un DataTable con los siguientes datos:

 


Lo que necesito es tener en el DataTable sólo las cadenas que están después del primer espacio en blanco (por ejemplo, si se desea separar apellidos y nombres).

Las columnas del DataTable no son necesariamente son del tipo string (pueden ser Date Time o tipos numéricos), si todas las columnas fueran tipo String sería fácil extraer las subcadenas. Cambiar el valor de la propiedad ValueType de las columnas no funciona cuando el DataTable ya tiene datos.

Lo que se me ocurrió fue crear cuatro columnas nuevas que almacenaran datos tipo string, leer el dato correspondiente de cada fila de cada columna original, convertirlo a cadena de caracteres, extraer las subcadenas que necesito, copiarlas a la fila respectiva de una de las columnas nuevas.

Este es el método que me devuelve el DataTable sólo con las subcadenas (las que están después del espacio en blanco):


            DataTable dTable = algunMetodo();

            int col = dTable.Columns.Count;

            dTable.Columns.Add("T11", typeof(string));
            dTable.Columns.Add("T22", typeof(string));
            dTable.Columns.Add("C11", typeof(string));
            dTable.Columns.Add("C22", typeof(string));

            for (int i = 0; i < dTable.Rows.Count; i++)
                for (int j = 0; j < col; j++)
                {
                    string subcadena;
                    int n;

                    subcadena = dTable.Rows[i][j].ToString();

                    n = subcadena.IndexOf(' ');
                    subcadena = subcadena.Substring(n + 1);
                    dTable.Rows[i][j + col] = subcadena;
                }

            // esta parte es opcional si no se desean las columnas originales
            for (int j = 0; j < col; j++)
                dTable.Columns.Remove(dTable.Columns[j]);
            
            return dTable;

Este método recorre cada celda del DataTable, copia su contenido en una variable tipo string llamada "subcadena" y usa la instrucciones IndexOf y Substring para extraer la parte de la cadena que está después del primer espacio en blanco, la cual copia a la celda respectiva en las nuevas columnas añadidas.

Y así es como queda el DataTable:



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.