python #7 ドラッグ&ドロップで図形を描く【マップGUI】

pygame
はじめに

最近、マスクを紙のやつから洗える布のやつに変えました
Gipheです

周りを見渡せばマスクをしていない人いないですよね

Gipheのように地方に住んでいても
どこでもみんなマスクです

最近紙マスクの息苦しさを感じ始めて、
スース―するスマートな洗えるマスクに変えたのですが

通気性が全然違う!

今まで呼吸ってどうやってたっけ、
と思うくらいのパラダイムシフトを味わったGipheでした

今日はGUI最後?!
マップを作成するGUIです

図形を描画する-canvas-

さてまずフィールドの単位ですが

王国、地方、地区のような単位でそれぞれ
マップ上の座標を扱っていきたいと思います

ゆくゆくは高さとかを考慮して物理量でシミュレーションとかしたいですね

Canvasはtkinterのウィジェットの一つで
図形の描画が可能です

ここにまとまった情報があるので是非参考にしてみてください

今回は長方形をドラッグ&ドロップで描画してみましょう

使うのはcreate_rectangleメソッドです

create_rectangle(x1,y1, x2, y2, outline='境界線の色', fill = '塗りの色')

こんな感じで四隅の座標を設定すれば描画が可能なので
動的に描くには便利です

成果物

いきなりですが、結果からみていきます

という感じですね

マップを作成するまでは程遠いですが、
取れた座標を領域として設定できればよさそうです

ソースはこちら

# 正方形描画処理★
import tkinter as tk
import tkinter.ttk as ttk
class createRectangle():
    def __init__(self):
        self.root = tk.Tk()
        self.root.title('create_rectangle')
        self.rect = None
        self.rect_start_x = tk.StringVar()
        self.rect_start_y = tk.StringVar()
        self.rect_stop_x = tk.StringVar()
        self.rect_stop_y = tk.StringVar()
        self.start_x = 0
        self.start_y = 0
        self.stop_x = 0
        self.stop_y = 0
        self.createDisplay()
        self.root.mainloop()
    # 画面を描画
    def createDisplay(self):
        # ウィンドウを分ける
        self.pw_main = tk.PanedWindow(self.root, orient='vertical')
        self.pw_main.pack(expand=True, fill = tk.BOTH, side="left")
        _pw = self.draw_rectangle(self.pw_main)
        self.pw_main.add(_pw)
        # 座標部
        _pw_grid = self.draw_point(self.pw_main)
        self.pw_main.add(_pw_grid)
        # 画面初期化
        self.init()
    # 長方形を描画
    def draw_rectangle(self,_pw):
        _pw_up = tk.PanedWindow(_pw, bg="pink", orient='horizontal')
        self.lblmap = tk.Label(_pw_up,text = 'map')
        self.lblmap.grid(row=0, column=2, padx=5, pady=2)
        # マップ描画処理
        self.pw_map = tk.Canvas(_pw_up, width=480, height=300,borderwidth=10) # , relief='sunken'
        self.pw_map.grid(row=1, column=2, padx=5, pady=2)
        self.pw_map.bind('<Button-1>', self.rect_start_pickup)
        self.pw_map.bind('<B1-Motion>', self.pickup_position)
        self.pw_map.bind('<ButtonRelease-1>', self.rect_stop_pickup)
        return _pw_up
    # 座標を表示
    def draw_point(self,_pw_grid):
        pw_point = tk.PanedWindow(_pw_grid, bg="#7AC5CD", orient='horizontal')
        # 座標表示ウィジェット→読み取り専用??
        self.lblx = tk.Label(pw_point,text = 'x1:', bg="#7AC5CD")
        self.lblx.grid(row=0, column=0, padx=5, pady=2)
        self.textx = tk.Entry(pw_point, textvariable=self.rect_start_x, width = 4, state='readonly')
        self.textx.grid(row=0, column=1, padx=5, pady=2)
        self.lbly = tk.Label(pw_point,text = 'y1:', bg="#7AC5CD")
        self.lbly.grid(row=0, column=2, padx=5, pady=2)
        self.texty = tk.Entry(pw_point, textvariable=self.rect_start_y, width = 4, state='readonly')
        self.texty.grid(row=0, column=3, padx=5, pady=2)
        self.lblvert = tk.Label(pw_point,text = 'x2:', bg="#7AC5CD")
        self.lblvert.grid(row=0, column=4, padx=5, pady=2)
        self.entvert = tk.Entry(pw_point, textvariable=self.rect_stop_x, width = 4, state='readonly')
        self.entvert.grid(row=0, column=5, padx=5, pady=2)
        self.lblhori = tk.Label(pw_point,text = 'y2:', bg="#7AC5CD")
        self.lblhori.grid(row=0, column=6, padx=5, pady=2)
        self.enthori = tk.Entry(pw_point, textvariable=self.rect_stop_y, width = 4, state='readonly')
        self.enthori.grid(row=0, column=7, padx=5, pady=2)
        # 初期化ボタン
        self.btnReset = ttk.Button(pw_point, text='リセット', width=10, style='MyWidget.TButton', command=self.init)
        self.btnReset.grid(row=0, column=8, padx=5, pady=2)
        return pw_point
    # マウスイベント・開始
    def rect_start_pickup(self, event):
        self.rect_start_x.set(str(event.x))
        self.rect_start_y.set(str(event.y))
        self.start_x = event.x
        self.start_y = event.y
    # マウスイベント・ドラッグ中
    def pickup_position(self, event):
        self.rect_stop_x.set(str(event.x))
        self.rect_stop_y.set(str(event.y))
        if self.rect:
            self.pw_map.coords(self.rect,
                min(self.start_x, event.x), min(self.start_y, event.y),
                max(self.start_x, event.x), max(self.start_y, event.y))
        else:
            self.rect = self.pw_map.create_rectangle(self.start_x,
                self.start_y, event.x, event.y, outline='#7AC5CD', fill = '#7AC5CD')
    # マウスイベント・終了
    def rect_stop_pickup(self, event):
        self.rect_stop_x.set(str(event.x))
        self.rect_stop_y.set(str(event.y))
    # リセットボタン処理
    def init(self):
        self.pw_map.delete(self.rect)
        self.rect = None
        self.rect_start_x.set(0)
        self.rect_start_y.set(0)
        self.rect_stop_x.set(0)
        self.rect_stop_y.set(0)
        self.start_x = 0
        self.start_y = 0
        self.stop_x = 0
        self.stop_y = 0
if __name__ == '__main__':
    c = createRectangle()

ちょっと長くなってしまいました

それではポイントを見ていきます

Canvasウィジェット

以下は画面上部のCanvasウィジェットの設定部分です

    def draw_rectangle(self,_pw):
        _pw_up = tk.PanedWindow(_pw, bg="pink", orient='horizontal')
        self.lblmap = tk.Label(_pw_up,text = 'map')
        self.lblmap.grid(row=0, column=2, padx=5, pady=2)
        # マップ描画処理
        self.pw_map = tk.Canvas(_pw_up, width=480, height=300,borderwidth=10) # , relief='sunken'
        self.pw_map.grid(row=1, column=2, padx=5, pady=2)
        self.pw_map.bind('<Button-1>', self.rect_start_pickup)
        self.pw_map.bind('<B1-Motion>', self.pickup_position)
        self.pw_map.bind('<ButtonRelease-1>', self.rect_stop_pickup)
        return _pw_up

マップ描画処理にはbindでマウスイベントを埋め込んでいます

<Button-1>はマウス左クリックしたときのイベント

<B1-Motion>はドラッグ中のイベント

<ButtonRelease-1>は左ボタンを離した時のイベントとなっております

それぞれイベントに対応するメソッドを設置すればOKです
次に行きましょう

マウスイベント

続いてマウスイベントを見ていきます

# マウスイベント・開始
    def rect_start_pickup(self, event):
        self.rect_start_x.set(str(event.x))
        self.rect_start_y.set(str(event.y))
        self.start_x = event.x
        self.start_y = event.y
    # マウスイベント・ドラッグ中
    def pickup_position(self, event):
        self.rect_stop_x.set(str(event.x))
        self.rect_stop_y.set(str(event.y))
        if self.rect:
            self.pw_map.coords(self.rect,
                min(self.start_x, event.x), min(self.start_y, event.y),
                max(self.start_x, event.x), max(self.start_y, event.y))
        else:
            self.rect = self.pw_map.create_rectangle(self.start_x,
                self.start_y, event.x, event.y, outline='#7AC5CD', fill = '#7AC5CD')
    # マウスイベント・終了
    def rect_stop_pickup(self, event):
        self.rect_stop_x.set(str(event.x))
        self.rect_stop_y.set(str(event.y))

イベントプロパティevent.xevent.y

それぞれCanvasに対するカーソルの座標です

わかりやすいように画面下部に座標を表示してあるので
値の動きを見てみてください

ポイントとなるのはドラッグイベントpickup_positionです

        if self.rect:
            self.pw_map.coords(self.rect,
                min(self.start_x, event.x), min(self.start_y, event.y),
                max(self.start_x, event.x), max(self.start_y, event.y))
        else:
            self.rect = self.pw_map.create_rectangle(self.start_x,
                self.start_y, event.x, event.y, outline='#7AC5CD', fill = '#7AC5CD')

self.rectは最初はNoneが設定されています

ドラッグが開始されたタイミングでelseへと入り
長方形が作成されます

その後のドラッグではself.rectがNoneでなくなるので
if条件がtrueの処理を続けることになります

coordsは少し難しいですが図形の変形ができるメソッドです

四隅の座標であることはcreate_rectangleと同じなので
マウスイベントでの変形をする場合はこれを使うと覚えておきましょう

Canvas削除

描画した図形を削除することもできます

    # リセットボタン処理
    def init(self):
        self.pw_map.delete(self.rect)
        self.rect = None

self.pw_map(Canvas)からself.rect(描画した図形のid)を削除する

ここは見たままだと思います

先にself.rect = Noneを持ってきてしまうと
描画された図形は削除されないので注意が必要です

まとめ

今回は長方形を紹介しましたが、

楕円形や多角形なども簡単に描画できるので
組み合わせると奥が深いのではないでしょうか?

調べればいろいろなイベントがあると思うので
ぜひ調べてみてください

pythonは調べれば何でも出てくるのですばらしい!

]]>

コメント

タイトルとURLをコピーしました