Pyslvs v0.9 新功能測試及解說
- 如何在 Qt 框架中達成 Widgets 間的滑鼠資訊轉移
Qt Drag and Drop
詳細說明可以參考這裡:http://doc.qt.io/qt-5/dnd.html
Pyslvs v0.9 新功能影片:
動作解析
在不同的桌面系統中,滑鼠的拖放動作都已經透過 Qt 框架轉化成以下幾個動作:
從 AWidget 到 BWidget。
-
抓起?(AWidget::
mousePressEvent
/mouseReleaseEvent
) -
移出(AWidget::
mouseMoveEvent
) -
移入(BWidget::
dragEnterEvent
) -
橫越(BWidget::
dragMoveEvent
) -
放下(BWidget::
dropEvent
)
接下來按照步驟配置所需的函式。
第一步就是開啟拖放置功能,以允許丟東西在 QWidget 中:
class BWidget(QWidget): def __init__(self, parent=None): super(BWidget, self).__init__(parent) self.setAcceptDrops(True)
需要注意的是,上述的這些 QEvent 函式都是私有的,意即無法(Python 其實可以強制設定,但是很難看)透過外部撰寫。
上面這句話的意思是,不能用 QtDesigner 來佈置這些 Widget!請按部就班繼承改寫新 Class,用指令插入 Layout 裡。
這邊就不再介紹如何手動放這些 Widget 了,較不瞭解的初學者可以用 QtDesigner 拉出 QLayout 後用 insertWidget
指令達成。
抓起?(AWidget::mousePressEvent
/ mouseReleaseEvent
)
這個函式專門處理 AWidget 中任何按下滑鼠的動作,因此如果你的 AWidget 並非原始無瑕的 QWidget,那 Qt 早已設定過一些互動函式了。
在 Python 中,子 Class 的新 method 若和任何父項 method 撞名,會造成覆蓋。為了不讓 Qt 的設定功能失效,請使用 super 函式讓 Qt 先做完應有的工作。
class AWidget(QWidget): def mousePressEvent(self, event): super(AWidget, self).mousePressEvent(event) def mouseReleaseEvent(self, event): super(AWidget, self).mouseReleaseEvent(event)
接下來的 QEvent 函式都是一樣,如果不小心蓋掉發生怪怪的狀況,例如 QTableWidget 無法再選起儲存格,那就加上 super 補上被忽略的工作。
言歸正傳,由於「按下滑鼠」這個事件不一定是想抓東西來拖移,因此不能就這樣亂槍打鳥,要先偷偷紀錄其的行為。在這裡設定一個名稱「draged」紀錄滑鼠的動作,決定是否為滑鼠左鍵抓住的動作。
放掉滑鼠的話就把這個值取消,表示使用者並沒有做出拖動的行為。
class AWidget(QWidget): def __init__(self, parent=None): super(AWidget, self).__init__(parent) self.draged = False def mousePressEvent(self, event): super(AWidget, self).mousePressEvent(event) if event.button()==Qt.LeftButton: self.draged = True def mouseReleaseEvent(self, event): super(AWidget, self).mouseReleaseEvent(event) self.draged = False
移出(AWidget::mouseMoveEvent
)
這個函式專門處理 AWidget 中滑鼠移動的動作,也包括憑空移動,因此剛才設定的「draged」可以告訴我們,使用者正想要「拿著東西移動」。
既然想拿東西,就必須把東西打包交給使用者,一般普遍為文字資訊,包裝後在 BWidget 解開,亦可以攜帶圖片、網址、HTML、顏色資訊。
這裡介紹兩個新的 Qt Class:
-
QMimeData:行李箱,將一堆雜亂的行李分裝後包起來。
-
QDrag:送貨員,可以附上一張圖片以辨識該行李的內容,另外送貨員知道寄件者地址(AWidget)、滑鼠停駐點等資訊,也可以幫旅行中的滑鼠換外觀。
接下來就來裝箱吧,根據 Pyslvs 的影片,我們可以推測出攜帶的資訊為 AWidget 的「被選擇行號」,因此透過一段小迴圈篩選出所有被選擇的行號。
用 Python 的 set 集合型態可以將重複選取的內容排除掉,接著重新排序轉回 list 類型儲存。
而 QTableWidget 的 mouseMoveEvent 其實有「按住連續選取」的功能,但是這樣會干擾我們拖移的行為,因此這邊不使用 super。
class AWidget(QTableWidget): def selectedRows(self): a = list() for r in self.selectedRanges(): a += [i for i in range(r.topRow(), r.bottomRow()+1)] return sorted(set(a)) def mouseMoveEvent(self, event): if self.draged: selectedRows = self.selectedRows() selectedRowCount = len(selectedRows) if selectedRowCount==2 or selectedRowCount==3: drag = QDrag(self) mimeData = QMimeData() mimeData.setText(';'.join([str(e) for e in selectedRows])) drag.setMimeData(mimeData) drag.setPixmap(QPixmap(":/icons/tooltips/need{}bearings.png".format(selectedRowCount)).scaledToWidth(50)) drag.exec_()
由於下方的表格需要 2 或 3 個選取的行號,因此以外的選項我們一律不接受。
行號資訊型態是 int,這邊轉換成字串後用分號 ;
包裝起來。而這裡還有設定 need{}bearings.png
的圖片,表示拿了 2 個或 3 個「軸承」。
最後下的 drag.exec_()
方法為延遲函式,表示送貨員已出發,它會一直等到貨物到達或被丟棄時才會回傳,因此要注意不要讓執行序被阻塞。
移入(BWidget::dragEnterEvent
)
這個函式專門處理 BWidget 中滑鼠帶著資訊進入的動作。這裡的 event 物件已經被 Qt 轉換成我們的送貨員,與剛才的 QDrag 物件有一定程度的相仿。要使用審核機制來判別這個送貨員是不是我們要接受的對象。
要如何審核呢?有兩種結果:
-
使用
acceptProposedAction
方法接受這名送貨員進入 BWidget 的領域。 -
使用一般的
ignore
方法無視這名送貨員,或是乾脆不理他,會造成滑鼠游標出現類似禁止的符號,依你的桌面系統而定。
被允許的送貨員能在進入的時候,依你的桌面系統出現類似抓著資訊的樣式,這時可以選擇是否放開以丟下資訊,或是將資訊帶走(我只是路過),不一定會送達 BWidget。
若是送貨員在此處被禁止投遞,卻仍然放開滑鼠,那他手中的資訊就會遺失,而且會無法重新取得(除非回到 AWidget 再打包)。
class BWidget(QTableWidget): def dragEnterEvent(self, event): mimeData = event.mimeData() if mimeData.hasText(): if len(mimeData.text().split(';'))==self.bearings: event.acceptProposedAction()
這邊的程式中,self.bearings
為允許的行號數,例如連桿表格為 2 個,當拆解用 ;
分號封裝的字串時,數量為 2 個,那此貨物就允許進入。
橫越(BWidget::dragMoveEvent
)
某些列表式的 QWidget,例如 QTableView、QListView、QTreeView,其實會內建清單拖移功能,讓使用者可以直接拖動儲存格,插到自己或是其他相同類型的 QWidget 中,而且不用自己寫 QEvent 函式,只是這些功能預設是關閉的。
由於上述原因,我們得將自訂送貨員打扮成上面較特殊 QWidget 的送貨員,否則這些類型會不允許他送進資訊,即使在上一小節中已經允許。
class BWidget(QTableWidget): def dragMoveEvent(self, event): event.setDropAction(Qt.MoveAction) event.accept()
這些特殊 QWidget 的送貨員會攜帶「放置行為」,如「遷移」、「複製」等等,因此將我們的送貨員設定為常見的 Qt.MoveAction
即可。
若是其他沒有這種 items 的 QWidget,是不用設定此步驟的。
放下(BWidget::dropEvent
)
終於到最後一步了,送貨員最終決定在此投遞,因此必須在此拆包,執行相關的函式。
為了方便,這裡與外界溝通的方式是使用信號槽將所有行號拆包發送出去,使用的是 self.dragIn
這個信號。
class BWidget(QTableWidget): def dropEvent(self, event): self.dragIn.emit(*[int(e) for e in event.mimeData().text().split(';')]) event.acceptProposedAction()
最後,還是要執行 acceptProposedAction
方法,才會關閉此事件。
之前介紹過如何接收外部拖入檔案,在程式中開啟的方法:
http://project.mde.tw/blog/40323230ri-zhi-1060116.html
這次挑戰較複雜的,在 Qt Widget 中抓起資訊的方式。
這三種 QTableWidget 的獨立原始碼提供在這裡,因為這其中的設定還透過繼承方式簡化函式,會較整齊些。
可以透過對照的方式看看自己寫的是否完整。
Comments
comments powered by Disqus