프로그래밍 관련/PyQt

PyQt. Drag & Drop에 대하여

존버매니아.임베디드 개발자 2021. 11. 1. 00:15
반응형

Drag Drop은 생각보다 내용이 복잡해서 전체적인 개요를 현재 이해하지 못한 상태다.

일단 아는데까지만 대강 정리한다.

정리한 내용에 오류가 있을 수 있음


내가 내용 파악하는데 진입장벽을 느낀 이유는 Drag & Drop 이 Model/Veiw Programming과 관련이 있기 때문이다.

Model/Veiw에 대해서 날 잡아서 한번 하긴 해야되는데 인터넷에 한글로 되어있는 강의들은 대부분 Model/View Programming을 사용하지 않은 예제가 많은것 같다.

사실 Model/View 프로그래밍이 뭔지  아예 아는것이 없어서 뭐라고 이야기 자체를 못하겠다.

내가 자주 사용하는 QTree, QTable 도 보면 QTreeWidget 클래스가 있고 QTreeview 클래스가 있는데

QTreeview 를 사용하는 방법에 대해서 제대로 파악을 못했다. 내가 여태까지 했던건 내용이 단순해서

QTreeWidget 클래스만으로도 충분히 구현이 가능했기 때문이다.


Model/View Programming | Qt Widgets 5.15.7

 

Model/View Programming | Qt Widgets 5.15.7

Model/View Programming Introduction to Model/View Programming Qt contains a set of item view classes that use a model/view architecture to manage the relationship between data and the way it is presented to the user. The separation of functionality introdu

doc.qt.io

QT 관련 공식 페이지인데 위에 내용을 날 잡아서 한번 찬찬히 공부해야 될 것 같다.

위 링크에 보면 Drag & Drop 에 대해서도 상세히 설명이 적혀있는데 내용이 100% 이해가 잘 되진 않는다.

 

그리고 위 링크의 내용을 보면

★ mimeData 라는 것에 대한 내용이 있는데 이것도 drag drop과 밀접한 연관이 있으니 나중에 공부해보자.

 


일단 되는대로 막 정리를 좀 해보면

 

PyQt에서 사용하는 다양한 Widget들은 Drag, Drop 이 가능하다.

(어떤거는 drag, drop이 모두 가능하고 어떤거는 drop만 되고 그러는 것 같다)

(근데 정확히 어떤게 drag 되고 어떤게 drop 되는지 전부 다 파악은 못했음)

 

일단 최소한 내가 확인한 것은

TreeWidget, TableWidget, ListWidget, LineEdit  <- 얘네는 Drag/ Drop이 모두 가능하다.

 

그 외에 여러가지 것들 ex) TextBrowser , 각종 Button 등등 들은 Drag가 되는건지 안되는건지 잘 모르겠다.

근데 기본적으로 Drop은 대부분 되는 거 같다.

 

여기서 drag가 된다는 소리는, 어떤 widget에 있는 내용을 마우스로 끌어다가 밖으로 정보를

내보낼 수 있는 것을 의미하고

drop이 된 다는 소리는, 다른 widget에서 어떤 내용을 drag 해서 해당 widget에 가져와서 마우스를 떼면(drop하면)

그 내용을 받아서 무언가 이벤트를 처리 해 줄 수 있는 것을 의미한다.


그런데 이러한 Widget들의 default 속성은 drag, drop을 사용하지 않는 것으로 되어있기 때문에

몇 가지 셋팅을 해줘야 한다. 모드설정 & Eanble 이다. 

1) drag, drop 모드 설정이라는 것이 있는데 우선 이것을 설정해주고, 그리고

 

2) Drag, Drop 기능을 사용하려면 이것을 enable해줘야한다.

 

그래서 관련 메소드가 뭐냐면 아래와 같다.

var = QTreeWidget()
var.dragDropMode(QAbstractItemView.DragDropMode.DragOnly)
#var.dragDropMode(QAbstractItemView.DragDropMode.DropOnly) 등등 골라서 쓰면 됨

var.setAcceptDrops(True)
var.setDragEnabled(True)
    class DragDropMode(int):
        NoDragDrop = ... # type: QAbstractItemView.DragDropMode
        DragOnly = ... # type: QAbstractItemView.DragDropMode
        DropOnly = ... # type: QAbstractItemView.DragDropMode
        DragDrop = ... # type: QAbstractItemView.DragDropMode
        InternalMove = ... # type: QAbstractItemView.DragDropMode

dragDropMode는 위 예시코드에서 이름을 보면 이해가 가능할 것이다.

drag만 쓸건지 drop만 쓸건지 둘다 쓸건지 아예 안쓸건지 우선 mode를 골라준다.

 

TreeWidget을 예로들면,  트리 안에 들어있는 내용이 원래는 마우스로 드래그가 안됐는데

DragEnable 를 True로 set 하고나면 선택된 내용이 마우스로 Drag 되는 모습을 볼 수가 있다.

 

마찬가지로, AcceptDrop의 경우, 

다른 Widget에서 어떠한 내용을 drag 해서 위 예시의 트리위로 가져가보면 금지 표시가 나오는데

AcceptDrop을 하면 그 금지표시가 없어지고 뭔가 drop이 되는 것을 볼 수 있다.

 

참고로, 이러한 셋팅은 Qt Designer의 속성 편집기에서 마우스로 선택할 수도 있다. 

근데 위 그림을 보면 drag drop과 관련하여 몇가지 옵션들이 더 있는데 이것에 대해서는 뒤에서 설명한다. 


Drag&Drop 했을때 동작에 대하여(Default 동작)

이렇게 drag&drop 모드를 설정하고, drag, drop을 enable 했을 때 "Default 동작"이 어떤지 살펴보자.

우선 같은 Widget 끼리 드래그,드랍을 하면 내용이 복사가 된다.

 

treewidget1 , treewidget2가 있을때

treewidget1의 내용을 drag 해서 treewidget2에 갖다놓으면 해당 내용이 복사가 된단 이야기다.

 

그리고 서로 다른 위젯간에도 드래그 드랍이 되긴한는데

tree에서 list로 혹은 tree에서 table로도 드래그 드랍이 되긴한다.

근데 .. 내용이 전부 옮겨지긴하는데 양식이 다르다보니까.. 좀 이상하게 옮겨진다??고 해야되나

뭐라 표현을 못하겠다.

 

 

★ 아무튼 Drag를 하면, 해당 내용이 바깥으로 전달 되는 것이고

★  Drop을 하면 Drag로 가져온 내용을 받아오는게 Default 동작이다.

(이 떄, Drag 되는 widget,  Drop 되는 widget이 똑같으면 단순히 복사가 된다고 생각하면되고,

widget이 다를 경우 동작이 좀 이상하게된다.)


Drop 했을 때의 동작을 유저가 임의로 셋팅하는 방법

한편, QTreeWidget, QListWidget 등등의 클래스에는

dragEnterEvent , dropEvent 라는 메소드가 사전에 정의되어있는데

이 메소드는 시그널/ 슬롯 메소드의 일종이다.

 

해당 Widget으로 누군가가 drag를 해서 정보를 가져오면

일단 dragEnterEvent 메소드가 실행된다. (drag가 내 widget으로 들어왔을 때 실행됨)

그리고 dropEvent 메소드는 drag로 가져온 내용을 최종적으로 drop 했을 때 실행되는 메소드이다.

 

이 메소드들이 실행된 결과, 앞에서 말했던 Default 동작이 실행됐던 것이다.

****************************************************************************************

한편,  위에서 살펴본 Default 동작 외에 Drop 발생했을 때 임의의 동작을 하도록 유저가 셋팅할 수 있다.

그 말은 즉슨 dragEnterEvent , dropEvent 메소드를 유저가 임의로 재정의를 해야한다는 소리다.

그 말은 즉슨 메소드 오버라이딩을 해야한다는 이야기고 그래서 우리는 Qt가 제공하는 기본 클래스를 상속받아서

새로운 클래스를 만들고, 그 안에서 메소드 오버라이딩을 해야한다.

 

※ 메소드 오버라이딩:메소드 오버라이딩/ 오버로딩 개념 (tistory.com)

 

QListWidget에 대하여 메소드 오버라이딩을 아래와 같이 해보자.

class drop_list(QListWidget):
    def __init__(self,input):
        super(drop_list,self).__init__(input)
        self.DragDropMode(QAbstractItemView.DragDropMode.DropOnly)
        self.setAcceptDrops(True)

    def dragEnterEvent(self,e):
        print("drag in")
        print(type(e.source()))
        if type(e.source())==QTreeWidget:            
            e.accept()
        else:
            e.ignore()

    def dropEvent(self,e):
        print("drop Event")
        self.addItem(e.source().currentItem().text(0))

dragEnterEvent 메소드 내용을 먼저 해석해보면

e.accept()가 뭐냐면

accpet를 해줘야 drop이 가능하다.

e.ignore()를 하면 drop이 불가하게 된다.

e.source()가 뭐냐면 drag로 가져온 아이템을 나타낸다.

 

그래서 위 메소드의 내용을 풀어써보면

drag로 어떤 데이터를 가져오면

일단 drag in 이라는 메세지를 출력한 후,

해당 data가 treewidget 에서 가져온 아이템이면 drop을 허용하고, 그렇지않으면 허용안하겠다는 소리다.

-------------------

dropEvent를 해석해보면

일단 dragEnterEvent를 통해서

treewidget으로부터의 데이터만 drop 가능하도록 했으므로,

dropEvent 에서 e.source를 실행하면 이것은 무조건 treewidget이다.

그래서 treewidget 첫번째 열의 데이터만을 가져와서 list에 추가하겠다는 소리다.

 

 

-----------------------------------------------------------------------------------------------

최종적으로,

3개의 컬럼을 갖고 있는 Tree에서 List widget으로 데이터를 drag&drop 했을 때

tree의 첫번째 열의 내용만 list에 추가하는 예제는 아래와 같다.

 

import sys
from PyQt5.QtWidgets import *

class drop_list(QListWidget):
    def __init__(self,input):
        super(drop_list,self).__init__(input)
        self.DragDropMode(QAbstractItemView.DragDropMode.DropOnly)
        self.setAcceptDrops(True)

    def dragEnterEvent(self,e):
        print("drag in")
        print(type(e.source()))
        if type(e.source())==QTreeWidget:            
            e.accept()
        else:
            e.ignore()

    def dropEvent(self,e):
        print("drop Event")
        self.addItem(e.source().currentItem().text(0))


class MyWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setupUI()

    def setupUI(self):
        self.setGeometry(1000, 200, 700, 300)

        # self.list = drop_list(self)
        self.list = drop_list(self)
        self.list.setGeometry(420,0,100,100)        


        #Tree 생성
        self.tree = QTreeWidget(self)
        self.tree.resize(400, 300)
        self.tree.setColumnCount(3)
        self.tree.setHeaderLabels(["컬럼1","컬럼2","컬럼3"])

        self.tree.setDragDropMode(QAbstractItemView.DragDropMode.DragOnly)
        self.tree.setDragEnabled(True)

        #Tree에 항목추가 (TreeWidgetItem 추가)
        itemA = QTreeWidgetItem(self.tree)
        itemA.setText(0,"0행 0열")
        itemA.setText(1,"0행 1열")
        itemA.setText(2,"0행 2열")

        itemB = QTreeWidgetItem(self.tree)
        itemB.setText(0,"1행 0열")
        itemB.setText(1,"1행 1열")
        itemB.setText(2,"1행 2열")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    mywindow = MyWindow()
    mywindow.show()
    app.exec_()

 

 

 

 

 

 

 

 

 

반응형