円、矩形、直線を描画&ドラッグできるようにする【tkinter】

Python

Python の tkinter を使って、GUI 上に図形を描き、それらをドラッグできるようにする方法ついてまとめます。

環境: Windows 10、Anaconda、Python 3

背景

Python の tkinter で GUI が動いたら、つぎにお絵描きをしたくなると思います。
また、描いた図形を操作したくなると思います。

そこで、円(楕円)、矩形(四角)、直線をお絵描きし、それらをドラッグできるようにします。
必要最小限の知識で済むようまとめておくことにします。

円、矩形等の基本図形のお絵かきさえできれば、どのようにでも応用できると思います。

使い方

① デスクトップなどにフォルダを作り、”drag_and_drop1.py” 等のテキストファイルを作成してください。
② ①のテキストファイルに以下のサンプルコードをコピー&ペーストして貼りつけて保存してください。
③ ①のスクリプトを実行してください。具体的には、コマンドプロンプト(または、Anaconda Prompt)を起動して、”python (パス) drag_and_drip1.py ” [enter] 等と入力して実行してください。
④ 実行すると、GUI 上に、円、矩形、直線が描画されます。これらをドラッグしてみてください
動いたら、サンプルコードの数値を変えるなどして、位置、大きさなどを変えてみてください。余力のある人は、個数なども変え、描画をしてみてください。

説明:図形の描き方と動かし方

GUI 画面上に描画用のキャンバス canvas を定義する

・ まず、import tkinter as tk1 として、tkinter をインポートします。as tk1 をつけて、以後、tk1.xxx 等として、インポートしたパッケージを使えるようにします。
・ def click1() と def drag1() は、クリック時、ドラッグ時の関数、動作を定義していますが、最初はスキップしてください。
・ つぎに、x1, y1 の座標とオブジェクトの識別子 id1 を定義しています。x1, y1 は、ドラッグするオブジェクトの座標として使います。id1 は、円、矩形、線のオブジェクトのどれをドラッグするのかを識別するために使います。
・ その後、frame1 として、GUI 画面を定義しています。これは、tkinter の説明をこのサイトで別途していますので詳細は省略します。
・ canvas1 としたところで、お絵かき用のキャンバスを定義します。これは GUI 上に配置するので、frame1 を指定し、キャンバスの大きさを指定しています。pack() で、GUI 画面に配置します。

キャンバス canvas 上に、円(楕円)、矩形(四角)、直線を描く

・ 続いて、canvas1.create_line()、create_rectangle()、create_oval() を実行することで、canvas 上に、直線、矩形、円のオブジェクトをクリエイトします。
引数は、canvas 上での位置、大きさ、カラーを定義します。さらに、各オブジェクトの名前を tags= として定義しておきます。ここでは、object1, 2, 3 とします。
・ なお、直線の描画 create_line() では、サンプルは2点のみを指定していますが、続けて複数の点を指定していくと、折れ線を描くことができます。
・ 円の描画 create_oval() では、2点を指定してできる矩形の内接円が描画されます。サンプルでは X、Y 方向の値の差分が同一となるよう指定しているので円となります。値を変えて縦横方向の差分を変えると楕円を描画できます。
・ 単にオブジェクトを描くだけであれば、ここまでで描画できます。

ドラッグするとオブジェクトが動くようにする

・ ここではさらに、各オブジェクトをドラッグしたとき、動くようにしたいです。そこで、tag_bind を使って、各オブジェクトをクリック、ドラッグしたときに実行したい関数を紐づけ(バインド)します。
・ 具体的には、マウスボタンを押下すると、click1 関数が実行されるように定義することにします。
また、マウスボタンを押下しつつマウスを動かすと、drag1 関数が実行されるように定義します。
これにより、オブジェクトをクリックしたとき、click1 関数が実行され、マウスを動かすと drag1 関数が実行されるようにします。

オブジェクトにタグ(tags)を振り、タグと関数をタグバインド (tag_bind、紐づけ)する

・ まず、前段として、前述のオブジェクトにタグ “tags” を定義しておきます。スクリプトでは、線、矩形、円のオブジェクトそれぞれに対し、”tags= … “として object1、object2、object3 の名前を定義します。
・ つぎに、canvas1 上で、各オブジェクトに対し、検出するイベントと、イベントを検出したときに実行する関数を定義します。
ここで <ButtonPress-1>、<B1-Motion> は何を検出するかを指定していますが、まずはお約束と理解すればよいです。それぞれ、ボタン1を押したときのイベントを取得し、click1 を実行する。また、マウスを動かしたとき、drag1 を実行する、という意味で tag_bind() を実行したことで紐づけが完了します。
・ つぎに、冒頭の def click1() に戻ります。click1() 関数では、クリックなどのイベントがあったとき、クリック座標をいったん取得し、その座標に最も近いオブジェクト id1 を見つけ、オブジェクト id1 とクリック座標 x1, y1 をグローバル変数として保存します。
・ drag1() 関数では、click1() と同じようにイベントを拾っているのですが、マウスクリック時とするのではなく、ドラッグ中につねに実行されるようにします。
まず、クリックすると click1 が実行されるので、クリックした時点で強制的にグローバル座標 x1, y1 が設定されます。つぎに、ドラッグがされると、ドラッグ中の2点について、カーソルのある座標と直前に検出した座標について、移動量(差分)を求めます。そして、対応するオブジェクト id1 に対し、その移動量分、座標をずらします。
この動作を連続させることで、オブジェクトを打点位置からスタートし、マウスの動きに沿ってオブジェクトを移動させることができることになります。

まとめ

Python で GUI を表示し、円、矩形、直線などのオブジェクトを描画する方法、また、それらを動かす方法についてまとめました。これで、基本的な図形の描画と、ドラッグ操作が自由になりました。
サンプルではドラッグする例としましたが、後半のタグバインドで座標を指定しているところを変えれば、時間の経過に伴ってオブジェクトを動かす等のアレンジも可能です。

なお、Python のアプリ上のお絵描きではなく、お絵描きをした HTML を Python で出力する等の場合についても、下記の関連リンクにまとめています。もし関心があるようでしたら、参考にしてみてください。

関連リンク
・ HTML で図形を描く 【Python】
・ 【tkinter】 テキストボックスとボタンの使い方 コピペ用サンプル

サンプルコード

import tkinter as tk1 

def click1( event ):
    global x1 
    global y1 
    global id1 
    x2 = event.x
    y2 = event.y
    id1 = canvas1.find_closest( x2, y2 )
    x1 = x2 
    y1 = y2 

def drag1( event ):
    global x1 
    global y1 
    global id1 
    x2 = event.x
    y2 = event.y
    id1 = canvas1.find_closest( x2, y2 )
    del_x1 = x2 - x1 
    del_y1 = y2 - y1 
    x0, y0, x1, y1 = canvas1.coords( id1 )
    canvas1.coords(id1, x0+del_x1, y0+del_y1, x1+del_x1, y1+del_y1)
    x1 = x2 
    y1 = y2 

x1  =  0
y1  =  0
id1 = -1

frame1  = tk1.Tk()
frame1.title( "drag and drop v0.1" ) 

canvas1 = tk1.Canvas(frame1, width=300, height=300, bg="#F0F0FF")
canvas1.pack()

canvas1.create_line(       60,  20,  80,  40,              tags="object1" )
canvas1.create_rectangle( 130,  20, 150,  40, fill="red" , tags="object2" )
canvas1.create_oval(      200,  20, 220,  40, fill="blue", tags="object3" )

canvas1.tag_bind( "object1", "<ButtonPress-1>", click1 )
canvas1.tag_bind( "object1", "<B1-Motion>", drag1 )

canvas1.tag_bind( "object2", "<ButtonPress-1>", click1 )
canvas1.tag_bind( "object2", "<B1-Motion>", drag1 )

canvas1.tag_bind( "object3", "<ButtonPress-1>", click1 )
canvas1.tag_bind( "object3", "<B1-Motion>", drag1 )

frame1.mainloop()
タイトルとURLをコピーしました