どん底から這い上がるまでの記録

どん底から這い上がりたいけど這い上がれない人がいろいろ書くブログ(主にプログラミング)

Canvasの図形や画像をドラッグ・アンド・ドロップで動かす。

 

この記事ではCanvasの図形や画像をtag_bind()を利用してドラッグ・アンド・ドロップで動かすということをやってみます。

tkinterのアプリケーションはmainloopメソッドを実行することにより、イベントループの状態になります。イベントループの状態になっているときにイベントを発生させることにより、様々な用途のアプリケーションを開発することができます。

イベントを発生させるには例えば、キーボードを押したりマウスをクリック、ドラッグしたときなどです。今回やることはマウス操作で図形や画像を動かすアプリケーションを作ることです。これを応用すればパズルゲームなどができるようになると思います。

 

 

関連リンク

Events and Bindings

長方形と画像をCanvas上でドラッグ・アンド・ドロップで動かす

今回はCanvas上に作成した長方形と画像をドラッグ・アンド・ドロップで動かしてみます。流れとしては以下のようになります。

  1. 長方形と画像を作る。
  2. 作ったオブジェクトとイベントを結びつける
  3. イベントが発生したときの処理

1. 長方形と画像を作る。

まずは、長方形と画像をCanvas上に用意します。

作り方についてはこちら。

www.pytry3g.com

 

今回、長方形と画像を作るときに引数のtagsにそれぞれのオブジェクトを識別するための名前を渡しています。長方形にはrect, 画像にはimgという名前をそれぞれ付けています。(※下のプログラムで使用しているsmile.pngここにあります。

import tkinter as tk
from PIL import Image, ImageTk

root = tk.Tk()
canvas = tk.Canvas(root, width=300, height=300, bg="white")
canvas.pack()
### 図形 ###
canvas.create_rectangle(100, 100, 120, 120, fill="red", tags="rect")
### 画像 ###
img = Image.open("smile.png")
tkimg = ImageTk.PhotoImage(img)
canvas.create_image(200, 200, image=tkimg, tags="img")
root.mainloop()

上のプログラムを実行すると下のウィンドウが立ち上がります。

f:id:pytry3g:20180929215502p:plain

2. 作ったオブジェクトとイベントを結びつける。

長方形と画像が用意できたので、それぞれのオブジェクトをイベントと結びつけます。今回はオブジェクトを左クリックしたとき(ButtonPress-1)と、左クリックした状態でマウスを動かしたとき(B1-Motion)にイベントを発生させます。(※そのほかのイベントについてはこちら。)

オブジェクトとイベントを結びつけるにはtag_bind(tag, event, func)を使います。こんな感じ。

# クリックされたとき
canvas.tag_bind("rect", "<ButtonPress-1>", pressed)
canvas.tag_bind("img", "<ButtonPress-1>", pressed)
# ドラッグされたとき
canvas.tag_bind("rect", "<B1-Motion>", dragged)
canvas.tag_bind("img", "<B1-Motion>", dragged)

tag_bindtageventを結び付け、イベントが発生したときに指定した関数が呼ばれます。

今回の例だとオブジェクトが左クリックされたときはpressed()が呼ばれ、左クリックした状態でマウスを動かしたときはdragged()が呼ばれます。

全体のコードは下のようになります。

import tkinter as tk
from PIL import Image, ImageTk

### ここにイベントが発生したときの処理 ###
def pressed(event):
    print("クリックされました")

def dragged(event):
    print("動かされてる~")

root = tk.Tk()
canvas = tk.Canvas(root, width=300, height=300, bg="white")
canvas.pack()
### 図形 ###
canvas.create_rectangle(100, 100, 120, 120, fill="red", tags="rect")
### 画像 ###
img = Image.open("smile.png")
tkimg = ImageTk.PhotoImage(img)
canvas.create_image(200, 200, image=tkimg, tags="img")
### ここにオブジェクトとイベントを結びつける ###
# クリックされたとき
canvas.tag_bind("rect", "<ButtonPress-1>", pressed)
canvas.tag_bind("img", "<ButtonPress-1>", pressed)
# ドラッグされたとき
canvas.tag_bind("rect", "<B1-Motion>", dragged)
canvas.tag_bind("img", "<B1-Motion>", dragged)
root.mainloop()

3. イベントが発生したときの処理。

オブジェクトを左クリックしたときと、左クリックした状態でマウスを動かしたときの処理をそれぞれpressed()dragged()に書いていきます。

まずpressed()について

### ここにイベントが発生したときの処理 ###
pressed_x = pressed_y = 0
item_id = -1
def pressed(event):
    global pressed_x, pressed_y, item_id
    item_id = canvas.find_closest(event.x, event.y)
    tag = canvas.gettags(item_id[0])[0]
    item = canvas.type(tag)
    #print(item)
    #print(tag)
    pressed_x = event.x
    pressed_y = event.y

pressed()は長方形か画像が左クリックしたときに呼び出されます。pressed()の引数にはeventがあります。このeventにはイベントが発生したオブジェクトの情報が入っています。

上のプログラムでやっていることは、オブジェクトをクリックしたときのCanvas上の位置(x, y)をcanvasのメソッドfind_closet()に渡して、クリックされたオブジェクトのIDをitem_idに入れています。(※オブジェクトのIDはオブジェクトを生成したときに戻り値として返ってきます。参考

次にオブジェクトのIDをcanvasのメソッドgettags()に渡して、オブジェクトに設定したタグを取得します。

さらに、canvasのメソッドtype()にタグを渡して、アイテム名を取得しています。

 

dragged()について。

def dragged(event):
    global pressed_x, pressed_y, item_id
    item_id = canvas.find_closest(event.x, event.y)
    tag = canvas.gettags(item_id[0])[0]
    item = canvas.type(tag) # rectangle image
    delta_x = event.x - pressed_x
    delta_y = event.y - pressed_y
    if item == "rectangle":
        x0, y0, x1, y1 = canvas.coords(item_id)
        canvas.coords(item_id, x0+delta_x, y0+delta_y, x1+delta_x, y1+delta_y)
    else:
        x, y = canvas.coords(item_id)
        canvas.coords(item_id, x+delta_x, y+delta_y)
    pressed_x = event.x
    pressed_y = event.y

dragged()でやっていることは、

  1. eventからイベントが発生したオブジェクトの情報を手に入れる。
  2. それぞれのオブジェクトを動かせるようにしたいので、canvasのメソッドcoords(tag or ID)にオブジェクトのIDかタグを渡してオブジェクトの現在の位置を取得する。
  3. coords(tag or ID)にオブジェクトのIDかタグと、オブジェクトの新しい位置を渡して移動させる。

ソースコード

この記事で使ったコードをまとめたもの。

import tkinter as tk
from PIL import Image, ImageTk

### ここにイベントが発生したときの処理 ###
pressed_x = pressed_y = 0
item_id = -1
def pressed(event):
    global pressed_x, pressed_y, item_id
    item_id = canvas.find_closest(event.x, event.y)
    tag = canvas.gettags(item_id[0])[0]
    item = canvas.type(tag)
    #print(item)
    #print(tag)
    pressed_x = event.x
    pressed_y = event.y

def dragged(event):
    global pressed_x, pressed_y, item_id
    item_id = canvas.find_closest(event.x, event.y)
    tag = canvas.gettags(item_id[0])[0]
    item = canvas.type(tag) # rectangle image
    delta_x = event.x - pressed_x
    delta_y = event.y - pressed_y
    if item == "rectangle":
        x0, y0, x1, y1 = canvas.coords(item_id)
        canvas.coords(item_id, x0+delta_x, y0+delta_y, x1+delta_x, y1+delta_y)
    else:
        x, y = canvas.coords(item_id)
        canvas.coords(item_id, x+delta_x, y+delta_y)
    pressed_x = event.x
    pressed_y = event.y

root = tk.Tk()
canvas = tk.Canvas(root, width=300, height=300, bg="white")
canvas.pack()
### 図形 ###
canvas.create_rectangle(100, 100, 120, 120, fill="red", tags="rect")
### 画像 ###
img = Image.open("smile.png")
tkimg = ImageTk.PhotoImage(img)
canvas.create_image(200, 200, image=tkimg, tags="img")
### ここにオブジェクトとイベントを結びつける ###
# クリックされたとき
canvas.tag_bind("rect", "<ButtonPress-1>", pressed)
canvas.tag_bind("img", "<ButtonPress-1>", pressed)
# ドラッグされたとき
canvas.tag_bind("rect", "<B1-Motion>", dragged)
canvas.tag_bind("img", "<B1-Motion>", dragged)
root.mainloop()

イベントを利用したアプリケーションの例

三目並べ

tkinterを使った三目並べの例。

www.pytry3g.com

オセロ

tkinterを使ったオセロの例。

www.pytry3g.com