Tengo un struct (Estructura) como el que se muestra a continuación (el constructor dentro del struct me permite asignarle valores usando la palabra reservada new):
private struct misEjes
{
public int ejeX;
public int ejeY;
public misEjes(int x, int y)
{
ejeX = x;
ejeY = y;
}
};
Y luego creo un array del struct misEjes de la forma:
misEjes[] ejes1 = Enumerable.Repeat(new misEjes(0, 0), 10).ToArray();
Usando Linq puedo no sólo declarar el array, si no también definir la cantidad de elementos e inicializarlos. Todo esto lo hace Enumerable.Repeat(). Como éste devuelve un IEnumerable, debo convertirlo con un ToArray(). Para este ejemplo, mi arrray tiene 10 elementos (sus índices van del 0 al 9).
Realizo varias operaciones con los elementos de mi array, y al final deseo quedarme con los que no están repetidos. Para ello hago:
ejes1 = ejes1.Distinct().ToArray();
El Distinct() devuelve todos los elementos que no se repiten. Independientemente los miembros (en este ejemplo ejeX y ejeY) de los elementos del array ejes1 pueden repetirse, pero el conjunto de los miembros de cada elemento de ejes1 (la pareja ejeX y ejeY) debe ser único.
El código completo es:
class Program
{
private struct misEjes
{
public int ejeX;
public int ejeY;
public misEjes(int x, int y)
{
ejeX = x;
ejeY = y;
}
};
static void Main(string[] args)
{
Random rnd = new Random(Convert.ToInt32(DateTime.Now.Millisecond));
misEjes[] ejes1 = Enumerable.Repeat(new misEjes(0, 0), 10).ToArray();
ejes1 =
ejes1.Select(c=> new misEjes(rnd.Next(0, 4), rnd.Next(0, 3))).ToArray();
foreach (misEjes em in ejes1)
Console.WriteLine(String.Concat(em.ejeX, ",", em.ejeY));
Console.WriteLine(String.Empty);
ejes1 = ejes1.Distinct().ToArray();
foreach (misEjes em in ejes1)
Console.WriteLine(String.Concat(em.ejeX, ",", em.ejeY));
Console.Read();
}
}
El resultado es:
Las líneas antes del espacio son el array original, las que están después son el array luego de aplicar Distinct(). Nótese cómo las parejas ejeX y ejeY no se repiten.
Códigos de ejemplo para aprender distintas tecnologías, o lo que sucede cuando a una cuarentona se la deja sola con una computadora
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 :)
viernes, 14 de diciembre de 2012
jueves, 29 de noviembre de 2012
Propiedad Select de un DataTable y Error al añadir un DataRow: ArgumentException Esta fila ya pertenece a otra tabla
Tengo un DataTable llamado dt1 que contiene muchos datos. Entre esos datos hay dos columnas llamadas "Nombre y Apellido" y "Edad". Deseo poner en otro DataTable los datos de todas las personas mayores de 18 años y que no sean Juan Perez ó Juan Pérez.
Los DataTables tienen una propiedad Select que acepta consultas tipo SQL para filtrar datos en función a los nombres de sus columnas. Si estos nombres contienen espacios se deben poner entre corchetes.
El código es el siguiente:
string query = @"([Nombre y Apellido] <> 'Juan Perez' or
[Nombre y Apellido] <> 'Juan Pérez') and Edad > 18";
DataTable dtFin = new DataTable();
DataRow[] miFiltro = dt1.Select(query);
if (miFiltro.Length > 0)
{
// primero añado las columnas con su respectivo nombre
for (int i = 0; i < miFiltro[0].Table.Columns.Count; i++)
dtFin.Columns.Add(miFiltro[0].Table.Columns[i].ColumnName);
// añado las filas
for (int i = 0; i < miFiltro.Count(); i++)
dtFin.Rows.Add(miFiltro[i]);
}
Pero al correr este código me tiraba una exceción del tipo "ArgumentException Esta fila ya pertenece a otra tabla".
Buscando en Internet, Pedro Hurtado me dió la solución. (yo no hice la pregunta, pero que es la solución, lo es :D)
El código correcto es:
string query = @"([Nombre y Apellido] <> 'Juan Perez' or
[Nombre y Apellido] <> 'Juan Pérez') and Edad > 18";
DataTable dtFin = new DataTable();
DataRow[] miFiltro = dt1.Select(query);
if (miFiltro.Length > 0)
{
// primero añado las columnas con su respectivo nombre
for (int i = 0; i < miFiltro[0].Table.Columns.Count; i++)
dtFin.Columns.Add(miFiltro[0].Table.Columns[i].ColumnName);
// añado las filas
for (int i = 0; i < miFiltro.Count(); i++)
dtFin.Rows.Add(miFiltro[i].ItemArray);
}
Faltaba ponerle la propiedad ItemArray al añadir los DataRows de miFiltro al DataTable dtFin.
Los DataTables tienen una propiedad Select que acepta consultas tipo SQL para filtrar datos en función a los nombres de sus columnas. Si estos nombres contienen espacios se deben poner entre corchetes.
El código es el siguiente:
string query = @"([Nombre y Apellido] <> 'Juan Perez' or
[Nombre y Apellido] <> 'Juan Pérez') and Edad > 18";
DataTable dtFin = new DataTable();
DataRow[] miFiltro = dt1.Select(query);
if (miFiltro.Length > 0)
{
// primero añado las columnas con su respectivo nombre
for (int i = 0; i < miFiltro[0].Table.Columns.Count; i++)
dtFin.Columns.Add(miFiltro[0].Table.Columns[i].ColumnName);
// añado las filas
for (int i = 0; i < miFiltro.Count(); i++)
dtFin.Rows.Add(miFiltro[i]);
}
Pero al correr este código me tiraba una exceción del tipo "ArgumentException Esta fila ya pertenece a otra tabla".
Buscando en Internet, Pedro Hurtado me dió la solución. (yo no hice la pregunta, pero que es la solución, lo es :D)
El código correcto es:
string query = @"([Nombre y Apellido] <> 'Juan Perez' or
[Nombre y Apellido] <> 'Juan Pérez') and Edad > 18";
DataTable dtFin = new DataTable();
DataRow[] miFiltro = dt1.Select(query);
if (miFiltro.Length > 0)
{
// primero añado las columnas con su respectivo nombre
for (int i = 0; i < miFiltro[0].Table.Columns.Count; i++)
dtFin.Columns.Add(miFiltro[0].Table.Columns[i].ColumnName);
// añado las filas
for (int i = 0; i < miFiltro.Count(); i++)
dtFin.Rows.Add(miFiltro[i].ItemArray);
}
Faltaba ponerle la propiedad ItemArray al añadir los DataRows de miFiltro al DataTable dtFin.
sábado, 17 de noviembre de 2012
Convertir los datos en la propiedad DataSource de un DataGridView en un DataTable
Se pueden convertir, ó pasar, los datos almacenados en la propiedad DataSource de un DataGridView a un DataTable con el siguiente código:
Este código no funciona para casos en que los datos se añadieron al DataGridView con el método "Add" de las propiedades "Rows" y "Columns", dado que éstas no modifican a la propiedad "DataSource".
Por ejemplo, en el siguiente código:
El DataGridView se ve así:
Pero el DataTable "dt" es nulo, pues la propiedad "DataSource" no tiene ningún valor asignado.
DataTable dt = (DataTable)miDataGridView.DataSource;
Este código no funciona para casos en que los datos se añadieron al DataGridView con el método "Add" de las propiedades "Rows" y "Columns", dado que éstas no modifican a la propiedad "DataSource".
Por ejemplo, en el siguiente código:
miDataGridView.DataSource = null;
miDataGridView.Columns.Add("p1", "Prueba 1");
miDataGridView.Columns.Add("p2", "Prueba 2");
object[] obj = {"p1", "p2"};
miDataGridView.Rows.Add(obj);
DataTable dt = (DataTable)miDataGridView.DataSource;
miDataGridView.Columns.Add("p2", "Prueba 2");
object[] obj = {"p1", "p2"};
miDataGridView.Rows.Add(obj);
DataTable dt = (DataTable)miDataGridView.DataSource;
El DataGridView se ve así:
Pero el DataTable "dt" es nulo, pues la propiedad "DataSource" no tiene ningún valor asignado.
jueves, 11 de octubre de 2012
VB6: Error al conectar un recordset a un archivo xls modificado con Open Office Calc
Me encontré con este error mientras experimentaba un poco con Open Office: en Visual Basic 6 conectaba un recordset a una hoja en un archivo xls, luego listaba las celdas de la primera fila en un control List (el recordset considera el archivo xls como una base de datos, sus hojas como tablas, y las celdas de la primera fila como los campos si la propiedad de la cadena de conexión HDR es "Yes".).
Todo salía bien mientras el archivo xls fuera modificado con MS Excel, pero si lo modificaba con Open Office Calc, la aplicación me tiraba un error de "La tabla externa no tiene el formato esperado".
El proyecto, junto a los archivos xls de prueba, pueden descargarse de aquí.
Mi teoría es que Open Office realiza algún cambio a las hojas de los archivos xls que no realiza MS Excel. Los archivos resultantes pueden abrirse tanto con MS Excel y Calc, y parecer idénticos, pero el cambio introducido al guardar con Calc hace que un recordset de VB6 ya no pueda reconocer las hojas como tablas válidas.
Por ejemplo, tengo este archivo creado con MS Excel:
¿Eliminará Open Office información que no usan ni MS Excel ni Calc, pero sí un recordset?
Dado el cambio en el tamaño del archivo, es probable.
Todo salía bien mientras el archivo xls fuera modificado con MS Excel, pero si lo modificaba con Open Office Calc, la aplicación me tiraba un error de "La tabla externa no tiene el formato esperado".
El proyecto, junto a los archivos xls de prueba, pueden descargarse de aquí.
Mi teoría es que Open Office realiza algún cambio a las hojas de los archivos xls que no realiza MS Excel. Los archivos resultantes pueden abrirse tanto con MS Excel y Calc, y parecer idénticos, pero el cambio introducido al guardar con Calc hace que un recordset de VB6 ya no pueda reconocer las hojas como tablas válidas.
Por ejemplo, tengo este archivo creado con MS Excel:
Al guardarlo con Open Office Calc queda como:
Dado el cambio en el tamaño del archivo, es probable.
miércoles, 29 de agosto de 2012
Tutorial: Ejemplo de uso de las apis BitBlt, SetDIBits y GetDIBits
Actualización de Electrónica a Martillazos, Sección Visual Basic 6 y C++:
Ejemplo de Win32 y uso de las apis BitBLT, GetDIBits y SetDIBits. Compilado para Visual C++ Express 2008.
Link Directo
Un Mirror
Enlace para descargar el proyecto
Enjoy!
Ejemplo de Win32 y uso de las apis BitBLT, GetDIBits y SetDIBits. Compilado para Visual C++ Express 2008.
Link Directo
Un Mirror
Enlace para descargar el proyecto
Enjoy!
jueves, 16 de agosto de 2012
VB6: CommonDialog: Cómo saber si se desea abrir un archivo como sólo lectura
En Visual Basic 6, el control CommonDialog tiene una propiedad llamada "Flags" la cual determina las propiedades del Cuadro de Diálogo que se mostrará en nuestra aplicación. Sus valores son (de la documentación de Microsoft):
Si deseo que, por ejemplo, el CommonDialog sólo admita abrir archivos que existen y que no muestre la casilla de sólo lectura haré:
Si deseo que sólo admita archivos existentes y que muestre la casilla de abrir como sólo lectura desactivada escribiré:
Si deseo que sólo admita archivos existentes y que muestre la casilla de abrir como sólo lectura activada escribiré:
En Windows XP el cuadro de diálogo Abrir será:
En Windows 7:
Para el caso en que la propiedad Flags del CommonDialog es &H1001 en Windows 7, si se desea abrir un archivo y poder modificarlo, se elige la opción "Abrir para escritura", si se elige simplemente "Abrir" el programa considerará que se abre el archivo como sólo lectura.
Hay un patrón aquí: los valores de las constantes de la propiedad Flags simplemente se suman para poder asignar una propiedad determinada al CommonDialog. Los caracteres "&H" indican que los valores asignados están en hexadecimal, no en decimal.
Para saber si el usuario ha abierto el archivo como sólo lectura se debe evaluar el valor de la propiedad Flags del CommonDialogs después que el usuario ha elegido su archivo y cerrado la ventana de diálogo. Si el valor en Flags es impar, el usuario eligió abrir el archivo como sólo lectura, si es par, ha elegido abrirlo y poder escribir en él (modificarlo).
El código completo para este caso es:
Dim miFile as String
El CommonDialog que estoy usando tiene su propiedad CancelError en Verdadero.
Caso Curioso: Si asigno a la propiedad Flags el valor de 5000 (en decimal) y ejecuto el programa en Windows XP SP3:
Me mostrará este cuadro de diálogo, al más puro estilo Windows 95:
File Open/Save Dialog Box Flags
Constant | Value | Description |
cdlOFNAllowMultiselect | &H200 |
Specifies that the File Name list
box allows multiple selections.
The user can select more than one file at run
time by pressing the SHIFT key and using the UP ARROW and DOWN
ARROW keys to select the desired files. When this is done, the
FileName property returns a string containing the names of
all selected files. The names in the string are delimited by
spaces. |
cdlOFNCreatePrompt | &H2000 | Specifies that the dialog box prompts the user to create a file that doesn't currently exist. This flag automatically sets the cdlOFNPathMustExist and cdlOFNFileMustExist flags. |
cdlOFNExplorer | &H80000 | Use the Explorer-like Open A File dialog box template. Common dialogs that use this flag do not work under Windows NT using the Windows 95 shell. |
CdlOFNExtensionDifferent | &H400 | Indicates that the extension of the returned filename is different from the extension specified by the DefaultExt property. This flag isn't set if the DefaultExt property is Null, if the extensions match, or if the file has no extension. This flag value can be checked upon closing the dialog box. |
cdlOFNFileMustExist | &H1000 | Specifies that the user can enter only names of existing files in the File Name text box. If this flag is set and the user enters an invalid filename, a warning is displayed. This flag automatically sets the cdlOFNPathMustExist flag. |
cdlOFNHelpButton | &H10 | Causes the dialog box to display the Help button. |
cdlOFNHideReadOnly | &H4 | Hides the Read Only check box. |
cdlOFNLongNames | &H200000 | Use long filenames. |
cdlOFNNoChangeDir | &H8 | Forces the dialog box to set the current directory to what it was when the dialog box was opened. |
CdlOFNNoDereferenceLinks | &H100000 | Do not dereference shell links (also known as shortcuts). By default, choosing a shell link causes it to be dereferenced by the shell. |
cdlOFNNoLongNames | &H40000 | Do not use long file names. |
CdlOFNNoReadOnlyReturn | &H8000 | Specifies that the returned file won't have the Read Only attribute set and won't be in a write-protected directory. |
cdlOFNNoValidate | &H100 | Specifies that the common dialog box allows invalid characters in the returned filename. |
cdlOFNOverwritePrompt | &H2 | Causes the Save As dialog box to generate a message box if the selected file already exists. The user must confirm whether to overwrite the file. |
cdlOFNPathMustExist | &H800 | Specifies that the user can enter only valid paths. If this flag is set and the user enters an invalid path, a warning message is displayed. |
cdlOFNReadOnly | &H1 | Causes the Read Only check box to be initially checked when the dialog box is created. This flag also indicates the state of the Read Only check box when the dialog box is closed. |
CdlOFNShareAware | &H4000 | Specifies that sharing violation errors will be ignored. |
Si deseo que, por ejemplo, el CommonDialog sólo admita abrir archivos que existen y que no muestre la casilla de sólo lectura haré:
CommonDialog1.flags = &H1004
CommonDialog1.ShowOpen Si deseo que sólo admita archivos existentes y que muestre la casilla de abrir como sólo lectura desactivada escribiré:
CommonDialog1.flags = &H1000
CommonDialog1.ShowOpen
Si deseo que sólo admita archivos existentes y que muestre la casilla de abrir como sólo lectura activada escribiré:
CommonDialog1.flags = &H1001
CommonDialog1.ShowOpen En Windows XP el cuadro de diálogo Abrir será:
Hay un patrón aquí: los valores de las constantes de la propiedad Flags simplemente se suman para poder asignar una propiedad determinada al CommonDialog. Los caracteres "&H" indican que los valores asignados están en hexadecimal, no en decimal.
Para saber si el usuario ha abierto el archivo como sólo lectura se debe evaluar el valor de la propiedad Flags del CommonDialogs después que el usuario ha elegido su archivo y cerrado la ventana de diálogo. Si el valor en Flags es impar, el usuario eligió abrir el archivo como sólo lectura, si es par, ha elegido abrirlo y poder escribir en él (modificarlo).
El código completo para este caso es:
On Error GoTo cogerE
CommonDialog1.flags = &H1001
CommonDialog1.flags = &H1001
CommonDialog1.ShowOpen
Dim miFile as String
miFile = CommonDialog1.Filename
Dim readOnly1 As Integer
readOnly1 = CommonDialog1.flags
readOnly1 = readOnly1 Mod 2
Dim readOnly1 As Integer
readOnly1 = CommonDialog1.flags
readOnly1 = readOnly1 Mod 2
' si es impar el usuario ha activado la casilla de abrir como sólo lectura
If readOnly1 = 1 Then
' desactivar las opciones que escriben en el archivo pues es sólo lectura
Else
' activar las opciones que escriben en el archivo pues se ha abierto
If readOnly1 = 1 Then
' desactivar las opciones que escriben en el archivo pues es sólo lectura
Else
' activar las opciones que escriben en el archivo pues se ha abierto
' como lectura y escritura
End If
Exit Sub
cogerE:
Exit Sub
cogerE:
El CommonDialog que estoy usando tiene su propiedad CancelError en Verdadero.
Caso Curioso: Si asigno a la propiedad Flags el valor de 5000 (en decimal) y ejecuto el programa en Windows XP SP3:
CommonDialog1.flags = 5000
CommonDialog1.ShowOpen Me mostrará este cuadro de diálogo, al más puro estilo Windows 95:
miércoles, 20 de junio de 2012
VB6 error: no se pudo encontrar el archivo ISAM instalable
Me conectaba a una base de datos en Access 2003 usando un objeto ADODB.Connection llamado miDB, y un Recordset llamado miRS. Luego cerraba ambas conexiones haciendo:
La propiedad State de ambas variables me dice si están cerradas o abiertas.
En resumen, una vez cerrada la conexión a la base de datos en Access, conectaba miDB a otra base de datos, a través de un DSN (ya creado mediante ODBC usando el aplicativo odbcad32.exe del Windows) y usando el formato de cadena de conexión para System DSN de la web de ConnectionStrings.
Y entonces me saltaba este error:
El mensaje es: "Error 80004005 en tiempo de ejecución: no se pudo encontrar el archivo ISAM instalable."
Parecía que me faltaba instalar algo, pero cuando realizaba primero la conexión por DSN (sin conectar miDB ó miRS a ninguna base de datos con anterioridad) la aplicación funcionaba de maravilla.
Deduje que no era problemas de instalación. Cuando conectaba miDB y miRS a una base de datos, cerraba ambos objetos e intentaba abrir una nueva conexión a DSN (¡y esto sólo ocurría al conectar por DSN!), "algo" se quedaba bloqueando un archivo, en algún lugar, y como la nueva conexión por DSN no podía abrirlo, consideraba que el archivo no existía y me lanzaba el error.
Entonces cambié mi código a:
If miDB.State <> adStateClosed Then miDB.Close
If miRS.State <> adStateClosed Then miRS.Close
miDB.Open miConnectionStringaDSN
miRS.Open "select * from Tabla", miDB
La propiedad State de ambas variables me dice si están cerradas o abiertas.
En resumen, una vez cerrada la conexión a la base de datos en Access, conectaba miDB a otra base de datos, a través de un DSN (ya creado mediante ODBC usando el aplicativo odbcad32.exe del Windows) y usando el formato de cadena de conexión para System DSN de la web de ConnectionStrings.
Y entonces me saltaba este error:
El mensaje es: "Error 80004005 en tiempo de ejecución: no se pudo encontrar el archivo ISAM instalable."
Parecía que me faltaba instalar algo, pero cuando realizaba primero la conexión por DSN (sin conectar miDB ó miRS a ninguna base de datos con anterioridad) la aplicación funcionaba de maravilla.
Deduje que no era problemas de instalación. Cuando conectaba miDB y miRS a una base de datos, cerraba ambos objetos e intentaba abrir una nueva conexión a DSN (¡y esto sólo ocurría al conectar por DSN!), "algo" se quedaba bloqueando un archivo, en algún lugar, y como la nueva conexión por DSN no podía abrirlo, consideraba que el archivo no existía y me lanzaba el error.
Entonces cambié mi código a:
If miDB.State <> adStateClosed Then miDB.Close
If miRS.State <> adStateClosed Then miRS.Close
Set miDB = Nothing
Set miRS = Nothing
Set miDB = New Connection
Set miRS = New Recordset
Set miRS = New Recordset
miDB.Open miConnectionStringaDSN
miRS.Open "select * from Tabla", miDB
Y resolví el problema, ya no aparece el error de ISAM instalable, y pude conectarme a mi DSN sin más inconvenientes, pues al anular ambos objetos (miDB y miRS) destruyo cualquier enlace que quede flotando, en algún lugar, perdido en las entrañas de mi Windows.
sábado, 19 de mayo de 2012
Arrays en C#
Encontré este tutorial sobre arrays en C#. Noté que le faltó mencionar un detalle: Según la documentación de msdn, un array no son sólo posiciones en memoria, son objetos que heredan de System.Array.
Primeo miramos la declaración de la clase Array:
La clase Array hereda los métodos de muchas otras clases, entre ellas IEnumerable. Es decir: todos los métodos que se pueden usar en IEnumerable, se pueden usar también con arrays.
A continuación un pequeño ejemplo:
Tengo un array de Integers (en realidad pueden ser cualquier tipo de datos: byte, double, DateTime, etc), quiero eliminar los que se repiten y convertirlos a cadenas de caracteres:
int[] misNumeros; // Contiene muchos números.
strings[] misCadenas; // aquí guardo los que no se repiten, luego de convertirlos a strings.
Como los métodos Select y Distinct devuelven un IEnumerable, lo convierto a Array usando el método ToArray().
Otro ejemplo: creo un array con números del 0 al 100, realizo algunas operaciones aritméticas cualquiera, elimino los repetidos, los ordeno en orden descendente, y finalmente los convierto a un array de cadena de caracteres:
int[] nums = Enumerable.Range(0, 100).ToArray();
nums = nums.Select(c => c * DateTime.Now.Millisecond / DateTime.Now.Second + DateTime.Now.Millisecond).ToArray();
string [] calculatedNums = nums.Distinct().OrderByDescending(c => c).Select(c => c.ToString()).ToArray();
Nótese cómo siempre utilizo ToArray() para convertir de IEnumerable a Array.
Primeo miramos la declaración de la clase Array:
La clase Array hereda los métodos de muchas otras clases, entre ellas IEnumerable. Es decir: todos los métodos que se pueden usar en IEnumerable, se pueden usar también con arrays.
A continuación un pequeño ejemplo:
Tengo un array de Integers (en realidad pueden ser cualquier tipo de datos: byte, double, DateTime, etc), quiero eliminar los que se repiten y convertirlos a cadenas de caracteres:
int[] misNumeros; // Contiene muchos números.
strings[] misCadenas; // aquí guardo los que no se repiten, luego de convertirlos a strings.
misCadenas = misNumeros.Distinct().Select(c => c.ToString()).ToArray();
Como los métodos Select y Distinct devuelven un IEnumerable, lo convierto a Array usando el método ToArray().
Otro ejemplo: creo un array con números del 0 al 100, realizo algunas operaciones aritméticas cualquiera, elimino los repetidos, los ordeno en orden descendente, y finalmente los convierto a un array de cadena de caracteres:
int[] nums = Enumerable.Range(0, 100).ToArray();
nums = nums.Select(c => c * DateTime.Now.Millisecond / DateTime.Now.Second + DateTime.Now.Millisecond).ToArray();
string [] calculatedNums = nums.Distinct().OrderByDescending(c => c).Select(c => c.ToString()).ToArray();
Nótese cómo siempre utilizo ToArray() para convertir de IEnumerable a Array.
viernes, 13 de abril de 2012
VB6 Code Snippet: Colocar el cero delante luego de convertir de Double a String
Quería convertir varios valores decimales, guardados en variables tipo Double ó Single, a cadenas de caracteres usando la función Str$ (el "$" le indica que devuelva un String, no un Variant). Pero sucedía que al querer convertir valores entre cero y uno, me eliminaba el cero a la izquierda del punto decimal. Por ejemplo, si convertía 0.12, obtenía ".12" en lugar de "0.12".
La manera de arreglarlo es evaluar el primer caracter de la cadena de caracteres resultante de la conversión, si es un punto se le añade un cero a la izquierda:
El código Ascii del punto es 46. Utilizo la función AscB porque es más rápida que Asc (AscB devuelve los bits del primer caracter, Asc además los convierte a Ansi). Uso la función Trim$ para eliminar cualquier espacio en blanco al inicio y al final de miString.
Trim devuelve un Variant. Trim$ devuelve un String.
Más información sobre optimización en el uso de Strings en Visual basic 6 aquí.
La manera de arreglarlo es evaluar el primer caracter de la cadena de caracteres resultante de la conversión, si es un punto se le añade un cero a la izquierda:
Dim miString as String
Dim miNum as Double
... coding coding coding...
miString = Str$(miNum)
If AscB(Trim$(miString)) = 46 Then miString = "0" & Trim$(miString) El código Ascii del punto es 46. Utilizo la función AscB porque es más rápida que Asc (AscB devuelve los bits del primer caracter, Asc además los convierte a Ansi). Uso la función Trim$ para eliminar cualquier espacio en blanco al inicio y al final de miString.
Trim devuelve un Variant. Trim$ devuelve un String.
Más información sobre optimización en el uso de Strings en Visual basic 6 aquí.
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:
Y me saltaba esta excepción:
string query = "INSERT INTO MiTabla (miID, Date) VALUES (1, '29/03/2012')";
Y me saltaba esta excepción:
En teoría mi query está correcto, pero entonces recordé que A las bases de datos en Access no les gustan los campos con nombres como "Date", "login" o "Password".
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'"
"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#:
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++;
}
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;int k;
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.
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]);
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
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();
{
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();
}
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:
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.
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.
Suscribirse a:
Entradas (Atom)