このページでは、キャンバスに描画した図形の “左上が欠ける”・描画した図形が “左上に寄る” 現象の対処法について解説していきます。

また、同様の対処法で、キャンバスの周りの意図しない余白を消すこともできます。

それ、おそらく誰もがハマる問題だよ
多分 highlightthickness のせいだね…


オッケー!
じゃあその問題の対処法や原因について解説していくよ!
それを読めば highlightthickness がどんなものかも分かるはず!

あなたもキャンバスに図形を描画した際、”ちゃんと描画時に座標を指定しているはず” なのに、図形や画像の左上が欠ける or 図形が左上に寄るような現象を経験したことはないでしょうか?
「経験したことない」という方もいるかもしれませんが、それはもしかしたら気づいていないだけかもしれません。
なぜなら、図形の描画時に上記のような現象が発生するよう、キャンバスがデフォルトで設定されているからです(少なくとも私の環境ではそのように設定されています)。
なので、上記のような現象が発生したことがないような方でも、対処法は知っておいた方が良いと思います。
ということで、このページでは “キャンバスに描画した図形の左上が欠ける・図形の位置が左上に寄る” 時の対処法およびその原因について解説していきます。
このページで解説している内容は、下記環境での動作確認に基づいて説明しています
- OS:macOS Big Sur
- Python:Version 3.9.6
- tkinter:TkVersion 8.6・TclVersion 8.6
他の環境の場合、デフォルト値や動作が異なる可能性があるので注意してください
Contents
原因と対処法
まず結論として、原因と対処法を先に書いておきます。
図形の左上が欠ける・図形が左上に寄る原因の大元は “キャンバスの highlightthickness の設定値が 0 でない” ことです。
この対処法としては下記の2つが考えられます。
highlightthicknessを0に設定する- 描画時の座標を
highlightthicknessを考慮して設定する
簡単なのは、前者の highlightthickness の設定値を 0 にする方です。
これは、キャンバス作成時(つまり tkinter.Canvas() 実行時)にオプションとして highlightthickness=0 を指定することで設定可能です(キャンバス作成後に config メソッドにより変更することも可能です)。
canvas = tkinter.Canvas(
app,
width=300,
height=200,
highlightthickness=0 # これを指定する
)

おっ!確かにこれで直った!!
でもなんで?
気になるよね?
次は highlightthickness によって図形の左上が欠ける・図形が左上に寄る理由を解説していくよ

左上が欠ける・図形が左上に寄る原因
では、描画した図形の左上が欠ける・描画した図形が左上に寄る原因について解説していきたいと思います。
スポンサーリンク
highlightthickness が 0 でないとキャンバスに囲み線ができる
これらの現象は、highlightthickness というオプションに起因します。
この highlightthickness がどういったものかご存知ない方も多いと思いますので、まずは highlightthickness について説明しておきます。
まず前提として、tkinter のウィジェットの多くはタブキーの押下によりフォーカスを当てることが可能です。
例えばボタンウィジェットであれば、フォーカスを当てた状態でスペースキーを押下すればボタンを押すことができます。
さらに、フォーカスが当てられたウィジェットは、フォーカスが当てられていることが分かるように線で囲まれます(ここからは、このフォーカスが当てられたことを示す囲み線を、単に “囲み線” と呼ばせていただきます)。
highlightthickness は、この囲み線の太さを設定するためのオプションになります(単位はピクセルで指定)。

ちなみにですが、キャンバスウィジェットの場合は、キャンバス作成時等に takefocus=True を指定した場合のみフォーカスを当てることが可能です。

ん?
図形の左上が欠けるのと全く関係なさそうだけど…
ここだけ聞くとそう思うよね?
でも実は関係あるんだよ…
どう関係があるかについて解説していくよ

キャンバスの座標は囲み線を含んで扱われる
キャンバスに描画した図形の左上が欠ける・描画した図形の位置が左上に寄る原因を考える上でポイントになるのが、先ほど説明したフォーカスが当てられた時の囲み線です。
実は tkinter では、キャンバス上の座標はキャンバスそのものだけでなく、この囲み線を含んだ状態で扱われます。
例えばキャンバスのサイズは、キャンバス作成時に指定したサイズ(tkinter.Canvas() 実行時に width と height で指定した値)に囲み線の太さを足し合わせたものとなります。

これは、下記のようなスクリプトを実行すると分かりやすいと思います。
# -*- coding: utf-8 -*-
import tkinter
def printSize():
# キャンバスのサイズを表示
print(canvas.winfo_width(), canvas.winfo_height())
canvas.after(100, printSize)
app = tkinter.Tk()
canvas = tkinter.Canvas(
app,
width=300,
height=200,
highlightthickness=20
)
canvas.pack()
canvas.after(100, printSize)
app.mainloop()
スクリプト内で実行している winfo_width メソッドと winfo_height メソッドは、実行したウィジェットの幅と高さを取得するメソッドです。
私の環境でスクリプトを実行すると、上記メソッドで取得して表示した値は 340 と 240 になりました(最初は両方が 1 で表示されるかもしれませんが、しばらく待つとキャンバスのサイズが表示されるようになるはずです)。
これらの値は、キャンバス作成時に指定している width と height の値(300 と 200) に対して highlightthickness の2倍の値(20 * 2)が足されたものであることが確認できると思います。
なぜ * 2 されているかというと、これは囲み線が上下と左右に存在するからです。


んー、
でも僕は highlightthickness なんて指定した覚えないよ?
highlightthickness が設定されているところが厄介なんだよね…

もし、highlightthickness を指定せずにキャンバスを作成した場合、winfo_width と winfo_height はどうなるでしょうか?
これを確認するために、先程のスクリプトの highlightthickness=20 の行を削除してスクリプトを実行してみてください!
私の環境では winfo_width と winfo_height は 306 と 206 になりました。
この winfo_width および winfo_height と、キャンバス作成時に width および height に指定した値とのそれぞれの “差の半分の値” が highlightthickness のデフォルト値になると思います。
なので、highlightthickness のデフォルト値は 3 ということになります(少なくとも私の環境では)。
つまり、私たちが使用しているキャンバスのサイズは、デフォルトでフォーカス時の囲み線の太さが含まれた状態で設定されていることになります(これはフォーカスが当てられているかどうかには関わりません)。
キャンバスの原点は囲み線の太さ分左上にずれる
で、これの何が厄介なのかというと、これは “キャンバスの原点” がキャンバスそのものの一番左上の位置ではなく、囲み線を含めた状態での一番左上の位置になってしまうところです。
つまり、キャンバスの原点は、キャンバスそのものの一番左上の位置から囲み線の太さ分(つまり highlightthickness の値分)、左上方向にずれます。

にも関わらず、囲み線の部分には図形が表示されません。
これが原因で、図形が欠けたり図形が左上に寄ってしまったりする現象が発生します。
スポンサーリンク
原点がずれているので図形が欠ける
highlightthickness が 0 以外の場合に原点から図形を描画した場合、囲み線と重なっている部分の図形は表示されず、その部分の図形が欠けてしまうことになります。

これは下記のようなスクリプトを実行してみるとわかりやすいと思います。
import tkinter
app = tkinter.Tk()
canvas = tkinter.Canvas(
app,
width=300,
height=200,
)
canvas.pack()
canvas.create_rectangle(
0, 0, 150, 50,
width=1, # 線の太さ
outline="red" # 線の色
)
app.mainloop()
上記スクリプトでは、座標 (0, 0) から長方形を描画しています。outline="red" を指定しているため、長方形の線が赤色で描画されることを想定しています。
ですが、実際に描画した結果は下の図のようになりました。

長方形の左上部分の線が表示されていないことが確認できると思います。表示されていない部分には囲み線が存在するはずなので、その囲み線上に描画された部分の図形が表示されずに、上図のような表示になったと考えられます。
つまり、highlightthickness が 0 以外の場合、図形を原点 (0, 0) から描画すると、highlightthickness の値分図形の左上側が欠けることになります。
さらに、highlightthickness はデフォルトで 0より大きい値が設定されている場合がある(少なくとも私の環境では 3 で設定されている)ため、highlightthickness を設定しなくても描画した図形の左上が欠ける可能性があります。


原点がずれているので図形が左上に寄る
図形が左上に寄ってしまう原因も、図形の左上が欠けてしまう原因と同じです。
highlightthickness が 0 以外の場合に、原点の位置をキャンバスの左上と考えて図形を描画する座標を計算した場合、その図形の位置が左上方向にずれてしまうことになります。これは、実際の原点が、キャンバスそのものの左上ではなく、highlightthickness 値分左上にずれた位置に存在するからです。
例えばキャンバスの中心座標を求める場合、普通に考えるとキャンバス作成時に width と height に指定した値を単に 2 で割れば良さそうです。
ですが、highlightthickness が 0 以外の値である場合、このような計算で求めた座標はキャンバスの中心座標から左上に寄ったものになってしまいます。

この現象は下記のようなスクリプトを実行してみると分かりやすいです。
import tkinter
app = tkinter.Tk()
# キャンバスのサイズ
canvas_width = 300
canvas_height = 200
canvas = tkinter.Canvas(
app,
width=canvas_width,
height=canvas_height,
highlightthickness=50
)
canvas.pack()
# キャンバスの中心座標を求める
center_x = canvas_width / 2
center_y = canvas_height / 2
# キャンバスの中心から±30の位置の座標を求める
x1 = center_x - 30
y1 = center_y - 30
x2 = center_x + 30
y2 = center_y + 30
canvas.create_rectangle(
x1, y1, x2, y2,
width=1, # 線の太さ
outline="red", # 線の色
fill="yellow" # 塗りつぶしの色
)
app.mainloop()
実行すると、下の図のような画面のアプリが起動します。

キャンバスの真ん中に長方形を描画するつもりで座標を求めていますが、明らかに左上方向に寄ってしまっているのが確認できると思います。これは highlightthickness=50 にしているので大袈裟な例ですが、highlightthickness が 0 以外の場合に図形が左上方向にずれてしまうことは理解していただけたのではないかと思います。
こんな感じで、highlightthickness が 0 以外の場合、原点 (0, 0) をキャンバスそのものの左上と考えて座標を指定すると、highlightthickness の値分図形が左上に寄ってしまうことになります。
さらに、highlightthickness はデフォルトで 0より大きい値が設定されている場合があり(少なくとも私の環境では 3 で設定されている)、その場合は、デフォルトの設定のまま図形を描画すると、わずかですが図形が左上に寄ってしまうことになります。
左上が欠ける・図形が左上に寄る時の対処法
ここまで解説してきたように、描画した図形の左上が欠ける・描画した図形が左上に寄る原因は、原点 (0, 0) がキャンバスそのものの左上の位置からずれている点にあります。
また、この原点の位置のずれは、”highlightthickness が 0 でない” ことに起因します。
さらに、この原点の位置のずれを発生させる highlightthickness は、デフォルトで 0 以外の値が設定されています(もしかしたら環境によっては 0 になっている場合もあるかもしれません)。
highlightthickness が 0 以外の場合、原点が “キャンバスそのものの左上の座標” から highlightthickness の値分ずれることになりますので、キャンバスそのものの左上の座標を原点として考えて座標を求めると、描画時に上記のような現象が発生することになります。
この現象の対処法としては下記の2つが考えられます。
highlightthicknessを0に設定する- 描画時の座標を
highlightthicknessを考慮して設定する
スポンサーリンク
highlightthickness を 0 に設定する
原点の位置は highlightthickness の値分左上にずれるわけですから、highlightthickness を 0 に設定してやれば問題は解決します。
これはキャンバス作成時に設定してやるのが一番簡単だと思います。
canvas = tkinter.Canvas(
# 略
highlightthickness=0 # これを指定する
)
この対処法のデメリットは、”フォーカスが当てられた時の囲み線が表示されなくなる” ことです。なので、キャンバスにタブキーでフォーカスを当てる&フォーカスが当てられた時に囲み線を表示したい場合はこの対処法は不適切です。
ただ、キャンバスにフォーカスを当てるようなケースってあんまりないと思うんですよね…。ボタンウィジェットなどであれば、フォーカスを当てることでスペースキーの押下によりボタンを押せるようになるので、フォーカスの意味はあると思うのですが…。
描画時の座標を highlightthickness を考慮して設定する
もう一つの対処法は描画時の座標を求める際に highlightthickness を考慮することです。
具体的には、キャンバスそのものの左上を原点として求めた図形の座標に対して highlightthickness の値を足すことで、その座標を右下方向にずらします。

これにより、キャンバスの原点のずれ(前述の通り、キャンバスそのものの左上から highlightthickness の値分、原点は左上方向にずれる)を吸収する形で座標を求めることができます。
この highlightthickness の値は、下記のように tkinter.Canvas のインスタンス canvas に cget メソッドを実行させることで取得することができます。
offset = int(canvas.cget("highlightthickness"))
さらに、キャンバスそのものの左上を原点として求めた x 座標・y 座標に対して、上記で取得した値を足し合わせることで、座標を右下方向に highlightthickness の値分ずらすことができます。
例えば、原点がずれているので図形が欠けるで紹介したスクリプトに対してこの対処法を適用した場合、スクリプトは下記のようになります。
import tkinter
app = tkinter.Tk()
canvas = tkinter.Canvas(
app,
width=300,
height=200,
)
canvas.pack()
# highlightthicknessの値を取得する
offset = int(canvas.cget("highlightthickness"))
# highlightthicknessの値を考慮して描画
canvas.create_rectangle(
0 + offset, 0 + offset,
150 + offset, 50 + offset,
width=1, # 線の太さ
outline="red" # 線の色
)
app.mainloop()
このスクリプトを実行すると下の図のように長方形が描画され、左上方向での図形の欠けが解消されていることが確認できると思います。

また、原点がずれているので図形が左上に寄るで紹介したスクリプトに対してこの対処法を適用した場合、スクリプトは下記のようになります。
import tkinter
app = tkinter.Tk()
# キャンバスのサイズ
canvas_width = 300
canvas_height = 200
canvas = tkinter.Canvas(
app,
width=canvas_width,
height=canvas_height,
highlightthickness=50
)
canvas.pack()
# highlightthicknessの値を取得する
offset = int(canvas.cget("highlightthickness"))
# キャンバスの中心座標を求める
center_x = canvas_width / 2
center_y = canvas_height / 2
# highlightthicknessを考慮して座標を計算する
x1 = center_x - 30 + offset
y1 = center_y - 30 + offset
x2 = center_x + 30 + offset
y2 = center_y + 30 + offset
canvas.create_rectangle(
x1, y1, x2, y2,
width=1, # 線の太さ
outline="red", # 線の色
fill="yellow" # 塗りつぶしの色
)
app.mainloop()
このスクリプトを実行すると下の図のように長方形が描画され、左上方向に図形がずれる現象が解消されていることが確認できると思います。

おそらく上記の cget メソッドにより highlightthickness の値を取得することはできると思うのですが、もし無理な場合などは下記のように、明示的に highlightthickness の値を指定し、その値分座標をずらすようにすれば良いと思います。
import tkinter
app = tkinter.Tk()
# highlightthicknessに指定する値を設定
offset = 3
canvas = tkinter.Canvas(
app,
width=300,
height=200,
highlightthickness=offset # 明示的に指定
)
canvas.pack()
# highlightthicknessの値を考慮して描画
canvas.create_rectangle(
0 + offset, 0 + offset,
150 + offset, 50 + offset,
width=1, # 線の太さ
outline="red" # 線の色
)
app.mainloop()
補足:キャンバスの周りの余白を消す
highlightthickness が原因で “キャンバスに描画した図形の左上が欠ける・図形の位置が左上に寄る” 以外の現象も発生します。
その現象とは、”キャンバスの周りに意図しない余白が付いてしまう” です。その余白の正体は、フォーカスが当てられたことを示すための囲み線で、その太さは highlightthickness の値となります。

例えば、下記のスクリプトのように2つのキャンバスを並べて配置したとします。
import tkinter
app = tkinter.Tk()
# キャンバスを2つ作成して横並びではいち
canvas = tkinter.Canvas(
app,
width=300,
height=200,
bg="blue"
)
canvas.grid(column=0, row=0)
canvas = tkinter.Canvas(
app,
width=300,
height=200,
bg="orange"
)
canvas.grid(column=1, row=0)
app.mainloop()
この場合、キャンバスの周りに highlightthickness の値分の囲み線が存在するため、下の図のようにキャンバス同士が離れて配置されてしまうことになります。

実は、実際は2つのキャンバスはくっついて配置されています。ですが、キャンバスの周りに余白が付いているので、上の図のようにキャンバス同士が離れて配置されているように見えます。

この現象も発生したことある!
ほっといたけど
離されて見えても問題ない時はそのままでいいよ
でもくっついてるように見せたい場合は対処が必要だね!

上の図のようにキャンバス同士が離れているように見えて良いのであれば問題ないですが、キャンバス同士がくっついているように見せたい場合はこの余白が邪魔になります。
ただ、このキャンバスの周りの余白も、結局はフォーカスが当てられたことを示す囲み線ですので、highlightthickness=0 を指定してやることで消すことができます。
例えば、上記のスクリプトに対してキャンバス作成時に highlightthickness=0 を指定するようにすることで、見た目的にもキャンバス同士がくっついた状態で配置することができます。
import tkinter
app = tkinter.Tk()
# キャンバスを2つ作成して横並びではいち
canvas = tkinter.Canvas(
app,
width=300,
height=200,
bg="blue",
highlightthickness=0
)
canvas.grid(column=0, row=0)
canvas = tkinter.Canvas(
app,
width=300,
height=200,
bg="orange",
highlightthickness=0
)
canvas.grid(column=1, row=0)
app.mainloop()
スクリプトを実行すると下の図のようなアプリが起動し、キャンバス同士がくっついて配置されていることが確認できると思います。

スポンサーリンク
まとめ
このページでは、”キャンバスに描画した図形の左上が欠ける・図形の位置が左上に寄る” 現象の対処法と原因について解説しました!
どちらの現象も highlightthickness が 0 でないことが原因で発生する現象であり、下記の2つの方法で解決することができます。
highlightthicknessを0に設定する- 描画時の座標を
highlightthicknessを考慮して設定する
前者の方が簡単ですが、フォーカスが当てられたことを示す囲み線を消したくない場合は後者の対処法の方が良いと思います。
また、キャンバスの周りに余白ができてしまう現象も、同様に highlightthickness が原因になり、こちらも highlightthickness=0 にすることで解決することができます。
特に厄介なのは、この highlightthicknes のデフォルト値が 0 でないところだと思います(これは環境によって異なるかもしれません)。
ですので、普通にキャンバス上に図形を描画しただけで上記のような現象が発生してしまいます。
気づかないうちに、”キャンバスに描画した図形の左上が欠ける・図形の位置が左上に寄る” 現象が発生している可能性があるので、まずはキャンバスの座標は囲み線を含んで扱われるで紹介した winfo_width・winfo_height メソッドや、描画時の座標を highlightthickness を考慮して設定するで紹介した cgetメソッドを利用して highlightthicknes のデフォルト値を確認してみるのが良いと思います!

