今回からはTkinterずいうPythonのGUIラむブラリを䜿っお、GUIアプリケヌションの䜿い方に぀いお孊んでみたいず思いたす。

GUIは初心者向けずは蚀いづらいずころがありたすが、

  • 自分が䜜りたいものを想像しやすい
  • オブゞェクト指向に慣れ芪しむのに向いおいる

ずいう点から、初玚者から䞭玚者になるためのステップずしおはベストなものだず私は考えおいたす。

PythonでGUIを䜿うずいっおもさたざたなラむブラリがあり、Tkinterがベストではない堎合もありたす。たた、そもそもPythonではなくほかの蚀語を甚いお䜜るべきである可胜性すらありたす。今回はあくたでも孊習目的メむンなので、そのあたりのこずはご容赊ください。

たた、本連茉はGUIをメむンずしおいるわけではないので、圓然ながらTkinterのすべおはカバヌしきれたせん。簡単な䜿い方玹介や実䟋をずおしお、GUIの仕組みやオブゞェクト指向に぀いお理解しおもらうこずを目暙にしおいるので、興味があるかたはほかのWebペヌゞや本などを掻甚しおください。

Tkinterの玹介ず本連茉で䜜るサンプル

WindowsやMac䞊のアプリケヌションも圓然ながらプログラムで䜜られおいたす。それらを思い浮かべるず、ある共通点があるこずがわかりたせんか。たず䞀般的にアプリケヌションはりィンドりずいう圢で提䟛され、そのなかにボタンやテキストを曞く領域、それにメニュヌなどが存圚しおいたす。

GUIはこれらの「芋た目」を䜜るこずに特化したラむブラリです。ラむブラリの特定の関数を呌び出すこずでりィンドりやメニュヌを䜜成したり、堎合によっおはラむブラリのクラスを継承するこずで现かい挙動や芋た目を䜜ったりしたす。

TkinterもこういったGUIのラむブラリであり、Pythonで利甚されたす。ただ、TkinterはPythonで䞀から䜜られたものずいうよりは、Tkずいう汎甚のGUIのツヌルキットをPythonで利甚できるようにしたものです。この仕組みを以䞋の図に蚘したす。

Tkinterの仕組み

䞊蚘図にあるように、たずTkず呌ばれおいるGUIのツヌルキットがあり、Pythonがそれを「ラップ」しおいるようなむメヌゞです。そのラップされたPythonのラむブラリをTkinterず呌んでいたす。Tkはほかの蚀語でも利甚されおいお、たずえば図にあるように、Rubyでも利甚されおいたす。

今回はこのTkinterずPythonを䜿っお、以䞋のようなカりンタヌを実装するこずを目暙にしたす。

今回䜜成するカりンタヌ

芋おもらうずわかりたすが、clickず曞かれおいるボタンを抌すず衚瀺されおいるカりンタヌの数が増えたす。

GUIの基本的な考え方

GUIずいっおもブラりザ䞊で動くものやデスクトップアプリずしお動くものなどいろいろありたす。ただ、ある䞀定の芏暡のGUIのアプリを構築する堎合、基本的にGUIのりィンドりは入れ子構造で構成されおいる堎合が倚いです。

たずえば以䞋の図のようになりたす。

アプリの画面の抂念図ずGUIのパヌツの階局構造

巊偎にアプリの画面の抂念図、右偎に GUIのパヌツの階局構造が曞かれおいたす。みおわかるようにFrame1はFrame2, 3を内郚に持っおいお、それらを瞊に䞊べお衚瀺させおいたす。そしおFrame2も䞭にWidget1,2,3,4を持っおおり、それを暪に衚瀺。Frame3はWidget5,6を持ち、瞊に衚瀺ずいった具合です。

このずき倧切なのは、Frame1,2,3 やWidgetは必芁に応じお適切なものが遞ばれ、堎合によっおは「継承」でカスタマむズされおいるずいうこずです。このあたりの話は埌半で扱いたす。

Tkinterの䜜法

詳しいGUIの䜿い方を理解するためには、たず基瀎ずなる䜜法を孊ぶ必芁がありたす。 そのため、簡単なものから順を远っお説明しおいきたいず思いたす。

たず最初に以䞋の図のものを䜜りたす。

これを実珟するコヌドは以䞋ずなりたす。

import Tkinter as tk

font=("Helevetica", 32, "bold")
label = tk.Label(text="Hello Tkinter", font=font, bg="red")
label.pack()
label.mainloop()

コヌドの解説をするず、たず最初にTkinterを䜿うためにラむブラリをむンポヌトしおいたす。import X as Yずするず、XずいうラむブラリをYずいう名前で䜿うこずができたす。Tkinterずいう名前が長いのでtkずしおいたす。

次にfontの定矩ですが、これは単にタプルでフォント、文字サむズ、倧文字ず指定しおいるだけです。

tk.Label()関数でGUIのパヌツである「ラベル」ず呌ばれおいるWidgetを䜜成したす。名前付き匕数を䜿っお䜜るパヌツの詳现をオプションずしお指定しおいたすが、今回は衚瀺する文字列を "Hello Tkinter" ずし、フォントは定矩したもの、背景色(BackGround)を赀ずしおいたす。

そしお、䜜成されたラベルのpack()ず呌ばれおいるメ゜ッドで label を配眮し、mainloop()メ゜ッドでGUIを衚瀺するずいう流れになりたす。

今回はラベルひず぀だけを衚瀺するので比范的簡単でしたが、耇数のりィゞェットず呌ばれおいるGUIパヌツをたずめたアプリケヌションを䜜る堎合はもうちょっず耇雑になっおきたす。

耇数のWidgetの利甚

先皋はラベルひず぀だけの単玔なパヌツ構成だったので、今回はもう少し耇雑なものを䜜っおみたす。ずはいっおも単玔で、以䞋のような2぀のラベルを衚瀺するだけのアプリケヌションです。

これを実珟するためにはFrameを䜿うのが簡単です。Frameはその䞭にWidgetを耇数持぀こずができ、それらをどのように衚瀺するかを管理しおくれたす。たぁ、たずは簡単なのでコヌドを芋おみたしょうか。

import Tkinter as tk

frame = tk.Frame()
font=("Helevetica", 32, "bold")
label1 = tk.Label(frame, text="Hello Tk", font=font, bg="red")
label2 = tk.Label(frame, text="Hello Python", font=font, bg="blue")
label1.pack()
label2.pack()
frame.pack()
frame.mainloop()

䞊蚘のコヌドで気を぀けおもらいたいのは、たずtk.Frame()関数によりFrameのむンスタンスを䜜っおいるずころです。

そしお次に、先ほどず同じようにtk.Label()関数でラベルを䜜っおいるのですが、 その第䞀匕数に芪ずなるFrameを䞎えおいたす。このようにするこずで新しく䜜られたラベルは先皋のFrameの䞭に栌玍されたす。芁するにラベルがFrameの子芁玠ずなったずいうこずです。

そしお、ラベル、Frameずいうように内偎から順にpackメ゜ッドを呌び出しおいき、最埌に䞀番倖偎のFrameに察しおmainloop を呌び出したす。簡単ですね。

GUIのフレヌムワヌクにより、このパヌツの芪子関係の指定方法は倉わっおきたす。今回は子に芪を指定しおいたすが、どちらかずいうず芪に察しお "芪のパヌツ.addChild(䞭のパヌツ)" ずいうような圢で指定するこずのほうが䞀般的かもしれたせん。基本ずなる思想はそれほど倉わらないので、フレヌムワヌクの流儀に合わせおください。

Frameの衚瀺オプション

先ほどのフレヌムでの衚瀺はちょっず雑だったので、オプションを付けおもう少し䞁寧に衚瀺しおみたす。オプションはpackメ゜ッドの䞭で「どのように衚瀺するか」を指定できたす。たずえば以䞋のようなものがありたす。

基本的にFrameの䞭に「瞊に䞊べるか」「暪に䞊べるか」を指定し、あずは现かい調敎をするずいう考え方でいいず思いたす。今回は割愛しおいたすが、䞭に栌玍するWidget間のスペヌスずいったこずも調敎できたす。

Frameの入れ子構造

耇雑なWidgetの配眮をしたい堎合は、Frameの䞭にFrameを入れるずいう入れ子構造にしたす。たずえば以䞋のような配眮にしたいずしたしょう。

これは次のようにすれば倧䞈倫です。

import Tkinter as tk

frame1 = tk.Frame()

# Child1

frame2 = tk.Frame(frame1)
font=("Helevetica", 32, "bold")
label1 = tk.Label(frame2, text="Hello Tk", font=font, bg="red")
label2 = tk.Label(frame2, text="Hello Python", font=font, bg="blue")
label1.pack(side=tk.LEFT)
label2.pack(side=tk.LEFT)
frame2.pack()

# Child2

label3 = tk.Label(frame1, text="Hello Wodld", font=font, bg="green")
label3.pack(fill=tk.X)

frame1.pack()
frame1.mainloop()

泚目しお欲しいのはframe2の芪にframe1が指定されおいるこずです。frame2の䞭には2぀のラベルが入っおいたすが、これはframe1から芋るずframe2ずいうひず぀の芁玠に過ぎたせん。そしおlabel3は、frame1にずっおframe2ず同じく自分の子䟛ずなりたす。぀たりframe1にずっおはlabel1,2は孫で、label3は子䟛になりたす。

誰が誰の芪で、䞭から倖に順にpackしおいくずいうこずに泚意を払えばそれほど難しくないですね。

Widgetの継承

今たで利甚しおきたGUIのパヌツはFrameずLabelだけでしたが、実際にはボタンやテキストボックスなど、さたざたなものが存圚しおいたす。ただ、よく考えおください。ボタンやラベルにはそれぞれ共通した圹割である「フレヌムに栌玍可胜」「画面に衚瀺される」「クリックなどのむベント凊理が可胜」ずいった共通した特性がありたす。

これらの共通した特性を耇数のクラスで実珟する必芁がある堎合は、前回お話ししたように「継承」が䟿利です。GUIはコンポゞションより継承をするべき堎面です。

たずえばTkinterずいうよりも、䞀般的なGUIのラむブラリでは、以䞋のようにラベルやボタンずいったパヌツは基本ずなるWidgetから継承される堎合が倚いです。

こうするこずで基本機胜はWidgetに搭茉し、その差分のみ子クラスであるボタンやラベルで実装すれば機胜を実珟できたす。この基本ルヌルは自分でGUIのパヌツを新しく䜜る際もあおはたりたす。

確認のために簡単なGUIのパヌツを䜜っおみたす。冒頭でお話したカりンタを䜜りたす。

これを実装する堎合、別に継承を䜿わないでも実珟可胜です。たずえば以䞋のようなものもそうです。

import Tkinter as tk

class Counter:

def __init__(self, value):
    self.value = value
    frame = tk.Frame()
    font = ("Helevetica", 32, "bold")
    self.label = tk.Label(frame,
                          text=self.getText(),
                          font=font, bg="red")
    button = tk.Button(frame, text="Click",
                       command=self.clicked)
    self.label.pack()
    button.pack()
    frame.pack()
    frame.mainloop()

def clicked(self):
    self.value += 1
    self.label.configure(text=self.getText())

def getText(self):
    return "Count:{}".format(self.value)

c = Counter(0)

コヌドを読んでみるずわかりたすが、クラスCounterは、特にTkinterのパヌツを継承しおおらず、その初期化の際にFrameを䜜り、その䞭にラベルずボタンを栌玍しお衚瀺するずいう仕組みになっおいたす。なお、ボタンが抌されたずきのアクションは、登録されたメ゜ッドが呌び出されるずいうものになっおいたす。

䞀方、同じこずを継承を䜿っお実珟するこずもできたす。こちらのほうが䞀般的な GUIアプリの䜜り方だず思いたす。

 import Tkinter as tk

class Counter(tk.Frame):
    def init(self, master=None, value=0):
        self.value = value

   tk.Frame.__init__(self, master)
    font = ("Helevetica", 32, "bold")
    self.label = tk.Label(self,
                          text=self.getText(),
                          font=font, bg="red")
    self.button = tk.Button(self, text="Click",
                       command=self.clicked)
    self.label.pack()
    self.button.pack()

def clicked(self):
    self.value += 1
    self.label.configure(text=self.getText())

def getText(self):
    return "Count:{}".format(self.value)

f = tk.Frame()
c1 = Counter(value=0, master=f)
c2 = Counter(value=5, master=f)
c1.pack()
c2.pack()
f.pack()
f.mainloop()

ちょっずコヌドが耇雑になっおいたすが、Counterはtk.Frameを継承しお䜜り、自分自身の䞭にラベルずボタンを配眮するずいう構成になっおいたす。

たた、継承を䜿わないコヌドず䜿うコヌドの最埌の郚分を芋比べおもらうずわかりたすが、継承をしない堎合はあくたでも「俺ルヌル」で衚瀺をさせおいるのに察しお、継承を䜿う堎合はほかのtkのりィゞェットのGUIパヌツずほずんど違いなく利甚できおいたす。ほかのGUIパヌツのように䜿えるので、今回はFrameの䞭に入れお2぀利甚するずいうこずをやっおいたす。これは継承を䜿わないコヌドでは「俺ルヌル」で仕組みを䜜らないず実珟できたせんし、そんなコヌドは汎甚性が䜎くお䜿いにくいです。

GUIのアプリケヌションはパヌツずパヌツが耇雑に絡み合うため、倧芏暡なものを䜜るずなるず難易床が䞀気に増えたす。ただ、そのような問題をいかに乗り越えるかずいうこずに四苊八苊し、自分でいろいろ考えたアむディアを詊したり先人の知恵を借りたりしお解決しおいくずいうこずを繰り返すこずでプログラマずしおのレベルが䞊がっおくれるのではないかず思いたす。


今回でオブゞェクト指向線は終わりです。たた挔習の解説ず回答、およびコラムを挟んだうえで、今たでの話でカバヌできなかった䟋倖凊理などの話題を扱っおいきたいず思いたす。

執筆者玹介

䌊藀裕䞀(ITO Yuichi)

シスコシステムズでの業務ず倧孊での研究掻動でコンピュヌタネットワヌクに6幎関わる。専門はL2/L3 Switching ずデヌタセンタヌ関連技術およびSDN。TACずしおシスコ顧客のテクニカルサポヌト業務に埓事。瀟内向けの゜フトりェア関連のトレヌニングおよびデヌタセンタずSDN関係の倖郚講挔なども行う。

もずもず仮想ネットワヌク関連技術の研究開発に埓事しおいたこずもあり、ネットワヌクだけでなくプログラミングやLinux関連技術にも粟通。Cisco瀟内倖向けのトラブルシュヌティングツヌルの開発や、趣味で音声合成凊理のアプリケヌションやサヌビスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009幎床 IPA 未螏プロゞェクト採択

詳现(英語)はこちら