自作コピーツール ドラッグアンドドロップで複数ファイル・フォルダ指定の問題を修正

自作コピーツール ドラッグアンドドロップで複数ファイル・フォルダ指定の問題を修正

自作コピーツール ドラッグアンドドロップで複数ファイル・フォルダ指定の問題を修正

こんにちは。今回は、自作コピーツールで発生していた「ドラッグアンドドロップでディレクトリを複数選択できない問題」を修正しましたので、何をしたのか白状します。あと自分のための記録です。

発生していた問題

以前のバージョンでは、ユーザーが複数のファイルやフォルダをドラッグアンドドロップで指定した際に、うまく認識されない問題がありました。具体的には以下のような現象が発生していました。

  • そもそも、ドラッグアンドドロップ操作を受け付けない
  • ディレクトリをドロップしても、何も反応がない(選択リストに追加されない)

これでは、ユーザーが複数のファイルやフォルダをコピーしたいときに、非常に不便ですよね。

原因はイベントハンドリングとQListWidgetの設定

この問題の原因は、大きく分けて2つありました。

  1. イベントハンドリングの実装が不適切:
    • ドラッグアンドドロップ操作は、dragEnterEvent(開始時)、dragMoveEvent(移動中)、dropEvent(ドロップ時)といった一連のイベントとしてプログラムに通知されます。
    • 以前のバージョンでは、これらのイベントを適切に処理できていませんでした。
    • 特に、メインウィンドウクラスでイベントを処理しようとしていたことが不適切でした。
  2. QListWidgetの設定が不十分:
    • QListWidget は、リスト形式で項目を表示するウィジェットです。
    • ドラッグアンドドロップを有効にするには、setAcceptDrops(True)setDragDropMode(QListWidget.DragDropMode.DragDrop)setDefaultDropAction(Qt.DropAction.CopyAction) といった設定が必要でした。
    • 以前のバージョンでは、これらの設定が不足していました。

修正方法:専用ウィジェットクラスと適切なイベントハンドラ

これらの問題を解決するために、以下の修正を行いました。

  1. 専用のウィジェットクラス DroppableQListWidget の作成

    Python

    class DroppableQListWidget(QListWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setAcceptDrops(True)
            self.setDragDropMode(QListWidget.DragDropMode.DragDrop)
            self.setDefaultDropAction(Qt.DropAction.CopyAction)
    
    • QListWidget を継承した新しいクラス DroppableQListWidget を作成しました。
    • コンストラクタ __init__ で、ドラッグアンドドロップに必要な設定を行っています。
      • setAcceptDrops(True): ウィジェットがドロップを受け付けるように設定します。
      • setDragDropMode(QListWidget.DragDropMode.DragDrop): ドラッグアンドドロップモードを有効にします。
      • setDefaultDropAction(Qt.DropAction.CopyAction): デフォルトのドロップアクションを「コピー」に設定します。
  2. 必要なイベントハンドラの実装

    DroppableQListWidget クラス内に、以下のイベントハンドラを実装しました。

    • dragEnterEvent: ドラッグ操作がウィジェットの範囲内に入ったときに呼び出されます。
    • dragMoveEvent: ドラッグ操作がウィジェットの範囲内で移動しているときに呼び出されます。
    • dropEvent: ドラッグ操作がウィジェットの範囲内でドロップされたときに呼び出されます。
    Python

    class DroppableQListWidget(QListWidget):
        # ... (省略) ...
    
        def dragEnterEvent(self, event):
            if event.mimeData().hasUrls():
                print("dragEnterEvent triggered")
                urls = [url.toLocalFile() for url in event.mimeData().urls()]
                print(f"URLs detected in drag event: {urls}")
                event.acceptProposedAction()
            else:
                event.ignore()
    
        def dragMoveEvent(self, event):
            if event.mimeData().hasUrls():
                print("dragMoveEvent triggered")
                event.acceptProposedAction()
            else:
                event.ignore()
    
        def dropEvent(self, event):
            if event.mimeData().hasUrls():
                print("dropEvent triggered")
                dropped_paths = [url.toLocalFile() for url in event.mimeData().urls()]
                print(f"Dropped directories: {dropped_paths}")
    
                for path in dropped_paths:
                        if path not in self.paths and os.path.isdir(path):
                            self.paths.append(path)
                            self.addItem(path)
                            print(f"Directory added: {path}")
                        elif path in self.paths:
                            print(f"Directory already in the list: {path}")
                        else:
                            print(f"Invalid path or not a directory: {path}")
    
                event.acceptProposedAction()
            else:
                event.ignore()
    
  3. 各イベントハンドラの役割

    • dragEnterEvent
      • event.mimeData().hasUrls() で、ドラッグされているデータにURL(ファイルやフォルダのパス)が含まれているかを確認します。
      • 含まれている場合のみ、event.acceptProposedAction() を呼び出して、ドロップを受け入れることを明示します。
      • ドラッグされたファイルパスのリストをログに出力します。
    • dragMoveEvent
      • dragEnterEvent と同様に、ドラッグされているデータにURLが含まれているかを確認し、含まれている場合のみドロップを受け入れます。
      • ドラッグ中のイベントが発生していることをログに出力します。
    • dropEvent
      • event.mimeData().urls() で、ドロップされたファイルやフォルダのURLのリストを取得します。
      • toLocalFile() メソッドを使って、各URLをローカルのファイルパスに変換します。
      • ドロップされたパスを一つずつチェックします。
        • 既にリストに追加されているパス (self.paths) にないか、かつ、os.path.isdir()でディレクトリであることを確認します。
        • 問題なければ、self.paths にパスを追加し、self.addItem() でリストに表示します。
      • 最後に、event.acceptProposedAction() を呼び出して、ドロップイベントを処理したことをシステムに伝えます。
      • ドロップされたファイルパスのリストや、追加されたファイルパスをログに出力します。

デバッグで動作を確認

上記の修正が正しく動作するかを確認するために、各イベントハンドラ内に print() 関数を使ってデバッグメッセージを追加しました。これにより、イベントの発生状況や変数の値を確認しながら、問題なく動作していることが確認できました。

以下は、実際に動作確認した際のログ出力例です。

DroppableQListWidget initialized
dragEnterEvent triggered
URLs detected in drag event: ['/path/to/directory1', '/path/to/directory2']
dragMoveEvent triggered
dropEvent triggered
Dropped directories: ['/path/to/directory1', '/path/to/directory2']
Directory added: /path/to/directory1
Directory added: /path/to/directory2

修正の結果、複数ファイル・フォルダのドラッグアンドドロップが可能に!

これらの修正により、ユーザーが複数のファイルやフォルダをドラッグアンドドロップで選択リストに追加できるようになりました。これで怒られません。

これで、ユーザーはより簡単に、効率的にコピー操作を行うことができるようになりました。

これで、怒られません!!!

ありがとうございました。

*この記事は大部分をAIに書いてもらいました。
*ちゃんと白状しておきます。

Comments

No comments yet. Why don’t you start the discussion?

    コメントを残す

    メールアドレスが公開されることはありません。 が付いている欄は必須項目です