【Python/tkinter】枠線(relief)の種類一覧

tkinterのウィジェットのButtonやFrame、Labelなどで設定できる枠線(relief)の種類ですが、

  • RAISED
  • GROOVE
  • SUNKEN
  • RIDGE
  • FLAT
  • SOLID

の6種類設定出来るのは、調べれば簡単に出てきますが、どれがどれ?と、いつもなってしまうので、一覧を示します。

サンプルコード

import tkinter as tk

root = tk.Tk()
root.geometry("300x320") 

# ボタンの作成
button1 = tk.Button(
    root, text="tk.RAISED", relief=tk.RAISED, bd=5, width=20)
button2 = tk.Button(
    root, text="tk.GROOVE", relief=tk.GROOVE, bd=5, width=20)
button3 = tk.Button(
    root, text="tk.SUNKEN", relief=tk.SUNKEN, bd=5, width=20)
button4 = tk.Button(
    root, text="tk.RIDGE", relief=tk.RIDGE, bd=5, width=20)
button5 = tk.Button(
    root, text="tk.FLAT", relief=tk.FLAT, bd=5, width=20)
button6 = tk.Button(
    root, text="tk.SOLID", relief=tk.SOLID, bd=5, width=20)

# 配置
button1.pack(pady=10)
button2.pack(pady=10)
button3.pack(pady=10)
button4.pack(pady=10)
button5.pack(pady=10)
button6.pack(pady=10)

root.mainloop()

【Python/tkinter】ウィンドウ(Frame)のxボタンがクリックされたときの終了確認

TkinterのFrameで作成したウィンドウのxボタンをクリックしたときに、終了確認を表示して、プログラムを閉じる/閉じないを選択する方法です。

 

まず、何もしないウィンドウを表示するだけのベースとなるプログラムから手を加えていきます。

 

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.geometry("300x300")       # ウィンドウサイズ(幅x高さ)

if __name__ == "__main__":
    root = tk.Tk()

    app = Application(master = root)
    app.mainloop()

(実行結果)

ここに、以下の一文を追加し、xボタンが押されたときに呼び出される関数を指定します。

関数名は何でも構いません。

self.master.protocol("WM_DELETE_WINDOW", self.delete_window)

全、コードはこちら

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.geometry("300x300")       # ウィンドウサイズ(幅x高さ)

        # ウィンドウの x ボタンが押された時に呼ばれるメソッドを設定
        self.master.protocol("WM_DELETE_WINDOW", self.delete_window)

    def delete_window(self):
        print("ウィンドウのxボタンが押された")
        pass

if __name__ == "__main__":
    root = tk.Tk()

    app = Application(master = root)
    app.mainloop()

このようにすると、delete_window()メソッドの部分で、printしているだけで特に何もしていないので、xボタンが押されても、ウィンドウが閉じられないプログラムとなります。

xボタンが押されたときにプログラムの終了確認を行うには、このメソッド内で行います。

終了確認を行うプログラムはこちら↓

import tkinter as tk
from tkinter import messagebox

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.geometry("300x300")       # ウィンドウサイズ(幅x高さ)

        # ウィンドウの x ボタンが押された時に呼ばれるメソッドを設定
        self.master.protocol("WM_DELETE_WINDOW", self.delete_window)

    def delete_window(self):
        print("ウィンドウのxボタンが押された")

        # 終了確認のメッセージ表示
        ret = messagebox.askyesno(
            title = "終了確認",
            message = "プログラムを終了しますか?")

        if ret == True:
            # 「はい」がクリックされたとき
            self.master.destroy()

if __name__ == "__main__":
    root = tk.Tk()

    app = Application(master = root)
    app.mainloop()

(実行結果)

delete_window()メソッド内で何もしなければ、ウィンドウは閉じられません。

プログラムを終了するには、明示的にdestroy()メソッドを呼び出して、プログラムを終了させる必要があります。

【Python/tkinter】図形の編集(削除、移動、変形など)

線や円などの図形の描画のページでは、tkinterでCanvas上に図形を描画しましたが、描画後に図形の移動や移動や変形、削除などを行う事ができ、その方法を紹介します。

ちょうどWordやExcelのように図形を選択し、移動や変形、削除、表示する順番の変更(最前面へ移動、最背面へ移動)などを行う操作と似ているので、これをイメージしてください。

実際にtkinterで、どのように図形を編集するのか?を紹介します。

tagとid

図形を編集するために、それぞれの図形を識別する必要があり、識別のためにid もしくは tag を用います。

id は create_line のように create_ から始まるメソッドを実行するたびに、1から始まるシリアル番号が返されます。

tag は図形を識別するために、create_ から始まるメソッドの tag オプションに任意文字列を指定します。
この文字列は重複していてもよく、複数の図形に同じtagの名前を指定すると、WordやExcelでいうところのグループ化のような事ができます。

(サンプル)

import tkinter as tk

root = tk.Tk()
root.geometry("300x200")

# Canvasの作成
canvas = tk.Canvas(root, bg = "white")
# Canvasを配置
canvas.pack(fill = tk.BOTH, expand = True)

# 線の描画
id1 = canvas.create_line(20, 10, 280, 190, fill = "Red", width = 5, tag="line")
# 矩形の描画
id2 = canvas.create_rectangle(100,50,200,150, fill="Yellow",width = 3, tag="rect")

root.mainloop()

(実行結果)

このプログラムを実行すると、id1=1, id2=2 となります。

 

以降は、このサンプルをベースに説明します。
また、tag もしくは idを tagOrId と表現します。

図形の削除(delete)

図形を削除します。

.delete(tagOrId)

.delete(“all”) とすると、Canvas上のすべての図形を削除します。
.delete(id1, “rect”)のように複数のtagOrIdを指定することもできます。

(例)

# 線の描画
id1 = canvas.create_line(20, 10, 280, 190, fill = "Red", width = 5, tag="line")
# 矩形の描画
id2 = canvas.create_rectangle(100,50,200,150, fill="Yellow",width = 3, tag="rect")

# 線の削除(id指定)
canvas.delete(id1)
# 線の削除(tag指定)
#canvas.delete("line")

 

 

 

 

 

図形の相対移動(move)

現在の位置からのX方向、Y方向の移動量を指定して、図形を移動します。(相対移動)

.move(tagOrId, x, y)
tagOrId 移動する図形のid もしくは tag
x X方向(右方向が正)に移動する量
y Y方向(下方向が正)に移動する量

(例)

# 線の描画
id1 = canvas.create_line(20, 10, 280, 190, fill = "Red", width = 5, tag="line")
# 矩形の描画
id2 = canvas.create_rectangle(100,50,200,150, fill="Yellow",width = 3, tag="rect")

# 矩形の移動(相対座標指定指定)
canvas.move(id2, 50, -30)

 

 

 

 

 

図形の拡大縮小(sacle)

点(xOffset, yOffset)を基点とした拡大縮小

.scale(tagOrId, xOffset, yOffset, xScale, yScale)
tagOrId 拡大縮小する図形のid もしくは tag
xOffset 基点となる点のx座標
yOffset 基点となる点のy座標
xScale
X方向(幅方向)の倍率
yScale Y方向(高さ方向)の倍率

(例1)矩形の左上の座標を基点として拡大縮小

# 矩形の描画
id = canvas.create_rectangle(100,50,200,150, fill="Yellow",width = 3, tag="rect")
canvas.create_rectangle(100,50,200,150) # 移動前の位置

# (xOffset, yOffset)を基点とした拡大縮小
canvas.scale(id, 100, 50, 1.5, 0.5) # 矩形の左上の座標を基点に拡大縮小

 

 

 

 

 

(例2)矩形の中心を基点として拡大縮小

# 矩形の描画
id = canvas.create_rectangle(100,50,200,150, fill="Yellow",width = 3, tag="rect")
canvas.create_rectangle(100,50,200,150) # 移動前の位置

# (xOffset, yOffset)を基点とした拡大縮小
canvas.scale(id, 150, 100, 1.5, 0.5) # 矩形の中心を基点に拡大縮小

 

 

 

 

 

図形を構成している座標を指定して変形(coords)

図形を作成した時の座標を書き換えます。

引数にtagOrIdのみを指定すると、図形を構成している座標を[x0, y0, x1, y1, …, xn, yn]のリストで取得します。

.coords(tagOrId, x0, y0, x1, y1, ..., xn, yn)

(例1)図形を構成している座標を指定して変形

# 線の描画
id = canvas.create_line(20, 10, 150, 190, 280, 50, fill = "Red", width = 5, tag="line")
canvas.create_line(20, 10, 150, 190, 280, 50) # 移動前の位置
# 図形を構成している座標を指定して変形
canvas.coords(id, 20, 50, 150, 120, 280, 100)

 

 

 

 

 

(例2)図形を構成している座標を取得し、部分的に座標を変更する

# 線の描画
id = canvas.create_line(20, 10, 150, 190, 280, 50, fill = "Red", width = 5, tag="line")
canvas.create_line(20, 10, 150, 190, 280, 50) # 移動前の位置
# 図形を構成している座標の取得
points = canvas.coords(id)
print(points) # [20.0, 10.0, 150.0, 190.0, 280.0, 50.0]
# 部分的に座標を修正
points[2:4]=[200, 80]
# 図形の変形
points = canvas.coords(id, points)

 

 

 

 

 

前面へ移動(lift)

図形の重なり具合を調整します。

tagOrIdで指定した図形をaboveThisの上へ移動します。

.lift(tagOrId, aboveThis)
tagOrId 重なりを調整する図形のid もしくは tag
aboveThis 移動先の図形のid もしくは tag
指定しない場合、最前面へ移動します。

(例)

# 矩形の描画
id1 = canvas.create_rectangle(20,20,120,120, fill="Red",width = 3)
# 線の描画
id2 = canvas.create_line(5,95,220,95, fill="Green",width = 10)
# 矩形の描画
id3 = canvas.create_rectangle(70,70,170,170, fill="Blue",width = 3)

# 矩形(id3)を矩形(id1)の上へ移動
canvas.lift(id3, id1)

 

 

 

 

 

背面へ移動(lower)

図形の重なり具合を調整します。

tagOrIdで指定した図形をbelowThisの下へ移動します。

.lower(tagOrId, belowThis)
tagOrId 重なりを調整する図形のid もしくは tag
belowThis 移動先の図形のid もしくは tag
指定しない場合、最背面へ移動します。

(例)

# 矩形の描画
id1 = canvas.create_rectangle(20,20,120,120, fill="Red",width = 3)
# 線の描画
id2 = canvas.create_line(5,95,220,95, fill="Green",width = 10)
# 矩形の描画
id3 = canvas.create_rectangle(70,70,170,170, fill="Blue",width = 3)

# 矩形(id3)を最背面移動
canvas.lower(id3)

 

 

 

 

 

参考

https://tkdocs.com/shipman/canvas-methods.html

【Python/tkinter】Canvas(キャンバス)の作成

【Python/tkinter】線や円などの図形の描画

【Python/tkinter】線や円などの図形の描画

前回は、Pillowで線や円などの図形の描画について説明しましたが、今回はtkinterでCanvasウィジェットの上に図形を描画する方法についてです。

前半では、線や矩形、楕円などのメソッドについて説明し、後半にこれらメソッドを用いて下図のような図形を描画するサンプルを示しています。

Pillowとtkinterで大きく異なるのは、Pillowでは画像の上に線などの図形を描画すると、図形データが画像データに反映されましたが、tkinterではCanvasウィジェットの上に図形を配置するイメージとなります。

ちょうどWordやExcelで図形を描画する事に似ています。

Wordの画面

 

Word/Excelでは図形の編集で、図形の移動や選択、削除、配置の順番の最前面へ移動/最背面へ移動などができますが、tkinterでも同様のことが出来ます。

tkinterでの図形の編集方法については図形の編集で説明しています。

ここでは、図形や画像を動画のように繰り返し描画すると、図形のオブジェクトが増え、描画速度が遅くなるため、図形を削除する必要がある事を覚えておいてください。
図形の削除方法は最後の部分に書きました。

簡単なサンプル

import tkinter as tk

root = tk.Tk()
root.geometry("300x200")

# Canvasの作成
canvas = tk.Canvas(root, bg = "white")
# Canvasを配置
canvas.pack(fill = tk.BOTH, expand = True)

# 線の描画
canvas.create_line(20, 10, 280, 190, fill = "Blue", width = 5)

root.mainloop()

図形の種類

図形はCanvasクラスにcreate_で始まる名前のメソッドが用意されています。

メソッド名 図形の種類
create_arc 円弧
create_bitmap ビットマップ
create_image イメージ
create_line 直線、折れ線
create_oval 楕円、円
create_polygon ポリゴン(閉じた多角形)
create_rectangle 矩形
create_text 文字
create_window ウィンドウ(ウィジェットの配置)

● 直線、折れ線

id = Canvas.create_line(x0, y0, x1, y1, ..., xn, yn, option, ...)
x0,y0… 線の始点と終点、もしくは折れ線を構成する交点の座標を指定します。
activedash マウスポインタが線上にあるときの破線のスタイルを設定します。
(例)activedash=(5,3) 
activefill マウスポインタが線上にあるときの線色を設定します。
(例)activefill=”Red”
activestipple マウスポインタが線上にあるときの線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(例)activestipple= ‘gray50’  のとき(参考)https://tkdocs.com/shipman/bitmaps.html
activewidth マウスポインタが線上にあるときの線幅を設定します。
arrow 線の矢印の設定をします。
arrow=tk.FIRST のとき、始点に矢印を追加
arrow=tk.LAST のとき、終点に矢印を追加
arrow=tk.BOTH のとき、両方に矢印を追加
arrowshape 矢印の形状を(d1, d2, d3)のタプルで指定します。
初期値:arrowshape=(8, 10, 3)
capstyle 線の端の形状を設定します。
初期値:capstyle=tk.BUTT
(参考)https://tkdocs.com/shipman/cap-join-styles.html
dash 輪郭線の破線スタイルを(線の長さ, 間隔)のタプルで設定します。
(例)dash=(5,3)
dashoffset 破線の開始位置を設定します。
disableddash 線が無効状態(state=tk.DISABLED)のときの破線のスタイルを設定します。
disabledfill 線が無効状態(state=tk.DISABLED)のときの線色を設定します。
disabledstipple 線が無効状態(state=tk.DISABLED)のときの線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
disabledwidth 線が無効状態(state=tk.DISABLED)のときの線幅を設定します。
fill 線色を指定します。
joinstyle 折れ線のつなぎ目のスタイルを設定します。
(参考)https://tkdocs.com/shipman/cap-join-styles.html
offset 線のパターン(stipple)のパターンの開始位置を指定します。
smooth smooth=True のとき、折れ線はスプライン曲線となります。
splinesteps スプライン曲線のセグメント数を設定します。
state 線の状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
stipple 線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(参考)https://tkdocs.com/shipman/bitmaps.html
tags 線の識別用任意文字列もしくは、文字列のタプルを指定します。
width 線幅を指定します。
戻り値 id, 線の識別用番号

● 矩形(四角形)

id = Canvas.create_rectangle(x0, y0, x1, y1, option, ...)
x0,y0… 左上の座標(x0, y0)と右下の座標(x1, y1)を指定します。
描画される矩形は、(x0, y0)の座標を含み、(x1, y1)より1画素内側に描画されます。
activedash マウスポインタが図形上にあるときの輪郭線の破線のスタイルを設定します。
(例)activedash=(5,3) 
activefill マウスポインタが図形上にあるときの塗りつぶしの色を設定します。
(例)activefill=”Red”
activeoutline
マウスポインタが図形上にあるときの輪郭線の色を設定します。
activeoutlinestipple マウスポインタが図形上にあるときの輪郭線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(例)activestipple= ‘gray50’  のとき(参考)https://tkdocs.com/shipman/bitmaps.html
activestipple マウスポインタが図形上にあるときの塗りつぶしのパターンを設定します。
activewidth マウスポインタが図形上にあるときの線幅を設定します。
dash 輪郭線の破線スタイルを(線の長さ, 間隔)のタプルで設定します。
(例)dash=(5,3)
dashoffset 破線の開始位置を設定します。
disableddash 線が無効状態(state=tk.DISABLED)のときの破線のスタイルを設定します。
disabledfill 線が無効状態(state=tk.DISABLED)のときの線色を設定します。
disabledoutline
線が無効状態(state=tk.DISABLED)のときの輪郭線の色を設定します。
disabledoutlinestipple 線が無効状態(state=tk.DISABLED)のときの 輪郭線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
disabledstipple 線が無効状態(state=tk.DISABLED)のときの塗りつぶしのパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
disabledwidth 線が無効状態(state=tk.DISABLED)のときの線幅を設定します。
fill 塗りつぶしの色を指定します。
offset 塗りつぶしのパターン(stipple)のパターンの開始位置を指定します。
outline
輪郭線の色を指定します。
outlineoffset 輪郭線のパターン(outlinestipple)のパターンの開始位置を指定します。
outlinestipple 輪郭線のパターンを指定します。
state 線の状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
stipple 線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(参考)https://tkdocs.com/shipman/bitmaps.html
tags 矩形の識別用任意文字列もしくは、文字列のタプルを指定します。
width 線幅を指定します。
戻り値 id, 矩形の識別用番号

● 文字

id = Canvas.create_text(x, y, option, ...)
x, y 文字を表示する位置の座標(x, y)を指定します。
座標の位置(文字の左上や中央など)はanchorオプションに依存します。
activestipple マウスポインタが文字上にあるときの塗りつぶしのパターンを設定します。
anchor 表示位置の座標の基準となる位置を指定します。
(初期値)anchor=tk.CENTER
disabledfill 文字が無効状態(state=tk.DISABLED)のときの文字色を設定します。
disabledstipple 線文字無効状態(state=tk.DISABLED)のときの文字のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
fill 文字色を指定します。
font 文字のフォントを指定します。
justify
文字列を複数行のまたぐときの、左寄せ(tk.LEFT)、中央寄せ(tk.CENTER)、右寄せ(tk.RIGHT)を設定します。
offset 線のパターン(stipple)のパターンの開始位置を指定します。
smooth smooth=True のとき、折れ線はスプライン曲線となります。
splinesteps スプライン曲線のセグメント数を設定します。
state 線の状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
stipple 線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(参考)https://tkdocs.com/shipman/bitmaps.html
tags 文字の識別用任意文字列もしくは、文字列のタプルを指定します。
text 描画する文字列を指定します。(日本語可)
width 文字列を描画する領域の幅を指定します。
描画する文字(text)が幅(width)より大きい場合、文字列は押し返し表示されます。
戻り値 id, 線の識別用番号

●楕円、円

id = C.create_oval(x0, y0, x1, y1, option, ...)
x0,y0,x1,y1 左上の座標(x0, y0)と右下の座標(x1, y1)を指定します。
描画される矩形は、(x0, y0)の座標を含み、(x1, y1)より1画素内側に描画されます。
activedash マウスポインタが図形上にあるときの輪郭線の破線のスタイルを設定します。
(例)activedash=(5,3) 
activefill マウスポインタが図形上にあるときの塗りつぶしの色を設定します。
(例)activefill=”Red”
activeoutline
マウスポインタが図形上にあるときの輪郭線の色を設定します。
activeoutlinestipple マウスポインタが図形上にあるときの輪郭線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(例)activestipple= ‘gray50’  のとき(参考)https://tkdocs.com/shipman/bitmaps.html
activestipple マウスポインタが図形上にあるときの塗りつぶしのパターンを設定します。
activewidth マウスポインタが図形上にあるときの線幅を設定します。
dash 輪郭線の破線スタイルを(線の長さ, 間隔)のタプルで設定します。
(例)dash=(5,3)
dashoffset 破線の開始位置を設定します。
disableddash 線が無効状態(state=tk.DISABLED)のときの破線のスタイルを設定します。
disabledfill 線が無効状態(state=tk.DISABLED)のときの線色を設定します。
disabledoutline
線が無効状態(state=tk.DISABLED)のときの輪郭線の色を設定します。
disabledoutlinestipple 線が無効状態(state=tk.DISABLED)のときの 輪郭線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
disabledstipple 線が無効状態(state=tk.DISABLED)のときの塗りつぶしのパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
disabledwidth 線が無効状態(state=tk.DISABLED)のときの線幅を設定します。
fill 領域を塗りつぶす色を指定します。
offset 塗りつぶしのパターン(stipple)のパターンの開始位置を指定します。
outline 輪郭線の色を指定します。
outlineoffset 輪郭線のパターン(outlinestipple)のパターンの開始位置を指定します。
stipple 線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(参考)https://tkdocs.com/shipman/bitmaps.html
outlinestipple 輪郭線のパターンを指定します。
state 線の状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
tags 楕円の識別用任意文字列もしくは、文字列のタプルを指定します。
width 線幅を指定します。
戻り値 id, 楕円の識別用番号

●ビットマップ

ビットマップの描画を行います。
ここでいうビットマップは二値化された画像で、一般的な画像の描画はcreate_imageを用います。
(参考)https://tkdocs.com/shipman/bitmaps.html

id = C.create_bitmap(x, y, *options ...)
x, y ビットマップを表示する位置の座標(x, y)を指定します。
座標の位置(ビットマップの左上や中央など)はanchorオプションに依存します。。
activebackground マウスポインタがビットマップ上にあるときの背景色を設定します。
activebitmap マウスポインタがビットマップ上にあるときのビットマップを設定します。
activeforeground マウスポインタがビットマップ上にあるときのビットマップの色を設定します。
anchor 表示位置の座標の基準となる位置を指定します。
(初期値)anchor=tk.CENTER 
background ビットマップの背景色を指定します。
bitmap 表示するビットマップを指定します。
disabledbackground 無効状態(state=tk.DISABLED)のときの背景色を設定します。
disabledbitmap 無効状態(state=tk.DISABLED)のときのビットマップを設定します。
disabledforeground 線が無効状態(state=tk.DISABLED)のときのビットマップの色を設定します。
foreground ビットマップの色を指定します。
state ビットマップの状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
tags ビットマップの識別用任意文字列もしくは、文字列のタプルを指定します。
戻り値 id, ビットマップの識別用番号

●イメージ

イメージ(Pillow(PIL)のPhotoImage もしくは tkinterのBitmapImage,PhotoImage)の描画を行います。

id = C.create_image(x, y, *options ...)
x, y ビットマップを表示する位置の座標(x, y)を指定します。
座標の位置(ビットマップの左上や中央など)はanchorオプションに依存します。
activeimage マウスポインタが画像上にあるときのイメージを設定します。
anchor 表示位置の座標の基準となる位置を指定します。
(初期値)anchor=tk.CENTER
disabledimage 無効状態(state=tk.DISABLED)のときのイメージを設定します。
image イメージを指定します。
通常はPillow(PIL)のPhotoImageを指定します。
state ビットマップの状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
tags イメージの識別用任意文字列もしくは、文字列のタプルを指定します。
戻り値 id, イメージの識別用番号

● ポリゴン

id = Canvas.create_polygon(x0, y0, x1, y1, ..., xn, yn, option, ...)
x0,y0… 線の始点と終点、もしくは折れ線を構成する交点の座標を指定します。
activedash マウスポインタが線上にあるときの破線のスタイルを設定します。
(例)activedash=(5,3) 
activefill マウスポインタが線上にあるときの線色を設定します。
(例)activefill=”Red”
activeoutline
マウスポインタが線上にあるときの輪郭線の色を設定します。
activelinestipple マウスポインタが線上にあるときの線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
activestipple マウスポインタが図形上にあるときの塗りつぶしのパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(例)activestipple= ‘gray50’  のとき(参考)https://tkdocs.com/shipman/bitmaps.html
activewidth マウスポインタが線上にあるときの線幅を設定します。
dash 輪郭線の破線スタイルを(線の長さ, 間隔)のタプルで設定します。
(例)dash=(5,3)
dashoffset 破線の開始位置を設定します。
disableddash 線が無効状態(state=tk.DISABLED)のときの破線のスタイルを設定します。
disabledfill 線が無効状態(state=tk.DISABLED)のときの線色を設定します。
disabledstipple 線が無効状態(state=tk.DISABLED)のときの線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
disabledwidth 線が無効状態(state=tk.DISABLED)のときの線幅を設定します。
fill 塗りつぶす色を指定します。
joinstyle 折れ線のつなぎ目のスタイルを設定します。
tk.ROUND, tk.BEVEL, tk.MITER
(参考)https://tkdocs.com/shipman/cap-join-styles.html
offset 線のパターン(stipple)のパターンの開始位置を指定します。
outline 輪郭線の色を指定します。
outlineoffset 輪郭線のパターン(outlinestipple)のパターンの開始位置を指定します。
outlinestipple 輪郭線のパターンを指定します。
smooth smooth=True のとき、折れ線はスプライン曲線となります。
splinesteps スプライン曲線のセグメント数を設定します。
state 線の状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
stipple 線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(参考)https://tkdocs.com/shipman/bitmaps.html
tags ポリゴンの識別用任意文字列もしくは、文字列のタプルを指定します。
width 線幅を指定します。
戻り値 id, ポリゴンの識別用番号

● 円弧

id = Canvas.create_arc(x0, y0, x1, y1, option, ...)
x0,y0… 線の始点と終点、もしくは折れ線を構成する交点の座標を指定します。
activedash マウスポインタが線上にあるときの破線のスタイルを設定します。
(例)activedash=(5,3) 
activefill マウスポインタが線上にあるときの線色を設定します。
(例)activefill=”Red”
activeoutline
マウスポインタが線上にあるときの輪郭線の色を設定します。
activelinestipple マウスポインタが線上にあるときの線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
activestipple マウスポインタが図形上にあるときの塗りつぶしのパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(例)activestipple= ‘gray50’  のとき(参考)https://tkdocs.com/shipman/bitmaps.html
activewidth マウスポインタが線上にあるときの線幅を設定します。
dash 輪郭線の破線スタイルを(線の長さ, 間隔)のタプルで設定します。
(例)dash=(5,3)
dashoffset 破線の開始位置を設定します。
disableddash 線が無効状態(state=tk.DISABLED)のときの破線のスタイルを設定します。
disabledfill 線が無効状態(state=tk.DISABLED)のときの線色を設定します。
disabledstipple 線が無効状態(state=tk.DISABLED)のときの線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
disabledwidth 線が無効状態(state=tk.DISABLED)のときの線幅を設定します。
extent 開始角度(start)から終了位置までの角度(反時計周りが正)
fill 塗りつぶす色を指定します。
offset 線のパターン(stipple)のパターンの開始位置を指定します。
outline 輪郭線の色を指定します。
outlineoffset 輪郭線のパターン(outlinestipple)のパターンの開始位置を指定します。
outlinestipple 輪郭線のパターンを指定します。
start
円弧の開始位置の角度(度)を指定します。3時方向が0度
state 線の状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
stipple 線のパターンを設定します。
‘gray75’, ‘gray50’, ‘gray25’, ‘gray12’, ‘hourglass’, ‘info’, ‘questhead’, ‘question’, ‘warning’ のいづれか
(参考)https://tkdocs.com/shipman/bitmaps.html
style
円弧のスタイルを指定します。
tk.PIESLICE (初期値) tk.CHORD tk.ARC
tags 円弧の識別用任意文字列もしくは、文字列のタプルを指定します。
width 線幅を指定します。
戻り値 id, 円弧の識別用番号

● ウィンドウ

Canvas上にウィジェットを配置します。

id = Canvas.create_window(x, y, option, ...)
x, y 文字を表示する位置の座標(x, y)を指定します。
座標の位置(文字の左上や中央など)はanchorオプションに依存します。
anchor 表示位置の座標の基準となる位置を指定します。
(初期値)anchor=tk.CENTER
height ウィジェットを配置する幅を設定します。
state ウィンドウの状態を設定します。
(初期値)state=tk.NORMAL
state=tk.HIDDEN のとき、線の非表示
state=tk.DISABLEDのとき、マウスオーバーの処理の無効
tags ウィンドウの識別用任意文字列もしくは、文字列のタプルを指定します。
width
ウィジェットを配置する高さを設定します。
window 配置するウィジェットオブジェクトを設定します。
戻り値 id, ウィンドウの識別用番号

備考

色の指定は”Red”, ”Green”などの色の名前の文字列を使うか、R,G,Bの順で16進数で指定(#FF0000だと赤)するか、(R,G,B)のタプルで指定します。

(参考)

HTML Color Names
W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Py…

楕円や円弧の座標指定位置は、下図のようになります。

サンプル

各種図形のオプション設定を行ったサンプルです。

import tkinter as tk
import os
from PIL import ImageTk

root = tk.Tk()
root.geometry("600x750")

# Canvasの作成
canvas = tk.Canvas(root, bg = "white")
# Canvasを配置
canvas.pack(fill = tk.BOTH, expand = True)

#######################################
# 線
canvas.create_line(10,   20, 290,  20)                  # オプションなし
canvas.create_line(310,  20, 590,  20, width = 5)       # 線幅5

canvas.create_line(10,   40, 290,  40, arrow=tk.FIRST)  # 始点の矢印
canvas.create_line(310,  40, 590,  40, arrow=tk.LAST)   # 終点の矢印
canvas.create_line(10,   60, 290,  60, arrow=tk.BOTH)   # 両方の矢印
canvas.create_line(310,  60, 590,  60, arrow=tk.BOTH, arrowshape=(24, 25, 8))   # 矢印のサイズ指定

canvas.create_line(10,   80, 290,  80, dash=(5, 3))         # 破線
canvas.create_line(310,  80, 590,  80, dash=(20, 3, 5, 3))  # 破線(1点鎖線)

canvas.create_line(10,  100, 290, 100, width = 15, capstyle=tk.BUTT)        # 終端形状
canvas.create_line(310, 100, 590, 100, width = 15, capstyle=tk.PROJECTING)  
canvas.create_line(10,  120, 290, 120, width = 15, capstyle=tk.ROUND)

canvas.create_line(10,  140, 290, 140, width = 15, stipple='gray12') # 線のパターン
canvas.create_line(310, 140, 590, 140, width = 15, stipple='gray50')

canvas.create_line(10, 160, 150, 180, 300, 160, 450, 180, 590, 160 ) # 折れ線


#######################################
# 矩形
canvas.create_rectangle(10, 200, 290, 220)              # オプションなし
canvas.create_rectangle(310,200, 590, 220, width = 5)   # 線幅5
canvas.create_rectangle(10, 240, 290, 260, fill="Blue") # 塗りつぶしの色指定
canvas.create_rectangle(310,240, 590, 260, fill="Green", outline="Red",width = 2) # 塗りつぶし、輪郭線の色指定

#######################################
# 文字
canvas.create_text(10, 300, text="あいうえお", anchor=tk.NW, font=("",20))   # 基準位置左上
canvas.create_oval(5, 295, 15, 305, fill="Red")

canvas.create_text(300, 300, text="あいうえお", font=("",20))                # 基準位置指定なし(CENTER)
canvas.create_oval(295, 295, 305, 305, fill="Red")

canvas.create_text(590, 300, text="あいうえお", anchor=tk.SE, font=("",20))  # 基準位置右下
canvas.create_oval(585, 295, 595, 305, fill="Red")

canvas.create_text(10, 340, text="あいうえお\nかきくけこ\nさしすせそ", anchor=tk.NW,font=("",20))          # 改行文字
canvas.create_text(310, 340, text="あいうえおかきくけこさしすせそ", anchor=tk.NW, width=120, font=("",20)) # 折り返し幅指定

#######################################
# 楕円
canvas.create_oval(10,450,290, 500)

#######################################
# ビットマップ
canvas.create_bitmap(310,450,bitmap='hourglass')

#######################################
# イメージ
if os.path.exists("Mandrill.png"):
    image = ImageTk.PhotoImage(file = "Mandrill.png")
    canvas.create_image(10,520,image=image, anchor=tk.NW)

#######################################
# ポリゴン
canvas.create_polygon(10,600,150,650,300,600,450,650,550,650)

#######################################
# 円弧
canvas.create_arc(10,670,60, 720)
canvas.create_arc(80,670,130, 720, extent=120)
canvas.create_arc(150,670,200, 720, extent=120, start=30)
canvas.create_arc(220,670,270, 720, extent=120, style=tk.CHORD)
canvas.create_arc(290,670,320, 720, extent=120, style=tk.ARC)

#######################################
# ウィンドウ(Canvas内にウィジェットの配置)
label = tk.Label(root, text = "ラベル", fg = "Red", font = ("", 14, "bold"))
canvas.create_window(300, 740, window=label, width = 300)

root.mainloop()

実行結果

注意点

tkinterのcreate_で始まるメソッドで線や画像などをCanvas上に描画すると、図形が重なっていて見えていなくても、各図形はオブジェクトとして認識しています。
(関数のスコープが外れても、図形のオブジェクトが自動で解放される事はありません。)

そのため、動画のように図形を何回も描画し続けると、オブジェクトの数(描画した図形の数)に比例して描画速度が低下します。

例えば、以下のようなプログラムを実行してみます。

import tkinter as tk
import time

root = tk.Tk()
root.geometry("500x200")

# Canvasの作成
canvas = tk.Canvas(root, bg = "white")
# Canvasを配置
canvas.pack(fill = tk.BOTH, expand = True)

colors = ("Red", "Green", "Blue")

for k in range(100):
    start = time.time()
    for i in range(100):
        canvas.create_line(0, i * 2, 500, i * 2, fill = colors[k%3])
    canvas.update()
    # 線100本分の描画時間    
    print((k+1)*100, time.time() - start)

root.mainloop()

実行結果

処理時間

このようにならないように、どこかのタイミングで描画した図形を削除する必要があるのですが、Canvasクラスにdelete()メソッドがあるので、これを用います。

delete()メソッドは、引き数にcreate_XXXXXメソッドで指定したtagの文字列もしくは戻り値のid番号を指定すると、その図形を削除することができます。

また、delete(“all”)と指定することで、すべての図形を削除することも可能です。

そこで、先ほどのプログラムを少し修正して、

import tkinter as tk
import time

root = tk.Tk()
root.geometry("500x200")

# Canvasの作成
canvas = tk.Canvas(root, bg = "white")
# Canvasを配置
canvas.pack(fill = tk.BOTH, expand = True)

colors = ("Red", "Green", "Blue")

for k in range(100):
    start = time.time()

    # 図形の削除
    canvas.delete("lines")
    # canvas.delete("all") # ←今回はこれでも同じ

    for i in range(100):
        canvas.create_line(0, i * 2, 500, i * 2, fill = colors[k%3], tag="lines")
    canvas.update()
    # 線100本分の描画時間  
    print((k+1)*100, time.time() - start)

root.mainloop()

処理時間

参考記事

https://tkdocs.com/shipman/canvas.html

【Python/tkinter】Canvas(キャンバス)の作成

【Python/tkinter】図形の編集(削除、移動、変形など)

【Python/tkinter】Canvasに画像を表示する

【Python/Pillow】線や円などの図形の描画

【Python】tkinterのGUIにmatplotlibのグラフを表示する

matplotlibを使ってグラフを表示すると、通常は、matplotlib独自のウィンドウで表示されますが、これをtkinterのGUIに組み込んで表示する方法を紹介します。

基本的な処理の流れとしては、matplotlibのFigureクラスでグラフの描画領域を確保し、グラフ描画用の座標軸を作成します。

FigureCanvasTkAggクラスで作成したFigureとFigureの配置先のウィジェットを指定し、matplotlib用のキャンバスを作成します。

グラフを描画する時は、作成した軸に対してグラフを描画し、最後にFigureCanvasTkAggクラスで作成したオブジェクトのdraw()メソッドを呼び出して、グラフを表示します。

 

以下にできるだけシンプルにしたサンプルを示します。

import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title('matplotlib graph')

        #-----------------------------------------------

        # matplotlib配置用フレーム
        frame = tk.Frame(self.master)
        
        # matplotlibの描画領域の作成
        fig = Figure()
        # 座標軸の作成
        self.ax = fig.add_subplot(1, 1, 1)
        # matplotlibの描画領域とウィジェット(Frame)の関連付け
        self.fig_canvas = FigureCanvasTkAgg(fig, frame)
        # matplotlibのツールバーを作成
        self.toolbar = NavigationToolbar2Tk(self.fig_canvas, frame)
        # matplotlibのグラフをフレームに配置
        self.fig_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # フレームをウィンドウに配置
        frame.pack()

        # ボタンの作成
        button = tk.Button(self.master, text = "Draw Graph", command = self.button_click)
        # 配置
        button.pack(side = tk.BOTTOM)

        #-----------------------------------------------

    def button_click(self):
        # 表示するデータの作成
        x = np.arange(-np.pi, np.pi, 0.1)
        y = np.sin(x)
        # グラフの描画
        self.ax.plot(x, y)
        # 表示
        self.fig_canvas.draw()

root = tk.Tk()
app = Application(master=root)
app.mainloop()

(実行結果)

 

ポイント

Figureは、今回のやり方とは別に、通常のmatplotlibのウィンドウで表示する場合は、

import matplotlib.pyplot as plt

fig = plt.figure()

のようにする場合もありますが、このようにすると、tkinterで作成したウィンドウの×ボタンでプログラムを終了させても、プロセスが終了しない状態になってしまうので、注意が必要です。

参考

https://matplotlib.org/stable/gallery/user_interfaces/embedding_in_tk_sgskip.html

【Python/tkinter】CanvasにStretchDIBitsで画像を表示する

tkinterでOpenCVなどの画像データ(numpyのndarray)をCanvasに表示する場合、画像がカラーだと、BGRからRGBに変換し、numpy→Pillow→PhotoImageと変換して、ようやくCanvasに画像を表示します。

さらに内部的にはおそらくRGBからBGRに変換している?と思います。

そのためnumpyの画像データをCanvasに表示するのは、かなり遅い。

VB6.0をメインで使っていた時代は遅い処理はWin32APIを使って高速に処理をするのが定番でしたが、Pythonで出来ないのか?調べてみました。

tkinterのCanvasにWin32APIのStretchDIBitsで画像を表示するとき、よく分からなかったポイントは2つ。

●Canvasのウィンドウハンドルをどのように取得するのか?

●numpyの配列(ndarray)のポインタの取得方法は?

これさえクリアできれば、なんとかなります。

Canvasのウィンドウハンドルの取得方法

Canvasに限らす、ウィジェットのウィンドウハンドルを取得するにはwinfo_id()関数を使います。

(例)

# Canvasの作成
canvas = tk.Canvas()
# キャンバスのウィンドウハンドルを取得
hWnd = canvas.winfo_id()

numpy配列のポインタの取得方法

numpy配列(ndarray)のデータのポイントを取得するには、ctypes.data_as()関数でポインタを取得します。

(例)

# 画像データの読み込み
img = cv2.imread("image.bmp", cv2.IMREAD_UNCHANGED)
# 画像データ(numpayのndarrayの配列)からポインタの取得
ptr_img = img.ctypes.data_as(ctypes.POINTER(ctypes.c_byte))

StretchDIBitsでCanvasに画像を表示するサンプル

できるだけシンプルなサンプルを作成しました。

imreadの部分のコメントを切り替えると、カラー/グレースケールの表示が可能になります。

import ctypes
import tkinter as tk

import cv2

# ----------------------------------------------------------------------
# 	構造体などの定義
# ----------------------------------------------------------------------
SRCCOPY		    = 0x00CC0020
DIB_RGB_COLORS  = 0

# StretchBlt() Modes
BLACKONWHITE = 1
WHITEONBLACK = 2
COLORONCOLOR = 3
HALFTONE     = 4

class RECT(ctypes.Structure):
    _fields_ = [
        ('left', ctypes.wintypes.LONG),
        ('top', ctypes.wintypes.LONG),
        ('right', ctypes.wintypes.LONG),
        ('bottom', ctypes.wintypes.LONG)
        ]

class BITMAPINFOHEADER(ctypes.Structure):
    _fields_ = [
        ('biSize', ctypes.wintypes.DWORD),
        ('biWidth', ctypes.wintypes.LONG),
        ('biHeight', ctypes.wintypes.LONG),
        ('biPlanes', ctypes.wintypes.WORD),
        ('biBitCount', ctypes.wintypes.WORD),
        ('biCompression', ctypes.wintypes.DWORD),
        ('biSizeImage', ctypes.wintypes.DWORD),
        ('biXPelsPerMeter', ctypes.wintypes.LONG),
        ('biYPelsPerMeter', ctypes.wintypes.LONG),
        ('biClrUsed', ctypes.wintypes.DWORD),
        ('biClrImportant', ctypes.wintypes.DWORD)
        ]

class RGBQUAD(ctypes.Structure):
    _fields_ = [
        ('rgbBlue', ctypes.wintypes.BYTE),
        ('rgbGreen', ctypes.wintypes.BYTE),
        ('rgbRed', ctypes.wintypes.BYTE),
        ('rgbReserved', ctypes.wintypes.BYTE)
    ]

class BITMAPINFO(ctypes.Structure):
    _fields_ = [
        ('bmiHeader', BITMAPINFOHEADER),
        ('bmiColors', RGBQUAD * 256)
        ]

# ----------------------------------------------------------------------
root = tk.Tk()
root.title("StretchDIBitsで画像の表示")       # ウィンドウタイトル
root.geometry("500x300")     # ウィンドウサイズ(幅x高さ)

# ----------------------------------------------------------------------
# Canvasの作成
canvas = tk.Canvas()
# Canvasを配置
canvas.pack(expand = True, fill = tk.BOTH)

# 画像の読込
#img = cv2.imread("Parrots.bmp", cv2.IMREAD_GRAYSCALE)  # グレースケールとして読み込む
img = cv2.imread("Parrots.bmp", cv2.IMREAD_COLOR)       # カラーとして読み込む
if len(img.shape) >= 3:
    # カラーの場合
    src_height, src_width, src_ch = img.shape
else:
    # グレースケールの場合
    src_height, src_width = img.shape
    src_ch = 1

# 画像データ(numpayのndarrayの配列)のポインタ
ptr_img = img.ctypes.data_as(ctypes.POINTER(ctypes.c_byte))

# ----------------------------------------------------------------------
# ヘッダの作成
bi = BITMAPINFO()
bi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bi.bmiHeader.biWidth = src_width
bi.bmiHeader.biHeight = -src_height
bi.bmiHeader.biPlanes = 1
bi.bmiHeader.biBitCount = 8 * src_ch

# カラーパレット
for i in range(256):
    bi.bmiColors[i].rgbBlue = i
    bi.bmiColors[i].rgbGreen = i
    bi.bmiColors[i].rgbRed = i
    bi.bmiColors[i].rgbReserved = 255

# ----------------------------------------------------------------------
# キャンバスのウィンドウハンドルを取得
hWnd = canvas.winfo_id()

# キャンバスの領域取得
canvas.update() # 領域を取得するために一旦更新しておく
win_rect = RECT()
ctypes.windll.user32.GetClientRect(hWnd, ctypes.byref(win_rect))

# デバイスコンテキストハンドルの取得
hDC = ctypes.windll.user32.GetDC(hWnd)

# ストレッチモードの設定(BLACKONWHITE, WHITEONBLACK, COLORONCOLOR, HALFTONE)
ctypes.windll.gdi32.SetStretchBltMode(hDC , COLORONCOLOR)

# 描画
ctypes.windll.gdi32.StretchDIBits(
    hDC, 
    win_rect.left, win_rect.top, win_rect.right, win_rect.bottom, # 描画先の領域
    0, 0, src_width, src_height,  # 描画元(画像)の領域
    ptr_img, ctypes.byref(bi), DIB_RGB_COLORS, SRCCOPY)

# 解放
ctypes.windll.user32.ReleaseDC(hWnd, hDC)

# ----------------------------------------------------------------------
root.mainloop()

(実行結果)
カラー画像の場合

グレースケールの場合

まとめ

カメラの画像データはOpenCVに限らず、numpyのndarrayで取得される事が多く、カラー画像の場合はデータの並びがBGRなので、StretchDIBitsを使うと、BGR→RGBの変換など無く、ダイレクトにCanvasに画像を表示することができます。

これで、高速に画像を表示できるようになるはず?!

ただ、最近はベタにWin32APIを使って画像を表示する事も少なくなってきたので、情報が少ないのが難点でしょうか?

【Python/tkinter】ツールバーの作成

tkinterにはToolBarのような、ツールバー用のウィジェットは無いのですが、ツールバーをFrameButtonを使って作ります。

フレームにアイコン付きのボタンを左側に配置するだけですが。。

(作成したツールバーのイメージ)

  • ファイルを開く
  • フォルダを開く
  • 保存

の3つのボタンを配置しています。

 

(ソースコード)

import tkinter as tk
from tkinter import filedialog
from PIL import ImageTk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ToolBar")    # ウィンドウタイトル
        self.master.geometry("300x200") # ウィンドウサイズ(幅x高さ)

        #---------------------------------------
        #  ツールバー
        #---------------------------------------     
        
        # ツールバー用Frame
        frame_toolbar = tk.Frame(self.master, bg = "gray90")
        
        #-------------------------
        # ボタン
        # ファイルを開く
        self.ico_open_file = ImageTk.PhotoImage(file = "OpenFile_16x.png")
        btn_open_file = tk.Button(frame_toolbar, command = self.open_file_click, image = self.ico_open_file)
        # フォルダを開く
        self.ico_open_folder = ImageTk.PhotoImage(file = "OpenFolder_16x.png")
        btn_open_folder = tk.Button(frame_toolbar, command = self.open_folder_click, image = self.ico_open_folder)
        # 保存
        self.ico_save = ImageTk.PhotoImage(file = "Save_16x.png")
        btn_save = tk.Button(frame_toolbar, command = self.save_click, image = self.ico_save)

        #-------------------------
        # ボタンをフレームに配置
        btn_open_file.pack(side = tk.LEFT, padx = (5, 0)) # 左側だけ隙間を空ける
        btn_open_folder.pack(side = tk.LEFT)
        btn_save.pack(side = tk.LEFT)

        #-------------------------
        # ツールバーをウィンドウに配置
        frame_toolbar.pack(fill=tk.X)
        #---------------------------------------     

    def open_file_click(self):
        '''[ファイルを開く]がクリックされたとき'''
        filename = filedialog.askopenfilename()
        print(filename)

    def open_folder_click(self):
        '''[フォルダを開く]がクリックされたとき'''
        dir_name = filedialog.askdirectory()
        print(dir_name)

    def save_click(self):
        '''[保存]がクリックされたとき'''
        filename = filedialog.asksaveasfilename()
        print(filename)

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

アイコンファイルはマイクロソフトのVisual Studio Image Libraryより入手しました。

https://www.microsoft.com/en-us/download/details.aspx?id=35825

【Python/tkinter】LabelFrame(ラベルフレーム、グループボックス)

複数のウィジェットをラベル付きの枠線で囲うウィジェットをLabelFrame(ラベルフレーム)と言います。

tkinter以外ではグループボックスなどと言われます。

このラベルフレームは何かの設定値など、共通の目的を持ったウィジェットを一つにまとめるときに用いられます。

基本的な使い方はFrameに似ていますが、Frameに枠線とラベルが追加されています。

(ラベルフレームの実行例)

(サンプルプログラム)

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ラベルフレームの作成")     # ウィンドウタイトル
        self.master.geometry("300x200")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルフレーム1の作成
        self.labelframe1 = tk.LabelFrame(self.master, text = "フレーム1")
        
        # ラジオボタンに設定する値
        self.radio_value1 = tk.IntVar(value = 0)

        # ラジオボタンの作成
        self.radio1 = tk.Radiobutton(self.labelframe1, text = "項目1", variable = self.radio_value1, value = 0)
        self.radio2 = tk.Radiobutton(self.labelframe1, text = "項目2", variable = self.radio_value1, value = 1)
        self.radio3 = tk.Radiobutton(self.labelframe1, text = "項目3", variable = self.radio_value1, value = 2)
        self.radio1.pack()
        self.radio2.pack()
        self.radio3.pack()

        # ラベルの配置
        self.labelframe1.pack()

        #--------------------------------------------------------
        # ラベルフレーム2の作成
        self.labelframe2 = tk.LabelFrame(self.master, text = "フレーム2", labelanchor = "n", width = 200, height = 100)
        self.labelframe2.propagate(False) # 幅と高さを指定する場合はこの設定が必要

        # ラジオボタンに設定する値
        self.radio_value2 = tk.IntVar(value = 1)

        # ラジオボタンの作成
        self.radio4 = tk.Radiobutton(self.labelframe2, text = "項目1", variable = self.radio_value2, value = 0)
        self.radio5 = tk.Radiobutton(self.labelframe2, text = "項目2", variable = self.radio_value2, value = 1)
        self.radio6 = tk.Radiobutton(self.labelframe2, text = "項目3", variable = self.radio_value2, value = 2)
        self.radio4.pack(anchor = tk.W)
        self.radio5.pack(anchor = tk.W)
        self.radio6.pack(anchor = tk.W)

        # ラベルの配置
        self.labelframe2.pack()
        #--------------------------------------------------------


if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

構文

オブジェクト = tk.LabelFrame(親ウィジェット, オプション1 = 設定値, オプション2 = 設定値,・・・)

オプション

オプション名 説明
bd 枠線の太さを指定します。(borderwidthと同じ)
borderwidth bdと同じ
bg 背景色を指定します。(backgroundと同じ)
background bgと同じ
cursor ウィジェット上にマウスポインタがある際のカーソルの種類を指定します。
(参考)https://tkdocs.com/shipman/cursors.html
font ラベルのフォントを指定します。
height フレームの高さを画素数で指定します。
highlightbackground
highlightcolor
highlightthickness
labelanchor ラベルの表示位置を指定します。
【設定値】e, en, es, n, ne, nw, s, se, sw, w, wn, ws
labelwidget ラベルの代わりにButtonなどのウィジェットを指定します。
padx フレームの内側に配置するウィジェットまでの横方向の隙間を指定します。
【初期値】0
pady フレームの内側に配置するウィジェットまでの縦方向の隙間を指定します。
【初期値】0
relief フレームのスタイルを指定します。
【設定値】tk.RAISED, tk.GROOVE, tk.SUNKEN, tk.RIDGE, tk.FLAT
takefocus
text ラベルの文字列を指定します。
width フレームの幅を画素数で指定します。

補足説明

ラベルフレームのサイズ(幅と高さ)指定

widthとheightの値を指定しても、デフォルト状態ではLabelFrameのサイズは変わらず、LabelFrameに配置したウィジェットの大きさに合わせて自動調整されます。

この自動調整を無効にするには ラベルフレームオブジェクト.propagate(False) を実行します。

labelanchorについて

labelanchorでラベルの位置を指定します。

実際にラベルが表示される位置は下図を参照してください。

labelwidgetについて

labelwidgetはラベルの代わりにウィジェットを配置することができます。

下図はラベルの代わりにButtonを配置した例になります。

labelwidget = ウィジェットのオブジェクト

のように指定します。

【Python/tkinter】ウィジェットの配置(place)

ウィジェットを配置するには、pack,grid,placeの3つのメソッドがありますが、ここではplaceについて説明します。

placeではウィジェットの位置を座標を指定して配置します。

初期状態では下記のように親(配置先)の左上を原点として、ウィジェットの左上の座標(初期状態の場合、anchorにより変更可)を指定します。

以下に簡単なサンプルを示します。

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ウィジェットの配置(place)")     # ウィンドウタイトル
        self.master.geometry("300x200")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ボタンの作成
        button = tk.Button(self.master, text = "ボタン")
        # 座標を指定して配置
        button.place(x = 100, y = 50)
        #--------------------------------------------------------

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

(実行画面)

構文

ウィジェット.place(オプション1 = 設定値, オプション2 = 設定値,・・・)

オプション

オプション名 説明
x 配置するX座標を指定します。
y 配置するY座標を指定します。
relx 配置先の座標を配置先の幅に対して相対的な位置を0.0~1.0の値で指定します。
0.0:左端、1.0:右端
rely 配置先の座標を配置先の高さに対して相対的な位置を0.0~1.0の値で指定します。
0.0:上端、1.0:下端
anchor ウィジェットを配置する座標の基準位置を指定します。
【設定値】tk.N, tk.NE, tk.E, tk.SE, tk.S, tk.SW, tk.W, tk.NW, tk.CENTER
【初期値】tk.NW
width ウィジェットの幅を画素数で指定します。
height ウィジェットの高さを画素数で指定します。
relwidth ウィジェットの幅を配置先の幅に対して相対的な値(0.0~1.0)で指定します。
relheight ウィジェットの高さを配置先の高さに対して相対的な値(0.0~1.0)で指定します。

anchor

anchorの基準位置はウィジェットに対して以下のようになります。

サンプル

各種オプション設定を使ったサンプルを示します。

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ウィジェットの配置(place)")     # ウィンドウタイトル
        self.master.geometry("300x400")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ボタンの作成
        button1 = tk.Button(self.master, text = "ボタン1")
        button1.place(x = 30, y = 20)

        button2 = tk.Button(self.master, text = "ボタン2")
        button2.place(x = 80, y = 70, anchor = tk.CENTER) # 指定座標の基準位置変更

        button3 = tk.Button(self.master, text = "ボタン3")
        button3.place(x = 30, y = 90, width = 100, height = 40) # ウィジェットのサイズ指定

        button4 = tk.Button(self.master, text = "ボタン4")
        button4.place(relx = 0.5, rely = 0.4, anchor = tk.CENTER) # 相対座標指定

        button5 = tk.Button(self.master, text = "ボタン5")
        button5.place(relx = 0.5, relwidth = 0.8, y = 200, anchor = tk.CENTER) # ウィジェットのサイズを相対的に指定
        #--------------------------------------------------------
if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

(実行画面)

【Python/tkinter】ウィジェットの配置(grid)

ウィジェットを配置するには、pack,grid,placeの3つのメソッドがありますが、ここではgridについて説明します。

gridでウィジェットを配置するのは、どことなくエクセルのセルにウィジェットを配置するようなイメージに似ています。

こんなイメージ↓

ただし、gridでは行番号(row)、列番号(column)は0(ゼロ)から始まります。

また、エクセルにはセルを結合して中央揃えという機能がありますが、gridにも同様の機能があり、rowspancolumnspanを使ってセルを結合します。

最初のエクセルのイメージで配置したプログラムがこちら↓(ウィジェットの範囲が分かり易いように背景色をつけました)

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ウィジェットの配置(grid)")     # ウィンドウタイトル
        self.master.geometry("300x180")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        label1 = tk.Label(self.master, text = "ラベル1", bg = 'cyan1')
        label2 = tk.Label(self.master, text = "ラベル2", bg = 'green1')
        label3 = tk.Label(self.master, text = "ラベル3", bg = 'yellow1')
        label4 = tk.Label(self.master, text = "ラベル4", bg = 'pink1')
        label5 = tk.Label(self.master, text = "ラベル5", bg = 'MediumPurple1')
        label6 = tk.Label(self.master, text = "***ラベル6***", bg = 'LightSteelBlue1')

        #--------------------------------------------------------
        # gridでウィジェットの配置
        label1.grid(row = 0, column = 1, columnspan = 3, sticky = tk.W+tk.E)
        label2.grid(row = 0, column = 0, rowspan = 5, sticky = tk.N+tk.S)
        label3.grid(row = 1, column = 1)
        label4.grid(row = 1, column = 3)
        label5.grid(row = 2, column = 2)
        label6.grid(row = 3, column = 1, columnspan = 3)
        #--------------------------------------------------------

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

構文

ウィジェット.grid(オプション1 = 設定値, オプション2 = 設定値,・・・)

オプション

オプション名 説明
column ウィジェットを配置する列番号(0始まり)を指定します。
columnspan グリッドを横方向に結合する数を指定します。
ipadx ウィジェットの内側の横方向の隙間を設定します。
ipady ウィジェットの内側の縦方向の隙間を設定します。
padx ウィジェットの外側の横方向の隙間を設定します。
pady ウィジェットの外側の縦方向の隙間を設定します。
row ウィジェットを配置する行番号(0始まり)を指定します。
rowspan グリッドを縦方向に結合する数を指定します。
sticky グリッド内のウィジェットを配置する位置
アンカーの機能にも似ていますが、例えば上下(tk.N+ tk.S)を指定すると、ウィジェットが上下方向にグリッド内いっぱいに広がります。【設定値】tk.N, tk.S, tk.W, tk.E, tk.NW, tk.NE, tk.SW, tk.SE, tk.NSEW
および上記組み合わせ(tk.N+ tk.Sなど)

ウィンドウのリサイズに合わせて行、列の幅、高さを調整する

gridでウィジェットを配置すると、行や列方向の最大の高さ、幅に合わせてグリッド状に配置されますが、ウィンドウをリサイズしても幅や高さが変わる事がありません。

↓ウィンドウのリサイズ

そこで、ウィンドウに合わせて行の高さを調整するにはgrid_rowconfigure()メソッドを、列の幅を調整するにはgrid_columnconfigure()メソッドを用います。

 

構文

親ウィジェット.grid_columnconfigure(列番号, オプション)
親ウィジェット.grid_rowconfigure(行番号, オプション)

オプション

オプション名 説明
weight 1以上の整数を指定すると、幅/高さが調整されます。
複数の行もしくは列に対してweightを指定すると、指定したweightの値の比率で幅/高さが調整されます。
minsize 最小の幅/高さを画素単位で指定します。
pad 列/高さの隙間を画素単位で指定します。

 

サンプル

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ウィジェットの配置(grid)")     # ウィンドウタイトル
        self.master.geometry("300x180")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        lbl_00 = tk.Label(self.master, text = "row,col")

        lbl_col1 = tk.Label(self.master, text = "col1")
        lbl_col2 = tk.Label(self.master, text = "col2")
        lbl_col3 = tk.Label(self.master, text = "col3")

        lbl_row1 = tk.Label(self.master, text = "row1")
        lbl_row2 = tk.Label(self.master, text = "row2")
        lbl_row3 = tk.Label(self.master, text = "row3")

        #--------------------------------------------------------
        # Entry(テキストボックス)の作成
        entry1 = tk.Entry(self.master, width = 20)
        entry2 = tk.Entry(self.master, width = 20)
        entry3 = tk.Entry(self.master, width = 20)

        #--------------------------------------------------------
        # ボタンの作成
        button1 = tk.Button(self.master, text = "...")
        button2 = tk.Button(self.master, text = "...")
        button3 = tk.Button(self.master, text = "...")

        #--------------------------------------------------------
        # gridでウィジェットの配置
        lbl_00.grid(row = 0, column = 0)

        lbl_col1.grid(row = 0, column = 1)
        lbl_col2.grid(row = 0, column = 2)
        lbl_col2.grid(row = 0, column = 3)

        lbl_row1.grid(row = 1, column = 0)
        lbl_row2.grid(row = 2, column = 0)
        lbl_row3.grid(row = 3, column = 0)

        entry1.grid(row = 1, column = 1, sticky=tk.EW) # 幅に合わせて大きくする
        entry2.grid(row = 2, column = 1, sticky=tk.EW) # 幅に合わせて大きくする
        entry3.grid(row = 3, column = 1, sticky=tk.EW) # 幅に合わせて大きくする

        button1.grid(row = 1, column = 3)
        button2.grid(row = 2, column = 3)
        button3.grid(row = 3, column = 3)

        #--------------------------------------------------------
        # ウィンドウのリサイズに合わせてEntryの幅(column=1)を広げる
        self.master.grid_columnconfigure(1, weight=1) # 列の調整
        #self.master.grid_rowconfigure(1, weight=1) # 行の調整

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

実行結果

↓ウィンドウのリサイズ

gridを使うサンプル

個人的にはウィンドウ内にFrameを配置し、その中にpackでウィジェットを配置する場合が多いのですが、gridはラベルテキストボックス(Entry)を並べて配置する場合に使っています。

以下は、ラベルとテキストボックスを並べたサンプルです。

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ウィジェットの配置(grid)")     # ウィンドウタイトル
        self.master.geometry("300x180")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        label0 = tk.Label(self.master, text = "設定値")

        label1 = tk.Label(self.master, text = "項目1")
        label1_1 = tk.Label(self.master, text = "項目1_1")
        label1_2 = tk.Label(self.master, text = "項目1_2")
        label1_3 = tk.Label(self.master, text = "項目1_3")

        label2 = tk.Label(self.master, text = "項目2")
        label2_1 = tk.Label(self.master, text = "項目2_1")
        label2_2 = tk.Label(self.master, text = "項目2_2")
        label2_3 = tk.Label(self.master, text = "項目2_3")

        # テキストボックス(Entry)の作成
        self.entry1_1 = tk.Entry(self.master, justify = tk.RIGHT)
        self.entry1_2 = tk.Entry(self.master, justify = tk.RIGHT)
        self.entry1_3 = tk.Entry(self.master, justify = tk.RIGHT)

        self.entry2_1 = tk.Entry(self.master, justify = tk.RIGHT)
        self.entry2_2 = tk.Entry(self.master, justify = tk.RIGHT)
        self.entry2_3 = tk.Entry(self.master, justify = tk.RIGHT)

        #--------------------------------------------------------
        # gridでウィジェットの配置
        label0.grid(row = 0, column = 0, rowspan = 8)

        label1.grid(row = 0, column = 1, columnspan = 2)
        label1_1.grid(row = 1, column = 1); self.entry1_1.grid(row = 1, column = 2)
        label1_2.grid(row = 2, column = 1); self.entry1_2.grid(row = 2, column = 2)
        label1_3.grid(row = 3, column = 1); self.entry1_3.grid(row = 3, column = 2)

        label2.grid(row = 4, column = 1, columnspan = 2)
        label2_1.grid(row = 5, column = 1); self.entry2_1.grid(row = 5, column = 2)
        label2_2.grid(row = 6, column = 1); self.entry2_2.grid(row = 6, column = 2)
        label2_3.grid(row = 7, column = 1); self.entry2_3.grid(row = 7, column = 2)
        #--------------------------------------------------------

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

(実行画面)

【Python/tkinter】Label(ラベル)

tkinterでラベルの作成のサンプルを以下に示します。

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ラベルの作成")     # ウィンドウタイトル
        self.master.geometry("300x100")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        self.label = tk.Label(self.master, text = "ラベルの文字")
        # ラベルの配置
        self.label.pack()
        #--------------------------------------------------------

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

(実行画面)

構文

ラベルオブジェクト = tk.Label(親ウィジェット, オプション1 = 設定値, オプション2 = 設定値,・・・)

オプション

オプション名 説明
activebackground クリックされたときの背景色を指定します。
activeforeground クリックされたときの文字色を指定します。
anchor 文字の配置位置を指定します。
【設定値】tk.N, tk.S, tk.W, tk.E, tk.NW, tk.NE, tk.SW, tk.SE, tk.CENTER
background 背景色を指定します。
bd 枠線の太さを指定します。
ただし、初期状態では枠線が表示されていないため、reliefで枠線のスタイルを指定する必要があります。(borderwidthと同じ)
bg 通常時の背景色を指定します。
bitmap モノクロのBitmapを指定します。
(参考)https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/bitmaps.html
borderwidth bdと同じ
compound 文字と画像の両方を表示する際に、文字に対して画像の表示位置を指定します。
【設定値】tk.LEFT, tk.RIGHT, tk.BOTTOM, tk.TOP, tk.CENTER
cursor ウィジェット上にマウスポインタがある際のカーソルの種類を指定します。
(参考)https://tkdocs.com/shipman/cursors.html
disabledforeground stateオプションで無効(DISABLED)に設定している際の文字色を設定します。
fg 表示する文字色を指定します。(foregroundと同じ)
font 表示する文字のフォントを指定します。
foreground fgと同じ
height ラベルの高さを文字数で指定します。
画像を配置した時は、画素数の指定になります。
highlightbackground
highlightcolor
highlightthickness
image 表示する画像を指定します。
justify 複数行の文字のときの、文字寄せ方向を指定します。
【設定値】左寄せ(tk.LEFT), 中央寄せ(tk.CENTER), 右寄せ(tk.RIGHT)
padx 文字の両側の隙間を指定します。
pady 文字の上下の隙間を指定します。
relief ラベルの枠線のスタイルを指定します。
【設定値】tk.RAISED, tk.GROOVE, tk.SUNKEN, tk.RIDGE, tk.FLAT
state
takefocus
text 表示する文字を指定します。
textvariable StringVarクラスオブジェクトを指定し、ラベルの文字列を指定します。
underline 指定した順番(先頭から0始まり)の文字にアンダーラインを付加します。
width ラベルの幅を文字数で指定します。
wraplength 文字の折り返し幅を指定します。

 

ラベルはプログラムの各種情報を表示するのに、プログラムの実行中に文字列を変更する場合が多いかと思います。

ラベルの文字を変更する方法は主に下記の2通りあります。

  • textvariableオプションのStringVarを指定し変更する方法
  • ラベルのtextオプションを直接変更する方法

 

textvariableオプションのStringVarを指定し変更する方法

以下のサンプルでは、LabeltextvariableオプションにStringVarを指定し、ボタンのクリック時にラベルの文字列を変更しています。

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ラベルの作成")     # ウィンドウタイトル
        self.master.geometry("300x100")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        self.label_text = tk.StringVar(value = "ラベルの文字") # valueは初期値
        self.label = tk.Label(self.master, textvariable = self.label_text)
        # ラベルの配置
        self.label.pack()
        #--------------------------------------------------------
        # ボタンの作成
        self.button = tk.Button(self.master, text = "ボタン", command = self.button_click)
        # ボタンの配置
        self.button.pack()
        #--------------------------------------------------------

    def button_click(self):
        '''ボタンがクリックされたとき'''
        # ラベル文字の変更方法1
        # StringVarの値を設定する
        self.label_text.set("ボタンがクリックされた")

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

(プログラム起動時)

(ボタンクリック後)

 

ラベルのtextオプションを直接変更する方法

以下のサンプルでは、ボタンのクリック時にラベルのtextオプションを直接変更しています。

ただし、textオプションとtextvariableオプションを両方指定することは出来ないので注意してください。

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ラベルの作成")     # ウィンドウタイトル
        self.master.geometry("300x100")       # ウィンドウサイズ(幅x高さ)

        #--------------------------------------------------------
        # ラベルの作成
        self.label = tk.Label(self.master, text = "ラベルの文字")
        # ラベルの配置
        self.label.pack()
        #--------------------------------------------------------
        # ボタンの作成
        self.button = tk.Button(self.master, text = "ボタン", command = self.button_click)
        # ラベルの配置
        self.button.pack()
        #--------------------------------------------------------

    def button_click(self):
        '''ボタンがクリックされたとき'''
        # ラベル文字の変更方法2
        # ラベルのプロパティを直接設定する。ただし、textvariableプロパティは設定しないこと
        self.label["text"] = "ボタンがクリックされた"

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

(プログラム起動時)

(ボタンクリック後)

【Python/tkinter】OpenCVのカメラ動画をCanvasに表示する

USBカメラなどで取得した画像(動画)をOpenCVの cv2.imshow() で表示するには比較的簡単に表示する事ができますが、tkinterを使ってWindow付でCanvasに表示したい場合には、少しコツが必要になります。

そこで、tkinterのCanvasに動画を表示する方法を紹介します。

OpenCVでUSBカメラの動画を表示

USBカメラの動画をOpenCVのの cv2.imshow() で表示するプログラムは、以下のようにすれば表示されます。

import cv2

# カメラをオープンする
capture = cv2.VideoCapture(0)

# カメラがオープン出来たか?
camera_opened = capture.isOpened()

while camera_opened:

    # フレーム画像の取得
    ret, frame = capture.read()
    
    # 画像の表示
    cv2.imshow("Image", frame)

    if cv2.waitKey(1) != -1:
        # キー入力で終了
        break

capture.release()
cv2.destroyAllWindows()

tkinterのCanvasに動画を表示

tkinterのCanvasに動画を表示する場合は、OpenCVの動画表示プログラムの cv2.imshow() の部分を書き換えて表示しようとしても動画が表示されません。

これは、while文中でCanvasへ画像を表示しようとすると、画像の更新スレッドがブロックされた状態となってしまい、画像を更新するには、少しの時間、スレッドを空ける必要があります。

このスレッドを空ける処理には after() 関数を使います。

after() 関数では、指定した時間を待ってから、指定した関数を実行することができます。

処理の意味合い的には sleep() 関数とも似ていますが、sleep() 関数では、指定時間分、スレッドをブロックしてしまいますが、 after() 関数ではスレッドがブロックされません。

それらを考慮し、tkinterのCanvas上にUSBカメラの動画を表示したプログラムが以下になります。

 

以下のサンプルプログラムでは、Camvas上をマウスの左ボタンをクリックすると、画像の取得が開始/停止するようになっています。

import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk, ImageOps  # 画像データ用

import cv2

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)
        self.pack()

        self.master.title("OpenCVの動画表示")       # ウィンドウタイトル
        self.master.geometry("400x300")     # ウィンドウサイズ(幅x高さ)
        
        # Canvasの作成
        self.canvas = tk.Canvas(self.master)
        # Canvasにマウスイベント(左ボタンクリック)の追加
        self.canvas.bind('<Button-1>', self.canvas_click)
        # Canvasを配置
        self.canvas.pack(expand = True, fill = tk.BOTH)

        # カメラをオープンする
        self.capture = cv2.VideoCapture(0)

        self.disp_id = None

    def canvas_click(self, event):
        '''Canvasのマウスクリックイベント'''

        if self.disp_id is None:
            # 動画を表示
            self.disp_image()
        else:
            # 動画を停止
            self.after_cancel(self.disp_id)
            self.disp_id = None

    def disp_image(self):
        '''画像をCanvasに表示する'''

        # フレーム画像の取得
        ret, frame = self.capture.read()
    
        # BGR→RGB変換
        cv_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        # NumPyのndarrayからPillowのImageへ変換
        pil_image = Image.fromarray(cv_image)

        # キャンバスのサイズを取得
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()

        # 画像のアスペクト比(縦横比)を崩さずに指定したサイズ(キャンバスのサイズ)全体に画像をリサイズする
        pil_image = ImageOps.pad(pil_image, (canvas_width, canvas_height))

        # PIL.ImageからPhotoImageへ変換する
        self.photo_image = ImageTk.PhotoImage(image=pil_image)

        # 画像の描画
        self.canvas.delete("all")
        self.canvas.create_image(
                canvas_width / 2,       # 画像表示位置(Canvasの中心)
                canvas_height / 2,                   
                image=self.photo_image  # 表示画像データ
                )

        # disp_image()を10msec後に実行する
        self.disp_id = self.after(10, self.disp_image)

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

実行結果

ポイント

  • 動画を表示するときは、while文ではなく、after()関数で繰り返し処理を行う。
  • OpenCVの画像は、カラー画像の場合、BGRのデータの並びをRGBへ並び替えてからOpenCVの画像データ(NumPyのndarray)をPillowのImageへImage.fromarray()関数で変換する。

関連記事

【OpenCV-Python】Tkinter GUI Sample

【Python/tkinter】Canvasに画像を表示する

【Python】画像データ(NumPy,Pillow(PIL))の相互変換

【Python/NumPy】カラー画像データをRGBからBGRへ変換

【Python/tkinter】新しいウィンドウを開く(モーダル、モードレスダイアログ)

新しくウィンドウを開く場合、モーダルダイアログモードレスダイアログというものがあります。

モーダルダイアログとは、新しいウィンドウを開いたとき、新しいウィンドウのクリックやテキスト入力などの操作ができるが、元のウィンドウの操作ができない表示方法で、モードレスダイアログとは、新しいウィンドウと元のウィンドウの両方とも操作できる表示方法となります。

PythonのtkinterではToplevel()メソッドを実行すると新しいウィンドウを作成することができますが、デフォルトではモードレスダイアログとして開きます。

モーダルダイアログにするには、Toplevelで作成したウィンドウに対して、grab_set()メソッドを実行することで、モーダルダイアログになります。

 

サンプル実行結果

サンプルプログラム

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("Main")       # ウィンドウタイトル
        self.master.geometry("300x200") # ウィンドウサイズ(幅x高さ)

        # ボタンの作成
        btn_modeless = tk.Button(
            self.master, 
            text = "Modeless dialog",   # ボタンの表示名
            command = self.create_modeless_dialog    # クリックされたときに呼ばれるメソッド
            )
        btn_modeless.pack()

        btn_modal = tk.Button(
            self.master, 
            text = "Modal dialog",      # ボタンの表示名
            command = self.create_modal_dialog    # クリックされたときに呼ばれるメソッド
            )
        btn_modal.pack()

    def create_modeless_dialog(self):
        '''モードレスダイアログボックスの作成'''
        dlg_modeless = tk.Toplevel(self)
        dlg_modeless.title("Modeless Dialog")   # ウィンドウタイトル
        dlg_modeless.geometry("300x200")        # ウィンドウサイズ(幅x高さ)

    def create_modal_dialog(self):
        '''モーダルダイアログボックスの作成'''
        dlg_modal = tk.Toplevel(self)
        dlg_modal.title("Modal Dialog") # ウィンドウタイトル
        dlg_modal.geometry("300x200")   # ウィンドウサイズ(幅x高さ)

        # モーダルにする設定
        dlg_modal.grab_set()        # モーダルにする
        dlg_modal.focus_set()       # フォーカスを新しいウィンドウをへ移す
        dlg_modal.transient(self.master)   # タスクバーに表示しない

        # ダイアログが閉じられるまで待つ
        app.wait_window(dlg_modal)  
        print("ダイアログが閉じられた")

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

ポイント

  • Toplevelメソッドで新しいウィンドウを作成することができる。
  • Toplevelではデフォルトでモードレスダイアログとなる。
  • モーダルダイアログボックスにするには、grab_set()メソッドを実行する。
  • 新しく作成したウィンドウの表示直後はフォーカスが無いため、フォーカスするにはfocus_set()メソッドを実行する。
  • 新しいウィンドウをタスクバーに表示したくない場合はtransient()メソッドを実行する。
  • モーダルダイアログボックスが閉じられるまで待つには、wait_window(ダイアログ)メソッドで待つ。

【Python/tkinter】Entry(テキストボックス)

tkinterでテキストボックスはEntryと言います。

以下に簡単なサンプルを示します。

 

(実行結果)

(サンプルプログラム)

import tkinter as tk

class Application(tk.Frame):

    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("Entryの作成")    # ウィンドウタイトル
        self.master.geometry("300x100")     # ウィンドウサイズ(幅x高さ)

        # 表示する値
        self.entry_text = tk.StringVar() 
        # Entry(テクストボックス)の作成
        entry = tk.Entry(self.master,
            width = 30,         # ウィジェットの幅(文字数で指定)
            justify = tk.RIGHT, # tk.RIGHT:右寄せ、tk.LEFT:左寄せ、tk.CENTER:中央寄せ
            textvariable = self.entry_text # 表示する値
            )
        # ボタンの作成
        btn_input = tk.Button(self.master, text = "入力", command = self.btn_input_click)
        btn_clear = tk.Button(self.master, text = "クリア", command = self.btn_clear_click)

        entry.pack()
        btn_input.pack()
        btn_clear.pack()

    def btn_input_click(self):
        ''' Entryに入力された値を表示 '''
        print("Text = ", self.entry_text.get())

    def btn_clear_click(self):
        ''' Entryのクリア '''
        self.entry_text.set("")

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

ポイント

  • Entryの値はtextvariableにStringVarクラスオブジェクトを指定し、Entryのテキストの取得はStringVarクラスオブジェクト.get()、設定はStringVarクラスオブジェクト.set()で行います。
  • 他のウィジェットには標準的にはるcommandオプションがEntryにはありません。
    その代わりにvalidatecommandでテキストが変更されたときの処理を行います。
    詳細は後半で説明します。

オプション

オプション名 説明
background 通常時(クリックされていないとき)の背景色を指定します。(bgと同じ)
bd 枠線の太さを指定します。
bg backgroundと同じ
borderwidth bdと同じ
cursor ウィジェット上にマウスポインタがある際のカーソルの種類を指定します。
(参考)https://tkdocs.com/shipman/cursors.html
disabledbackground stateオプションで無効(DISABLED)に設定している際の背景色を設定します。
disabledforeground stateオプションで無効(DISABLED)に設定している際の文字色を設定します。
exportselection
fg 表示する文字色を指定します。(foregroundと同じ)
font 表示する文字のフォントを指定します。
foreground fgと同じ
highlightbackground
highlightcolor
highlightthickness
insertbackground
insertborderwidth
insertofftime
insertontime
insertwidth テキスト挿入時のカーソルの太さを指定します。
invalidcommand
invcmd
justify 文字寄せ方向を指定します。
【設定値】左寄せ(tk.LEFT), 中央寄せ(tk.CENTER), 右寄せ(tk.RIGHT)
relief テキストボックスの枠線のスタイルを指定します。
【設定値】tk.RAISED, tk.GROOVE, tk.SUNKEN, tk.RIDGE, tk.FLAT
【初期値】tk.FLAT(枠線なし)
selectbackground
selectborderwidth
selectforeground
show テキストボックスに表示する文字列を指定します。
(使用例)パスワード入力のとき
show = “*”
state ウィジェットの有効/無効(操作できない状態)を指定します。
【設定値】tk.NORMAL, tk.DISABLED
【初期値】tk.NORMAL
takefocus
textvariable Entryの値を取得、設定するためのStringVarクラスのインスタンスを指定します。
他にもIntVar, DoubleVar, BooleanVarの指定が可能です。
これらを指定することで、入力する値の型制限が可能になります。
validate 検証の対象を指定します。
‘none’, ‘key’, ‘focus’, ‘focusin’, ‘focusout’, ‘all’ のいづれか
validatecommand 検証の登録を行います。
width テキストボックスの幅を文字数で指定します。
xscrollcommand

メソッド

オプション名 説明
delete(first, last=None) 最初と最後の文字の位置を指定してテキストを削除します。
(例)最初から最後までを削除する方法
entry.delete(0, tk.END)
get() テキストボックスの文字列を取得します。
icursor(index) 挿入するカーソルの位置を指定します。
index(index)
insert(index, s) 指定した位置に文字を挿入します。
select_adjust ( index ) カーソルの位置から指定した位置までを選択します。
select_clear() テキストの選択状態を解除します。
select_from ( index )
select_present() 文字が選択されている場合はTrue, 選択されていない場合はFalseを返します。
select_range ( start, end ) 開始位置、終了位置を指定して文字を選択します。
select_to ( index ) 指定位置からカーソルの位置までを選択します。
xview ( index ) 横方向のスクロール位置を指定します。
xview_scroll ( number, what )

 

入力検証

テキストボックスに文字を入力してから表示するまでの間に、実際に表示するかどうか?を制御することが可能です。例えば、文字数の制限や、数字のみの入力などの制限をかけることができます。

 

(サンプルプログラム)テキストボックスの入力文字数を5文字までに制限する例

import tkinter as tk

class Application(tk.Frame):

    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("Entryの作成")     # ウィンドウタイトル
        self.master.geometry("300x100")       # ウィンドウサイズ(幅x高さ)

        validate_command = self.master.register(self.enty_validate)
        
        entry = tk.Entry(self.master,
            width = 30,
            validate='all', # 検証をどのタイミングで行うか?を指定します
            validatecommand = (
                validate_command, 
                '%d',   # アクションの種類 1:挿入、0:削除、-1:再検証
                '%i',   # 挿入/削除される文字の位置 挿入/削除されない場合は-1
                '%P',   # テキスト編集後の文字列
                '%s',   # テキスト編集前の文字列
                '%S',   # 挿入/削除される文字列
                '%v',   # validateオプションで指定した種類
                '%V',   # 実際に実施されたvalidateの種類
                '%W'    # Entryウィジェットの名前
                )
        )
        entry.pack()

    def enty_validate(self, action, index, prevalidation, current, test, validata_option, condition, name):
        '''入力検証'''
        print("enty_validate", action, index, prevalidation, current, test, validata_option, condition, name)

        if len(prevalidation) > 5:
            # 入力文字数を5文字までに制限
            # Falseを返すとテキストボックスに入力した文字は反映されない
            return False
        else:
            # Trueを返すとテキストボックスに入力した文字が反映される
            return True

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

上記サンプルは、すべてのタイミング(validateオプション)で、すべての情報を取得していますが、’%P’や’%S’だけを使用しても構いません。ただし、オプションの数に合わせて、呼ばれるメソッド(上記サンプルでは entry_validate)の引数の数を調整する必要があります。

validateオプション

オプション名 説明
none 検証なし(初期値)
key キー入力時
focus フォーカス時?
focusin フォーカスを取得したとき
focusout フォーカスを失ったとき
all 全て

validatecommandオプション

オプション名 説明
‘%d’ アクションの種類 1:挿入、0:削除、-1:再検証
‘%i’ 挿入/削除される文字の位置 挿入/削除されない場合は-1
‘%P’ テキスト編集後の文字列
‘%s’ テキスト編集前の文字列
‘%S’ 挿入/削除される文字列
‘%v’ validateオプションで指定した種類
‘%V’ 実際に実施されたvalidateの種類
‘%W’ Entryウィジェットの名前

 

参考

https://tcl.tk/man/tcl8.6/TkCmd/ttk_entry.htm

【Python】画像ビューア(ズーム(拡大/縮小)、移動表示)

Canvasに画像を表示する のページではtkinterでGUIを作り画像ビューアを作りましたが、これに アフィン変換 を追加し、画像の拡大/縮小、移動の出来る画像ビューアを作成しました。

機能は、Fileメニューから画像ファイルを開き、マウスホイールの上下で画像の拡大/縮小を行い、マウスの左ボタンのドラッグで画像を移動します。
左ボタンのダブルクリックで画像全体を表示します。
また、ウィンドウ下にはCanvas上のマウスポインタの座標と、マウスポインタ位置の画像の座標および、その輝度値を表示します。
ウィンドウの右下には画像ファイルの種類、画像サイズ、画像の種類を表示します。

全ソースコード

参考

【Python/tkinter】Canvasに画像を表示する

アフィン変換(平行移動、拡大縮小、回転、スキュー行列)

【Python/NumPy】行列の演算(積、逆行列、転置行列、擬似逆行列など)

【Python/tkinter】Scale(トラックバー、スライダー)

つまみを動かして値を調整できる、C#でいうところのトラックバーは、tkiterでは Scale といいます。

以下にサンプルプログラムを示します。

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("Scaleの作成")     # ウィンドウタイトル

        #---------------------------------------------------------------
        # Scaleの作成

        # Scale(デフォルトで作成)
        scaleV = tk.Scale( self.master)
        scaleV.pack(side = tk.RIGHT)

        # Scale(オプションをいくつか設定)
        self.scale_var = tk.DoubleVar()
        scaleH = tk.Scale( self.master, 
                    variable = self.scale_var, 
                    command = self.slider_scroll,
                    orient=tk.HORIZONTAL,   # 配置の向き、水平(HORIZONTAL)、垂直(VERTICAL)
                    length = 300,           # 全体の長さ
                    width = 20,             # 全体の太さ
                    sliderlength = 20,      # スライダー(つまみ)の幅
                    from_ = 100,            # 最小値(開始の値)
                    to = 300,               # 最大値(終了の値)
                    resolution=0.5,         # 変化の分解能(初期値:1)
                    tickinterval=50         # 目盛りの分解能(初期値0で表示なし)
                    )
        scaleH.pack()
        #---------------------------------------------------------------

    def slider_scroll(self, event=None):
        '''スライダーを移動したとき'''
        print(str(self.scale_var.get()))

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

オプション

オプション名 説明
activebackground マウスカーソルがスケール(つまみ)上にあるときのスケールの色を指定します。
background 背景色(値、メモリの文字、スケール(つまみ)の背景色)を指定します。
bd スライダー部の枠線の太さを指定します。
【初期値】2
bg 背景色(値、メモリの文字、スケール(つまみ)の背景色)を指定します。
borderwidth スライダー部の枠線の太さを指定します。
【初期値】2
command 値が変更された時に呼び出されるメソッド名を指定します。
cursor ウィジェット上にマウスポインタがある際のカーソルの種類を指定します。
digits 値、メモリに表示される値の桁数を指定します。
fg フォントの文字色を設定します。
font フォントを指定します。
foreground フォントの文字色を設定します。
from_ 最小値(開始の値)を指定します。
highlightbackground
highlightcolor
highlightthickness
label スライダーの左上(水平方向に配置のとき)もしくは右上(垂直方向に配置のとき)に表示する文字(ラベル)を指定します。
length 全体の長さを指定します。
orient Scaleの配置の向き(縦(tk.VERTICAL)か横(tk.HORIZONTAL))を指定します。
【初期値】tk.HORIZONTAL
relief Scale全体の枠線のスタイルを設定します。
【設定値】tk.RAISED, tk.GROOVE, tk.SUNKEN, tk.RIDGE, tk.FLAT
repeatdelay
repeatinterval
resolution つまみを移動した時の変化の分解能を指定します。
【初期値】1
showvalue スライダーの値を表示する(True) /表示しない(False)を指定します。
sliderlength スライダー(つまみ)の幅
sliderrelief
state state = tk.DISABLEDを指定するとScaleが無効(変更できない)になります。
takefocus
tickinterval 目盛りの文字の分解能を指定します。
【初期値】1
to 最大値(終了の値)を指定します。
troughcolor スライダが動く領域(つまみの背景)の色を指定します。
variable Scaleの値をIntVar,DoubleVar,StringVarクラスオブジェクトで指定します。
width 全体の太さを指定します。