Python tkinter GUI プログラミング Canvas move3

 今日も見に来てくださって、ありがとうございます。石川さんです。

 少しあいて、一週間ぶりの更新ですね。お仕事で、ちょっと忙しくしていたのもあるのですけど、採用活動のページも作ったりしていたので、まあ、仕方ありませんね。ということで今日は、またしてもtkinterのCanvasについて書いていきたいと思います。Canvas上に描かれた要素の移動については、以前にも書いているのですが、今回は、移動するものをクラスとして扱ったらどうなるのか、ということでまとめてみました。

できあがりイメージ

キャンバス上の項目を移動できます

 このプログラムは実行すると、最初にふたつ箱が表示されます。何もないところをクリックすると赤い箱が追加されます。追加された箱はマウスでドラッグすることができます。これまでは、箱の移動についてはメインのクラスで実行していたのですが、今回別のクラスに動作を委譲している、というところが大きく異なる点です。

ソースコード

import tkinter as tk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Canvas move with class")
        self.geometry("1200x800")

        self.canvas = tk.Canvas(self, background="white")

        self.canvas.grid(row=0, column=0, sticky=tk.NSEW)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        self.canvas.bind("<Motion>", self.move)
        self.canvas.bind("<ButtonPress>", self.button_press)
        self.canvas.bind("<ButtonRelease>", self.button_release)

        self.movers = [Movable(self.canvas, 160, 160, "AliceBlue"),
                       Movable(self.canvas, 200, 200, "LightYellow")]

    def move(self, event):
        for m in self.movers:
            m.move(event)

    def button_press(self, event):
        event.consume = False
        for m in self.movers:
            m.button_press(event)
            if event.consume:
                return
        m = Movable(self.canvas, event.x, event.y, "Red")
        self.movers.append(m)
        m.button_press(event)

    def button_release(self, event):
        for m in self.movers:
            m.button_release(event)


class Movable():
    def __init__(self, canvas, x, y, color):
        self.canvas = c = canvas
        self.textid = c.create_text(x, y, text=color)
        self.rectid = c.create_rectangle(c.bbox(self.textid), fill=color)
        c.tag_lower(self.rectid, self.textid)
        self.start = None

    def move(self, event):
        if self.start is None:
            return
        x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y)
        dx, dy = x - self.start[0], y - self.start[1]
        self.canvas.move(self.textid, dx, dy)
        self.canvas.move(self.rectid, dx, dy)
        self.start = x, y

    def button_press(self, event):
        x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y)
        b = self.canvas.bbox(self.rectid)
        if b[0] <= x <= b[2] and b[1] <= y <= b[3]:
            self.start = x, y
            event.consume = True

    def button_release(self, event):
        self.start = None


if __name__ == "__main__":
    app = App()
    app.mainloop()

説明

 初期化処理(__init__)の、6~8行目は、Tkの初期化処理、タイトルとサイズの設定をしています。10行目で背景が白色のCanvasを作成して、12~14行目でgridを使用して配置しています。そして、16~18行目でCanvasに対するイベントをメソッドに割り当てています。20行目は、初期値として、Movableクラスを二つ追加しています。

 イベントに割り当てたメソッドはそれぞれ追加したMovableクラスの同名のメソッドを実行するように記述してあります。button_pressだけちょっと変更していて、どのMovableクラスのインスタンスも対応する処理を実行しなかったとき、要するに、クリックしたのが箱じゃなかったとき、赤い箱を追加するようにしました。

 Movableクラスの初期化処理では、渡されたキャンバスのx,yに色の名前のテキストをcreate_text()メソッドを使って描画します。その後、そのテキストを囲む箱をcreate_rectangle()メソッドで描画します。このままだと、テキストの上に箱が描画されて字が見えないので、tag_lower()メソッドを呼び出すことで上下を入れ替えてテキストが表示されるようにしています。あと、移動開始用のポジションを保持するための変数を初期化しています。

 50~57行目もMovableクラスのmove()メソッドは、self.startの位置からeventが発生させた位置へ移動させます。self.startがセットされていない場合は特に動作しません。このmove()メソッドが動作するために、59~64行目のbutton_press()メソッドでは、自分自身が選択された場合に限り、self.startで開始位置をセットします。箱がクリックされたかどうかを判定するために、event.consumeを利用するようにしています。

 66~67行目はbutton_release()では、マウスボタンが離されたときは箱が動かないように、self.startにNoneをセットしています。

 もっといいやり方があるかも知れませんが、これで責任範囲を何とか分離することができました。他に、クリックされたら選択されたような枠の印をつけるとか、その枠を使って大きさを拡大縮小するとか、ダブルクリックしたときにメニューを出すとか、必要に応じてMovableクラスへ実装することができるようになりました。

まとめ

 キャンバス上の項目をひとつのクラスの中でまとめて一度に扱うことができるようになりました。いずれは、MVCパターンを検討してみたいと思います。

Python プログラミング Enum

 今日も見に来てくださって、ありがとうございます。石川さんです。

 今回は、Pythonプログラムでコンスタント値を扱うのはどうするのかなぁ、と調べていて発見した、Enumを紹介したいと思います。

ソースコード

import enum


class PaperSize(tuple, enum.Enum):
    # Paper size
    A0 = (841, 1189)
    A1 = (594, 841)
    A2 = (420, 594)
    A3 = (297, 420)
    A4 = (210, 297)
    A5 = (148, 210)

    def __str__(self):
        return self.name

 このコードを例えばconstants.pyのようなファイルに保存することで、モジュールが作成できます。利用するためには、

from constants import *

 のようにすればよいでしょう。
※追記:この、インポートで*を使うのはよく見かけますが、バッドプラクティスだそうです。名前空間を汚染するから、というのがその理由です。なるほど。きちんと名前を指定しましょう。

 さて、標準モジュールにあったenum.Enumですが、ちゃんとドキュメントがありました。読んでもイマイチわからないので(笑)、とりあえず使い方ですね。

In [69]: for paper in PaperSize:
    ...:     print(paper)
    ...:     
A0
A1
A2
A3
A4
A5

 このように、イテレータとして使うことができます。

In [70]: PaperSize.A0
Out[70]: <PaperSize.A0: (841, 1189)>

In [71]: PaperSize.A0[0], PaperSize.A0[1]
Out[71]: (841, 1189)

 また、A0、A1といった、それぞれの要素を直接参照することができます。

In [72]: for paper in PaperSize:
    ...:     print(paper.name, paper.value)
    ...:     
A0 (841, 1189)
A1 (594, 841)
A2 (420, 594)
A3 (297, 420)
A4 (210, 297)
A5 (148, 210)

 このように「name」「value」といった属性が利用できます。

まとめ

 このような、複数の値を保持する必要がある場合は、enumモジュールのEnumを利用できるかも知れませんね。

Pythonプログラミング Spyder設定について

 今日も見に来てくださって、ありがとうございます。石川さんです。

 今日は、Python Anacondaで提供されているSpyderというIDEの設定についてです。ちょうど先ほどまで勉強会をしていたのですけど、動きが遅い、というクレームがあったのですよねぇ。確かに遅い、と、思いましたが、カーソルの表示はちゃんとしていたので、わざと遅くしているんじゃないかなぁ、と、いうことで調べてみました。

何が遅いのかというと

 動画をご覧ください。メソッドや変数名などの名称をクリックすると、同じ名称のものをハイライトしてくれるのですが、ハイライトされるまでの時間がかかりすぎです。うん、確かに遅いよね。

ハイライトされるまでの時間

 クリックされてカーソルが表示されるのは一瞬ですね。ただ、そのあとの薄い黄色に反転するまでに、1.5秒ほどでしょうか、かかっていますね。

設定

 設定を調べたら、ありましたよ。

ツール(T)-設定(F)

 「ツール(T)」の「設定(F)」を選択します。

「エディタ」の「表示」プロパティ

 開いた「設定」ウィンドウの「エディタ」を選択すると、ありました。「表示」の「変更後に構文を強調するまでの時間」が「1500ms」に設定されていました。はい、1.5秒ほどですね。

 この値を300msほどに設定しなおすと、サクサク表示されました。

まとめ

 変更後に構文を強調するまでの時間まで設定可能とは、Spyderもなかなかやりますねぇ。

Python プログラミング __name__ってなに?

 今日も見に来てくださって、ありがとうございます。石川さんです。

 お友だちから、__name__のことがよく分からない、と、質問をいただいたので、実際にどうなっているのか、実行結果を確認しつつ、説明したいと思います。

はじめに

 まず初めに、dir()という組み込み関数がありまして、コマンドインタプリターで実行すると、現在のローカルスコープにある名前のリストを返してくれます。

In [1]: print(dir())
['In', 'Out', '_', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '_dh', '_i', '_i1', '_ih', '_ii', '_iii', '_oh', 'exit', 'get_ipython', 'quit']

 出力結果の1行目の右端にありますね。こんな感じで、「__name__」という名前がPythonインタープリタの中に事前定義されています。さて、こちらがなにか、ちょっと調べてみます。組み込み関数のtype()を使うとオブジェクトの型を返してくれます。まずはtype()で型を調べます。

In [2]: type(__name__)
Out[2]: str

 なるほど、ここで、「__name__」が文字列の変数であることがわかりました。中身を見てみましょう。

In [3]: print(__name__)
__main__

 変数「__name__」の中身は「”__main__”」ということがわかりました。

ファイルをつくって確認します

 コマンドインタープリタで確認してみると、セットされた値は「__main__」でした。この値は、「__main__」以外に設定されることはあるのでしょうか?利用されている箇所で知っているのはファイルの中でした。と、いうことで、以下のように「mynameis.py」ファイルを作ってみました。

"""" __name__ にどんな値がセットされているか、確認します。 """

print("My name is " + __name__)

 単純に、値をprint()するだけの記述を含むファイルです。
さて、実行してみます。

In [4]: %run mynameis.py
My name is __main__

 フツーに実行すると、やはり「__main__」がセットされています。ちなみに%runはIPythonのマジックコマンドで、指定されたファイルを直接実行します。コマンドプロンプトだと、「python mynameis.py」と実行するのと同じ意味になります。
さて、次にこの「mnameis.py」ファイルがモジュールだということにして、importしてみます。

In [5]: import mynameis
My name is mynameis

 importすると、mynameis.pyの内容が読み込まれ、すべて上から順番に実行されます。ただし、「__name__」には、このようにモジュール名がセットされることになっています。モジュールをつくるときは、この違いを利用して、

if __name__ == "__main__":

というふうにモジュール内にif文を記載することで、importから実行されたときと、%runなどで直接実行されたときのふるまいを変更することができます。

まとめ

 モジュールを作るとき、importで呼び出して利用するだけでなく、直接実行することができるようにするために、「__name__」を利用することができます。

Python tkinter GUIプログラミング ログインダイアログ

 今日も見に来てくださって、ありがとうございます。石川さんです。

 今回は、ログインダイアログを作ってみました。ログインダイアログは別ウィンドウなので、値の引き渡し方法をどうするのか、というのがポイントになります。

出来上がりイメージ

ログインダイアログ

ソースコード

import tkinter as tk
from tkinter.simpledialog import Dialog

class LoginDialog(Dialog):
    def __init__(self, root):
        super().__init__(root, title="Login")
        
    def body(self, master):
        self.username = tk.StringVar()
        self.password = tk.StringVar()
        userLabel = tk.Label(master, text="User:")
        userLabel.grid(row=0, column=0)
        username = tk.Entry(master, textvariable=self.username)
        username.grid(row=0, column=1)
        passwdLabel = tk.Label(master, text="Pass:")
        passwdLabel.grid(row=1, column=0)
        password = tk.Entry(master, show="*", textvariable=self.password)
        password.grid(row=1, column=1)
        self.information = tk.Label(master)
        self.information.grid(row=2,columnspan=2)
        
    def validate(self):
        self.information.configure(text="")
        if not self.username.get():
            self.information.configure(text="Input username")
            return False
        if not self.password.get():
            self.information.configure(text="Input password")
            return False
        return True

    def apply(self):
        self.result = self.username.get(), self.password.get()

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Login dialogue")
        self.geometry("400x100")
        
        show = tk.Button(self, text="login", command=self.show)
        close = tk.Button(self, text="close", command=self.destroy)
        show.grid(row=0, column=0, sticky=tk.EW, ipadx=20, padx=40, pady=20)
        close.grid(row=0, column=1, sticky=tk.EW, ipadx=20, padx=40, pady=20)
    
    def show(self):
        ret = LoginDialog(self)
        print(ret.result)

if __name__ == "__main__":
    application = Application()
    application.mainloop()

説明

 ログインダイアログは、tkinter.simpledialogのDialogを継承して作成しました。4~33行目です。ログインダイアログの中身は、body()メソッドをオーバーライドして実装します。ユーザー名とパスワードとメッセージ出力用のラベルを追加しました。もし、OKとCancel以外のボタンを使いたいときは、buttonbox()メソッドをオーバーライドしますが、今回はこれで充分でしょう。

 OKボタンを押したときに、値をチェックするには、validate()メソッドをオーバーライドします。今回は、22~30行目に記載があります。ユーザー名かパスワードが入力されていないとき、Falseを戻します。成功時はTrueを戻すようにしました。30行目のTrueを戻すことを忘れると、PythonはデフォルトでNoneを戻す約束ですので、OK押してもその後続処理のapply()は実行されませんので、注意が必要です。

 OKボタンを押したときの処理は、apply()メソッドをオーバーライドします。今回は、33行目で、ダイアログボックスに入力した値をself.resultへセットしています。self.resultは、初期化時にNoneをセットされていますので、Cancel押されたかどうか、この値を判定すればわかりますね。

 呼び出し方法は、47行目のようにします。戻り値の判定は48行目のret.resultをチェックすれば完璧です。Noneのときは、キャンセルされています。

まとめ

 ログインダイアログを作成することを通して、簡単なダイアログを作成するための基本を学びました。これで、いろいろなダイアログ、作成できそうですね。

Python tkinter GUIプログラミング 透明なキャンバス

 今日も見に来てくださって、ありがとうございます。石川さんです。

 先日、透明なウィンドウをつくったところで、ひらめきました。透明なキャンバスのウィンドウをつくって、それを別のウィンドウに重ねるのは、どうでしょうか。と、いうことで試しにやってみました。ちょっと実用には遠いですが、アイディアとして記録しておきます。

出来上がりイメージ

透明なキャンバスを仮想的に実現しました

ソースコード

import tkinter as tk

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Transparent Canvas Challenge!")
        self.geometry("500x300")

        self.top = tk.Toplevel()
        self.top.wm_attributes("-topmost", True)
        self.top.overrideredirect(True)
        self.top.geometry("500x300")
        self.top.forward = tk.Canvas(self.top, background="white")
        self.top.forward.pack(fill=tk.BOTH, expand=True)
        self.top.forward.create_oval(50,50,450,250,fill="lightblue")
        self.top.wm_attributes("-transparentcolor", "white")
        
        self.back = tk.Canvas(self, background="white")
        self.back.pack(fill=tk.BOTH, expand=True)
        self.back.create_rectangle(50,50,450,250)
        self.back.create_rectangle(100,100,400,200)
        self.bind('<Configure>', self.change)
        self.back.bind("<Unmap>", self.unmap)
        self.back.bind("<Map>", self.map)
        self.bind("<1>", self.draw1)
        self.bind("<3>", self.draw3)
        self.top.bind("<1>", self.draw1)
        self.top.bind("<3>", self.draw3)
        
    def draw1(self, event):
        self.top.forward.create_oval(event.x, event.y, event.x + 30, event.y + 30)
        
    def draw3(self, event):
        self.back.create_rectangle(event.x, event.y, event.x + 30, event.y + 30)

    def unmap(self, event):
        self.top.withdraw()

    def map(self, event):
        self.lift()
        self.top.wm_deiconify()
        self.top.attributes("-topmost", True)
        
    def change(self, event):
        x, y = self.back.winfo_rootx(), self.back.winfo_rooty()
        w, h = self.winfo_width(), self.winfo_height()
        self.top.geometry(f"{w}x{h}+{x}+{y}")
        
if __name__ == "__main__":
    application = Application()
    application.mainloop()

説明

 まずは、背景になるキャンバスをつくります。そして、前面に来る透明なキャンバスをつくるのですが、そのときに、Toplevelのウィンドウをつくって、このウィンドウ用のキャンバスとします。そして、Toplvelの方に透明となる設定を施す、というのがおおまかな流れです。

 9行目でToplevelのウィンドウをつくっています。10行目「self.top.wm_attributes(“-topmost”, True)」は、割とポイントになりますが、常に一番上のウィンドウとなるようにする設定です。これで、透明なキャンバスが常に上に表示されるようになります。

 11行目、「self.top.overrideredirect(True)」では、ウィンドウのタイトル部分を消去しています。これでアイコン化や閉じるボタンなども使えなくなります。

 13行目、14行目でキャンバスをつくって、配置しています。キャンバスの背景色は、「”white”(白)」にセットしていますが、これが16行目の「self.top.wm_attributes(“-transparentcolor”, “white”)」のところで効いてきます。ここで-transparentcolorは透明にする色で、「”white”(白)」を指定しています。こうすることでキャンバスの背景色の白が透明になります。

 23行目から、いくつかイベントをバインドしています。最初のイベントは<Configure>です。ウィンドウのサイズなどが変更されたときに発生します。ウィンドウサイズが変更されたときに、透明なキャンバスのサイズも同時に変更するために指定しました。<Unmap>と<Map>はウィンドウがアイコン化から戻されたときとアイコン化されたときに発生します。こちらもウィンドウを閉じたときに透明なキャンバスのウィンドウも閉じるために指定してあります。

 25~28行目は左クリックされたときと右クリックされたときにキャンバス上に丸と四角を描くためにセットしました。ちゃんと透明なキャンバスが上になっていることを確認するためにおまけで付けてみました。

 37行目の「self.top.withdraw()」は、ウィンドウを非表示にします。40行目の「self.lift()」は、ウィンドウを上に移動します。41行目「self.top.wm_deiconify()」でアイコン化から復帰し、42行目「self.top.attributes(“-topmost”, True)」で常に一番上のウィンドウとなるように再設定しています。

 44~47行目のchangeメソッドでは、透明なキャンバスのウィンドウのサイズを合わせています。

まとめ

 ウィンドウをふたつつくって、下のウィンドウにあわせて上のウィンドウが移動したりサイズ変更したり、ということで、透明なキャンバスを実現してみました。思ったとおりに動作させるのは、けっこう難しいですね。

 

Python tkinter GUIプログラミング 透明なウィンドウ

 今日も見に来てくださって、ありがとうございます。石川さんです。

 透明なキャンバスを重ねるというアイディアを実現しようといろいろ調べまわりましたが、なかなか良いものが見つかりません。その中で見つけたちょっと面白い技を紹介します。透明なウィンドウです。

出来上がりイメージ

背景色が透明なウィンドウ

ソースコード

import tkinter as tk

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Transparent window")
        self.geometry("500x300")
        self.config(bg="white")
        self.attributes("-transparentcolor", "white")
        
if __name__ == "__main__":
    application = Application()
    application.mainloop()

説明

 ポイントはただ一つ、9行目の「self.attributes(“-transparentcolor”, “white”)」です。これで透明になる色をセットしています。この場合、白(”white”)ですね。8行目でbg(背景色)に白を設定しています。この色が透明になる、ということで画像のようなウィンドウが出来上がります。

まとめ

 背景を透明にすることで、変わった形のウィンドウを作ったり、特殊な効果を実現することができそうですね。ちなみに、Raspbianではうまく動作しない、と、言われていました。

Python プログラミング sqlite3を使ってみる

 今日も見に来てくださって、ありがとうございます。石川さんです。

 Pythonからデータを保持するための方法のひとつ、sqlite3を利用してみます。sqlite3はPythonの標準ライブラリに含まれていますので、特別なインストールなど、不要です。それでいて、SQLが使えて、テーブルを作成してデータを簡単に保存したいというニーズに充分応えてくれます。

 ぼくは普段のおしごとでは、Oracleを専門に扱っているのですけど、他のデータベースも知っておいた方がよいでしょう、ということで、ちょっぴりチャレンジしてみました。データベースをつくって、テーブルをつくって、データを入れて、検索する、という一連の流れを実行してみました。

 ちなみに、sqlite3は、別のサーバプロセスを用意する必要はありません。その名のとおり軽量で、ディスク上のデータベースを提供してくれます。

ソースコード

import sqlite3

conn = sqlite3.connect('test.db')

curs = conn.cursor()

curs.execute("SELECT tbl_name FROM sqlite_master WHERE type = 'table'")
tables = [table[0] for table in curs.fetchall()]

if "BOOKS" in tables:
    curs.execute("DROP TABLE BOOKS")
curs.execute("CREATE TABLE BOOKS ( ID NUMBER PRIMARY KEY, TITLE VARCHAR(30), PUBLISHED DATE)")
curs.execute("INSERT INTO BOOKS (ID, TITLE, PUBLISHED) VALUES"
             " (1, '入門Python3', '2015-12-01')"
             ",(2, '実践Python3', '2015-12-01')"
             ",(3, 'Fluent Python', '2017-10-11')"
             ",(4, 'Effective Python', '2016-01-22')"
            )
conn.commit()
curs.execute("SELECT * FROM BOOKS")
rows = curs.fetchall()
print(rows)

説明

 1行目、ライブラリ名はsqlite3です。利用できるようにするため、importします。

 3行目、sqlite3のデータベースを使えるようにするためには、まずConnectionオブジェクトを作る必要があります。データは「test.db」ファイルに格納されます。「:memory:」という特殊な名前を指定すると、RAM上にデータベースが作れるそうです。ちなみに、ファイルが存在しなくてもエラーにならず、勝手にファイルが作成されます。

 5行目、データベースへの操作には、必ずカーソルが必要です。ということで、cursor()メソッドでカーソルを作成します。

 このデータベースの中に、「BOOKS」テーブルを作成しようと思ったのですけど、既にこの「BOOKS」テーブルが存在した場合はいったん削除してから作成しましょう。7行目で指定した「sqlite_master」テーブルには、このデータベースの中に入っているシステムテーブルで、作られたオブジェクトの情報が入力されています。

 7行目でSELECT文を定義して、8行目でデータを取り出しています。データはタプルで取り出されるので、リスト内包表記で一つ目の項目だけを取り出しています。10行目で比較するためです。1回目はテーブルが存在しないはずですので、12行目のテーブル削除は実行されず、12行目のCREATE TABLE文でテーブルをつくることから始めます。

 13行目のINSERT文でデータを投入しています。今回は、1行で4件データを作成しています。sqlite3はINSERT一文で複数件投入できるのですね、便利です。

 19行目、commit()です。これで、データが確定されました。

 20行目以降、SELECT文でデータを取得しています。

まとめ

 sqlie3、簡単ですね。ほとんど悩むことなく使えるようになりました。さすが標準モジュールです。

Python tkinter GUIプログラミング Canvas項目の前後切り替え

 今日も見に来てくださって、ありがとうございます。石川さんです。

 EclipseのGraphical Editing Framework(GEF)には透明なキャンバスがあって、背面にConnection layer、前面にPrimary layerを配置することで、コネクションをうまくあつかう工夫をしています。tkinterでも同じことができないかといろいろと調べていたのですが、ちょっと難しそうです。

 レイヤーが変えられないので、項目を前後に移動して重なりを制御するしかありませんね。と、いうときに使えるのが、tag_raise()、tag_lower()です。

できあがりイメージ

初期画面
クリックで長方形が上にきます

ソースコード

import tkinter as tk

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.geometry("600x400")
        self.title("canvas tag_raise")
        
        self.canvas = tk.Canvas(self, background="white")
        self.canvas.pack(fill=tk.BOTH, expand=True)
        
        self.r = self.canvas.create_rectangle(90,40,510,360,fill="lightblue")
        self.o = self.canvas.create_oval(30,30,560,370,fill="lightyellow")
        
        self.item = "r"
        
        self.canvas.bind("<ButtonPress>", self.clicked)
        
    def clicked(self, event):
        if self.item == "r":
            self.canvas.tag_raise(self.r)
            self.item = "o"
        else:
            self.canvas.tag_raise(self.o)
            self.item = "r"
        
if __name__ == "__main__":
    application = Application()
    application.mainloop()

説明

 12行目、13行目でcreate_rectangle()、create_oval()でキャンバス上に四角形と楕円を描画します。このときの戻り値はidで、キャンバス上に描画した項目を管理する番号です。17行目で、クリックしたときに実行するメソッドをバインドしています。

 21行目と24行目のtag_raise()で指定されたidの項目を上に移動させます。

まとめ

 tag_raise()とtag_lower()を使って、項目をz軸方向に移動させることができます。ただし、一番上と一番下しか指定できませんので、複数項目があって、重なる順番が必要な時は、工夫が必要になりそうですね。

Python tkinter GUIプログラミング フレームの切り替え

 今日も見に来てくださって、ありがとうございます。石川さんです。

 今日は、キャンバスの切り替えのやり方です。透明なキャンバスを二枚重ねる方法はないかと調べていたのですが、見つからず、かわりにフレームを切り替える方法を見つけましたので、紹介します。

できあがりイメージ

 初期表示画面は以下のように三角形を描いてあります。

初期表示画面

 キャンバスをクリックすると次のように切り替わります。

キャンバスをクリックすると切り替わります。

ソースコード

import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("test")
        self.frames = {}
        
        self.frames["top"] = top = tk.Frame(self,width=300,height=200)
        topcanvas = tk.Canvas(top)
        topcanvas.place(x=0, y=0)
        topcanvas.create_oval(30,30,270,170,outline="blue",fill="white")
        top.grid(row=0,column=0)

        self.frames["bottom"] = bottom = tk.Frame(self,width=300,height=200)
        bottomcanvas = tk.Canvas(bottom)
        bottomcanvas.place(x=0, y=0)
        bottomcanvas.create_polygon(150,30,30,170,270,170,fill="lightgrey")
        bottom.grid(row=0,column=0)

        self.bind("<ButtonPress>", self.clicked)
        self.frame = "top"

    def clicked(self, event):
        self.frames[self.frame].tkraise()
        if self.frame == "top":
            self.frame = "bottom"
        else:
            self.frame = "top"
        
if __name__ == "__main__":
    app = App()
    app.mainloop()

説明

 クリックしたら描かれたキャンバスが切り替わります。正確には、フレームが切り替わります。まずは、アプリケーション作成、いつものようにtkinterのTkを継承してクラスを開始しています。__init__メソッドでは、最初にsuper().__init__()と記述して、親クラスTkの__init__()初期化メソッドを呼び出しています。その後、タイトルをセットして、self.framesをディクショナリとして初期化しています。

9行目でフレームを作成、10行目でそのフレーム上にキャンバスを作成します。11行目でplaceを使って配置しています。12行目でキャンバスへ楕円を描画しています。13行目でフレームをgridを使って配置しています。作ったフレームはディクショナリにセットしています。

 15~19行目、同様にフレームとキャンバスを作成、配置しています。21行目、クリックされたときに呼び出すメソッドをセット、作ったフレームはディクショナリにセットしています。

 25行目、ここが実際のフレームの切り替えを実行している箇所です。ディクショナリに登録したフレームのtkraise()メソッドを呼び出して、フレームを最上位に移動しています。

 フレームをたくさん作って、タイマーでパカパカ切り替えたら、アニメーションが作れそうですね。

まとめ

 フレームの切り替え、やり方さえ知っていれば、簡単ですね。キャンバスを二枚重ねて表示する方法は、いまだ不明です。どなたか知っている方がいらっしゃれば、教えてください。