naritoブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

Tkinter、Textを使う

プログラミング関連 Python Tkinter 約5日前
2017年10月12日14:41
今回はtk.Textの説明です。これは複数行の文字列を書いたりするのに使います。

文字を入力することも当然できます。




単純に作成するだけならば、tk.Text()とするだけです。
import tkinter as tk

root = tk.Tk()
root.title('Editor Test')

text_widget = tk.Text(root)
text_widget.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W))

root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.mainloop()





tk.Textを使う上でまず理解するべきなのは、文字の位置を表す方法です。
文字を指定位置に挿入するinsertメソッドを使いながら、実際に見ていきましょう。
text_widget.insert('1.0', 'あいうえお\nかきくけこ\nさしすせそ')


insertメソッドは、第一引数に文字の位置を、第二引数に挿入する文字を指定します。
'1.0'は、1行目0文字目を表します。行番号は1から、文字番号は0から始まります。
Textの一番最初に文字を挿入するときは、'1.0'を使うことになるでしょう。




2.0は、2行目0文字目です。
text_widget.insert('1.0', 'あいうえお\nかきくけこ\nさしすせそ')
text_widget.insert('2.0', '2行目に挿入されます\n')



このように、ちゃんと2行目0文字目に挿入されますね。



1行目、3文字目に文字を挿入してみましょう。
text_widget.insert('1.0', 'あいうえお\nかきくけこ\nさしすせそ')
text_widget.insert('1.3', '★')


これもOKですね。



実際のところは、何かのイベントの際に文字を挿入したりすることの方が多いです。
F1キーを押したら起きる関数を定義し、この関数からTextウィジェットの各機能を操作していきます。
import tkinter as tk


def test(event):
    pass


root = tk.Tk()
root.title('Editor Test')

text_widget = tk.Text(root)
text_widget.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W))

root.bind('<F1>', test)

root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.mainloop()




今までは1.0 のように指定していましたが、位置を表す特殊な文字列があります。
test関数をこのようにし、F1で呼び出してみましょう。
def test(event):
    text_widget.insert('end', 'おわりです')



何となく察した方もいると思いますが、これは最後の位置に文字を挿入します。



次はこのように。これもわかりやすいですね。
def test(event):
    text_widget.insert('insert', 'カーソル位置に挿入されます')



これはTextウィジェット内の、カーソル位置に文字列が挿入されます。
画像だとカーソルが消えてますが、ちゃんとカーソル位置に「カーソル位置に挿入されます」が挿入されています。




これらの位置指定方法は、insertメソッド以外でも使えます。全て共通の指定方法です。
例えば、ウィジェット内の文字を取得するgetメソッドを使ってみましょう。
第一引数に取得の開始位置、第二引数は取得の最後の位置です。
def test(event):
    result = text_widget.get('1.0', 'end')
    print(result)


このように入力し、F1を押してみます。



このように出力されます。全ての文字を取得できているようですが...
あいうえお
かきくけこ
さしすせそ




以下のように、reprで出力してみるとあることに気づきます。
def test(event):
    result = text_widget.get('1.0', 'end')
    print(repr(result))



出力を見ると、「さしすせそ」の後に改行は入れていません。なのに改行がたされています。
テキストエディタなんかを作ってると、ファイルに保存するたびに改行が足されていき、いつのまにか改行だらけになる、といったこともよく起こります。
'あいうえお\nかきくけこ\nさしすせそ\n'



なので、最初から最後の文字を取得する場合は以下のようにします。
'文字位置'の後には、いくつかの特別な文字を足すことができます。
今回の-1cは「-1 char」を意味しています。endから1文字引いた位置、ということですね。
result = text_widget.get('1.0', 'end -1c')



-もあれば、+もあります。カーソル位置の次にある文字を確認するならば、以下のようにできます。
def test(event):
    result = text_widget.get('insert', 'insert +1c')
    print(result)



linestart,、lineendというものもあります。これは行の最後、最後を意味します。
以下は、カーソルがある行のテキストを全て取得する例です。
def test(event):
    result = text_widget.get('insert linestart', 'insert lineend')
    print(result)



以下のようにすると、カーソル位置までの現在行のテキスト
def test(event):
    result = text_widget.get('insert linestart', 'insert')
    print(result)



以下のようにすると、カーソル位置以降の現在行のテキスト
def test(event):
    result = text_widget.get('insert', 'insert lineend')
    print(result)



getメソッドが使いこなせれば、deleteメソッドも扱えます。
以下は1.0からend、つまり文字を全部消します。
def test(event):
    text_widget.delete('1.0', 'end')



あまり使う機会はないかもしれませんが、現在入力中の単語を取得する例はこんな感じで書けます。
エディタのコード補完に使っています。
def test(event):
    text = ''
    start_i = 1
    end_i = 0
    while True:
        start = 'insert-{0}c'.format(start_i)
        end = 'insert-{0}c'.format(end_i)
        text = text_widget.get(start, end)

        # 1文字ずつ見て、スペース、改行、タブ、空文字にぶつかったら終わり
        if text in (' ', '\t', '\n', ''):
            text = text_widget.get(end, 'insert')
            print(text)
            break

        start_i += 1
        end_i += 1



他によく使うのは、indexメソッドです。
以下は、文字挿入カーソルの位置を返します。
def test(event):
    pos = text_widget.index('insert')
    print(pos)


以下のような文字列が返ってきます。「行.列」の形ですね。
1.0
5.0
8.1



insert lineend のような指定も勿論できます。
def test(event):
    pos = text_widget.index('insert linestart')
    print(pos)



長い行のテキストを書いたとします。100行や200行になると、全てを表示できなくなり、スクロールする必要があります。
スクロールをしたあと、一番上に表示されてるのは実際の行番号で言えばどこか?といったことを知りたいこともあるでしょう。
下の画像は5行目なら5、と数字を入れていますが、この数字がなくなったらどう判別できるでしょうか。



これは結構簡単です。'@0,0' (ドットじゃなく、コンマです)とすると、Textウィジェット内で今見えている位置で、最初の'行.0'を返してくれます。
def test(event):
    pos = text_widget.index('@0,0')
    print(pos)



ちゃんと5.0と表示されました。
5.0



正確には、'@x,y'という書き方はウィジェット内のx, y座標に近い文字がある位置を返します。
今回はx座標0, y座標0 なので、Textウィジェットの一番左上の部分の位置を返した、ということです。


他によくやるのは、以下のように文字を選択した状態で...



選択の開始位置、終了位置を知りたい場合です。
これは以下のように書けます。
def test(event):
    start = text_widget.index('sel.first')
    end = text_widget.index('sel.last')
    print(start, end)


ちゃんと取得できていますね。
2.0 3.5



Textにはタグという機能があり、これを使うと文字の一部の色を変えたり色々できます。
selというのはタグの名前で、選択状態時に使用されるタグです。これはデフォルトで用意されているタグで、自分でカスタムタグを定義することもできます。
このタグも文字位置として利用でき、sel.first のようにtag_name.first tag_name.last が使えます。


ちなみにですが、単純に範囲を取得したいだけならばtag_rangesを使うこともできます。
もし選択が何もなければ空タプルが帰ります。それをexcept ValueErrorで対処してもいいし、
pos = text_widget.tag_ranges('sel') として1変数にまず代入し、posが空かどうかで判別しても良いです。
def test(event):
    start, end = text_widget.tag_ranges('sel')
    print(start, end)  # 2.0 3.5のように表示される。



範囲の文字を取得するならば、getメソッドを使います。
def test(event):
    # こちらで取得しても良い
    # start = text_widget.index('sel.first')
    # end = text_widget.index('sel.last')

    start, end = text_widget.tag_ranges('sel')
    text = text_widget.get(start, end) 
    print(text)




タグを反映するには、tag_add(tag_name, start, end)のように指定します。
例えば、全ての文字を選択状態にするならば以下のようにする...
def test(event):
    text_widget.tag_add('sel', '1.0', 'end')



タグを解除するには、tag_removeを同様に使うだけです。
def test(event):
    text_widget.tag_remove('sel', '1.0', 'end')



独自のタグも定義してみましょう。
選択した部分に、色をつけてみる例です。
まずはタグの定義をどこかに入れておきましょう。
text_widget.tag_configure('Red', foreground='#ff0000')



そして処理
def test(event):
    # 選択範囲の取得を試みる
    try:
        start, end = text_widget.tag_ranges('sel')

    # 何も選択していない場合
    except ValueError:
        pass

    # ちゃんと選択されていた場合
    else:
        text_widget.tag_add('Red', start, end)



選択して、F1を押すと...


ちゃんと赤くなりました。



この独自タグも'sel.first'のように、'Red.first'と使えますが、注意点があります。
それはタグを複数つけた場合です。



以下の場合は、始めと終わりだけ取得されます。
start = text_widget.index('Red.first')
end = text_widget.index('Red.last')
print(start, end)


1.4 3.5


text_widget.tag_ranges('Red')を使った場合は、全ての範囲を返してくれます。
なので、同じタグを複数使う場合は、pos = text_widget.tag_ranges('Red')を使うほうが無難でしょう。
pos = text_widget.tag_ranges('Red')
print(pos)


(<textindex object: '1.4'>, <textindex object: '1.5'>, <textindex object: '2.4'>, <textindex object: '2.5'>, <textindex object: '3.0'>, <textindex object: '3.5'>)