Python
python3
wxpython

wxPythonでドラッグアンドドロップした画像ファイルを表示する。そのいくつかのTips ~ サンプルコードとともに ~

はじめに

wxPythonには、決めた領域にドラッグアンドドロップすると、そのファイル名を取得するクラスが定義されています。個人的にファイルのパスを変数に受け渡し、画像を表示するのに戸惑ったので、ここでまとめておきます。

この記事でまとめたのは、

  • サンプルコード その1: 画面全体にファイルをドラッグアンドドロップすれば、テキストボックスにそのファイルの絶対パスを表示する。
  • サンプルコード その2: その1のプログラムから、ドラッグアンドドロップするたびにファイル名をターミナル(シェル画面)に出力する。
  • サンプルコード その3: 画像ファイルを指定された領域にドラッグアンドドロップすれば、そのパネルをその画像で置き換える。(ファイルの絶対パスに間接的にアクセスさせる)

です。以下のソースコードはご自由に使用されて構いません。基本的にMITライセンスで公開します。こちらからどうぞ.

動作環境

Python3, wxPython 4.0.0 b2

サンプルコード

その1

ドラッグアンドドロップしたファイルのパスをテキストボックスwx.TextCtrlで表示させるには、以下のコードを実行すればいいです。

drag_and_drops_sample1.py
# -*- coding: utf-8 -*-

import wx


class MyFileDropTarget(wx.FileDropTarget):
    def __init__(self, window):
        wx.FileDropTarget.__init__(self)
        self.window = window

    def OnDropFiles(self, x, y, filenames):
        # ファイルパスをテキストフィールドに表示
        for file in filenames:
            self.window.text.SetValue(file)
        return True


class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="Drop Target", size=(500, 200))
        p = wx.Panel(self)
        sizer = wx.BoxSizer(wx.HORIZONTAL)

        label = wx.StaticText(p, -1, "File name:")
        self.text = wx.TextCtrl(p, -1, "", size=(400, -1))
        sizer.Add(label, 0, wx.ALL, 5)
        sizer.Add(self.text, 0, wx.ALL, 5)
        p.SetSizer(sizer)

        dt = MyFileDropTarget(self)
        self.SetDropTarget(dt)
        self.Show()


if __name__ == '__main__':
    app = wx.App()
    MyFrame()
    app.MainLoop()

Untitled1.gif

参考記事
- 画面上にファイルをドラッグ&ドロップすると、ファイル名が表示されるようなアプリケーションの実装

その2

公式ドキュメントによれば, 入力された(パスを含めた)ファイル名はfilenamesに入るので、これを変数dd_input_image_pathに入れます。filenames[0]としているのは最新のファイル名のみを受け取ることにしているためです。その1同様、fileにはwxPython固有のオブジェクトが入り、文字列が入らないので、テキストとして表示させるためにfor文を用いています。

以下を実行すればファイルをドラッグアンドドロップするたびに、ターミナルにファイルパスが表示されます。

なおテキストボックスにドラッグアンドドロップしてもちゃんとファイル名が表示されますが、それはドラッグアンドドロップの領域がメインウインドウ全体selfで指定されているためです。

drag_and_drops_sample2.py
# -*- coding: utf-8 -*-

import wx


class MyFileDropTarget(wx.FileDropTarget):

    def __init__(self, window):
        wx.FileDropTarget.__init__(self)
        self.window = window

    def OnDropFiles(self, x, y, filenames):

        # 最後に追加したファイルのファイルパスを取得
        dd_input_image_path = filenames[0]
        print(dd_input_image_path)

        # ファイルパスをテキストフィールドに表示
        for file in filenames:
            self.window.text.SetValue(file)

        return True


class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="Drop Target", size=(500, 200))
        p = wx.Panel(self)
        sizer = wx.BoxSizer(wx.HORIZONTAL)

        label = wx.StaticText(p, -1, "File name:")
        self.text = wx.TextCtrl(p, -1, "", size=(400, -1))
        sizer.Add(label, 0, wx.ALL, 5)
        sizer.Add(self.text, 0, wx.ALL, 5)
        p.SetSizer(sizer)

        dt = MyFileDropTarget(self)
        self.SetDropTarget(dt)
        self.Show()


if __name__ == '__main__':
    app = wx.App()
    MyFrame()
    app.MainLoop()

その3

これまでは画面全体にファイルをドラッグアンドドロップすれば、そのファイルのパスを表示していました。これを指定されたウインドウ内フレームにドラッグアンドドロップするたびに、そのフレームの画像を置き換えるようにします。この場合、メインウインドウから画像の絶対パスを取得する事なく、個別に定義したImagePanelクラスにて更新することにします。

ちなみにテキストボックスにドラッグアンドドロップしてもできるように試してみましたが、うまく行きませんでした(その場合はエラーが出る)。このサンプルコードでは、テキストボックスを削除しました。

drag_and_drops_sample3.py
# -*- coding: utf-8 -*-

import wx
import os


class ImagePanel(wx.Panel):
    def __init__(self, parent, panel_size):
        wx.Panel.__init__(self, parent)
        self.panel_size = panel_size
        self.load_image()

    # -------------------------------------------------------------------------

    def load_image(self, input_image=''):
        if input_image == '':
            image = 'input.png'
        else:
            image = input_image

        img = wx.Image(image)
        Newimg = img.Scale(self.panel_size[0], self.panel_size[1], wx.IMAGE_QUALITY_HIGH)
        wx.StaticBitmap(self, -1, wx.Bitmap(Newimg))


class MyFileDropTarget(wx.FileDropTarget):
    def __init__(self, window):
        wx.FileDropTarget.__init__(self)
        self.window = window

    def OnDropFiles(self, x, y, filenames):

        # D&Dされた最後の画像パスを取得し、ImagePanelクラスのメソッドload_imageに渡す.
        dd_input_image_path = filenames[0]
        self.window.load_image(dd_input_image_path)
        return True


class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="Drop Target", size=(400, 400))
        p = wx.Panel(self)
        sizer = wx.BoxSizer(wx.VERTICAL)

        input_image_panel = ImagePanel(p, panel_size=(300, 300))
        sizer.Add(input_image_panel, 0, wx.ALL, 5)
        p.SetSizer(sizer)

        dt = MyFileDropTarget(input_image_panel)
        input_image_panel.SetDropTarget(dt)

        self.Center()
        self.Show()


if __name__ == '__main__':
    app = wx.App()
    MyFrame()
    app.MainLoop()

Untitled.gif

なお初期画像input.pngとして以下を使いました:

input.png

ここでのポイントは、2つあります。一つは、新たに定義したImagePanelのインスタンスinput_image_panelを用いて

        dt = MyFileDropTarget(input_image_panel)
        input_image_panel.SetDropTarget(dt)

としているので、このパネルに対してドラッグアンドドロップが定義されていること。試しにメインウインドウのサイズ(400,400)を増やし、パネル外の領域にドラッグアンドドロップしても画像が更新されないことを確認してください。(もしinput_image_panelselfなら全画面にドラッグアンドドロップすれば良いことになる。)

二つ目は、MyFileDropTarget内で、ImagePanelクラスのload_imageメソッドを呼ぶことで、ドラッグアンドドロップするたびにImagePanelの画像パスを更新していることです。直接的にMyFrame(メインウインドウを定義するクラス)でファイルパスを呼ばなくても、画像を更新する事ができています。

  • なおサンプルコードのため例外処理(対応する画像ファイル以外をドラッグアンドドロップしたときの処理)については書きませんでした。

終わりに

ドラッグアンドドロップを全画面にしても良いですが、画像パネルを2つ用いて画像を2つ表示するような場合、どちらかをドラッグアンドドロップ対応にするようにすれば、もっと気の利いた(?)プログラムになるかと思います。

謝辞

gifアニメの収録にはこちらの記事を参考にさせていただきました。