Este es uno de los textos de error que mas amenudo se enfrenta a los que intentan actualizar la informacion enlazada de un DGV’s y una MDB.
Aunque en muchos otros foros/post se advierte de debemos utilizar nombres de campo que no esten reservados, caemos y volvemos a caer en el error de usar nombres con caracteres no admitidos. Un simple espacio al nombrar el campo provocara una excepcion tanto en la operación de ‘INSERT’ Como ‘UPDATE’. Suerte que a grandes males… grandes remedios J
Ejemplo : el Nombre ‘Columna A’ generara una excepcion
Sin embargo el nombre ‘Columna_A’ no genera ninguna excepcion… asi de simple.
Os recuerdo el enlace con la lista de palabras reservadas que no pueden utilizarse para nombrar los campos de una MDB que posteriormente utilizaremos enlazada con un DataGridView y un ‘OleDb.OleDbCommandBuilder(Me.MiMdbAdaptador)’
http://support.microsoft.com/kb/248738
En caso contrario las únicas opciones disponibles para continuar utilizando dichos nombres, será construir el’INSERT’… ‘UPDATE’ commands de forma manual o utilizar la opción de ‘Origen de Datos’ generado por Visual Studio.
Saludos,
Pep Lluis,
Independientemente de las mil y una formas para filtrar/formatear entradas de texto, me gustan las sencillas sin retorcimientos. Aquí te dejo una idea de cómo aplicar reglas cuando se trata de todo un form, aunque trabajandolo puedes customizar el comportamiento en funcion al control que tiene el foco. No olvides que tambien dispones de un espacio de nombres sensacional :
Imports System.Text.RegularExpressions
Aunque como te digo, en determinadas ocasiones cabe aplicar lo sencillo si con ello cubrimos las necesidades. ;-)
El siguiente ejemplo demuestra como filtrar la pulsación de cualquier tecla que no sea numerica o coma.
Private Sub Form1_Load(…) Handles MyBase.Load
Me.KeyPreview = True
End Sub
Protected Overrides Function ProcessDialogKey(ByVal keyData As System.Windows.Forms.Keys) As Boolean
Select Case keyData
' 48 a 57 = numericas encima teclado alfabetico
' 97 a 106 = Numericas teclado numerico
Case 48 To 57, 97 To 106, Keys.Oemcomma
'Solo devolver las pulsaciones que pasan el filtro
Return MyBase.ProcessDialogKey(keyData)
Case Else
'Evitar la propagacion de la pulsacion
'de teclas que no nos interesan
Return True
End Select
End Function
Lo Bueno y simple… Dos veces bueno!
Pep Lluis
Desde la aparición de VB.NET y a pesar de las grandes prestaciones SQL Express frente al extinto MSDE, una de las inquietudes que en más ocasiones he visto solicitadas por los desarrolladores, ha sido la portabilidad y la manejabilidad de formatos MDB, XLS, TXT, como moneda de cambio entre aplicaciones, a pesar de todo, supongo que continuamos dependiendo de los antiguos compañeros de viaje.
Son muchas las ocasiones en que se pregunta cómo convertir el contenido de un 'DataGridBView' a una Base de datos Access o transportar el contenido de una tabla a un Excel. Creo que este ejercicio puede ser útil como punto de entradilla a ese tipo de aplicaciones “Atípicas” J. Así que finalmente me he decidido por investigar un poquito para poder llegar a ciertas conclusiones y limitaciones del trabajo con MDB's usando VB 9. Aquí os dejo una muestra de los resultados, ya sabéis que no es habitual en mí, pero no he tenido tiempo de comentar el código y ponerlo bonito, a pesar de ello a todos los que tengáis un especial interés, os animo a contactar, para poder enviaros el proyecto de pruebas.
Veamos entonces como procesar una hoja de cálculo de Excel para crear un nuevo catalogo en una BD, diseñando la tabla a partir de las columnas del ‘DGV’ y rellenándola con el contenido de celdas.
Evidentemente el punto de partida será esta bonita hoja:

el segundo paso ... Nuestro POgrama pa leer lescel!
Private Sub LeerXls()
'Abrir y llenar el DGV con la hoja de excel
MiXlsConexion = New OleDbConnection _
("Provider=Microsoft.Jet.OLEDB.4.0;" + _
"Extended Properties = 'Excel 8.0';" + _
"Data Source=|DataDirectory|\libro1.xls;")
MiXlsAdaptador = New OleDbDataAdapter("SELECT * FROM [Hoja1$]", MiXlsConexion)
MiXlsConexion.Open()
MiXlsAdaptador.Fill(MiXlsDataSet)
MiXlsDGV.DataSource = MiXlsDataSet.Tables(0)
End Sub

Al pulsar sobre "Generar Archivo" obtendremos ...
'
' crear estructura de la tabla en funcion a columnas de la hoja
Dim Campos As String = "Create Table [" + NombreDe_MiTabla + "] ("
For Each col As DataGridViewTextBoxColumn In MiXlsDGV.Columns
If Campos.EndsWith(")") Then Campos += ", "
Campos += "[" + Regex.Replace(col.DataPropertyName, " ", "_") + "] Text(50)"
Next
Campos += ")"
Dim MiMandato As New OleDbCommand(Campos, MiMDBConexion)
MiMandato.ExecuteNonQuery()
...
...
...
Dim Registro As DataRow
'Añadir las filas del DGV a la tabla del DataSet
For Each row As DataGridViewRow In MiXlsDGV.Rows
If row.Index < MiXlsDGV.RowCount - 1 Then
Registro = MiMdbDataSet.Tables(0).NewRow
For Each col As DataGridViewColumn In MiXlsDGV.Columns
'Añadir una entrada por celda
Registro(col.Index) = row.Cells(col.Index).Value
Next
'Añadir una linea por fila
MiMdbDataSet.Tables(0).Rows.Add(Registro)
End If
Next
MiMdbAdaptador.Update(MiMdbDataSet)

Sin trampa ni carton, en la carpeta de la aplicacion veremos aparecer un misterisoso Bd1.Mdb

Los Mas desconfiados podeis hacer clic ... e volila!

Saludos,
Pep Lluis,
El próximo 1 de Julio de 2008 os anunciamos el de Second Nug que desvelará muchas de las incognitas expuestas en estos foros sobre los Grids.
Aunque creo que nunca podran develar las que io se :-)))
Pep Lluis,
''
'' Crear un archivo de texto con el contenido
'' de las celdas de un DataGridView de los
'' datos cargados de una hoja de excel.
''

Imports System.Data.OleDb
Public Class Form1
'Definir conexion,adaptador y Dataset
Private MiConexion As New OleDbConnection _
("Provider=Microsoft.Jet.OLEDB.4.0;" + _
"Extended Properties = 'Excel 8.0';" + _
"Data Source=|DataDirectory|\libro1.xls;")
Private MiAdaptador As New OleDbDataAdapter("SELECT * FROM [Hoja1$]", MiConexion)
Private MiDataSet As New DataSet()
Private Sub Form1_Load() Handles MyBase.Load
'Abrir y llenar el DGV con la hoja de excel
MiConexion.Open()
MiAdaptador.Fill(MiDataSet)
Me.DataGridView1.DataSource = MiDataSet.Tables(0)
'Crear un documento de texto
Dim ATexto = IO.File.CreateText("MiExport.txt")
For Each row As DataGridViewRow In Me.DataGridView1.Rows
For Each col As DataGridViewColumn In Me.DataGridView1.Columns
'Añadir una entrada por celda
ATexto.Write(row.Cells(col.Index).Value)
'separar con un tabulador
ATexto.Write(Chr(Keys.Tab))
Next
'Añadir una linea por fila
ATexto.WriteLine()
Next
ATexto.Close()
End Sub
End Class

:-)
Siguiendo la consulta de Jesús Pérez, preguntando como totalizar la suma de una columna de un DataGridView, me complace compartiros este código en espera de vuestros comentarios.
Saludos,
Pep Lluis,

.. Handles Me.Load
'Disparar el calculo cada vez que editamosAddHandler Me.dataGridView1.CellEndEdit, AddressOf Sumar'Calcular el contenido inicial de la columnaMe.dataGridView1.Columns(3).HeaderText = "Salario"Me.dataGridView1.AllowUserToAddRows = FalseMe.dataGridView1.CurrentCell = dataGridView1.Rows(0).Cells(3)Sumar()..End Sub'' Sumar las cantidades de una columna'Private Columna As Integer = 3
Sub Sumar() Dim Total As Integer = 0 'Acumular el total Dim UltimaFila As Integer = Me.dataGridView1.Rows.Count
'Solo calculamos si modificamos la columna definida
If Me.dataGridView1.CurrentCell.ColumnIndex = Columna Then
For Each row As DataGridViewRow In Me.dataGridView1.Rows
If row.Index < UltimaFila - 1 Then Total += row.Cells(Columna).Value
Next
Me.dataGridView1.Rows(UltimaFila - 1).Cells(3).Value = Total.ToString
End If
End Sub
Pd. No dudeis en pedirme el proyecto completo si estais interesados, o si quereis comentar alguna parte del codigo.
Atendiendo a la consulta de Cesar, os dejo un fragmento de código, explicando cómo enumerar las unidades que corresponden a conexiones de Red. De hecho es una variacion del anterior post 'Enumerar las unidades logicas de nuestros discos'.
Sub Main() Handles MyBase.Load
' Obtener todas las unidades logicas de mi equipo
Dim MisUnidades = From Unidad In My.Computer.FileSystem.Drives _
Select Unidad.Name
' Componer la informacion que visualizare de mis unidades
' que correspondan a conexiones de red.
Dim InfoUnidad = From info In MisUnidades _
Where My.Computer.FileSystem.GetDriveInfo(info).DriveType = IO.DriveType.Network _
Select Unidad = My.Computer.FileSystem.GetDriveInfo(info).Name, _
Tipo = My.Computer.FileSystem.GetDriveInfo(info).DriveType
Dim miVista As New DataGridView
miVista.Dock = DockStyle.Fill
Me.Controls.Add(miVista)
miVista.DataSource = InfoUnidad.ToList
End Sub
Este es el resultado

Saludos,
Pep Lluis,
Ejemplo de cómo añadir una columna de selección unida a una tabla de ‘maestros’ y relacionada con su identificador en la tabla ‘detalles’, en casos reales solo teneis que substituir las tablas por los ‘sources’ de las tablas en vuestra BD.
Me gustara recibir vuestros comentarios... ;-)

Imports System
Imports System.Data
Public Class Form1
Private dataGridView1 As New DataGridView()
Private Sub Form1_Load() Handles Me.Load
'
' Simular tabla con los Detalles
'
Dim Detalles As New DataTable("Detalle")
Detalles.Columns.Add("Actividad")
Detalles.Columns.Add("Nombre")
Detalles.Columns.Add("Telefono")
Detalles.Rows.Add("001", "Pep Lluis", "231.321.321")
Detalles.Rows.Add("002", "Luis Franco", "Internacional")
Detalles.Rows.Add("003", "Marc Rubiño", "111.222.333")
Detalles.Rows.Add("004", "Javier Conesa", "91.244.32.32")
'
' Simular tabla con los Id's Maestros
'
Dim Maestros As New DataTable("Maestro")
Maestros.Columns.Add("Actividad")
Maestros.Columns.Add("Descripcion")
Maestros.Rows.Add("001", "POgramaor")
Maestros.Rows.Add("002", "Torero")
Maestros.Rows.Add("003", "Banderillero")
Maestros.Rows.Add("004", "ANALista")
'
' Construir el DS y establecer relaciones
'
Dim MiDataSet As New DataSet
MiDataSet.Tables.Add(Detalles)
MiDataSet.Tables.Add(Maestros)
MiDataSet.Relations.Add("Actividad", _
MiDataSet.Tables("Maestro").Columns("Actividad"),_
MiDataSet.Tables("Detalle").Columns("Actividad"))
'
' Añadir una columna 'ComboBox'
' Conteniendo la tabla 'Maestro'
'
Dim MiBoxColumn As New DataGridViewComboBoxColumn
MiBoxColumn.DisplayMember = "Descripcion"
MiBoxColumn.ValueMember = "Actividad"
MiBoxColumn.DataPropertyName = "Actividad"
MiBoxColumn.DataSource = MiDataSet.Tables("Maestro")
Me.dataGridView1.Columns.Add(MiBoxColumn)
'
' Visualizar el DGV con la columna combo 'Maestro'
' y las columnas y tablas de 'Detalle'
'
Me.dataGridView1.Dock = DockStyle.Fill
Me.Controls.Add(dataGridView1)
Me.dataGridView1.DataSource = MiDataSet.Tables("Detalle")
End Sub
End Class
Si os interesa en determinadas situaciones podeis "camuflar" el combo para que tenga un aspecto como este :

De esta forma no podran modificar ni desplegar las opciones y su aspecto sera como el de cualquier otra columna.
Ajustando las propiedades :
MiBoxColumn.DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing
MiBoxColumn.ReadOnly = True
Saludos,
Pep Lluis,
PD. Agradecemos la participacion de los actores secundarios (Javier, Luis i Marc), aunque tengo mis dudas sobre si son personajes reales o ficticios ;-)
' En respuesta a la pregunta de como enumerar las unidades
' Logicas de nuestros discos desde el prompt de DOS, os
' dejo este ejemplo utilizando el espacio de nombres
' My.Computer.Filesystem, en Vb9 y Linq
' Una vez compilado el proyecto, puede incluirse
' en el archivo de proceso o 'Batch' segun necesidad.
'
'
' Listar las unidades logicas del Equipo
Module Module1 Sub Main()
' Obtener todas las unidades logicas de mi equipo
Dim MisUnidades = From Unidad In My.Computer.FileSystem.Drives _
Select Unidad.Name
' Componer la informacion que visualizare de mis unidades
Dim InfoUnidad = From info In MisUnidades _
Select Unidad = My.Computer.FileSystem.GetDriveInfo(info).Name,
Volumen = My.Computer.FileSystem.GetDriveInfo(info).VolumeLabel(), _
Tipo = My.Computer.FileSystem.GetDriveInfo(info).DriveType, _
Espacio = My.Computer.FileSystem.GetDriveInfo(info).AvailableFreeSpace
Try
For Each Info In InfoUnidad
'listar la informacion obtenida
Console.WriteLine(Info)
Next
Catch ex As Exception
'despreciar la informacion de las unidades que no estan a punto
End Try
' esperar a que se pulse return
Console.ReadLine()
End Sub
End Module
''
'' Ejemplo de utilizacion de WNetConnection
'' Correspondiente al ejemplo de : http://support.microsoft.com/kb/173011/es
'' Adaptado a VB9/Vista y complementando la respuesta del post
'' http://forums.microsoft.com/MSDN-ES/ShowPost.aspx?PostID=3423382&SiteID=11
''
'' En este ejemplo las variables lpLocalName lpRemoteName se asignan desde
'' el codigo, para asignarlos dinamicamente podeis usar un listbox & textbox.
''
'' Si os conectais a un recurso de servidor con un Usuario / Clave distinto
'' distinto del utilizado para iniciar la session, debereis reasignar
'' las variables 'Usuario' y 'Clave'.
''
Public Class Form1
Private MiRdR As New RecursoDeRed
'
' Conectar un recurso de RED
'
Private Sub Conectar()
'Definir el tipo de conexión
'Segun documentacion del API
MiRdR.dwScope = Recurso.CONNECTED
MiRdR.dwType = Recurso_Tipo.DISK
MiRdR.dwDisplayType = Recurso_TipoVista.SHARE
MiRdR.dwUsage = Recurso_TipoUso.CONNECTABLE
'Definir letra de unidad y nombre del recurso
MiRdR.lpLocalName = "Z:"
MiRdR.lpRemoteName = "\\servidor\recurso"
MiRdR.lpComment = "** Mi Nueva Conexion**"
MiRdR.lpProvider = ""
'Si las credenciales son diferentes a las actuales
'Sustituir por las adecuadas
Dim Usuario = vbNullString 'Nombre de usuario
Dim Clave = vbNullString 'Clave de acceso
'Llamar a la funcion AñadirUnidad para WNetAddConnection2A
'CONNECT_UPDATE_PROFILE = &H1
MessageBox.Show( _
CodigoError( _
AñadirUnidad(MiRdR, Clave, Usuario, &H1)), _
"Conectando..." + MiRdR.lpLocalName + " a " + MiRdR.lpRemoteName)
End Sub
'
' Desconectar un recurso de red
'
Private Sub Desconectar()
MiRdR.lpLocalName = "Z:"
'CONNECT_UPDATE_PROFILE = &H1
If QuitarUnidad(MiRdR.lpLocalName, &H1, False) = 0 Then
MessageBox.Show("La unidad ha sido desconectada.", _
"Desconectando..." + MiRdR.lpLocalName)
Else
MessageBox.Show("Fallo en la desconexion del dispositivo", _
"ERROR! Desconectando...")
End If
End Sub
'
' Añadir los botones de Conectar/Desconectar
'
Private Sub Form1_Load() Handles Me.Load
Me.Text = "Conectar unidades de Red"
Dim BotonDeConectar As New Button
BotonDeConectar.Text = "Conectar"
BotonDeConectar.Dock = DockStyle.Bottom
AddHandler BotonDeConectar.Click, AddressOf Conectar
Dim BotonDeCancelar As New Button
BotonDeCancelar.Text = "Desconectar"
BotonDeCancelar.Dock = DockStyle.Bottom
AddHandler BotonDeCancelar.Click, AddressOf Desconectar
Me.Controls.AddRange(New Control() {BotonDeConectar, BotonDeCancelar})
End Sub
End Class
'
' Definicion y Referencias a la libreria mrp.dll
' para llamar a las funciones de WNetConnection, Add y Cancel
'
Module WNet
Declare Function AñadirUnidad Lib "mpr.dll" Alias "WNetAddConnection2A" (ByRef lpRecursoDeRed As RecursoDeRed, ByVal lpPassword As String, ByVal lpUserName As String, ByVal dwFlags As Long) As Integer
Declare Function QuitarUnidad Lib "mpr.dll" Alias "WNetCancelConnection2A" (ByVal lpName As String, ByVal dwFlags As Long, ByVal fForce As Long) As Integer
'
' Estructura para llamar a la funcion de añadir
'
Structure RecursoDeRed
Public dwScope As Integer
Public dwType As Integer
Public dwDisplayType As Integer
Public dwUsage As Integer
Public lpLocalName As String
Public lpRemoteName As String
Public lpComment As String
Public lpProvider As String
End Structure
'
' Definir el recurso
'
Enum Recurso
CONNECTED = &H1
REMEMBERED = &H3
GLOBALNET = &H2
End Enum
'
' Definir el tipo
'
Enum Recurso_Tipo
DISK = &H1
PRINT = &H2
ANY = &H0
End Enum
'
' Definir la vista
'
Enum Recurso_TipoVista
DOMAIN = &H1
GENERIC = &H0
SERVER = &H2
SHARE = &H3
End Enum
'
' Definir el uso
'
Enum Recurso_TipoUso
CONNECTABLE = &H1
CONTAINER = &H2
End Enum
'
' Asignar codigos de retorno errores
'
Function CodigoError(ByVal Codigo As Integer) As String
Select Case Codigo
Case 0 : Return "La unidad se agrego correctamente."
Case 5 : Return "Acceso denegado!"
Case 66 : Return "El tipo de dispositivo, no es correcto."
Case 67 : Return "El nombre de red, no es correcto."
Case 85 : Return "La unidad ya esta asignada!"
Case 86 : Return "La clave de acceso no es valida!"
Case 170 : Return "Ocupado!"
Case 1200 : Return "Dispositivo Incorrecto."
Case 1202 : Return "El dispositivo se encuentra registrado"
Case 1203 : Return "Sin red o ruta incorrecta!"
Case 1204 : Return "El suministrador es Incorrecto."
Case 1205 : Return "No se puede abrir el perfil"
Case 1206 : Return "El perfil es Incorrecto."
Case 1208 : Return "Error extendido"
Case 1223 : Return "Cancelado!"
Case Else
Return "ERROR, Codigo desconocido:" + Codigo.ToString
End Select
End Function
End Module
Con vuestro permiso y para no perder costumbre os invito a leer este relato corto en tono de humor… que lo disfrutéis!
Hace algunos días, en una de esas reuniones de profesionales, en las que aprovechando el buen recaudo de una cena, se discuten y se tratan todo tipo de temas sin orden ni concierto; recuerdo como entre medio de disertaciones sobre metodologías, lenguajes y sintaxis, me encontré “para variar” evocando aquellos tiempos en que el pan era pan, el vino, vino y los lenguajes eran lenguajes de ¡alto nivel!.
A lo ancho de la alargada mesa se mantenían diversos “hilos” activos con diversas conversaciones “a saber cual la mejor”. Mientras sin saber porque, y acaloradamente empecé a recordar aquellos tiempos en los que los compiladores eras verdaderas joyas de la ingeniería. Aquellos tiempos en los que sin existir la programación orientada a objeto éramos capaces de depurar los ensamblados de los Intel 8086 de las lanzaderas espaciales “online” y en tiempo real, para terminar llevando a un puñado de astronautas a la luna… de pronto me grite para mis adentros :
“Pep!!! Eres un viejo incorregible, siempre terminas hablando de cómo era, pareces un petrificado historiador de informática”
A caldo de la conversación y después de mi pausa se fueron apagando el resto de conversaciones. Sin darme cuenta concentre todas las miradas mientras argumentaba que a pesar de entender la incorporación de ciertas metodologías tales como el procedimiento de realizar un “Invoke” para poder manejar datos entre hilos, tenía cierta sensación de que la medida que avanzábamos en las técnicas perdíamos ciertas purezas del lenguaje. Para poner un ejemplo, desde cierto punto de vista aunque no global, podría entender que fuera el propio compilador que decidiera como cruzar esos datos que se generan entre hilos de ejecución diferentes, de forma que no me obligara a invocar un procedimiento a no ser que yo lo explicitara.
Me gustaría poder enseñaros una foto del las caras que reflejaban los que estaban atentos a mi conversación, en ese momento no se si ponían cara de bobo por absortos o porque realmente estaban pensando que Pep estaba más loco que una cabra. Una vez mas para mis adentros pensé ..
“quien me mandara a mi meterme en semejantes pasteles! … luego me quejo de mi fama! J”.
En honor a todos los presentes y en pro de mi integridad y dedicado a mis contertulios más próximos Vicenc Masanas y Marc Rubiño, os dejo el apunte que en una línea resume lo estaba intentando explicar y que ¡en una línea! Explica que mis pensamientos no eran tan salidos de contexto ni estaban en absoluto equivocados.
CheckForIllegalCrossThreadCalls = false;
Para los que no estuvisteis en la cena… con este pragma instruimos