tkinterでGUIアプリ - HondaLab/Robot-Intelligence GitHub Wiki

GUIとはGraphical User Interfaceの略語です。 最近のコンピュータやスマホは、すべてGUIが最初から起動しています。 あらためてそれらをGUIだと意識することは少ないかもしれません。 普段つかているブラウザやLINEなども、すべてGUIアプリケーションの一種です。

GUIとの対義語として、CUIがあります。 Character User Interfaceの略語です。 CUIでは文字通り、キーボードからの文字入力しか利用できません。 つまり、マウスや指でタップしたりドラッグしたりできる機能は、GUIアプリだけのものです。

ここでは、PythonをつかってGUIアプリをつくります。 tkinterというモジュールをインポートして利用します。 tkというGUI部品を提供するライブラリをPythonから利用する(つなぐ)のがtkinterです。

参考 http://www.shido.info/py/tkinter12.html

(参考ページでは、Textを使って、簡易的なエディターjmemo.pyをつくっている。 それに、Buttonを追加して、gvim的なレイアウトにした。)

実行イメージ

GUIの基本部品、窓やボタンなどのことをwidgetといいます。

ここでは、すべてのwidgetを網羅的に説明することはしません。 テキスト表示やボタンなど基本的なwidgetの使い方について説明します。 そのほかのwidgetについても、基本的な考え方は同様です。 オプションなどについて他のサイトや書籍などを参考にしながらプログラミングするとよいでしょう。

https://www.nakamuri.info/mw/index.php/いろいろなウィジェット

基本的な考え方は、Frameという枠widgetの中に、ボタンや文字入力枠やテキスト表示などを並べて行きます。 それぞれのwidgetがクラスで定義されたオブジェクト(インスタンス)として扱われていきます。

基本構成

全体をMenu + Button + Text という3つの要素から構成すします。 MenuはtkinterのMenuクラスのインスタンス、ButtonとTextはFrameクラスのインスタンスとしてつくります。

MenuにはFileと編集という2つのプルダウンメニューをもつことにします。

ButtonとTextはそれぞれFrameにして、topとbottomに配置(pack)することにします。

{{ :5.ノート・note:python_note:idfsr1.py |}}

だたのフレーム窓をつくる

tkinterなどをインポートする

import tkinter as Tk
import tkinter.scrolledtext as S
import tkinter.filedialog as D
import os
from subprocess import Popen

Tk.Frameを継承してFrameクラスを作る

class Frame(Tk.Frame):
          
   def __init__(self, master=None):
      Tk.Frame.__init__(self, master)
      self.master.title('簡易的なeditor 1.0')

ScrolledテキストFrame

     f_txt=Tk.Frame(self,relief=Tk.RIDGE, bd=2)
     self.txt = S.ScrolledText(f_txt, font=('Helvetica', '14'))
     self.txt.pack()
     f_txt.pack(side=Tk.BOTTOM)

メイン

if __name__ == '__main__':
   f = Frame()
   f.pack()
   f.mainloop()

課題1

だたのフレームを表示するだけのPythonプログラムを作って、実行してみよう

Menuをつくる

コンストラクタのなかに、menu_barをつくっていく

      menu_bar = Tk.Menu(self, tearoff=0)
      # File
      menu_file = Tk.Menu(menu_bar, tearoff=0)
      menu_bar.add_cascade(label=u"ファイル(F)", menu=menu_file,  underline=5)
      menu_file.add_command(label=u"新規作成(N)", command=self.new_memo, underline=5)
      menu_file.add_command(label=u"開く(O)", command=self.open_memo, underline=3)
      # 以下同様に
  
      # Edit
      menu_edit = Tk.Menu(menu_bar, tearoff=0)
      menu_bar.add_cascade(label=u'編集(E)', menu=menu_edit, underline=3)
      menu_edit.add_command(label=u'全てを選択(A)', command=self.select_all, underline=6)
      menu_edit.add_command(label=u'切り取り(X)', command=self.cut, underline=5,
               accelerator = os.name=='posix' and 'Ctrl-W' or 'Ctrl-X')
      menu_edit.add_command(label=u'コピー(C)', command=self.copy, underline=4, 
               accelerator = os.name=='posix' and 'Alt-W' or 'Ctrl-C')
      menu_edit.add_command(label=u'ペースト(V)', command=self.paste, underline=5, 
               accelerator = os.name=='posix' and 'Ctrl-Y' or 'Ctrl-V')
      menu_edit.add_command(label=u'カーソルのある行を削除', command=self.delete_line, 
               accelerator = 'Shift-Del')

Menuを追加

      try: 例外処理(実行中のエラー)
           self.master.config(menu=menu_bar)     # メニューバーをmasterに配置
      except AttributeError:
         self.master.Tk.call(master, "config", "-menu", menu_bar)

課題2

先に作った、ただフレームを表示するだけのプログラムにMenuを追加してみよう。 commandに指定する関数はまだ作っていないので、省略してください。 何も実行されないMenuをつくります。 関数は後ほど作ります。

ボタンFrame

     f_btn=Tk.Frame(self,relief=Tk.FLAT, bd=2)
     # Button
     Tk.Button(f_btn, text='Load', command=self.open_memo, relief=Tk.FLAT).pack(side=Tk.LEFT)   
     Tk.Button(f_btn, text='Save', command=self.saveas_memo, relief=Tk.FLAT).pack(side=Tk.LEFT)   
     Tk.Button(f_btn, text='terminal', command=self.mate_term, relief=Tk.FLAT).pack(side=Tk.LEFT)  
     Tk.Button(f_btn, text='Exit', command=self.exit, relief=Tk.FLAT).pack(side=Tk.LEFT)   
     f_btn.pack(side=Tk.TOP)

課題3

コンストラクタ内にボタンFrameを追加しよう。

コールバック関数

commandなどの引数の中で指定される関数のことをコールバック関数といいます。 以下に定義していきます。

ボタンで実行する関数

   def mate_term(self, event=None):
      cmd = "mate-terminal &"
      os.system(cmd)

ファイル新規作成

   def new_memo(self, event=None):
       self.file_name=None
       self.master.title(os.name=='posix' and 'untitled' or u'無題')
       self.txt.delete('1.0', Tk.END)

ファイルを開く

   def open_memo(self, event=None):
       fname = D.askopenfilename(filetypes =[('Python Files', '*.py'), ('text files', '*.txt'), ('all files', '*.*')])
                                    
       if fname:
          self.txt.delete('1.0', Tk.END)
          f=open(fname) # Python3ではopenを使う
          self.txt.insert(Tk.END, f.read()) 
          f.close()
          self.file_name = fname
          self.master.title(fname)

   def open_vi(self, event=None):
      fname = D.askopenfilename(filetypes =[('Python Files', '*.py'), ('text files', '*.txt'), ('all files', '*.*')])
                                    
      if fname:
          cmd = "gvim "+fname 
          vi = Popen( cmd .strip().split(" ") )  #プロセス生成

保存関数

   def save(self, f):
      f.write(self.txt.get('1.0', Tk.END)) 
      f.close()           
  
   def save_memo(self, event=None):
      if self.file_name:
         self.save(open(self.file_name, 'w'))
      else:
         self.saveas_memo()

ファイル名をつけて保存関数

   def saveas_memo(self):
      fname = D.asksaveasfilename(filetypes =[('Python Files', '*.py'), ('text files', '*.txt', '*.py')])
      if fname:
         self.save(open(fname, 'w'))
         self.file_name=fname
         self.master.title(fname)

編集関数

   def select_all(self, event=None):
      self.txt.tag_add(Tk.SEL, '1.0', Tk.END+'-1c')
      self.txt.mark_set(Tk.INSERT, '1.0')
      self.txt.see(Tk.INSERT)

   def cut(self, event=None):
      if self.txt.tag_ranges(Tk.SEL):
         self.copy()
         self.txt.delete(Tk.SEL_FIRST, Tk.SEL_LAST)
 
   def copy(self, event=None): 
      if self.txt.tag_ranges(Tk.SEL): 
         text = self.txt.get(Tk.SEL_FIRST, Tk.SEL_LAST)  
         self.clipboard_clear()              
         self.clipboard_append(text)
 
   def paste(self, event=None):
      text = self.selection_get(selection='CLIPBOARD')
      if text:
         self.txt.insert(Tk.INSERT, text)
         self.txt.tag_remove(Tk.SEL, '1.0', Tk.END) 
         self.txt.see(Tk.INSERT)

   def delete_line(self, event=None):
      self.txt.delete(Tk.INSERT + " linestart", Tk.INSERT + " lineend")

終了関数

   def exit(self, event=None):
      self.master.destroy()

課題4

各コールバック関数を定義して、メニューバーやボタンを押したときに実行されるプログラムをつくろう。

文字ラベル

フレーム内に短い文字列を表示するときには、Labelを用いる。

label1=tk.Label(フレーム名, text="表示文字列")
label1.pack(side=tk.LEFT)

ここで、「フレーム名」とは、ボタンフレームの際のf_btnのような、Frameクラスあるいは、LabelFrameクラスのインスタンスのことです。

表示する文字列は、上記のように、Labelのtextキーワードで指定する。 packを実行して、フレームにこのLabelを貼り付ける。

grid

ボタンの場合も同様だが、ラベルも、Frame内の配置を細かく指定したい場合には、gridを用いる。

label1.grid(row=0, column=0, sticky=tk.W)

貼り付ける行をrowで、列をcolumnで指定する。この値を調節することで、ButtonやLabelの配置を細かく指定できる。 stickyをtk.Wとすることで左寄せで表示する。

入力Entry

文字の入力はEntryを用いて行う。

entry1= = tk.Entry(フレーム名)
entry1.insert(tk.END, "デフォルト文字列")
entry1.grid(row=0, column=1, sticky=tk.W)

Entryにはデフォルト文字列をinsertメソッドで指定することができる。

Entryに入力した文字列はget()メソッドで読み取ることができる。

food=food+ int(entry1.get())

たとえば、ここでは、entry1に入力された数値をfoodという変数に整数として足した。

課題5

種類、日付、金額、備考をEntryとして入力し、その値をファイルに書き出すプログラムを作成してみよう。

新しい窓を開く

計算結果などを表示する場合、print文をそのまま使うと、コンソール端末に表示される。 表示用の窓を用意してその中に文字列などを表示することができる。

Toplevelを用いる。

text_win=tk.Toplevel()
text_win.title('文字列表示用の窓')
text_win.geometry('+100+600')
text_frame=tk.Frame(text_win) # text_win用のフレーム
text_frame.pack(side=tk.TOP, fill, tk.X)

この例では、サイズが100x600の窓にtext_frameというフレームを配置している。

テキストの表示

TextをFrameに配置して、insertメソッドで文字列を表示する。

text=tk.Text(text_frame)
・・・
text.insert('1.0',文字列)

text.grid(row=0, column=0, sticky=tk.N)

最初の'1.0'はその文字列のindexです。

課題6

家計簿データファイルから書籍を抜き出して、新しい窓に表示してみよう。