PySide6のWidgetから他のアプリ(エクスプローラーなど)へファイルをドラックアンドドロップ

python コンピュータ
python

PySide6を始めようと思い、最初にドラックアンドドロップ機能を確認したいと思います。

他のアプリケーションとのデータ連携にドラックアンドドロップがありますが、PySide6のアプリから他のアプリケーション(今回はWindowsのエクスプローラー)へドラックアンドドロップドロップすることでファイルをコピーするスクリプトを作成してみます。

Drag&Dropデータの流れ:PySide6のアプリ⇒他のアプリ(エクスプローラーなど)

pipによるPySide6のインストール方法

pip install pyside6

サンプルスクリプト

import sys
from PySide6.QtWidgets import QApplication, QLabel
from PySide6.QtCore import QMimeData, QPoint, QUrl
from PySide6.QtGui import QDrag
from PySide6.QtCore import Qt

class MyLabel(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:  # 左クリックの場合
            file_path = "H:\\py\\pyside6\\test\\test.txt"  # 実際にドラッグするファイルのパスを設定してください
            if file_path:
                mime_data = QMimeData()
                urls = [QUrl.fromLocalFile(file_path)]
                mime_data.setUrls(urls)

                drag = QDrag(self)
                drag.setMimeData(mime_data)
                drag.setHotSpot(QPoint(0, 0))  # ドラッグ開始位置(左上)
                drag.exec()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    label = MyLabel("こちらをクリックしエクスプローラーへドラッグしてください")
    label.show()
    sys.exit(app.exec())

GoogleのGeminiにコードを書いてもらい、動作するように手直しをしました。

手直しした個所は、

if event.button() == Qt.MouseButton.LeftButton:

の部分で、元は以下のような感じでした。

if event.button() == 1

Qt.MouseButton.LeftButtonを使えるようにするために

from PySide6.QtCore import Qt

動くことを確認したので、コードの解説をしてみます。

pythonスクリプトの場合、基本的にスクリプトの先頭から処理されていて、c言語などのコンパイラ型言語にあるエントリーポイントは存在しません(?)が、スクリプトファイルを他のスクリプトファイルから参照する都合上、

if __name__ == "__main__":

でスクリプトが直接実行(参照でない)判定をしており、その次の行がエントリーポイントと同じような役割になります。

そちらからコードを読み進めていきます。

まず、

app = QApplication(sys.argv)

はargvという変数名からアプリケーションに渡されたコマンドライン引数を引数にQApplicationを生成しているようです。
こちらはアプリケーション又はトップレベルのフォームウィンドウに当たるオブジェクトだと思われます。

次の行からラベルWidgetを生成し、表示しています。

最終行で

sys.exit(app.exec())

app.exec()を実行し、終了した戻り値をsys.exet()に渡しスクリプトを終了しています。

気になるのはラベルを生成していますが、appオブジェクトとの紐づけが無いので、フォームウィンドウではなくアプリケーションのオブジェクトなのかもしれません。

ラベルの生成は、

    label = MyLabel("こちらをクリックしエクスプローラーへドラッグしてください")
    label.show()

MyLabelというクラス名からユーザーが作成したクラスだと考えられます。
クラスの定義部分を見てみます。

class MyLabel(QLabel):

調べたところQLabelを継承した派生クラスがMyLabelという定義になるようです。

    def __init__(self, parent=None):
        super().__init__(parent)
__init__

メソッドがクラスのコンストラクタのようです。

super().__init__(...)

で親クラスのQLabelのコンストラクタを呼び出しています。
コンストラクタの引数にparent=Noneとありますが親となるWidgetが無い(None)でトップレベルだという意味だと想像します。
前述したフォームウィンドウとの紐づけがない部分とも一致します。

ラベルWidget単体で使うようなことは通常無いので、Pyside6初手のプログラムとしてふさわしくないサンプルであることに気が付きました。

閑話休題

    def mousePressEvent(self, event):

メソッド名からマウスボタンを押した際、発火するイベントだと読み取れます。
selfはラベルWidgetで、eventはイベント発生時の情報が詰まったオブジェクトだと思われます。

event.button()でマウスのどのボタンが押されたかの情報から左だったらドラック処理を開始します。

if file_path:

は、ファイルの有無を確認しているかとおもいましたが、file_pathという変数が空で無いか確認しているだけの様です。


                mime_data = QMimeData()
                urls = [QUrl.fromLocalFile(file_path)]
                mime_data.setUrls(urls)

QMimeData()はドラックで渡すデータの種類を表すクラスだと思われます。

こちらのサンプルではurlの配列のようです。

urls = [QUrl.fromLocalFile(file_path)]

ローカルパスであるfile_pathからQUrl形式のオブジェクトを生成し配列化した物をurlsにセットしています。

mime_data.setUrls(urls)
                drag = QDrag(self)
                drag.setMimeData(mime_data)
                drag.setHotSpot(QPoint(0, 0))  # ドラッグ開始位置(左上)
                drag.exec()
drag = QDrag(self)

QDragクラスのオブジェクトをself=ラベルWidgetを引数に生成しています。

drag.setMimeData(mime_data)

先ほど生成したmime_dataをdragオブジェクトにセット

drag.setHotSpot(QPoint(0, 0))  # ドラッグ開始位置(左上)

こちらは何をしているか不明です。

最後に

drag.exec()

を実行して終了です。字面からドラックが実行されていると思われます。

スクリプトを実行すると「こちらをクリックしエクスプローラーへドラッグしてください」ラベルが表示されたウィンドウが起動します。

ラベル部分をクリックした状態で移動(ドラック)しエクスプローラーでボタンを離す(ドロップ)とfile_pathで設定したファイルが移動していました。コピーされることを期待したので少し残念です。

ドラック実行の行を

drag.exec()

から

drag.exec(Qt.CopyAction)

へ変更したところ、移動ではなくコピーされました。
drag.exe()のデフォルトはQt.MoveActionだと考えられます。

いきなりPySide6でドラックアンドドロップを試してみましたが、C#あたりの言語知識で適当に解説してみました。
はじめてなので解説が正しくないかもしれませんが、コードは思った通りの動作はしてくれているので正しい部分もあると思われます。

コメント