Drag and Drop con PyQt 4

Este artículo muestra específicamente cómo llevar a cabo el mecanismo de «Drag and Drop» (arrastrar y soltar) entre dos objetos de la misma aplicación. Se basa en el documento «Drag and Drop» de la documentación oficial de Qt 4.8.

El código presenta dos partes. La primera es el widget que será arrastrado hacia un determinado lugar. El segundo es dicho lugar, otro widget el cual recibirá al primero. Para esto se han creado dos clases: DropBox y DraggableLabel, respectivamente. El widget que será arrastrado es una etiqueta, la clase QLabel, que puede trasladarse entre las dos «cajas» (DropBox) o en la que se encuentre en el momento.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#       dnd.py
#
#       Copyright 2013 Recursos Python - www.recursospython.com
#       

from PyQt4.QtCore import Qt, QMimeData
from PyQt4.QtGui import QApplication, QDrag, QFrame, QMainWindow, QLabel


class DropBox(QFrame):
    
    def __init__(self, parent):
        QFrame.__init__(self, parent)
        self.setAcceptDrops(True)  # Aceptar objetos
        self.setStyleSheet("background-color: #E6E6E6;")
    
    def dragEnterEvent(self, event):
        # Ignorar objetos arrastrados sin información
        if event.mimeData().hasText():
            event.acceptProposedAction()
    
    def dropEvent(self, event):
        # Establecer el widget en una nueva posición
        pos = event.pos()
        self.label = event.source()
        self.label.setParent(self)
        self.label.setGeometry(pos.x(), pos.y(), 150, 20)
        self.label.show()
        
        event.acceptProposedAction()


class DraggableLabel(QLabel):
    
    def __init__(self, parent):
        QLabel.__init__(self, parent)
        self.setStyleSheet("background-color: white;")
    
    def mousePressEvent(self, event):
        # Inicializar el arrastre con el botón derecho
        if event.button() == Qt.LeftButton:
            self.drag_start_position = event.pos()
    
    def mouseMoveEvent(self, event):
        # Chequear que se esté presionando el botón derecho
        if not (event.buttons() and Qt.LeftButton):
            return
        
        # Verificar que sea una posición válida
        if ((event.pos() - self.drag_start_position).manhattanLength()
            < QApplication.startDragDistance()):
            return
        
        drag = QDrag(self)
        mime_data = QMimeData()
        
        # Establecer el contenido del widget como dato
        mime_data.setText(self.text())
        drag.setMimeData(mime_data)
        
        # Ejecutar la acción
        self.drop_action = drag.exec_(Qt.CopyAction | Qt.MoveAction)


class Window(QMainWindow):
    
    def __init__(self):
        QMainWindow.__init__(self)
        
        self.setWindowTitle("Drag and Drop")
        self.resize(600, 300)
        
        self.dropbox1 = DropBox(self)
        self.dropbox1.setGeometry(10, 10, 580, 130)
        self.dropbox2 = DropBox(self)
        self.dropbox2.setGeometry(10, 150, 580, 130)
        
        self.label = DraggableLabel(self.dropbox1)
        self.label.setGeometry(10, 10, 150, 20)
        self.label.setText("Hazme click y mueveme")


if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec_()

La etiqueta comienza el proceso de arrastrado con el evento mousePressEvent, en el cual verifica que el botón empleado sea el derecho. El evento mouseMoveEvent crea el objeto QDrag y añade información que luego será utilizada por el receptor. En este ejemplo únicamente se establece como dato el texto del widget, a través de QMimeData, pero ésta, además de texto plano, puede incluír imágenes, URLs, código HTML, entre otras (véase la documentación de la clase). En el otro extremo, el receptor (la clase DropBox) acepta el objeto arrastrado una vez llamado al evento dragEnterEvent, realizando una verificación para no aceptar objetos vacíos. Por último, una vez soltado el ratón y arrastrado el objeto, el evento dropEvent es llamado y se establece el nuevo widget padre para el objeto junto con su nueva posición. La función event.source() retorna el widget que se ha pasado como primer argumento al crear una instancia de la clase QDrag en el objeto arrastrado, por lo tanto se realiza una copia idéntica y únicamente se cambia el widget padre o parent (desde dropbox1 hacia dropbox2 y viceversa) y se establece una nueva posición.

Implementar el mecanismo D&D en Qt no es una tarea dificultosa, tomando como base este código puedes arrastrar y soltar objetos más complejos; recuerda que puedes añadir todo tipo de dato a través de la clase QMimeData. Por ejemplo, puedes añadir una imágen al objeto antes de ser arrastrado utilizando mime_data.setImageData() y luego recibirla en la clase receptora (en el evento dragEnterEvent) realizando event.mimeData().imageData().

Versión

Python 2.7

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

Deja una respuesta