Python+TkでD&D
標準ライブラリリファレンスを読むとTkdndが使えるみたいなことが書いてあるが、リファレンスには説明がなくソースを読めと言わんばかりの状態。Tkdnd.pyを見ても要素をウィンドウ間でやり取りできるがD&Dされたファイルの情報の受け取り方はさっぱり。結局win32 API直接呼出しという品のない方法に。
基本方針としては、以下のとおり
- 何はなくともWindowハンドルの取得
- DragAcceptFilesでD&Dを許可
- GetWindowLongWでWindowプロシージャを取得
- SetWindowLongWでD&D用のコールバック関数を登録、この際コールバック関数内でTkに関するものをいじるともれなく落ちるのでモジュールスコープの変数かクラス変数に保存する
- 保存先をチェックしてTk側に反映させる関数をTk.after()で定期的に実行させる
コードとしてはこんな感じ
import Tkinter from Tkconstants import * import ctypes from ctypes.wintypes import HWND, UINT, WPARAM, LPARAM prototype = ctypes.WINFUNCTYPE(ctypes.c_long, HWND, UINT, WPARAM, LPARAM) WM_DROPFILES = 0x0233 GWL_WNDPROC = -4 WINPROC = None def py_drop_func(hwnd, msg, wp, lp): u"""D&D用のコールバック ファイルのドラッグアンドドロップイベント(WM_DROPFILES)を検出して、 ドロップされたファイル名を保持する。 ここでウィンドウ(tk)を使用するとハングアップするのでデータ保存だけ行う。 """ if msg == WM_DROPFILES: i = ctypes.windll.shell32.DragQueryFile(wp, -1, \ None, None) print i for tmp in range(i): szFile = ctypes.c_buffer(260) ctypes.windll.shell32.DragQueryFile(wp, \ tmp , szFile, ctypes.sizeof(szFile)) TkApp.dropnames.append(\ szFile.value.decode(\ sys.getfilesystemencoding())) ctypes.windll.shell32.DragFinish(wp) return ctypes.windll.user32.CallWindowProcW(\ WINPROC, hwnd, msg, wp, lp) class TkApp(Tkinter.Frame): dropnames = [] def __init__(self, *args, **kargs): Tkinter.Frame.__init__(self, *args, **kargs) self.createwidget() def drop_check(): if TkApp.dropnames: tmp = TkApp.dropnames TkApp.dropnames = [] self.open(tmp) self._root().after(10,drop_check) self._root().after(10,drop_check) def createwidget(self): u"何かウィジット作成" pass def open(self, filenames): u"ファイルを開く" pass if __name__ == "__main__": a = TkApp() hwnd = a._root().winfo_id() ctypes.windll.shell32.DragAcceptFiles(hwnd, True) WINPROC = ctypes.windll.user32.GetWindowLongW(\ hwnd, GWL_WNDPROC) drop_func = prototype(py_drop_func) ctypes.windll.user32.SetWindowLongW(hwnd, \ GWL_WNDPROC, drop_func) a.mainloop()
これでファイルをD&Dされたとき反応できる。
2019/09/08追記
新しめの環境にも対応できるよう書き直しました。
masahero.hatenablog.jp