Access: Cuadro de Lista con selección múltiple
04/04/2022Access: 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.
Drag & Drop en Access
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.