
Access: Cuadro de Lista con selección múltiple
04/04/2022
Access: reloj permanente en TextBox
05/04/2022¿Qué vamos a ver en esta clase?
En esta clase vamos a tratar el tema de Drag & Drop en Access.
Exploraremos todos los métodos que nos permitirán obtener el deseado efecto. Dependiendo del tipo de objeto que queramos "arrastrar" y del quipo que queramos "soltar", utilizaremos uno de los métodos analizados.
Para ilustrar esta funcionalidad hemos preparado 7 ejemplos que no te dejarán indiferente:
1. Haremos Drag&Drop entre diversos objetos de un formulario (Checbox, combobox, cuadro de texto, botones de opción,...)
2. Pasaremos registros de un ListBox a otro ListBox.
3. Arrastraremos un fichero PDF a un cuadro que nos permitirá mostrar ese fichero en un objeto webBrowser, en tiempo de ejecución.
4. Arrastraremos una imagen a un formularrio y la mostraremos en ese instante en un objeto "picture".
5. Desplazaremos un nodo de un TreeView a otro TreeView.
6. Arrastraremos un nodo de un TreeView a un ListView para ejecutar una acción con el registro del ListView seleccionado.
7. Moveremos nodos de un TreeView dentro del propio objeto.
Nota de última hora: cuando ya teníamos preparada nuestra clase, nuestro amigo McPegasus, nos ha propuesto el ejemplo 8. Arrastrar un botón y colocarlo en un cuadro de texto. Hemos incluido este ejemplo que hemos desarrollado utilizando el método 1.
8. Arrastrar un "CommandButton" a un "TextBox".
Como podéis ver, hemos intentado ser rigurosos con esta clase y mostrar todas las opciones reales que nos ofrece Access para "simular" el proceso de Drag&Drop.
Al final de este artículo encontrarési un fichero .accdb con todos los ejemplos explicados y comentados como se ve en el vídeo, además de imágenes que hemos utilizado en nuestros ejemplos.
El código que te mostramos a contnuación está ordenado según el método utilizado. En la clase mostramos 3 maneras diferentes de ejecutar Drag&Drop en Access, por eso hemos incluido 3 bloques de código.
'---------
'MÉTODO 1
'---------
'-----------------------------------------------------------------------------------------------------------------------------------------------
' Fuente : https://access-global.net/drag-drop-en-access
'-----------------------------------------------------------------------------------------------------------------------------------------------
' Título : Drag&Drop
' Autor original : Doug Steele, MVP AccessHelp@rogers.com
' Adaptado por : Luis Viadel | https://cowtechnologies.net
' Actualizado : mayo 2020
' Propósito : crear un procedimiento DragDrop que se ejecutará en respuesta a un control que se arrastra a otro control
' ¿Cómo funciona? : hay dos funciones
' DragStart inicializa la funcionalidad "Drag"
' DropDetect captura los controles involucrados y sus posiciones
' DragStop finaliza la funcionalidad "Drag"
' Dispone de tres funciones de apoyo:
' - ListBoxExample para realizar la operación entre dos ListBox
' - ProcessDrop que permite discernir entre los distintos controles para adaptar el gdato que se coloca (Drop).Esta rutina
' debe llamarse desde el evento DetectDrop de cualquier control que desee que pueda ser un destino de un control arrastrado.
' Inputs: DragForm Formulario que contiene el control que está siendo arrastrado
' DragCtrl Control, del formulario DragForm, que está siendo arrastrado
' DropForm Formulario que contiene el control donde va a ser colocado el dato
' DropCtrl Control, del formulario DropForm, que va a ser colocado
' Button, Shift, X, Y Parámetros asociados con los eventos del ratón
' - ReturnSelectedOption es una función para los OptionGroup
' Más información : Microsoft Knowledge Base Article 137650
' https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/137650
'-----------------------------------------------------------------------------------------------------------------------------------------------
Sub DragStart(SourceFrm As Form)
Dim strIconPath As String
Set DragFrm = SourceFrm
Set DragCtrl = Screen.ActiveControl
CurrentMode = DRAG_MODE
'Incorpora el icono de nuestra elección
strIconPath = Application.CurrentProject.Path & "\Recursos\DragDrop.ico"
If Len(Dir$(strIconPath)) > 0 Then
SetMouseCursorFromFile strIconPath
Else
SetMouseCursor IDC_IBEAM
End If
End Sub
Sub DragStop()
CurrentMode = DROP_MODE
DropTime = Timer
End Sub
Sub DropDetect(DropFrm As Form, DropCtrl As control, Button As Integer, Shift As Integer, x As Single, y As Single)
If CurrentMode <> DROP_MODE Then Exit Sub
CurrentMode = NO_MODE
' Se permite el intervalo de temporizador entre el evento MouseUp y el evento
' MouseMove. Esto garantiza que el evento MouseMove no
' invoca el procedimiento de colocación a menos que sea el evento MouseMove
' que Microsoft Access desencadena automáticamente para el control de colocación
' que sigue al evento MouseUp de un control de arrastre. Los eventos
' MouseMove posteriores no superarán la prueba de temporizador y se pasarán por alto.
If Timer - DropTime > MAX_DROP_TIME Then Exit Sub
' ¿Arrastramos o colocamos en nosotros mismos?
If (DragCtrl.Name <> DropCtrl.Name) Or (DragFrm.hwnd <> DropFrm.hwnd) Then
' En caso negativo, se arrastró o colocó correctamente.
DragDrop DragFrm, DragCtrl, DropFrm, DropCtrl, Button, Shift, x, y
End If
End Sub
Sub DragDrop(DragFrm As Form, DragCtrl As control, _
DropFrm As Form, DropCtrl As control, _
Button As Integer, Shift As Integer, _
x As Single, y As Single)
' ¿En qué formulario se colocó?
' Es conveniente utilizar el procedimiento DragDrop para
' determinar qué operación de arrastrar y colocar se realizó; a continuación, invocar
' el código apropiado para tratar los casos especiales.
Select Case DropFrm.Name
Case "02_DragDropListViews"
ListBoxExample DragFrm, DragCtrl, DropFrm, DropCtrl, Button, Shift, x, y
Case Else
ProcessDrop DragFrm, DragCtrl, DropFrm, DropCtrl, Button, Shift, x, y
End Select
End Sub
Sub ListBoxExample(DragFrm As Form, DragCtrl As control, DropFrm As Form, DropCtrl As control, Button As Integer, Shift As Integer, x As Single, y As Single)
Dim db As DAO.Database
Dim SQL As String
Set db = CurrentDb()
' Crear una instrucción SQL para actualizar el campo Seleccionado del
' .. elemento del cuadro de lista de arrastrar/colocado.
SQL = "UPDATE Clientes SET cueact="
' Arrastrar desde Lista1 alternar Seleccionado=Verdadero, Lista2 cambia a Falso.
SQL = IIf(DragCtrl.Name = "Lista1", SQL & "False", SQL & "True")
' Si no se utiliza la tecla CTRL, modificar únicamente el valor arrastrado.
If (Shift And CTRL_MASK) = 0 Then
SQL = SQL & " WHERE [Cliente]='" & DragCtrl & "'"
End If
' Ejecutar la consulta de actualización para alternar
' el campo Seleccionado del registro o los registros de Cliente.
db.Execute SQL
' Volver a consultar los controles del cuadro de lista para mostrar
' las listas de actualización.
DragCtrl.Requery
DropCtrl.Requery
End Sub
Sub ProcessDrop(DragForm As Form, DragCtrl As control, DropForm As Form, DropCtrl As control, Button As Integer, Shift As Integer, x As Single, y As Single)
Dim ctlCurr As control
Dim strSelectedItems As String
Dim varCurrItem As Variant
On Error GoTo linErr
If TypeOf DragCtrl Is CheckBox Then
DropCtrl = IIf(DragCtrl, "True", "False")
ElseIf TypeOf DragCtrl Is OptionGroup Then
'Adaptamos los datos a los controles de origen y de destino
If TypeOf DropCtrl Is TextBox Then
DropCtrl = DragCtrl & ": " & ReturnSelectedOption(DragCtrl)
End If
Else
DropCtrl = DragCtrl
End If
Exit Sub
linErr:
'Desarrolla el control de errores que más te guste
End Sub
Function ReturnSelectedOption(OptionGroup As OptionGroup) As String
Dim ctlCurr As control
Dim booGetText As Boolean
Dim strSelected As String
On Error GoTo linErr
For Each ctlCurr In OptionGroup.Controls
If TypeOf ctlCurr Is OptionButton Or TypeOf ctlCurr Is CheckBox Then
If ctlCurr.OptionValue = OptionGroup.Value Then
strSelected = ctlCurr.Name
booGetText = True
Exit For
End If
ElseIf TypeOf ctlCurr Is ToggleButton Then
If ctlCurr.OptionValue = OptionGroup.Value Then
ReturnSelectedOption = ctlCurr.Caption
booGetText = False
Exit For
End If
End If
Next ctlCurr
If booGetText Then
For Each ctlCurr In OptionGroup.Controls
If TypeOf ctlCurr Is Label Then
If ctlCurr.Parent.ControlName = strSelected Then
ReturnSelectedOption = ctlCurr.Caption
Exit For
End If
End If
Next ctlCurr
End If
Exit Function
linErr:
'Desarrolla el control de errores que más te guste
End Function
'---------
'MÉTODO 2
'---------
'(incluye el módulo de clase que compone el bloque sigiente)
Public CDrag As CDragMetodo02
Public Function WindowProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wp As Long, ByVal lp As Long) As Long
Dim idtraba As Integer
'Para que funione, el control debe tener el foco
If Msg = WM_DROPFILES Then
CDrag.AcceptDroppedFiles wp
'Vemos que formulario lo está llamado
If EstaAbierto("03_DragDropDocumentos") Then
'Capturamos la dirección del fichero y lo mostramos en la imagen del form principal
Form_03_DragDropDocumentos.WebDrop.visible = True
Form_03_DragDropDocumentos.WebDrop.Navigate strDrop
Form_03_DragDropDocumentos.Refresh
DoCmd.Close acForm, "paneldragdrop"
Exit Function
End
ElseIf EstaAbierto("05_DragDropImages") Then
DoCmd.Close acForm, "paneldragdrop"
'La colocamos en el objeto
strDrop = Left(strDrop, Len(strDrop) - 2)
Debug.Print strDrop
Form_05_DragDropImages.ImgDrop.Picture = strDrop
Exit Function
End
End If
Else
WindowProc = CallWindowProc(lpPrevWndProc, hwnd, Msg, wp, lp)
End If
End Function
'---------
'MÉTODO 2
'---------
Option Compare Text
Option Explicit
'------------------------------
'Método 2: clase CDragMetodo02
'------------------------------
Private frm As Object
Private txt As Object
Public Property Set Form(frmIn As Object)
Set frm = frmIn
End Property
Public Property Set TextBox(txtin As Object)
Set txt = txtin
End Property
Public Sub SubClassHookForm()
Call DragAcceptFiles(frm.hwnd, 1)
lpPrevWndProc = SetWindowLong(frm.hwnd, GWL_WNDPROC, AddressOf WindowProc)
Set CDrag = Me
End Sub
Public Sub SubClassUnHookForm()
Call SetWindowLong(frm.hwnd, GWL_WNDPROC, lpPrevWndProc)
Call DragAcceptFiles(frm.hwnd, 0)
End Sub
Sub AcceptDroppedFiles(hDrop As Long)
Dim lNumOfFiles As Long
Dim lReturn As Long
Dim sFilename As String
Dim lm As Long
'Get the number of dropped files
lNumOfFiles = DragQueryFile(hDrop, GetNumOfFiles, 0&, 0)
For lm = 0 To lNumOfFiles
'Allocate buffer for the name of the file
sFilename = String$(257, Chr$(0))
'Get the name of the file
lReturn = DragQueryFile(hDrop, lm, sFilename, Len(sFilename))
'Add the file name to the list
If lReturn > 0 Then
strDrop = txt.Text & Left$(sFilename, lReturn) & vbCrLf
End If
Next lm
'Tell Windows to free the memory allocated to store the dropped files
DragFinish hDrop
End Sub
'---------
'MÉTODO 3
'---------
Private mbytCurrentMode As Byte
Private mbytDragQuantity As Byte
Private DragFrm As Form
Private DragCtrl As control
Private DropTime As Single
Private CurrentMode As Integer
Private Const MAX_DROP_TIME = 0.1
Private Const NO_MODE = 0
Private Const DROP_MODE = 1
Private Const DRAG_MODE = 2
Private Const SINGLE_VALUE = 1
Private Const MULTI_VALUE = 2
'------------------------------------------------------------------------------------------------------------------------
' Módulo original : Drag & Drop
' Revisión :
' Creación : agosto/2021
' Autor : Luis Viadel
'------------------------------------------------------------------------------------------------------------------------
'------------------------------------------------------------------------------------------------
' Procedure : dragdropTW
' Autor : Luis Viadel
' Creación : agosto/2021
' Revisión :
' Propósito : mover un nodo de un treeview a otro en el calendario de planificación, mediante la
' propiedad "Drag&Drop". Mueve el nodo y cambia los datos de la BD (planilin) para que
' que quede modificada la planificación sin modificar la ficha original.
'------------------------------------------------------------------------------------------------
'
Public Function dragdropTW(strDatos As String, ctrlorigen As control, ctrldestino As control, FOrigen As Date, Fdestino As Date, idtraba As Integer) As Boolean
'-----------------------------------------------------------------------------------------------------------------------------------------------
' Fuente : https://access-global.net/drag-drop-en-access
'-----------------------------------------------------------------------------------------------------------------------------------------------
' Título : dragdropTW
' Autor original : Luis Viadel
' Actualizado : agosto 2021
' Propósito : mover un nodo de un treeview a otro en el calendario de planificación, mediante la
' propiedad "Drag&Drop". Mueve el nodo y cambia los datos de la BD (planilin) para que
' que quede modificada la planificación sin modificar la ficha original.
' Retorno : verdadero/False según la ejecución de la función
' Argumento/s : La sintaxis de la función consta de los siguientes argumentos:
' Parte Modo Descripción
'-----------------------------------------------------------------------------------------------------------------------------------------------
' strDatos Obligatorio Nombre del formulario de que queremos saber su posición
' ctrlorigen Obligatorio Nombre del formulario de que queremos saber su posición
' ctrldestino Obligatorio Nombre del formulario de que queremos saber su posición
' FOrigen Obligatorio Nombre del formulario de que queremos saber su posición
' Fdestino Obligatorio Nombre del formulario de que queremos saber su posición
' idtraba Obligatorio Nombre del formulario de que queremos saber su posición
'-----------------------------------------------------------------------------------------------------------------------------------------------
'Test: : Para adaptar este código en tu aplicación puedes basarte en este procedimiento test. Copiar el bloque siguiente al
' portapapeles y pega en el editor de VBA. Descomentar la línea que nos interese y pulsar F5 para ver su funcionamiento.
'
'Sub PosForm_test()
'
' Call PosForm(Me)
'
'End Sub
'-----------------------------------------------------------------------------------------------------------------------------------------------
Dim strTemp As String, keynode As String
Dim idcueloc As Integer, idservicio As Integer, idplanilin As Integer, intWhere As Integer
Dim ctrl As control
Dim idservfr As Integer, idcueloctip As Integer
Dim servrev As Boolean
Dim objnode As Node, objChild As Node
Dim keynum As Integer
Dim Dia As String, Mes As String, Ano As String
On Error GoTo LinError
'Buscamos el idservicio que se encuentra al final de la cadena con la que se ha formado el nodo
intWhere = InStr(strDatos, "|")
idservicio = Right(strDatos, Len(strDatos) - intWhere)
'Recuperamos el id del local sobre el que se presta el servicio
strTemp = Left(strDatos, intWhere - 1)
intWhere = InStrRev(strDatos, ",")
idcueloc = Right(strTemp, Len(strTemp) - intWhere)
'Cambiamos el formato de la fecha a dd/mm/aaaa
Mes = Left(Fdestino, 2)
Dia = Right(Left(Fdestino, 5), 2)
Ano = Right(Fdestino, 4)
Fdestino = Format(Dia & "/" & Mes & "/" & Ano, "dd/mm/yyyy")
'Buscamos la línea de planificación para modificarla en la base de datos
'¡No se modifica la ficha de servicio, solo el servicio seleccionado!
Set rstTable = CurrentDb.OpenRecordset("SELECT * FROM planilin WHERE idtraba= " & idtraba & " AND planilinfec=#" & FOrigen & _
"# AND idcueloc=" & idcueloc & " AND idservicio=" & idservicio)
idplanilin = rstTable!idplanilin
idservfr = DLookup("[idservfr]", "servicio", "[idservicio]=" & idservicio)
servrev = DLookup("[servrev]", "servicio", "[idservicio]=" & idservicio)
idcueloctip = DLookup("[idcueloctip]", "cueloc", "[idcueloc]=" & idcueloc)
rstTable.Edit
'Cambiamos la programación de fecha
rstTable!planilinfec = Fdestino
rstTable.Update
Set rstTable = Nothing
'Modificamos el treeview para no tener que reconstruir el calendario
'Grabamos el nuevo nodo en el treeview de destino y cargamos sus componentes (imagen, color de periodicidad, color de aviso)
For Each ctrl In Form_04_DragDropCalendarMonth.Controls
If ctrl.Name = ctrldestino.Name Then
For Each objnode In ctrl.Nodes
Set objChild = objnode.Parent
If objChild Is Nothing Then keynum = keynum + 1
Set objChild = Nothing
Next objnode
GoTo LinContinua
End If
Next ctrl
LinContinua:
keynum = keynum + 1
Set objnode = ctrl.Nodes.Add(, tvwChild, "B" & keynum, strDatos)
objnode.Selected = False
' objnode.BackColor = DLookup("[paramnum]", "param", "[idparam]=42")
' objnode.ForeColor = vbRed
' objnode.image = "Casa"
Set objnode = Nothing
'Eliminamos el nodo del treeview de origen
For Each ctrl In Form_04_DragDropCalendarMonth.Controls
If ctrl.Name = ctrlorigen.Name Then
For Each objnode In ctrl.Nodes
If objnode.Text = strDatos Then
keynode = objnode.Key
ctrl.Nodes.Remove (keynode)
Debug.Print "Ha sido borrado"
dragdropTW = True
Exit Function
End If
Next objnode
End If
Next ctrl
Exit Function
LinError:
MsgBox Err.Source & vbNewLine & Err.Description, , "Cow Harmony"
End Function
Además de todo el código anterior, en el ejemplo que te puedes descargar encontraras otros módulos en los que declaramos todas las variables o agrupamos todas las funciones de apoyo.
Índice de contenidos del vídeo (haz clíck en el enlace e irás directamente al monento del vídeo):
Presentación e introducción teórica de todas las posibilidades que existen para este evento.
Recursos que se ofrecen en esta clase
Método 1
Ejemplo 1: arrastrar y colocar diferentes objetos de formulario
Ejemplo 2: arrastrar y colocar campos entre dos ListView
Método 2
Ejemplo 3: arrastramos un fichero PDF a un formulario y lo mostramos en un objeto WebBrowser.
Ejemplo 4: arrastramos una imagen a un formulario y lo mostramos en un objeto picture.
Método 3
Ejemplo 5: arrastramos uno de un TreeView a otro TreeView.
Ejemplo 6: arrastramos nodo de un TreeView a un ListView.
Ejemplo 7: arrastramos y colocamos los nodos de un TreeView.
Código para construir los ejemplos 5 y 6.
Por último, te adjuntamos el ejemplo en Access para que te lo puedas descargar y utilizar en tus aplicaciones.
[wpdm_package id='11342']
Créditos:
Imagen presentación: Mike Linksvayer | Flickr
Icono "Drag&Drop": rupa c | thenounproject
Música: Admiral Bob | Dig ccmixter




3 Comments
Buenos días,
Me parece un tutorial magnífico. Mis sinceras felicitaciones.
Tenéis disponible alguna versión que corra en 64bits?
Un saludo,
Hola Daniel, si adaptas las funciones de la API con ptrsafe, no deberías encontrar grandes problemas, pero no lo hemos probado ni adaptado a 64bits. No obstante, si lo haces, estaremos encantados de publicarlo. Gracias por tu interés.
Hola Luis, muchas gracias por tu respuesta. Tengo limitado conocimiento sobre llamadas a la API de Windows.
El compilador se atasca únicamente con la función SetWindowLong que he reemplazado con la equivalente en 64-bits: . Cuando recompilo, me lanza un error de tipos en el tercer parámetro de la llamada de la Sub SubcClassHookForm del modulo de clase. Veo que WindowProc devuelve un tipo Long, que es el mismo que requiere SetWindowLong para el tercer parámetro, por lo que no encuentro la forma de arreglarlo. Agradecería si podéis ayudarme en esto. Muchas gracias de antemano por vuestro tiempo.