今回の目次:
前回掲載したサンプルプログラム (01-06.py) では、ウィンドウとボタンを表示しただけで、押しても何も起こりませんでした。今回は、ボタンを押すと文字列を出力するようにしてみます。
次のようにします。ボタンを押すと、コンソールに「ボタンが押された!」と表示されます。
自分で関数を定義するには、次のように書きます;
def 定義する関数名 ( 引数名, ... ) :
文
文
...
defの行の最後に「:」(コロン)を書き、次の行から行を字下げ(左側に空白を入れる)して、実行する文を書いていきます。字下げするのを止めたところがその関数の終わりになります。
tkinter.Button()のキーワード引数 command で on_clicked 関数を登録します。ボタンが押された時には、Tk#mainloop()の中から登録された関数が呼び出されます。
ウィンドウを表示するプログラムや、あるいはWebサーバで動くプログラムなどでは、ユーザからの操作を待ち受けます。
あらかじめ何をどのような順序で操作されるか、プログラムの側では分かりません。ですので、プログラミングでは, 自分の関数を登録しておいて、実際にユーザがそのような操作 (上の例ではボタンを押す。) があったときに、呼び出してもらうようにします。
これをイベント駆動プログラミング (event-driven programming) といいます。
Python の特徴の一つが、字下げでソースコード上の固まりを表すことです。上の例でも、字下げで関数の範囲を示しました。
次の例を見てください。for は繰り返しを行う命令です。
02-06.py
$ python3 02-06.py value: 1 x value: 10 x value: False x value: (1, 2, 3) x value: 日本語テキスト x value: あああ x finish.
forの次の行から字下げしています。print("x")までが固まりです。ここまでが繰り返されます。
前回, 変数がオブジェクトを指すラベル(付箋)だということを解説しました。変数は、ソースコード上の文脈によっては、同じ名前であっても別のオブジェクトを指すことがあります。ある変数が有効なソースコード上の範囲をスコープ (scope) といいます。
実行時, それぞれの変数は, いずれかの名前空間の中にあります。ある関数が呼び出されてその中の文が実行される時、新しい名前空間が導入されます。関数の引数、それから関数の中で代入した変数は、関数の外側の変数とは別の名前空間に属します。関数の中の文の実行が終わるときに、その関数の名前空間は消滅します。
次のスクリプトは、関数fooの外側と内側で, 同じ名前の変数yを使っています。
02-07.py
$ python3 02-07.py 2 4 100
return文は、関数の戻り値を伴って、関数から抜けます。
foo() の中で変数yに代入していますが、外側の変数yには影響しません (外側のyが指すオブジェクトは変わりません。) (図3)。
関数内でのみ有効な変数をローカル変数といいます。言い換えると、ローカル変数のスコープは関数です。なお、すべての関数のなかから参照できる変数をグローバル変数といいます。
グローバル変数は、ソースコードが大きくなると、どこで値が書き換えられるかが分かりにくくなるため、あまり使うべきではありません。global宣言でグローバル変数になります。
ただし注意したいのは、変数が新しく取られても、オブジェクトがコピーされるわけではないことです。
関数呼び出しの引数は、名前 (変数) が新しく取られるだけで、呼び出し元のオブジェクトが共有されます。関数のなかで, 引数の変数を介してオブジェクトを操作すると、その影響は呼び出した側にまで及びます。
変数から変数への代入と同じです。
次のソースコードは、リストを関数の中で変更します。関数の外側で生成したオブジェクトが変更されます。
02-08.py
少し高度になりますが、Python では、変数が見つからないときは、ソースコードの字面上で外側に変数を探しに行きます。
02-09.py
Python は関数を入れ子にできます。関数fの中には変数yしかありません。変数xは、外側の、g()の引数のxを使います。
g(2)とすると、def f(y)の中では、2 * yとなります。戻り値である関数fに3を与えると, (2 * 3) で 6が得られます。
これまでのサンプルでは、ラベル、ボタンをウィンドウに貼り付けてきました。Tkinterには、それ以外にも、いろいろなものがあります。
ウィンドウに貼れるものをウィジェット (widget) といいます。
Note.
ウィジェットは、「コントロール」あるいは「コンポーネント」と呼ぶこともあります。
いくつかのウィジェットをウィンドウに並べてみます (02-10.py)。実行すると、画面1のように表示されます。
ウィジェットのクラスとその内容は表1のとおりです。それぞれのクラスの使い方は、紙面もないので、リファレンスマニュアルを参照してください。
| Tkinterクラス名 | 内容 | ||
|---|---|---|---|
| Widget | |||
| Button | 押しボタン | ||
| Canvas | キャンバス | ||
| Checkbutton | チェックボタン | ||
| Entry | エントリ(1行テキスト入力) | ||
| Frame | フレーム | ||
| Label | ラベル | ||
| LabelFrame | ラベル付きフレーム | ||
| Listbox | リストボックス | ||
| Menu | メニューバー | ||
| Menubutton | (推奨されない。MenuかOptionMenuを使う。) | ||
| OptionMenu | オプションメニュー | ||
| Message | (推奨されない。Labelを使う。) | ||
| PanedWindow | ペイン区切り | ||
| Radiobutton | ラジオボタン | ||
| Scale | スケール(スライダ) | ||
| Scrollbar | スクロールバー | ||
| Spinbox | スピンボックス | ||
| Text | 複数行テキスト入力 | ||
ウィジェットの機能のうち、共通の部分は Widgetクラスで定義されています。リファレンスマニュアルなどでは、調べたいウィジェットのクラスだけではなく、Widgetクラスも調べる必要があります。オプションメニューについても、OptionMenuクラス、Menubuttonクラス、Widgetクラスを遡って調べる必要があります。
Tkinterでは、マウスのボタンが押された、ドラッグされた、キーボードのキーが打たれた、など、ユーザーがウィンドウ上で何らかのことをしたりすると、イベントとしてプログラムに伝えられます。
イベントは、マウスのボタンを押したときの座標、どのボタンを押したのか、どのキーを打ったのか、などの情報をまとめたものです。イベントを受け取るためには、あらかじめ関数を登録しておく必要があります。関数を登録しておくと、これらユーザーの操作などがあったときに、Tk#mainloop()の中からその関数が呼び出されます。
これが「イベント駆動プログラミング」でしたね。
すでに見たように、押しボタン (Buttonクラス) では、commandキーワード引数で関数を登録しておけば、クリックしたときにその関数が呼び出されました。ウィジェット特有のイベントはこのように特別な指定方法がありますが、一般的なイベントは、bindメソッドでイベントと関数を結び付け(束縛)ます。
bindメソッドは次のように書きます。
widgetオブジェクト . bind ( イベントを表す文字列, 関数・メソッド名 )
例えば、リスト7のようにします。「<Button-1>」は、マウスの左ボタンを押す、というイベントです。実際にイベントが発生したときには、イベントの情報を格納したオブジェクトを引数として、登録しておいた関数が呼び出されます。
表2に、イベントの一部を掲げます。
| イベント | 内容 |
|---|---|
| <Button-x> | マウスのボタンが押された。xはボタン番号で、1=左ボタン、2=中ボタン、3=右ボタン |
| <Bx-Motion> | マウスがドラッグされた。xはボタン番号。 |
| <ButtonRelease-x> | マウスのボタンが離された。xはボタン番号 |
| <Double-Button-x> | マウスがダブルクリックされた。xはボタン番号 |
| <Enter> | マウスポインタがウィジェットに重なった(入ってきた)。キーボードのEnterキーが押されたにあらず。 |
| <Leave> | マウスポインタがウィジェットから離れた |
関数は、引数として与えられたオブジェクトと、関数内で生成したオブジェクト、グローバル変数しか参照できません。大きな仕事をするには、多くの関数が協力・分担して、いろいろなことができなければなりません。
すでに見てきたように、それぞれのオブジェクトは多くのメソッドを持ち、これらがオブジェクトの内部情報を共有しています。外側から見ると、メソッドに対して指図することで(メソッド呼び出し)、オブジェクトを操作します(図4)。
我々もこのようなオブジェクトを作っていい頃合いです。オブジェクトの振る舞い(メソッド)は、クラスで定義します。
クラスを定義する文法は、次のようになります。
class 定義するクラス名 :
メソッド定義など...
メソッドを定義する文法は、関数とほとんど同じで、クラス定義のなかで次のようにします。
def 定義するメソッド名 ( 自オブジェクト変数名, 引数, ... ) :
文...
関数定義と違うのは、最初の引数として, 操作対象となるオブジェクトを指す変数を用意することだけです。この変数の名前は何でもいいのですが、慣例的に selfとします。
字下げして文を書いたり、呼び出されるたびに新しい名前空間が導入されるのも同じです(ローカル変数のスコープも同じ)。
オブジェクトの内部状態を保持する変数をインスタンス変数といいます。インスタンス変数は、次のように書きます。
オブジェクト . 変数名 # 文法
実際の書き方を 02-12.py に示します。__init__メソッドは、特別なメソッドで、オブジェクトが生成されるときに呼び出されます。
上記の 02-12.py の self.value がインスタンス変数です。インスタンス変数は、オブジェクトが生成されるたびに作られ、オブジェクトのメソッドで共有できます。オブジェクトが消滅するまで生き続けます。
それでは、これまでに見てきたことを踏まえて、ごく短いお絵かきプログラムを書いてみます(リスト9)。実行結果は、画面2のようになります。
02-13.py をダウンロード ダウンロード後、拡張子を .py に変更してください。
どのようなことをしているか、順に見ていきましょう。
Scribble#__init__() は、単にcreate_window() を呼び出すだけです。
お絵かきのためのキャンバスオブジェクトと、プログラムを終了するためのボタンオブジェクトを作ります。キャンバスオブジェクトは、bg引数で背景を白に、widthとheightで大きさを指定します。
ボタンオブジェクトには、tkinter.Tk#quitメソッドを登録しておきます。クリックされたら、このメソッドが呼び出され、プログラムが終了します。
キャンバス はbind() でイベントを結び付けます。ユーザーがマウスの左ボタンを押したとき、マウスをドラッグしたときに、それぞれon_pressedメソッド、on_draggedメソッドが呼び出されるようにします。
ボタンが押されたら点を描きます。tkinter.Canvas#create_oval()は楕円を描くメソッドです。この上下左右の座標を1点にすることで、点を描きます。
インスタンス変数sx、syに現在位置を入れておきます。on_dragged()でこれを使います。
self.color, self.widthについては後述。
マウスがドラッグされたときは、直前の座標との間に線を描けばうまくいきます。on_pressed()で保存していた座標との間に、tkinter.Canvas#create_line()で線を描きます。
その後、最後の座標(sx, sy)を更新しておきます。こうすることで、さらにマウスを動かしたときに、今度はここから次の座標まで線を引けます。
オプションメニューとスケールを使って、線の色と太さを変更できるようにしてみましょう。
Scribble#create_window() の中頃です。
オプションメニュー (OptionMenuクラス) オブジェクトを生成し、それをウィンドウに配置します。
動作を順に説明すると、
"red", "green" など色の名前を指定することも、"#FF00FF" のように赤、緑、青それぞれ00〜255(16進数で00〜FF)の範囲で指定することもできます。
color に StringVar オブジェクトを代入します。現在の色の意図です。
StringVarオブジェクトは、setメソッドで値を設定でき、getメソッドで値を読み取ることができます。代入だとオブジェクトが差し替わってしまう対策です。
StringVar#set() で設定します。COLORS[n]で (n + 1) 番目の値が得られるので、ここでは2番目の"green"を設定することになります。
OptionMenuオブジェクトを生成します。最初の引数として, 貼り付けるウィンドウのオブジェクトを、2番目の引数としてオプションメニューで選択された項目の値を格納するオブジェクトを指定します。3番目以降の引数で選択肢を指定します。
COLORSの前の「*」は、リストの要素をあたかもソースコード上に展開する記号で、次のように書いたかのように動きます。
tkinter.OptionMenu(window, self.color, "red", "green", "blue", "#FF00FF", "black")
OptionMenuオブジェクトの pack メソッドで、左詰めにします。
点を打つところ、線を引くところで、self.color.get() を呼び出して、そのときそのときの選択色を使うようにします。
線の太さを, スケールを使って変えられるようにしましょう。create_window() の後半です。
スケールオブジェクトを生成し、ウィンドウに貼り付けます。
tkinter.Scale()でスケールオブジェクトを生成します。最初の引数としてウィンドウのオブジェクトを指定します。キーワード引数from_ とtoで、スケールの範囲を指定します。また、orientで方向を指定します。tkinter.HORIZONTALだと水平方向のスケールになります。
Scaleオブジェクトのsetメソッドで指定します。
Scaleオブジェクトのpackメソッドで、左詰めで詰めます。
on_pressed(), on_dragged() にて, self.width.get() で現在のスケールの値を得ます。これでペンの太さと色を変えられるようになりました(画面4)。
●