Python(Textウィジェット)

今回はtkinterのTextウィジェットについて以下のサイトで学習しました。
12. Tk.Text の使い方

Textウィジェットでは、文字位置の表し方として、以下のような方法があります。
・ 行、列を指定する方法
・ あらかじめ定義されたインデックスで指定する方法
・ マーク名で指定する方法
・ 埋め込まれたオブジェクトで指定する方法

テキストの表示の仕方を調節したり、テキストを関数と結びつけるのにタグを用いることで、マウスカーソルがやってきたとき、マウスカーソルが去ったとき、マウス左ボタンがダブルクリックされたとき等の処理を行うことができます。

tk.Text は、window_create メソッドを使ってtk.Widget を埋め込むことができるので、作成したアプリケーションを実際に試しながら、説明を読み進めることができる取扱説明書を作成することもできます。

以下、紹介されていたアプリをPython3系に修正して実行可能にしたプログラムを記載します。

簡易メモ帳

import tkinter as Tk
import tkinter.scrolledtext as S
import tkinter.filedialog as D
import os

## classes -----------------------------------------------------------------------------------
class Frame(Tk.Frame):
    """ The main class of this program. """

    def __init__(self, master=None):
        Tk.Frame.__init__(self, master)
        self.master.title(os.name=='posix' and 'untitled' or u'無題')
        self.file_name=None

        ### Menu
        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, accelerator = 'Ctrl-N')
        menu_file.add_command(label=u"開く(O)", command=self.open_memo, underline=3, accelerator = 'Ctrl-O')
        menu_file.add_command(label=u"保存(S)", command=self.save_memo, underline=3, accelerator = 'Ctrl-S')
        menu_file.add_command(label=u"名前をつけて保存(A)", command=self.saveas_memo, underline=9)
        menu_file.add_separator()
        menu_file.add_command(label=u"終了(Q)", command=self.exit, underline=3 , accelerator = 'Ctrl-Q')

        # 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, accelerator = 'Ctrl-A')
        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')

        # short-cuts
        self.master.bind('<Control-KeyPress-o>', self.open_memo)
        self.master.bind('<Control-KeyPress-s>', self.save_memo)
        self.master.bind('<Control-KeyPress-q>', self.exit)
        self.master.bind('<Control-KeyPress-a>', self.select_all)
        self.master.bind('<Shift-KeyPress-Delete>', self.delete_line)
        self.master.bind('<Double-Button-1>', self.delete_line)
        if os.name == 'posix':
            self.master.bind('<Alt-KeyPress-w>', self.copy)

        # add menu bar
        try:
            self.master.config(menu=menu_bar)     # this required to show the menu bar
        except AttributeError:
            self.master.Tk.call(master, "config", "-menu", menu_bar)

        self.txt = S.ScrolledText(self, font=('Helvetica', '10'))
        self.txt.pack(fill=Tk.BOTH, expand=1)
        self.txt.focus_set()

    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 =[('text files', '*.txt'), ('all files', '*.*')])

        if fname:
            self.txt.delete('1.0', Tk.END)
            with open(fname, mode="r", encoding='shift_jis') as f:
                for i in f:
                    self.txt.insert(Tk.END,i)

            self.file_name = fname
            self.master.title(fname)

    def save(self, f):
        with open(f, mode="w", encoding='shift_jis') as fp:
            fp.write(self.txt.get('1.0', Tk.END))

    def save_memo(self, event=None):
        if self.file_name:
            self.save(self.file_name)
        else:
            self.saveas_memo()

    def saveas_memo(self):
        fname = D.asksaveasfilename(filetypes =[('text files', '*.txt')])
        if fname:
            self.save(fname)
            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()

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

ファイル処理について修正することで、ファイルの保存も保存したファイルを開くこともできるようになりました。
(保存するときに拡張子.txtまで入力する必要があります)

アプリケーションが実行可能な取り扱い説明書

import tkinter as Tk
import rhello as RH
import thello as TH
import timer as Ti
import tkinter.scrolledtext as S
import os

PATH = CLOCK = os.path.dirname(__file__)+"/"

## functions ------------------------------------------------------------
def read_contents(fname):
    """ read contens of `fname' """
    with open(PATH+fname, mode="r") as f:
        string = ""
        for i in f:
            string += i
    return string    

def left_slide(string):  
    ls0 = string.split('+')
    ls1 = ls0[0].split('x')
    return ('500x600+%d+%s' % (int(ls1[0]) + int(ls0[1]), ls0[2]))

## classes ---------------------------------------------------------------
class Frame(Tk.Frame):
    """ The main class of this program. """

    def __init__(self, master=None):
        Tk.Frame.__init__(self, master)
        self.visited=dict()
        self.code_window = None
        self.master.geometry('500x600+20+20')
        self.st = S.ScrolledText(self, padx=20, pady=25, cursor='arrow', wrap=Tk.WORD, font=('Helvetica', '12'))
        self.st.pack(fill=Tk.BOTH, expand=1)

        ### tag configuration
        self.st.tag_config('h1', font=('Helvetica', '24'), justify=Tk.CENTER)
        self.st.tag_config('h2', font=('Helvetica', '18'))
        self.st.tag_config('link', font=('Helvetica', '12'), foreground='blue', underline=1)
        self.st.tag_config('em', font=('Helvetica', '12', 'bold'), foreground="red")
        self.st.tag_config('cite', font=('Helvetica', '12'), foreground="navy")
        self.st.tag_config('var', font=('Helvetica', '12', 'bold'), foreground="darkgreen")
        self.st.tag_config('center', justify=Tk.CENTER)
        self.st.tag_bind('link', "<Double-Button-1>", self.on_clicked)
        self.st.tag_bind('link', "<Enter>", self.on_enter, '+')
        self.st.tag_bind('link', "<Leave>", self.on_leave, '+')

        ### title
        self.st.insert(Tk.END, u'Label を動かして遊んでみよう\n', 'h1' )
        hr = Tk.Frame(self.st, relief=Tk.RIDGE, height=0, width=460)
        self.st.window_create(Tk.END,  window=hr, align=Tk.CENTER)
        self.st.tag_add('center', hr)
        self.st.insert(Tk.END, u'\n')

        ### section 1
        self.st.insert(Tk.END, u'1. 初めに\n\n', 'h2' )
        self.st.insert(Tk.END,    
             u"Tk のデモのラベルの項には、 ”ラベルは動かせないからつまらない”\n")
        self.st.insert(Tk.END,    
             u"(\"Labels are pretty boring because you can't do anything with them.\")\n", 'cite')
        self.st.insert(Tk.END,    
             u"と書かれていますが、マウスのクリック、キープレスなどのイベントと結びつけると"
             u"いろいろと動かすことができます。\n"
             u"ここでは、クリックするとランダムに文字色が変わる")
        self.st.insert(Tk.END,  u'Hello world', 'var')  
        self.st.insert(Tk.END,  u'、クリックすると文字の温度が上がっていく ')  
        self.st.insert(Tk.END,  u'Hello world', 'var')  
        self.st.insert(Tk.END,  u'、クリックすると動き出す ')  
        self.st.insert(Tk.END,  u'Timer', 'var')  
        self.st.insert(Tk.END,  u' を例にとって 説明 しようと思います。 \n\n')  

        ## section 2
        self.st.insert(Tk.END, u'2. 色がランダムに変わるラベル\n\n', 'h2' )
        self.st.insert(Tk.END, u'とりあえず、刺激に反応するラベルを作って見ましょう。\n'
                          u'図1は一見何の変哲もない Hello world ですが、 クリック すると 背景色が変わります。\n(')
        self.st.insert(Tk.END, u'コードを見る', ('link', 'rhello.py') )
        self.st.insert(Tk.END,  ")\n\n")                          
        hello1 = RH.Label(self.st, relief=Tk.RIDGE)
        self.st.window_create(Tk.END,  window=hello1, align=Tk.CENTER, padx=20, pady=20)
        self.st.tag_add('center', hello1)
        self.st.insert(Tk.END, u'\n図1: クリックすると背景色が変わる hello world\n', 'center')
        self.st.insert(Tk.END,    u'実際にクリックしてみてください。\n背景色が変わります。\n\n'
                             , ('center','em'))

        ## section 3
        self.st.insert(Tk.END, u'3. 反応が継続するラベル\n\n', 'h2' )
        self.st.insert(Tk.END, u'次に動作が継続するラベルを作ってみましょう。\n'
                          u'図2クリックすると Hello world の文字が現れ、 文字の 温度が 徐々に上昇していきます。\n(')
        self.st.insert(Tk.END, u'コードを見る', ('link', 'thello.py') )
        self.st.insert(Tk.END,  ")\n\n")
        hello2 = TH.Frame(self.st, relief=Tk.RIDGE)
        self.st.window_create(Tk.END,  window=hello2, align=Tk.CENTER, padx=20, pady=20)
        self.st.tag_add('center', hello2)
        self.st.insert(Tk.END, u'\n図2: クリックすると文字色の温度が上がる hello world\n', 'center')
        self.st.insert(Tk.END,    u'実際にクリックしてみてください。\n文字色が変わります。\n'
                             u'右クリックで元に戻ります。\n\n', ('center','em'))

        ### section 4
        self.st.insert(Tk.END, u'4. Label だけでタイマーを作ろう\n\n', 'h2' )
        self.st.insert(Tk.END,    
                 u"実用的な例として、 図3に示すようなアラームクロックをラベルだけで作ってみましょう。"
                 u"このアラームはマウスの左ボタンで Start/Stop し、右ボタンで Reset します。\n(" )
        self.st.insert(Tk.END, u'コードを見る', ('link', 'timer.py') )                 
        self.st.insert(Tk.END,  ")\n\n")
        app1 = Ti.Frame(self.st, 3, relief=Tk.RIDGE)
        self.st.window_create(Tk.END,  window=app1, align=Tk.CENTER, padx=20, pady=20)
        self.st.tag_add('center', app1)
        self.st.insert(Tk.END,  "\n")
        self.st.insert(Tk.END, u'\n図3: Label だけでできたアラームクロック\n', 'center')
        self.st.insert(Tk.END,    u'実際にクリックしてみてください。\n動いたり止まったりします。\n\n', ('center','em'))
        self.st.insert(Tk.END,    u'この時計は残り 20 秒をきると、 図4を クリックした ときに 見えるように、'
                             u' 時計アイコンの背景が黄色くなります。\n')
        app2 = Ti.Frame(self.st, 0.2, relief=Tk.RIDGE)
        self.st.window_create(Tk.END,  window=app2, align=Tk.CENTER, padx=20, pady=20)
        self.st.tag_add('center', app2)
        self.st.insert(Tk.END, u'\n図4: 残り 20 秒を切ったところ。\n', 'center')
        self.st.insert(Tk.END, u'実際にクリックしてみてください。\n動いたり止まったりします。\n\n', ('center','em'))
        self.st.insert(Tk.END,    u'さらに、残り時間がなくなると、図5をクリックしたときに見えるように、'
                             u'時計アイコンの背景が赤、黄と点滅します。\n')
        app3 = Ti.Frame(self.st, 0.0, relief=Tk.RIDGE)
        self.st.window_create(Tk.END,  window=app3, align=Tk.CENTER, padx=20, pady=20)
        self.st.tag_add('center', app3)
        self.st.insert(Tk.END, u'\n図5: 残時間がなくなったところ。\n', 'center')
        self.st.insert(Tk.END, u'実際にクリックしてみてください。\n動いたり止まったりします。\n\n', ('center','em'))

        ### section 5
        self.st.insert(Tk.END, u'5. 終わりに\n\n', 'h2' )
        self.st.insert(Tk.END, u'Tk.Label だけでも動かせるアプリを作れることがお分かりいただけたと思います。\n'
                          u'また、Tk.Text を使うと インターラクティブな 説明が できることが お分かりいただけたと思います。\n\n'
        )
        self.st.configure(state=Tk.DISABLED)

    ##----
    def on_leave(self, event):
        self.st.tag_config(self.link, foreground=self.link in self.visited and 'deepskyblue' or 'blue')        

    def on_clicked(self, event):
        if self.code_window and self.code_window.winfo_exists():
            self.code.delete('1.0', Tk.END)
        else:
            self.code_window = Tk.Toplevel()
            self.code_window.geometry(left_slide(self.master.winfo_geometry()))
            self.code = S.ScrolledText(self.code_window, wrap=Tk.WORD)
            self.code.pack(fill=Tk.BOTH, expand=1)

        self.code_window.title(self.link)
        self.code.insert(Tk.END, read_contents(self.link))
        self.code_window.focus_set()
        self.visited[self.link]=True

    def on_enter(self, event):
        tags = self.st.tag_names(Tk.CURRENT)
        self.link = tags[-1]
        self.st.tag_config(self.link, foreground="#FF0099")

##------------------------------------------------ 
if __name__ == '__main__':
    f = Frame()
    f.pack(fill=Tk.BOTH, expand=1)
    f.mainloop()

以前紹介されていた「イベントをバインドしたHelloWorldラベル」や「簡易タイマー」のプログラムファイルとリンクさせ、それぞれをウィジェットとしてTextウィジェット内に表示させた説明書になります。

サイトで紹介されていた初期の各アプリプログラムでは、bind_all()メソッドでイベント実行していたため、そのままでは他のプログラムで同様の処理がされた場合、イベントが上書きされてしまい、一番最後のプログラムしか実行されません。
そこで、各プログラム自身のオブジェクトに対してbind()メソッドを設定しています。

また、デフォルト引数にオプションが設定できるようにしたり、「簡易タイマー」では、引数で時間の設定ができるようにし、タイトルはrootでないと設定できないので削除したりという修正が行われています。

ファイル処理については、ファイル名だけでは読み込めなかったので、絶対パスを渡しています。


これまでの学習から、2DのGUIツールは、tkinterモジュールで大体作れそうです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

Python

前の記事

Python(Canvasウィジェット)
Python

次の記事

Python(Timerアプリをexe化)