キャンバスのありがちなお絵描きなの

 Tcl/Tkで最も複雑なウィジェットがキャンバス(Canvas)でありますが、 その機能を網羅して紹介することはちょっと当サイトの手に負えづらいので、 やはり簡単なサンプルでとっつきだけご紹介します。 どのGUIツールキットでもそうですが、 分からない点がある場合、 膨大なマニュアルやドキュメントの中からそれを調べる方法さえ分かれば、 類推でなんとかなるものです。その「当たり」をつけるところまでに、 当サイトの小ネタが少しでもお役に立てられれば幸いです。

from u4 import *

class A(U4Widget):
  def init(self):
    can = Canvas(self, bg="white", width=300, height=200)
    can.pack()
    self.sx = self.sy = None
    can.bind("<Button1-Motion>", self.motion)
    can.bind("<ButtonRelease-1>", self.release)
    can.bind("<Button-3>", self.button3)
  # 右ボタンが押されると呼ばれます
  def button3(self, event):
    self.quit()
  # 左ボタンが押されたまま引きずられると呼ばれます
  def motion(self, event):
    can = event.widget
    if self.sx != None:
      can.create_line(self.sx, self.sy, event.x, event.y,
          fill="#700040", width=5)
    self.sx = event.x; self.sy = event.y
  # 左ボタンが離されると呼ばれます
  def release(self, event):
    self.sx = self.sy = None

a = A("Canvas Example")
# end.

スクリーンショット

 だいたいナニをやっているかはお分かり頂けると思います。 キャンバスに線を引くには、キャンバスのcreate_line メソッドを使います。先頭から4つが(x1, y1, x2, y2)で、 始点の座標と終点の座標を指定します。そのあとの fill=とwidth=はオプションです。


キャンバスと図形アイテム

 あら、今度は何かしら?今度はですね、 これもキャンバスのサンプルとしてはポピュラーな、 描いた図形をマウスで引きずって動かせるというサンプルです。

from u4 import *

class A(U4Widget):
  def init(self):
    can = Canvas(self, bg="white", width=300, height=200)
    can.pack()
    self.circ = can.create_oval(135, 85, 165, 115, outline="#000060",
        fill="#600000")
    can.bind("<Button1-Motion>", self.motion)
    can.bind("<Button-3>", self.button3)

  def button3(self, event):
    self.quit()

  def motion(self, event):
    can = event.widget
    can.coords(self.circ, event.x-15, event.y-15, event.x+15, event.y+15)

a = A("Canvas Example")
# end.

スクリーンショット

 一度描いた図形に対し、あとになって座標を変えたり、 色などの属性を変えたりしたいという場合には、キャンバスの create_なにがしというメソッドが返す値(図形ID) をPython変数に持たせておき、それを使って処理をします。 ここではスクリーンショットでご覧の通り円を描いているので、 create_ovalメソッドを使います。 その先頭から4つの引数は、 (左上のx座標, 左上のy座標, 右下のx座標, 右下のy座標)です。

self.circ = can.create_oval(135, 85, 165, 115, outline="#000060",
  fill="#600000")

座標を変えたい場合には、coords メソッドに新しい座標値を渡してコールします。

can.coords(self.circ, event.x-15, event.y-15, event.x+15, event.y+15)

またサンプルコードには出てきていませんが、 図形の色などの属性を変えたい場合には、 itemconfigure というメソッドを使えばOKです。

can.itemconfigure(self.circ, fill="#000060")


円が動くなり

 今度のサンプルも上とほぼ同じですが、 今度は描いた円が一定時間ごとにだんだん下に動いていきます。 この、「一定時間おきに何かする」という処理はTcl/Tkでは afterというコマンドで実現していますが、 これは実際にはTkのコマンドではなく、Tcl言語のコマンドなので、 TkinterからはTcl/Tkのafterをそのまま使うことはできません。 そこでTkinterはTcl/Tkのafterとは違う、独自のタイマー監視機構を持っており、 その関数を「偶然」afterと名づけることで、 Tcl言語のafterをエミュレートというかシミュレートというか、 とにかくしています。

from u4 import *

class A(U4Widget):
  def init(self):
    can = Canvas(self, bg="white", width=300, height=200)
    can.pack()
    self.circ = can.create_oval(135, 85, 165, 115,
        outline="#000060", fill="#600000")
    can.bind("<Button1-Motion>", self.motion)
    can.bind("<Button-3>", self.button3)
    self.after(300, self.autodrop, can)

  def autodrop(self, can):
    (x1, y1, x2, y2) = can.coords(self.circ)
    can.coords(self.circ, x1, y1+3, x2, y2+3)
    self.after(300, self.autodrop, can)

  def button3(self, event):
    self.quit()

  def motion(self, event):
    can = event.widget
    can.coords(self.circ, event.x-15, event.y-15,
                          event.x+15, event.y+15)
a = A("Canvas Example")
# end.

ウィジェットのafter関数は通常2個ないし3個の引数をとります (3個目の引数がオプションです)。

self.after(300, self.autodrop)

これすなわち、300ミリ秒後に関数self.autodropを実行するように、 という指令です。このとき、呼び出したい関数に何か追加の情報を渡したい場合には、

self.after(300, self.autodrop, can)

このようにその情報を後ろにつけます。 ここで呼ばれるように指定される(コールバック)関数の定義は、

def autodrop(self, can):
  (x1, y1, x2, y2) = can.coords(self.circ)
  can.coords(self.circ, x1, y1+3, x2, y2+3)
  self.after(300, self.autodrop, can)

このようになります。Tcl/Tkのafterと同様、 Tkinterのafterも一度呼ばれたらそれっきりなので、 一定間隔ごとに繰り返し呼ばせたい場合には、 コールバック関数の中で再度同じ関数を呼ぶようにafter関数を使います。


Python/Tkinterのもっとキャンバス

 キャンバスは非常に広範な使い方ができるため、ネタとしては山ほどあるので、 とても紹介しきれないのですが、今度は図形の操作を中心にもう少し別のサンプルで切ってみます。

import sys
from Tkinter import *

class Application(Frame):
  def quit_clicked(self):
    sys.exit(0)

  def __mousepressed(self, event):
    self.start_x = event.x
    self.start_y = event.y

  def __mousemoved(self, event):
    self.can.coords(self.circ, event.x-15, event.y-15,
                    event.x+15, event.y+15)
  def __enter(self, event):
    self.can.itemconfigure(self.circ, fill='yellow', outline='yellow')
  def __leave(self, event):
    # アイテムの変数でなく、タグでも指定できます。
    self.can.itemconfigure('sample_circle', fill='red', outline='yellow')

  def init(self):
    self.can = Canvas(self, bg='white')
    cmda = Button(self, text='Quit', command=self.quit_clicked)
    self.can.pack(side='top')
    cmda.pack(side='top', anchor='e')
    # タグは文字列で指定
    self.circ = self.can.create_oval(50, 50, 80, 80,
                fill='red', outline='red', tag='sample_circle')
    self.can.bind('<Button1-Motion>', self.__mousemoved)
    # アイテムへのバインドは(Canvasのbindサブコマンド)は
    # tag_bindメソッドになっています
    self.can.tag_bind(self.circ, '<Enter>', self.__enter)
    self.can.tag_bind(self.circ, '<Leave>', self.__leave)

  def __init__(self, master=None):
    Frame.__init__(self, master)
    self.master.title('Main')
    self.init()
    self.pack()

app = Application()
app.mainloop()
# end.

 一度描いた図形を後からコマンドひとつで場所や色などを変えることができるのは、 数あるGUIツールキットの中でTcl/Tkの最も強力な長所ですが (Java AWT/Swing, Gtk+, Qtなどでは、一旦描いた図形を動かしたりするには、 描画を全部クリアして描きなおす処理を全部書かないといけません)、 Python/Tkinterでも同じ要領でメソッドひとつで図形を操作できます。
 図形の位置を動かすにはcoordsまたはmoveメソッドを使います。前者は動かす先の座標を直接指定します。 後者は現在の位置からどれだけずらすかを指定します。 使いやすいほうを適宜選べばOKです。
 図形の色や形状に関わるオプションはitemconfigure メソッドで操作できます。

self.can.itemconfigure(self.circ, fill='yellow', outline='yellow')

 ところで、図形にはタグとゆーものが使えます。 これはPython/Tkinterでは好きな文字列が指定でき、 このタグをcoordsやitemconfigureに渡すこともできます。 複数の図形に同じタグをつけると、それらの図形をグループ化してまとめて動かしたり、色を変えたり、寿命を終えたら消したりすることができるので便利です。

self.can.itemconfigure('sample_circle', fill='yellow', outline='yellow')

 最後に、ウィジェットにイベントをバインドするのと同様に、 キャンバスウィジェットの図形にイベントをバインドすることもできます。 Tcl/Tkではキャンバスのbindサブコマンドを使いますが、 Python/Tkinterではキャンバス自身のイベントを操るbindメソッドと名前がぶつかってしまうため、 tag_bindという名前になっています。

self.can.tag_bind(self.circ, '<Enter>', self.__enter)


スケール

 スケールとは、マウスで引っ張ることによって、 連続する一定の値域の中から数値をひとつ選択する機能をもつウィジェットです。 下のサンプルでは、2つのスケールによって、 それぞれ円の半径と、円の落ちるスピードを動的に変更することができます。

from u4 import *

class A(U4Widget):
  def init(self):
    f = Frame(self)
    self.R = IntVar();    self.S = IntVar()
    self.R.set(15);       self.S.set(300)
    sca1 = Scale(f, label="R(pixels):", variable=self.R, to=20,
           orient=HORIZONTAL)
    sca1["from"]=2
    sca2 = Scale(f, label="Speed:", variable=self.S, to=500,
           orient=HORIZONTAL)
    sca2["from"] = 30
    for e in (sca1, sca2): e.pack(side=LEFT)
    can = Canvas(self, bg="white", width=300, height=200)
    for e in (can, f): e.pack(side=TOP)
    self.circ = can.create_oval(135, 85, 165, 115, outline="#000060",
        fill="#600000")
    can.bind("<Button1-Motion>", self.motion)
    can.bind("<Button-3>", self.button3)
    self.after(self.S.get(), self.autodrop, can)

  def autodrop(self, can):
    (x1, y1, x2, y2) = can.coords(self.circ)
    if y1+3 >= 200: y1 = 0
    self.moveCircleTo(can, x1, y1+3)
    self.after(self.S.get(), self.autodrop, can)

  def button3(self, event):
    self.quit()

  def moveCircleTo(self, can, x, y):
    can.coords(self.circ, x, y, x+self.R.get()*2, y+self.R.get()*2)

  def motion(self, event):
    self.moveCircleTo(event.widget, event.x, event.y)

a = A("Canvas Example")
# end.

スクリーンショット

スケールはScaleクラスのオブジェクトです。 現在選択されている値を変数に保持させるために、 エントリやチェックボタンのところで出てきた変数オブジェクト をまたしても使っています。 値の性質上、IntVarかDoubleVarオブジェクトを使うことになるでしょう。

self.R = IntVar()
self.R.set(15)
sca1 = Scale(f, label="R(pixels):",
       variable=self.R, to=20, orient=HORIZONTAL)
sca1["from"]=2

一点困ったことがあります。そのスケールが取りうる値の範囲は、 from=オプションとto=オプションで指定するのですが、 「from」がPython言語のキーワード(予約語)になっていて、 from=という形でパラメータ指定しようとすると文法エラーを起こしてしまいます。 そこで多少不恰好ですが、 上のようにfromだけディクショナリのスタイルでオプションを設定しています (上の例では、from=2、to=20と設定しています…って、分かりますよね?)

セクションのサブメニューに戻る
(first uploaded 2001/08/06 last updated 2002/03/11)


[PR]≪占い奇跡の恋愛術≫初回無料:幸せな結婚へ導きます。本格結婚鑑定