ファイルをドラッグ&ドロップする

     GUIを作るとやっぱり活用頻度が高くなるのがドラッグ&ドロップですね。
     現在仕事でファイルブラウザ的なモノを作ってますが、やはり要望として来るのがアプリケーションへのドラッグ&ドロップ。

    qt_dragAndDrop_001.jpg

     しかしQtどうしのドラッグ&ドロップは比較的簡単に分かったのですが、他のアプリケーションへ渡すための方法がわからず苦悶してましたw
     最近ようやく分かったので忘れないウチにメモっときます。


    って事でさっそく
    # -*- coding:utf-8 -*-
    import sys
    import os
    from PyQt4 import QtGui, QtCore
    
    
    class DragDropItemModel( QtGui.QStandardItemModel ):
        '''ドロップ可能なQStandardItemModelの派生クラス。'''
        def __init__( self, parent=None ):
            super( DragDropItemModel, self ).__init__( 0, 3, parent )
            self.setHeaderData( 0, QtCore.Qt.Horizontal, 'File Path' )
            self.setHeaderData( 1, QtCore.Qt.Horizontal, 'Last Update' )
            self.setHeaderData( 2, QtCore.Qt.Horizontal, 'File Size' )
    
        def addObject( self, url ):
            '''QUrlを受け取って、リストに追加するメソッド。'''
            filepath = url.path()            # QUrlからパスを取得。
    
            # Windowsから送られてくるuri-listは頭に/が付いているので
            # それをはずす。
            filepath = filepath[1:]
    
            # uri-listはパスの区切り文字が/なので、Windows風に¥に置き換える。
            filepath = filepath.replace( '/', '\\' )
    
            # 追加するアイテムを作成。---------------------------------------------------------
            row = self.rowCount()
            # ファイルパスのアイテムの追加。
            item = QtGui.QStandardItem()
            item.setText( filepath )
            self.setItem( row, 0, item )
    
            fileinfo = QtCore.QFileInfo( filepath )
            # ファイルの更新日時のアイテムの追加。
            item = QtGui.QStandardItem()
            item.setText( fileinfo.lastModified().toString( 'yyyy/MM/dd hh:mm:ss' ) )
            self.setItem( row, 1, item )
    
            # ファイルのサイズのアイテムを追加。
            item = QtGui.QStandardItem()
            item.setText(
                str( round( fileinfo.size() / 1024.0, 2 ) ) + 'KB'
            )
            self.setItem( row, 2, item )
            # ---------------------------------------------------------------------------------
    
        def mimeData( self, indexes ):
            '''ドラッグ先に渡すためのMime Dataを作成し、返す。'''
            mimedata = QtCore.QMimeData()        # Mime Dataを作成。
            urllist  = []
            for index in indexes:
                # 渡されたインデックスのColumnが0以外なら処理をスルー。
                if index.column() != 0:
                    continue
    
                item     = self.itemFromIndex( index )
                filepath = item.text()
                # Windowsのuri-listは、ファイルのあたまに"/"が必要なため"/"を付加する。
                # またQMimeDataのsetUrlsメソッドはQUrlのリストを要求するため、
                # QUrlにキャストしてからリストに追加。
                urllist.append( QtCore.QUrl('/' + filepath) )
            mimedata.setUrls( urllist )
    
            return mimedata
    
    
    
    class DragDropTreeView( QtGui.QTreeView ):
        '''ドラッグ&ドロップ可能なQTreeViewの派生クラス。'''
        def __init__( self, parent=None ):
            super( DragDropTreeView, self ).__init__( parent )
            self.setDragEnabled( True )            # ドラッグ可能に設定。
            self.setAcceptDrops( True )            # ドロップ可能に設定。
            self.setSortingEnabled( True )        #  ソート可能に設定。
            # ドラッグ&ドロップのモードを指定。
            self.setDragDropMode(
                QtGui.QAbstractItemView.InternalMove
            )
    
        def dragEnterEvent( self, event ):
            '''ファイルがこのビューにドラッグされてきた時の挙動を指定。'''
            # 渡されるeventオブジェクトからドラッグされたデータのmimeデータを取得。
            mimedata = event.mimeData()
            if mimedata.hasUrls():
                # ファイルのパス情報を含むmime-type(uri-list)の場合ドロップを許可する
                # 通知を送る。
                event.accept()
            else:
                # それ以外の場合はドロップを無視する通知を送る。
                event.ignore()
    
        def dropEvent( self, event ):
            '''dropを許可して、かつユーザーがこのビューにドロップした場合の処理'''
            mimedata = event.mimeData()
    
            # このTreeViewにセットされているItemModelを取得する。
            model = self.model()
    
            if mimedata.hasUrls():
                # QUrlListを取得すし、それをfor文で個別に処理をする。
                urllist = mimedata.urls()
                for url in urllist:
                    model.addObject( url )
                event.accept()
            else:
                event.ignore()
    
    
    
    
    class MainWindow( QtGui.QWidget ):
        def __init__( self, parent=None ):
            super( MainWindow, self ).__init__( parent )
            self.resize( 640, 480 )
            self.setWindowTitle( 'Drag and Drop Example' )
    
            layout   = QtGui.QVBoxLayout( self )
    
            # カスタムItem Modelの作成。
            model = DragDropItemModel()
    
            # カスタムTreeviewの作成。-----------------------------------------------------
            treeview = DragDropTreeView()
            treeview.setModel( model )
            # カラムの幅を指定する。
            treeview.setColumnWidth( 0, 400 )
            treeview.setColumnWidth( 1, 140 )
            treeview.setColumnWidth( 2, 100 )
            # -----------------------------------------------------------------------------
    
            layout.addWidget( treeview )
    
    
    
    if __name__ in '__main__':
        app    = QtGui.QApplication( sys.argv )
        window = MainWindow()
        window.show()
    
        sys.exit( app.exec_() )
    

    ■ポイント - ドロップされた時編

    -QTreeView
    • 72行目 : setDragEnabled( True )でリストビューのドラッグを可能に設定。
    • 73行目 : setAcceptDrops( True )でリストビューがドロップされる設定をOnに。
    これらを設定しないととりあえず始まりません。目的に応じてTrueにしておきます。
    • 80行目 : dragEnterEvent( self, event )
    • 92行目 : dropEvent( self, event )
     dragEnterEventは、何かファイルをドラッグ中のマウスカーソルがTreeViewに入ってきた時に実行するメソッドです。
     引数eventからmimeDataを受け取り、そこから望むフォーマットかどうかを判断します。(今回の場合はファイルのリストuli-listが欲しいので、mimeData.hasUrls()でuri-listがあるかどうかを判断しています)
     判断した結果、望むデータフォーマットがある場合はevent.accept()を、ない場合はevent.ignore()を実行します。

     dropEventはdragEnterEventでaccept()が発行され、かつファイルがドロップされた場合のみ実行されるメソッドです。
     ここから、やはり同様に渡される引数eventからmimeDataを受け取り、それをTreeviewに追加するよう処理します。

     これらのメソッドはQTreeView固有のメソッドではなく、もっと上のクラスQWidgetから引き継いでいるため、Qtを構成するGUIのほとんどが同様の処理が出来るようになっています。この合理性は素晴らしい!



    ■ポイント - ドラッグする時編

    -QStandardItemModel
     毎度おなじみStandardItemModelでございます。こちらではリストからドラッグする時に渡すデータを設定します。
    • 47行目 : mimeData( self, indexes )
     mimeDataメソッドはドラッグを開始した時に、ドラッグ先に送るデータを生成するためのメソッドです。ここでmimeDataを作成しリターンすると、ドロップ先でこのデータが受け取られます。
     引数indexesにはQStandardItemModelでドラッグ時にどのアイテムが選択されていたかのインデックス情報がリストで入っています。
     今回は0行目に格納されているファイルパス情報だけが欲しかったので、for文でindex.column()で行数を取り、0以外の場合はスルーしています。
     取得したパスをQUrlにセットしますが、uri-listのパスには頭に”/”を付けなくてはならないようなので、QUrlにセットする前に"/"をつけてしまってます。  それらQUrlをリストに追加、そのリストを先に定義していたQMimeDataにsetUrlsでセットし、そのMimeDataを返して処理は終了です。



     これで、Qtで作成したウィンドウにエクスプローラなどからドラッグでファイルを追加できるようになり、また、Qtのウィンドウから他のアプリケーションやエクスプローラにドラッグでファイルを持っていけるようなります。
     因みにエクスプローラからドラッグすると、今回使用したuri-list以外にもたくさんのWindows固有のmimeDataが含まれています。これらもキチっと付加しないと、全てのアプリケーションでは使えない可能性がありますが、今回は面倒なんでこれにて。


    D&Dは夢が広がるなぁ~
  1. スポンサーサイト

    コメントの投稿

    非公開コメント

    プロフィール

    Eske

    Author:Eske
    萌えイラストレーターを目指す3DCGイラストレーター。
    現在ポケモンカードゲーム、ガンダムトライエイジ、ガンダムコンクエスト、妖怪ウォッチとりつきカードゲームなどで3DCGを使用したイラストレーターとして参加中。

    主にここでは日々気づいたメモなんかを残してます。
    イラストのお仕事も受け付けております。ココのメールアドレスからご連絡できますので、お気軽にご相談下さい。

    最新記事
    最新コメント
    カテゴリ
    最新トラックバック
    月別アーカイブ
    検索フォーム
    リンク
    QRコード
    QR