Mostrando entradas con la etiqueta Visual Basic. Mostrar todas las entradas
Mostrando entradas con la etiqueta Visual Basic. Mostrar todas las entradas

sábado, 24 de septiembre de 2022

Subir y bajar archivos desde un servidor FTP con una aplicación de VB.Net

Para subir o bajar archivos desde un equipo local a una dirección FTP remota con Visual Basic.Net antes se utilizaba la librería de Visual Studio FtpWebRequest. Pero actualmente la documentación indica que dicha librería será descontinuada y que para nuevos desarrollos se utilicen librerías de terceros. Una librería muy utilizada es FluenFTP que es la que vamos a utilizar.

Lo primero que vamos a hacer es descargar dicha librería, para ello desde nuestro proyecto en Visual Studio vamos al menú  de Proyecto y elegimos la opción Administrar paquetes NuGet.

Administrar paquetes NuGet.

Esto nos abre una pantalla desde al que podemos instalar en nuestra aplicación multitud de paquetes de terceros. En nuestro caso ponemos FTP en el buscador y nos muestra una lista de las librerías disponibles. Elegimos FluentFTP pulsamos el botón Instalar. Esto instala automáticamente la librería en nuestro proyecto. Ahora ya podemos utilizar esta librería desde nuestra aplicación.

FluentFTP

Para tener acceso a sus propiedades, funciones y métodos basta con poner la correspondiente llamada Imports, al comienzo de nuestra aplicación de Visual Basic.Net

 

Imports FluentFTP

 

El siguiente paso será definir un Objeto de la clase FTPClient, para ello escribimos

 

Dim client = New FtpClient(strFTP, strUsuario, strPassword)

 

Donde strFTP será una variable de tipo string que traerá la dirección del FTP, ojo, hay que tener en cuenta que será una dirección del tipo ftp://servidor.remoto.es/DIRECTORIO  Si no trae el ftp:// del principio se lo podemos añadir por programa con algo así:

 

strFTP = "ftp://" & strDireccionDestino

 

strUsuario y strPassword traerán el usuario y contraseña correspondientes para conectarnos al servidor FTP deseado. También debemos conectarnos al servidor. Con esta librería es tan fácil como escribir:

 

client.Connect()

 

Ahora lo que queremos es pasar un archivo desde un directorio de nuestro equipo a un FTP remoto. Escribimos la siguiente línea.

 

client.UploadFile(strDireccionOrigen & "\" & strNombreOrigen, strNombreDestino)

 

Donde en strDireccionOrigen vendrá una cadena con la dirección de nuestro equipo del tipo  “C:\Directorio\Subdirectorio” en strNombreOrigen vendrá el nombre de nuestro archivo de origen, y si deseamos nombrarlo con un nuevo nombre podemos almacenar el nuevo nombre en strNombreDestino o podemos enviar directamente strNombreOrigen en el segundo parámetro si deseamos que el archivo se llame igual que el del origen.

 

Si deseamos copiarlo en un subdirectorio del FTP podemos hacer

 

client.UploadFile(strDireccionOrigen & "\" & strNombreOrigen, strSubdirectorio_FTP & "/" & strNombreDestino)

 

Donde strSubdirectorio_FTP traerá el nombre del subdirectorio del tipo “/SUBDIRECTORIO” no siendo necesario escribir el nombre completo del FTP.

 

Esta función tiene varias sobrecargas, de modo que podemos hacer

 

client.UploadFile(strDireccionOrigen & "\" & strNombreOrigen, strSubdirectorios_FTP & "/" & strNombreDestino, FtpRemoteExists.Overwrite, True, FtpVerify.Retry)

 

Esta llamada Sobreescribe el archivo en destino si ya existe uno del mismo nombre. Esto lo hace el parámetro FtpRemoteExists.Overwrite, el siguiente parámetro True indica que si no existe el directorio en el destino lo crea, y el último parámetro FtpVerify.Retry verifica si se ha copiado el archivo correctamente en el FTP.

 

Finalmente cerramos la conexión

 

client.Disconnect()

 

Ahora vamos a traer un archivo desde FTP a nuestro equipo. Para ello creamos el objeto y abrimos la conexión igual que hicimos anteriormente

 

Dim client = New FtpClient(strFTP, strUsuario, strPassword)

client.Connect()

 

Ahora para traer el archivo desde el FTP remoto tenemos que hacer un For Each sobre los objetos del FTP remoto para traer todos los que encontremos

 

For Each item As FtpListItem In client.GetListing(strSubdirectorios_FTP)

                    client.DownloadFile(strDireccionDestino & item.FullName, item.FullName, FtpLocalExists.Overwrite)

                   

                    client.DeleteFile(item.FullName)

Next item

                   

En este caso, si el archivo se encuentra en un subdirectorio del FTP, nos lo creará en el destino indicado de nuestro equipo y escribirá los archivos en el directorio creado, estos vendrán en strSubdirectorios_FTP. Si el FTP no tiene subdirectorios podemos hacer strSubdirectorios_FTP = “”. Para no dejar los archivos copiados en el FTP de origen podemos borrarlos con

 

client.DeleteFile(item.FullName)

 

Terminamos igual que en el caso anterior con

 

client.Disconnect()

 

Finalmente dejo como podrían ser las dos Funciones para enviar y obtener ficheros desde nuestro equipo a un FTP.  Esta sería para llevar un archivo desde nuestro equipo al FTP remoto.

 

Private Function Trata_Archivo_FTP(ByVal Folder_file As Object,  ByVal strDireccionOrigen As String, ByVal strDireccionDestino As String, ByVal strUsuario As String, ByVal StrPassword As String) As Boolean

        Dim strNombreOrigen As String = ""

        Dim strNombreDestino As String = ""

        Dim strSubdirectorios_FTP As String = "/SUBDIR_FTP"

 

                Try

                

            'crea un cliente FTP

            Dim client = New FtpClient(strFTP, strUsuario, StrPassword)

            client.Connect()

 

            For Each File As FileInfo In Folder_file

                strNombreOrigen = File.Name

                strNombreDestino = File.Name

                    'sube un archivo a un FTP

                    client.UploadFile(strDireccionOrigen & "\" & strNombreOrigen, strSubdirectorios_FTP & "/" & strNombreDestino, FtpRemoteExists.Overwrite, True, FtpVerify.Retry)

                    'Como el origen del fichero no es FTP, borramos archivo en nuestro equipo

                    My.Computer.FileSystem.DeleteFile(strDireccionOrigen & "\" & strNombreOrigen)

        Next

            'desconecta FTP

            client.Disconnect()

            Trata_Archivo_FTP = False

        Catch ex As Exception

        End Try

    End Function

 

La función anterior recorre nuestro directorio local pasado en strDireccionOrigen para ver los archivos que tiene, para ello tiene como parámetro de entrada el objeto Folder_file. La llamada a esta función será de este estilo:

 

Dim folder As New DirectoryInfo(strDireccionOrigen)

bErrores = Trata_Archivo_FTP(folder.GetFiles(), strDireccionOrigen, strDireccionDestino, strUsuarioFTP, strPasswordFTP)

 

Una definición de la función para traer un archivo al equipo local desde un FTP podría ser de este estilo:

Private Function Trata_FTP_Archivo(ByVal strDireccionDestino As String, ByVal strUsuario As String, ByVal strPassword As String) As Boolean

        Dim strNombreOrigen As String = ""

        Dim strNombreDestino As String = ""

        Dim strSubdirectorios_FTP As String ="/SUBDIR_FTP"

 

        Try

 

            'crea un FTP cliente

            Dim client = New FtpClient(strFTP, strUsuario, strPassword)

            client.Connect()

            'Se trae todo lo que hay en el FTP remoto

            For Each item As FtpListItem In client.GetListing(strSubdirectorios_FTP)

               

                    client.DownloadFile(strDireccionDestino & item.FullName, item.FullName, FtpLocalExists.Overwrite)

               client.DeleteFile(item.FullName)

 

            Next ítem

            'desconecta

            client.Disconnect()

            Trata_FTP_Archivo = False

        Catch ex As Exception

            MsgBox(ex.Message)

        End Try

    End Function

Si queremos hacer un traspaso de FTP a FTP yo no he sido capaz de hacerlo directamente con FTPFluent, pero si llamando a estas dos  funciones descritas arriba alternativamente utilizando el directorio del equipo local C:\FTP como repositorio para almacenar los archivos traídos desde un FTP antes de enviarlos al FTP de destino.

Dim strAuxDireccion As String

bErrores = Trata_FTP_Archivo(strDireccionOrigen, "C:\FTP", strUsuarioFTP_Ori, strPasswordFTP_Ori)

Dim folder2 As New DirectoryInfo(strAuxDireccion)

 

bErrores = Trata_Archivo_FTP(folder2.GetFiles(),strAuxDireccion, strDireccionDestino, strUsuarioFTP_Des, strPasswordFTP_Des)

Hay que tener en cuenta, que si el FTP de origen tiene los archivos en un subdirectorio, creará el subdirectorio sobre C:\FTP con lo que tendremos que tratarlo para que no de error al leerlo de nuevo. Dentro de Trata_Archivo_FTP debemos sacar el subdirectorio creado con algo como esto:

Dim strFTP As String

Dim intPosBarra As Integer

            'Si la direccion FTP es un directorio separa el servidor y los subdirectorios

intPosBarra = InStr(strDireccionDestino, "/")

If intPosBarra <> 0 Then

      strSubdirectorios_FTP = Mid(strDireccionDestino, intPosBarra)

      strFTP = "ftp://" & Mid(strDireccionDestino, 1, intPosBarra)

Else

      strSubdirectorios_FTP = ""

      strFTP = "ftp://" & strDireccionDestino

End If

 

Y en Trata_FTP_Archivo, algo como esto

 

Dim intPosBarra As Integer

Dim strAuxDir() As String

            'Si la direccion FTP es un directorio separa el servidor y los subdirectorios

            intPosBarra = InStr(strDireccionOrigen, "/")

            If intPosBarra <> 0 Then

                strSubdirectorios_FTP = Mid(strDireccionOrigen, intPosBarra)

                strFTP = "ftp://" & Mid(strDireccionOrigen, 1, intPosBarra)

            Else

                strSubdirectorios_FTP = ""

                strFTP = "ftp://" & strDireccionOrigen

            End If




sábado, 17 de octubre de 2020

Pantalla de mantenimiento con campos asociados



Ahora vamos a ampliar un poco más los conceptos y vamos a ver como hacer un enlace (binding) a otra tabla similar a un INNER JOIN de Oracle pero desde el código de Visual Basic. También veremos como posicionar en el DataGridView  las columnas donde deseemos independientemente de su orden de carga.

Finalmente añadiremos una sub-pantalla para rellenar dos campos de mantenimiento  desde otra pantalla con combos anidadas 

La pantalla de mantenimiento será una pantalla sencilla con un grid,  cuatro campos de filtrado y mantenimiento, los botones Alta, Baja, Modificación. Aceptar, Cancelar y finalmente un botón que nos llevará a la sub-pantalla con dos combos anidadas para la edición de los dos campos nivel 1 y nivel 2.

En el form load indicamos la asociación con los botones de Alta, Baja y Modificación.

Pantalla de mantenimiento con campos asociados
Si quieres esta foto.



Private Sub frmCamposaAsociados_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Me.grdResult.DataSource = bs

        txtColumna.MaxLength = 40
  

        CamposAsociados()
        btnModificar.Visible = IIf(ObjetosPermitidos.ContainsKey("btnModificar"), True, False)
        btnBaja.Visible = IIf(ObjetosPermitidos.ContainsKey("btnBaja"), True, False)
        btnAlta.Visible = IIf(ObjetosPermitidos.ContainsKey("btnAlta"), True, False)

        cboDataSource.Text = gDataSource
        btnCombosAnidadas.Enabled = False
    End Sub

Definimos un procedimiento donde leemos de la Base de Datos de Oracle y hacemos el enlace (binding) entre nuestra pantalla y la base de datos.

Sub CamposAsociados()


        Dim tb As DataTable
        Dim srSQL As StreamReader
        Dim strConsulta As String
        Dim cmdbuilder As OleDbCommandBuilder

        tb = New DataTable

        'Repeticion si no es la primera vez es que se ha llamado desde el mantenimiento y antes de cargarse debe borrarse la grid
        If Not bolPrimeraVez Then
            ds.Tables("CamposAsociados").Clear()
        End If
        '***********************

        strStartupPath = My.Application.Info.DirectoryPath
        srSQL = New System.IO.StreamReader(strStartupPath & "\CargarCamposAsociados".sql")
        strConsulta = srSQL.ReadToEnd
        cmdPD = New OleDb.OleDbCommand(strConsulta, cnxPD)
        dA.SelectCommand = cmdPD
        cmdbuilder = New OleDbCommandBuilder(dA)
        dA.Fill(ds, "CamposAsociados")

        If Not bolPrimeraVez Then
            Exit Sub
        End If

        bolPrimeraVez = False

        'Codigo nuevo para primer campo asociado
        srSQL = New System.IO.StreamReader(strStartupPath & "\CargarCampoAso1.sql")
        strConsulta = srSQL.ReadToEnd
        cmdPD = New OleDb.OleDbCommand(strConsulta, cnxPD)
        dA.SelectCommand = cmdPD
        dA.Fill(ds, "CampoAso1")

        cboLevel1.ValueMember = "COD_CAMPO1"
        cboLevel1.DisplayMember = "DES_CAMPO1"
        cboLevel1.DataSource = ds.Tables("CampoAso1").DefaultView

        'Codigo nuevo para primer campo asociado
        srSQL = New System.IO.StreamReader(strStartupPath & "\CargarCampoAso2.sql")
        strConsulta = srSQL.ReadToEnd
        cmdPD = New OleDb.OleDbCommand(strConsulta, cnxPD)
        dA.SelectCommand = cmdPD
        dA.Fill(ds, "CampoAso2")

        cboLevel1.ValueMember = "COD_CAMPO_SEC2"
        cboLevel1.DisplayMember = "DES_CAMPO2"
        cboLevel1.DataSource = ds.Tables("CampoAso2").DefaultView

      
        'Código para definir las relaciones con las tablas de los campos asociados ojo notar 'que COD_CAMPO1 se refiere a una columna de cada tabla, es decir COD_CAMPO1 de Tabla 'Relacionada y COD_CAMPO1 de tabla principal. Esto en la simulación por código del INNER 'JOIN

        ds.Relations.Add("CampoAso1_Relacion", ds.Tables("CampoAso1").Columns("COD_CAMPO1"),
ds.Tables("CamposAsociados").Columns("COD_CAMPO1"))

     ds.Relations.Add("CampoAso2_Relacion",ds.Tables("CampoAso2").Columns("COD_CAMPO_SEC2"), ds.Tables("CamposAsociados").Columns("COD_CAMPO_PRINC2"))

    
        Dim oNivel1 As DataColumn = New DataColumn
        oNivel1.DataType = System.Type.GetType("System.String")
        oNivel1.ColumnName = "CampoAso1"
        oNivel1.Expression = "Parent(CampoAso1_Relacion).DES_CAMPO1"
        ds.Tables(Tabla).Columns.Add(oNivel1)

        Dim oNivel2 As DataColumn = New DataColumn
        oNivel2.DataType = System.Type.GetType("System.String")
        oNivel2.ColumnName = "CampoAso2"
        oNivel2.Expression = "Parent(CampoAso2_Relacion).DES_CAMPO2"
        ds.Tables(Tabla).Columns.Add(oNivel2)

      'Aquí fuerzo de nuevo la select principal para que no me de error al realizar 'operaciones con el formulario una vez he cargado el data GridView
        dA.SelectCommand = New OleDb.OleDbCommand("SELECT  COD_ID, DES_PRINCIPAL, COD_CAMPO1, COD_CAMPO_PRINC2 FROM PROPIETARIO.TABLA_PRINCIPAL", cnxPD)

        dA.UpdateCommand = cmdbuilder.GetUpdateCommand()
        dA.DeleteCommand = cmdbuilder.GetDeleteCommand()
        dA.InsertCommand = cmdbuilder.GetInsertCommand()

        Try

            bs.DataSource = ds.Tables(Tabla)

       'Aquí hacemos el binding de los campos de la base de datos con los controles del formulario

            Me.txtColumna.DataBindings.Add(New Binding("Text", bs, "COD_ID"))
            Me.txtDescripcion.DataBindings.Add(New Binding("Text", bs, "DES_PRINCIPAL"))
            Me.cboLevel1.DataBindings.Add(New Binding("SelectedValue", bs, "COD_CAMPO1"))
            Me.cboLevel2.DataBindings.Add(New Binding("SelectedValue", bs, "COD_CAMPO_PRINC2"))
          
         
            grdResult.Columns(0).MinimumWidth = 50
            grdResult.Columns(1).MinimumWidth = 50  'Descripcion
            grdResult.Columns(2).MinimumWidth = 30  'Campo1
            grdResult.Columns(3).MinimumWidth = 200 'Campo2
        
          
            grdResult.Columns(0).HeaderText = "Identificador"
            grdResult.Columns(1).HeaderText = "Descripción"
            grdResult.Columns(2).HeaderText = "Campo asociado 1"
            grdResult.Columns(3).HeaderText = "Campo Asociado 2"

            grdResult.Refresh()

        Catch ex As Exception
        End Try

    End Sub

Las SELECT principal será de este estilo:

SELECT  COD_ID,  DES_PRINCIPAL, COD_CAMPO1, COD_CAMPO_PRINC2  FROM PROPIETARIO.TABLA_PRINCIPAL

Y las de las tablas secundarias serán así:

SELECT COD_CAMPO1, DES_CAMPO1 FROM PROPIETARIO.TABLA_SEC1

SELECT COD_CAMPO_SEC2, DES_CAMPO2 FROM PROPIETARIO.TABLA_SEC2

Definición del botón para las altas

Private Sub btnAlta_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAlta.Click
   
        grbDetalle.Enabled = True
        grbBuscar.Enabled = False
        btnCombosAnidadas.Enabled = False
        drEdit = bs.AddNew()
        bs.MoveLast()
 
        grdResult.Refresh()
    End Sub

Definición del botón para las bajas

Private Sub btnBaja_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBaja.Click
        Dim drv As DataRowView
        Dim b As Integer
       
        Try

            If MsgBox("¿Esta usted seguro que desea borrar este registro?", MsgBoxStyle.YesNo + MsgBoxStyle.Question, "Confirmación de Baja") = MsgBoxResult.Yes Then
                drv = bs.Current
                b = grdResult.CurrentRow.Index
                drv.Delete()
            End If
            dA.Update(ds, Tabla)
            grbDetalle.Enabled = False
            grdResult.Refresh()
        Catch ex As Exception
            If Mid(ex.Message, 1, 9) = "ORA-00001" Then
                MsgBox("El código  COD_ID debe ser único revise los datos e inténtelo de nuevo", MsgBoxStyle.Critical, "Error al actualizar los datos")
            End If
            If Mid(ex.Message, 1, 9) = "ORA-02292" Then
                MsgBox("No está permitido el borrado en cascada, asegúrese que el COD_ID no tiene Sub ID´s antes de borrarlo", MsgBoxStyle.Critical, "Error al actualizar los datos")
            End If
            Recargar()
        End Try
    End Sub

Botón para las modificaciones

Private Sub btnModificar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnModificar.Click
     
        grbDetalle.Enabled = True
        grbBuscar.Enabled = False
        
End Sub

Botón aceptar, graba las altas, bajas o modificaciones

Private Sub btnAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAceptar.Click
        Dim drv As DataRowView
        Try
            drv = bs.Current

            drv("COD_CAMPO1") = cboLevel1.SelectedValue
            drv("COD_CAMPO_SEC2") = cboLevel2.SelectedValue
         
            Traduce(drv) 'traduce las descripciones a códigos
            bs.EndEdit()
            dA.Update(ds, Tabla)

            grbBuscar.Enabled = True
            grbDetalle.Enabled = False

            grdResult.Refresh()
            CamposAsociados() 'refresca los cambios
            bolRefrescar = False
        Catch ex As Exception
            If Mid(ex.Message, 1, 9) = "ORA-00001" Then
                MsgBox("El código COD_ID debe ser único revise los datos e inténtelo de nuevo", MsgBoxStyle.Critical, "Error al actualizar los datos")
            End If
            bolRefrescar = True
        End Try

End Sub

Botón cancelar

Private Sub btnCancelar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancelar.Click
        bs.CancelEdit()
        If bolRefrescar Then
            Recargar()
        End If
        grbDetalle.Enabled = False
        grbBuscar.Enabled = True
End Sub

Botón recargar

Sub Recargar()
     bolRefrescar = False
     ds.Tables(0).Clear()
     CamposAsociados()
     grdResult.Refresh()
End Sub

Finalmente este será el código del botón que llama a la pantalla con las dos combos anidadas que cargará los dos campos asociados.

Public Sub btnPaisDom_Click(sender As Object, e As EventArgs) Handles btnPaisDom.Click
        Dim d As New frmNiveles()  'envia los datos del grid al formulario hijo


     
        d.gID = txtColumna.Text
      
        d.cboLevel1.Text = cboLevel1.Text
        d.cboLevel2.Text = cboLevel2.Text

        d.cboLevel1.SelectedItem = cboLevel1.SelectedItem
        d.cboLevel2.SelectedItem = cboLevel2.SelectedItem

        d.ShowDialog()
       
        cboLevel1.Text = d.cboLevel1.Text
        cboLevel2.Text = d.cboLevel2.Text
      
        If Not d.cboLevel1.SelectedItem Is Nothing Then
            txtNivel1.Text = d.cboLevel1.SelectedItem.clave
        End If

        If Not d.cboLevel2.SelectedItem Is Nothing Then
            txtNivel2.Text = d.cboLevel2.SelectedItem.clave
        End If

        d.Close()
    End Sub

End Class

Pasamos al código de la pantalla frmNiveles que cargará dos combos anidadas.

Public Class frmNiveles
  
    Public gID As String

    Private Sub cboLevel1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboLevel1.SelectedIndexChanged
        cboLevel2.Items.Clear()
        cboLevel2.Text = ""

        frmNiveles.Cargacombo("\CargarCampoAso2.sql", cboLevel2, DirectCast(Me.cboLevel1.SelectedItem, AddItem).Clave.ToString())
        btnSeleccionar.Enabled = True
    End Sub

    Private Sub frmNiveles_Load(sender As Object, e As EventArgs) Handles MyBase.Load
      
        cboLevel1.Items.Clear()
        cboLevel1.Text = ""
        cboLevel2.Items.Clear()
        cboLevel2.Text = ""

        frmNiveles.Cargacombo("\CargarCampoAso1.sql", cboLevel1, gID)
        btnSeleccionar.Enabled = True
    End Sub

    Private Sub btnSeleccionar_Click(sender As Object, e As EventArgs) Handles btnSeleccionar.Click
       
       Me.Hide()
    End Sub
End Class

El código completo del formulario principal es este:

Imports System.IO
Imports System.Data.OleDb
Imports System.Windows.Forms
Imports System.Data.SqlClient

Public Class frmCamposAsociados

    Public gDataSource As String
    Dim ds As New DataSet()
    Dim bs As BindingSource = New BindingSource()
    Dim dA As New OleDb.OleDbDataAdapter
    Dim drEdit As DataRowView
    Dim bolRefrescar As Boolean
    Dim bolPrimeraVez As Boolean = True
    Const Tabla = "CamposAsociados"

     Private Sub frmCamposaAsociados_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Me.grdResult.DataSource = bs

        txtColumna.MaxLength = 40
  

        CamposAsociados()
        btnModificar.Visible = IIf(ObjetosPermitidos.ContainsKey("btnModificar"), True, False)
        btnBaja.Visible = IIf(ObjetosPermitidos.ContainsKey("btnBaja"), True, False)
        btnAlta.Visible = IIf(ObjetosPermitidos.ContainsKey("btnAlta"), True, False)

        cboDataSource.Text = gDataSource
        btnCombosAnidadas.Enabled = False
    End Sub

    Sub CamposAsociados()

        Dim tb As DataTable
        Dim srSQL As StreamReader
        Dim strConsulta As String
        Dim cmdbuilder As OleDbCommandBuilder

        tb = New DataTable

        'Repeticion si no es la primera vez es que se ha llamado desde el mantenimiento y antes de cargarse debe borrarse la grid
        If Not bolPrimeraVez Then
            ds.Tables("CamposAsociados").Clear()
        End If
        '***********************

        strStartupPath = My.Application.Info.DirectoryPath
        srSQL = New System.IO.StreamReader(strStartupPath & "\CargarCamposAsociados".sql")
        strConsulta = srSQL.ReadToEnd
        cmdPD = New OleDb.OleDbCommand(strConsulta, cnxPD)
        dA.SelectCommand = cmdPD
        cmdbuilder = New OleDbCommandBuilder(dA)
        dA.Fill(ds, "CamposAsociados")

        If Not bolPrimeraVez Then
            Exit Sub
        End If

        bolPrimeraVez = False

        'Codigo nuevo para primer campo asociado
        srSQL = New System.IO.StreamReader(strStartupPath & "\CargarCampoAso1.sql")
        strConsulta = srSQL.ReadToEnd
        cmdPD = New OleDb.OleDbCommand(strConsulta, cnxPD)
        dA.SelectCommand = cmdPD
        dA.Fill(ds, "CampoAso1")

        cboLevel1.ValueMember = "COD_CAMPO1"
        cboLevel1.DisplayMember = "DES_CAMPO1"
        cboLevel1.DataSource = ds.Tables("CampoAso1").DefaultView

        'Codigo nuevo para primer campo asociado
        srSQL = New System.IO.StreamReader(strStartupPath & "\CargarCampoAso2.sql")
        strConsulta = srSQL.ReadToEnd
        cmdPD = New OleDb.OleDbCommand(strConsulta, cnxPD)
        dA.SelectCommand = cmdPD
        dA.Fill(ds, "CampoAso2")

        cboLevel1.ValueMember = "COD_CAMPO_SEC2"
        cboLevel1.DisplayMember = "DES_CAMPO2"
        cboLevel1.DataSource = ds.Tables("CampoAso2").DefaultView

      
        'Código para definir las relaciones con las tablas de los campos asociados ojo notar 'que COD_CAMPO1 se refiere a una columna de cada tabla, es decir COD_CAMPO1 de Tabla 'Relacionada y COD_CAMPO1 de tabla principal. Esto en la simulación por código del INNER 'JOIN

        ds.Relations.Add("CampoAso1_Relacion", ds.Tables("CampoAso1").Columns("COD_CAMPO1"),
ds.Tables("CamposAsociados").Columns("COD_CAMPO1"))

     ds.Relations.Add("CampoAso2_Relacion",ds.Tables("CampoAso2").Columns("COD_CAMPO_SEC2"), ds.Tables("CamposAsociados").Columns("COD_CAMPO_PRINC2"))

    
        Dim oNivel1 As DataColumn = New DataColumn
        oNivel1.DataType = System.Type.GetType("System.String")
        oNivel1.ColumnName = "CampoAso1"
        oNivel1.Expression = "Parent(CampoAso1_Relacion).DES_CAMPO1"
        ds.Tables(Tabla).Columns.Add(oNivel1)

        Dim oNivel2 As DataColumn = New DataColumn
        oNivel2.DataType = System.Type.GetType("System.String")
        oNivel2.ColumnName = "CampoAso2"
        oNivel2.Expression = "Parent(CampoAso2_Relacion).DES_CAMPO2"
        ds.Tables(Tabla).Columns.Add(oNivel2)

      'Aquí fuerzo de nuevo la select principal para que no me de error al realizar 'operaciones con el formulario una vez he cargado el data GridView
        dA.SelectCommand = New OleDb.OleDbCommand("SELECT  COD_ID, DES_PRINCIPAL, COD_CAMPO1, COD_CAMPO_PRINC2 FROM PROPIETARIO.TABLA_PRINCIPAL", cnxPD)

        dA.UpdateCommand = cmdbuilder.GetUpdateCommand()
        dA.DeleteCommand = cmdbuilder.GetDeleteCommand()
        dA.InsertCommand = cmdbuilder.GetInsertCommand()

        Try

            bs.DataSource = ds.Tables(Tabla)

       'Aquí hacemos el binding de los campos de la base de datos con los controles del formulario

            Me.txtColumna.DataBindings.Add(New Binding("Text", bs, "COD_ID"))
            Me.txtDescripcion.DataBindings.Add(New Binding("Text", bs, "DES_PRINCIPAL"))
            Me.cboLevel1.DataBindings.Add(New Binding("SelectedValue", bs, "COD_CAMPO1"))
            Me.cboLevel2.DataBindings.Add(New Binding("SelectedValue", bs, "COD_CAMPO_PRINC2"))
          
         
            grdResult.Columns(0).MinimumWidth = 50
            grdResult.Columns(1).MinimumWidth = 50  'Descripcion
            grdResult.Columns(2).MinimumWidth = 30  'Campo1
            grdResult.Columns(3).MinimumWidth = 200 'Campo2
        
          
            grdResult.Columns(0).HeaderText = "Identificador"
            grdResult.Columns(1).HeaderText = "Descripción"
            grdResult.Columns(2).HeaderText = "Campo asociado 1"
            grdResult.Columns(3).HeaderText = "Campo Asociado 2"

            'coloca las columnas en la posición deseada
            grdResult.Columns(0).DisplayIndex = 2  'Identificador
            grdResult.Columns(1).DisplayIndex = 1 'Descripcion
            grdResult.Columns(2).DisplayIndex = 4 'Campo1
            grdResult.Columns(3).DisplayIndex = 3 'Campo2

            grdResult.Refresh()

        Catch ex As Exception
        End Try

    End Sub

    Function BuscarCodigoRelacion() As Integer
        Stop
    End Function


    Private Sub grdResult_RowEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles grdResult.RowEnter

    End Sub

    Private Sub btnFiltro_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFiltro.Click
        Dim strFiltro As String = ""
        Dim vwCamposAsociados As DataView


        If txtFiltroColumna.Text <> "" Then
            If Not strFiltro = "" Then
                strFiltro = strFiltro & " AND "
            End If
            strFiltro = strFiltro & "COD_ID LIKE '%" & UCase(txtFiltroColumna.Text) & "%'"  
        End If

     

        If txtFiltroDescripcion.Text <> "" Then
            If Not strFiltro = "" Then
                strFiltro = strFiltro & " AND "
            End If
            strFiltro = strFiltro & "DES_PRINCIPAL LIKE '%" & txtFiltroDescripcion.Text & "%'"
        End If

     
        If Not ds.Tables("CamposAsociados") Is Nothing Then
            vwCamposAsociados = ds.Tables("CamposAsociados").DefaultView
            vwCamposAsociados.RowFilter = strFiltro
        End If
    End Sub

    Private Sub btnAlta_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAlta.Click
      
        grbDetalle.Enabled = True
        grbBuscar.Enabled = False
        btnPaisDom.Enabled = False
        drEdit = bs.AddNew()
        bs.MoveLast()
      
        grdResult.Refresh()
    End Sub

    Private Sub btnBaja_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBaja.Click
        Dim drv As DataRowView
        Dim b As Integer
      
        Try

            If MsgBox("¿Esta usted seguro que desea borrar este registro?", MsgBoxStyle.YesNo + MsgBoxStyle.Question, "Confirmación de Baja") = MsgBoxResult.Yes Then
                drv = bs.Current
                b = grdResult.CurrentRow.Index
                drv.Delete()
            End If
            dA.Update(ds, Tabla)
            grbDetalle.Enabled = False
            grdResult.Refresh()
        Catch ex As Exception
            If Mid(ex.Message, 1, 9) = "ORA-00001" Then
                MsgBox("El código de ID debe ser único revise los datos e inténtelo de nuevo", MsgBoxStyle.Critical, "Error al actualizar los datos")
            End If
            If Mid(ex.Message, 1, 9) = "ORA-02292" Then
                MsgBox("No está permitido el borrado en cascada, asegurese que el ID no tiene Sub ID´s antes de borrarla", MsgBoxStyle.Critical, "Error al actualizar los datos")
            End If
            Recargar()
        End Try

    End Sub

    Private Sub btnModificar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnModificar.Click
     
        grbDetalle.Enabled = True
        grbBuscar.Enabled = False
        
    End Sub

    Private Sub btnAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAceptar.Click
        Dim drv As DataRowView
        Try
            drv = bs.Current

         

            drv("COD_CAMPO1") = cboLevel1.SelectedValue
            drv("COD_CAMPO_SEC2") = cboLevel2.SelectedValue
          

            bs.EndEdit()
            dA.Update(ds, Tabla)

            grbBuscar.Enabled = True
            grbDetalle.Enabled = False

            grdResult.Refresh()
            CamposAsociados() 'refresca los cambios
            bolRefrescar = False
        Catch ex As Exception
            If Mid(ex.Message, 1, 9) = "ORA-00001" Then
                MsgBox("El código ID debe ser único revise los datos e inténtelo de nuevo", MsgBoxStyle.Critical, "Error al actualizar los datos")
            End If
            bolRefrescar = True
        End Try

    End Sub
   
    Private Sub btnCancelar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancelar.Click
        bs.CancelEdit()
        If bolRefrescar Then
            Recargar()
        End If
        grbDetalle.Enabled = False
        grbBuscar.Enabled = True
    End Sub

    Sub Recargar()
        bolRefrescar = False
        ds.Tables(0).Clear()
        CamposAsociados()
        grdResult.Refresh()
    End Sub

    Private Sub txtCodigo_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)

    End Sub

  
    Private Sub txtColumna_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtColumna.TextChanged
       
        txtColumna.Text = UCase(txtColumna.Text)
        txtColumna.SelectionStart = txtColumna.TextLength + 1
        txtColumna.MaxLength = 10
    End Sub

 
    Sub AplicarFiltro()
        Dim strFiltro As String = ""
        Dim vw CamposAsociados As DataView
    
        If txtFiltroColumna.Text <> "" Then
            If Not strFiltro = "" Then
                strFiltro = strFiltro & " AND "
            End If
            strFiltro = strFiltro & "COD_ID LIKE '%" & UCase(txtFiltroColumna.Text) & "%'"  
        End If

     
        If txtFiltroDescripcion.Text <> "" Then
            If Not strFiltro = "" Then
                strFiltro = strFiltro & " AND "
            End If
            strFiltro = strFiltro & "DES_PRINCIPAL LIKE '%" & txtFiltroDescripcion.Text & "%'"
        End If

     
        If Not ds.Tables("CamposAsociados") Is Nothing Then
            vwCamposAsociados = ds.Tables("CamposAsociados").DefaultView
            vwCamposAsociados.RowFilter = strFiltro
        End If

    End Sub

    Private Sub txtFiltroColumna_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtFiltroColumna.TextChanged
        AplicarFiltro()
    End Sub

    Private Sub txtFiltroDescripcion_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtFiltroFormato.TextChanged
        AplicarFiltro()
    End Sub

Public Sub btnPaisDom_Click(sender As Object, e As EventArgs) Handles btnPaisDom.Click
        Dim d As New frmNiveles()  'envia los datos del grid al formulario hijo

        d.gID = txtColumna.Text
      
        d.cboLevel1.Text = cboLevel1.Text
        d.cboLevel2.Text = cboLevel2.Text

        d.cboLevel1.SelectedItem = cboLevel1.SelectedItem
        d.cboLevel2.SelectedItem = cboLevel2.SelectedItem

        d.ShowDialog()
      
        cboLevel1.Text = d.cboLevel1.Text
        cboLevel2.Text = d.cboLevel2.Text
      
        If Not d.cboLevel1.SelectedItem Is Nothing Then
            txtNivel1.Text = d.cboLevel1.SelectedItem.clave
        End If

        If Not d.cboLevel2.SelectedItem Is Nothing Then
            txtNivel2.Text = d.cboLevel2.SelectedItem.clave
        End If

        d.Close()
    End Sub

End Class