kivyMDチュートリアル其の参什壱 Components - TextField篇

どうもー、virtyですー。よろしくお願いしますー。

なんとなく、漫才コンビのような挨拶で始まりましたけれども、今日も元気にやって
いきたいと思います。

というのはどうでもよいことですが、いかがお過ごしでしょうか。なんか寒かったり
暑かったり嫌な季節ですね。カラッと過ごしやすかったり、ジメジメしたりとなかなか
安定することはありません。

今週から、おしごとの方が再開していやだーとなっている方が多いでしょうかね。自身
としてはおかまいなく仕事をしていましたが、街の感じを見てるとなんかそんなことを
思っていました。気のせいですかね。

という前置きを挟みながら、今日はというとTextField篇となります。なかなか量が
多いので次週に持ち越したいなーと勝手に思っていますが、できる限り頑張りたいと
思います。よし、がんばるぞい。

## TextField

とまぁ、元気にマテリアルデザインのリンクは飛ばしはするのですが・・
時間があったらね。取り組みたいのだけれども。

これほど、歴史のあるUIコンポーネントもなかなかないのではないでしょうか。他は
ボタンとかはあるけれども・・それほど、GUI開発をする上ではなくてはならない存在
かと思われます。

KivyMDではこれまでのテキストフィールドの互換性を持たせながら、バリデーションや
インタラクティブな使用が出来るようになっています。まぁ、これもMaterialDesignの
仕様として定められているので、ここから相違はないというのはあるのですが。いや、あっ
たらごめんなさい。もしかしたらKivyMDというのもあるかも。

KivyMDとしては以下のように概要が書かれています。

> Text fields let users enter and edit text.

さらに、用意されているクラスは以下の3種類ということも記載があります。

* MDTextField
* MDTextFieldRound
* MDTextFieldRect

基本的には文字通り、普通タイプと角丸タイプ、それから角四角タイプみたいなものが
あるのだなと予想がつくかと思われます。実際にそれぞれ見ていきましょう。

とその前に、なにやらNoteパネルで有益そうな情報が書いていますね。見てみましょう。

:::note
MDTextField inherited from TextInput. Therefore, most parameters 
and all events of the TextInput class are also available in the MDTextField class.
:::

どうやら、MDTextFieldはKivyのTextInputを継承しているようですね。なので
TextInputのパラメータやイベントとかが使えるよという案内になります。何が使える
のってなったら、以下のリンクを見た方が良さそうです。

https://kivy.org/doc/stable/api-kivy.uix.textinput.html

### MDTextField

こちらも概要が書かれています。

> MDTextField can be with helper text and without.

helper textとそれがなしかという2通りあるということでしょうか。今回は依頼
してないよ、というどうでもよい話は置いておいて。

1つずつ丁寧に見ていきたいというのはあったのですが、イロイロな事情もありで
ここに書かれているものをまとめてみました。コードも見た方が早いと思うので
さっそくコードに入っていきますね。

```python:xxxi/textfield.py
from kivy.lang import Builder

from kivymd.app import MDApp

KV = '''

ScrollView:

    MDList:
        MDLabel:
            text: "MDTextField"
        
        # Without helper text mode
        MDTextField:
            hint_text: "No helper text"
    
        # Helper text mode on on_focus event
        MDTextField:
            hint_text: "Helper text on focus"
            helper_text: "This will disappear when you click off"
            helper_text_mode: "on_focus"

(略)    

'''


class Test(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = Builder.load_string(KV)
    
    def build(self):
        self.screen.ids.text_field_error.bind(
            on_text_validate=self.set_error_message,
            on_focus=self.set_error_message,
        )
        return self.screen

    def set_error_message(self, instance_textfield):
        self.screen.ids.text_field_error.error = True

Test().run()

```

少し長いのでkvで定義したものは端折りました。これはkv側でじっくり触れたいと
思います。

#### kv側

インポート文に関しては特にいつも通りなのでスキップします。

少し、kv側は前半少し記載をしていましたが、それ以降は端折っています。少しこれ
だけだとなんのこっちゃとなりそうなので、全て定義したものを以下に抜粋しておき
ます。MDLabelは定義したプロパティを省略できるところは省略しています。

```python
ScrollView:

    MDList:
        MDLabel:
            ~
        
        #1 Without helper text mode
        MDTextField:
            hint_text: "No helper text"
        
        #2 Helper text mode on on_focus event
        MDTextField:
            hint_text: "Helper text on focus"
            helper_text: "This will disappear when you click off"
            helper_text_mode: "on_focus"
    
        #3 Persistent helper text mode
        MDTextField:
            hint_text: "Persistent helper text"
            helper_text: "Text is always here"
            helper_text_mode: "persistent"
    
        #4 Helper text mode ‘on_error’
        MDTextField:
            id: text_field_error
            hint_text: "Helper text on error (press 'Enter')"
            helper_text: "There will always be a mistake"
            helper_text_mode: "on_error"
    
        #5 Helper text mode ‘on_error’ (with required)
        MDTextField:
            hint_text: "required = True"
            required: True
            helper_text_mode: "on_error"
            helper_text: "Enter text"

        #6 Text length control    
        MDTextField:
            hint_text: "Max text length = 5"
            max_text_length: 5
    
        #7 Multi line text
        MDTextField:
            multiline: True
            hint_text: "Multi-line text"

        #8 Rectangle mode
        MDTextField:
            hint_text: "Rectangle mode"
            mode: "rectangle"
        
        #9 Fill mode 
        MDTextField:
            hint_text: "Fill mode"
            mode: "fill"
        
```

ということで合計9つのMDTextFieldが出来上がっています。全てのコンポーネントで
ヒントテキストという共通のプロパティがあることは注目ポイントの内の1つです。また、
全てマニュアルで例を出されているものから変わっていませんので、そこもまた記憶に
留めて頂ければ。

ヒントテキストとは、これはマテリアルデザインのリンクを見れば分かりやすいですが、
簡単に言うとテキストフィールドがフォーカスされる前にその中で表示するテキストの
ことです。「お前は何言ってるんだ？」状態になった方は先に結果の方を見るとよいで
しょう。

では、1つ1つがどのように異なっているか見てみましょう。

1つ1つと言ったばかりですが、まとめて触れるんですけどね。
とまぁどうでもよいことは置いておいて、1~3個目をまとめてみます。

まず1つ目は「Without helper text mode」と名前が付いているばかりでヘルパー
テキストがないということになりますね。これはどういうことかと言うとテキストフィ
ールドがフォーカスするときに、すぐ下あたりに補助文みたいなテキストが表示され
ます。2つ目がそれがあるときと3つ目がそれをフォーカスするしないに関わらず、
常に表示させておく設定です。

少しこれだけだと説明が完全ではないので、1つ目はなんとなく分かるけど2つ目と
3つ目はどう分けるんじゃいという方のために補足です。これは話は単純で、「helper
_text_mode」でモードを切り分けます。それがそれぞれ定義されている、「"on_focus"」
と「"persistent"」という2つになります。

4つ目はというと、これは「Helper text mode ‘on_error’」でサンプルコードが
あったものを無理くり繋げてみたのでメインとしてはクラス側となります。後ほど詳細
を述べます。あれっ、プロパティは触れないの？という方はご名答なのですが、プロパ
ティは5つ目とほぼ変わらないので、そちらにお譲りします。

で、お譲りされた5つ目の「Helper text mode ‘on_error’ (with required)」
ですが、プロパティとしては新しいものとして「required」があります。これは必須
項目を意味するプロパティになり、何も入力がされないとエラー表示をします。その他は
1~3個目から変わりありませんが、requiredプロパティと関連させてhelper_text_mode
プロパティは「"on_error"」だということに注意が必要ですね。

ちなみに、4つ目との違いになりますが、requiredプロパティがなく代わりにidを定め
てクラス側でよりバリデーションをカスタムするということになります。今回はEnter
を押した結果、全てエラーと表示させるみたいなことをやっています。

で、6~7つ目に入りますが、まず6つ目はテキストフィールドのテキスト文字列の長さを
元にバリデーションをしています。こういうのやってもらえるのはありがたいですね。
さらに7つ目はというと、改行込みでのテキストフィールドになりますね。特に触れて
なかったですが、これまでは全て1行表示でのテキストフィールドでした。

んで、またプロパティについて触れていないので触れておくと、6つ目は「max_text_
length」プロパティを使用します。7つ目は「multiline」プロパティになりますね。
ここで、「あ、デフォルトは定義されなかったりFalseだったりするから定義しなけれ
ばならないのだな」と思った方はもうkivyMDマスターです。\# ちょっと言い過ぎかな

最後に8~9個目になりますが、それぞれ矩形型・なんか周りがグレーで囲われていること
でのテキストフィールドになります。すみません、ちょっと9個目が適当になっていますが
ニュアンスで伝わっていれば助かります。それぞれ共通に「mode」プロパティを置いて
「"rectangle"」、「"fill"」というように分けます。

#### クラス側

kv側は過去最大に長かったのではないか、というくらいでしたがここからはクラス側に
入っていきます。再度クラス側を以下に抜粋しておきます。

```python
class Test(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = Builder.load_string(KV)
    
    def build(self):
        self.screen.ids.text_field_error.bind(
            on_text_validate=self.set_error_message,
            on_focus=self.set_error_message,
        )
        return self.screen

    def set_error_message(self, instance_textfield):
        self.screen.ids.text_field_error.error = True
```

変わり種のあるところでいうと、buildメソッドからになるでしょうか。その中の
「self.screen.ids.text_field_error」ってなんぞいとなるかもしれませんが
もう分かっているよという方はちらちらいるのではないでしょうか。そうですね、これは
kv側を振り返ってみると明白で、4つ目のテキストフィールドのid値でしたね。

これは何をしているかというと、そのテキストフィールドが持つon_text_validateと
on_focusプロパティにset_error_messageコールバックメソッドをバインドしています。

んで、set_error_messageはなんなのよというとその下にあるメソッドになりますね。
これが呼ばれるとtext_field_errorが持つerrorプロパティはTrueの値が設定される
ということになります。なので常に呼ばれるので、何をしてもvalidateされることになり
ます。

### 結果

ということでお待たせしました！実行結果の方に移りたいと思います。
ここでキャプチャを複数用意するのは気が狂うかもしれないので、gif動画にて様子を見て
みます。

![143.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/649de2e1-08cd-b627-b8f8-6597c4bb3f0d.gif)

再生速度が遅いと、試したことがじっくり確認できますね。kv側で記載したことと照らし
合わせみても有効かもしれません。そこで触れたところは全て当てはまっていることが
分かります。

1番目のウィジェットとMDTextFieldと記載のあるラベルが少し被っていますが、これは
アンチパターンとも言えるかもしれません。このあとでも触れるところですが、MDText-
Fieldは配置に少し苦労するところがあります。


### MDTextFieldRect&MDTextFieldRound

ここについては、残りのMDTextField(Rect|Round)を合体させてみました。というか
この方が効率が良かったのです。名前の通り、上で見たテキストフィールドから角が四角か
丸かの違いになります。こちらもさっそくコードから入っていきますね。

```python:xxxi/textfield_round_rect.py
from kivy.lang import Builder

from kivymd.app import MDApp

KV = '''
Screen:

    BoxLayout:
        orientation: "vertical"
        
        MDLabel:
            text: "MDTextFieldRect"

        MDTextFieldRect:
            size_hint: 1, None
            height: "30dp"

        MDSeparator:
        
        MDLabel:
            text: "MDTextFieldRound"
 
        MDTextFieldRound:
            hint_text: 'Empty field'
    
(略)
'''


class Test(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = Builder.load_string(KV)
    
    def build(self):
        return self.screen

Test().run()

```

ということで、こちらもkv側でところどころ端折りながら触れ込んでいきたいと思います。
でも、ベースとしては先程よりかは省コード化されていて共通部分が多いので、kv側だけを
見ることとします。

#### kv側

さっそくですが、kv側の全体象を見ることとします。全体からをkv側を抜き出します。

```python
Screen:

    BoxLayout:
        orientation: "vertical"
        
        MDLabel:
            text: "MDTextFieldRect"
        
        #1 MDTextFieldRect
        MDTextFieldRect:

        MDSeparator:
        
        MDLabel:
            text: "MDTextFieldRound"
 
        #2 MDTextFieldRound - Without icon
        MDTextFieldRound:
            hint_text: 'Empty field'

        #3 With left icon
        MDTextFieldRound:
            icon_left: "email"
            hint_text: "Field with left icon"
    
        #4 With left and right icons
        MDTextFieldRound:
            icon_left: 'key-variant'
            icon_right: 'eye-off'
            hint_text: 'Field with left and right icons'
    
        #5 Control background color
        MDTextFieldRound:
            icon_left: 'key-variant'
            normal_color: app.theme_cls.accent_color
    
        #6 Control background color - color active
        MDTextFieldRound:
            icon_left: 'key-variant'
            normal_color: app.theme_cls.accent_color
            color_active: 1, 0, 0, 1

```

ということで、全ての6個のコンポーネントを抜粋してみました。MDLabelとかMDSepa-
ratorとかは区切りを付けて分かりやすくしただけなので、ただただそういうことなんだ
なと思ってもらえれば。ここも全て1~4個目まではhint_textがあるということも注目
ポイントです。

1つ目に入っていきますが、これはMDTextFieldRectですね。今回は何もパラメータを
指定していません。

2~4つ目のウィジェットはまずMDTextFieldRoundに切り替わりました。アイコンがあり
なし、ありの場合は左右にそれぞれ配置している様子になります。2つ目がなにもなし、
3つ目が左アイコンで4つ目が左右どちらもアイコンがあるということになります。

5~6番目は背景色に関わるウィジェットになります。5つ目がnormal_colorにアクセント
カラーを指定して、6個目はその上フォーカスした際に色を変更できるといった仕様になって
います。

### 結果

ここも色々触れてみましたが、実際の様子を見るに越したことはありません。
さっそくこちらも見てみましょう。

![144.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/d149b52e-fec6-073b-b8ed-d937bf66b0a0.gif)

少し予想したレイアウトと変わっているところがありましたが、上のキャプチャのように
なります。結構pos_hintなりを試してみましたが、なかなか有効化されずもしかすると
発展途上の段階かもしれません。これが仕様通りとなってこのまま配置されるのであれば、
少しレイアウトの設計時は注意が必要そうです。

ですが、テキストフィールドに関するウィジェットは、kv側で記載した通りになります。
ここも、そこで記載したものと照らし合わせてみてはいかがでしょうか。

## API - kivymd.uix.textfield.textfield

とまぁ、まとめに入りたいところですが、最後の大仕上げ。使用したAPIについて
触れていきたいと思います。あ、ちなみにですが、「Clickable icon for MDText-
FieldRound」であるコードは動きませんでした。今後の課題が増える一方だぁ。

### class kivymd.uix.textfield.textfield.MDTextFieldRect(**kwargs)

順番が異なりますが、マニュアルを優先して。

なにやらWarningで記載されていることが気になりますね。依頼してみましょう。

:::note warn
When changing a TextInput property that requires re-drawing, 
e.g. modifying the text, the updates occur on the next clock cycle 
and not instantly. This might cause any changes to the TextInput 
that occur between the modification and the next cycle to be ignored, 
or to use previous values. For example, after a update to the text, 
changing the cursor in the same clock frame will move it using 
the previous text and will likely end up in an incorrect position. 
The solution is to schedule any updates to occur on the next clock 
cycle using schedule_once().
:::

> テキストの修正など、再描画が必要な TextInput のプロパティを変更する場合、
> 更新は次のクロック サイクルに行われ、即座には行われません。このため、変更後
> から次のサイクルまでの間に発生したTextInputの変更が無視されたり、以前の値が
> 使用されたりすることがあります。例えば、テキストを更新した後、同じクロック
> フレーム内でカーソルを変更すると、以前のテキストを使用してカーソルが移動
> するため、誤った位置になってしまう可能性があります。これを解決するには、
> schedule_once()を使って、次のクロックサイクルで更新が行われるように
> スケジュールする必要があります。

結構難しいことが書いていますが、まだまだ発展途上ということでしょうか。
現段階では反映がちゃんとされるかどうか検証してみることが優先されそうです。

そして、その下にもNoteがありますね。こちらも依頼してみます。

:::note
Selection is cancelled when TextInput is focused. If you need 
to show selection when TextInput is focused, you should delay 
(use Clock.schedule) the call to the functions for selecting 
text (select_all, select_text).
:::

> TextInputがフォーカスされると、選択はキャンセルされます。TextInputが
> フォーカスされているときに選択を表示する必要がある場合は、テキストを選択
> する関数（select_all、select_text）の呼び出しを遅らせる
> （Clock.scheduleを使用する）必要があります。

初めて見たときは、結構それが普通の仕様ではないのかと思いましたが、なかなか
無限の可能性を秘めているのかなと感じました。

### class kivymd.uix.textfield.textfield.MDTextField(**kwargs)

こちらは前半部分で、中心的に取り上げたウィジェットになりますね。説明としては
以下のように記載があります。

> TextInput class. See module documentation for more information.

いや、ちゃんとモジュールのドキュメントを見ろってことかいってツッコミそうですが、
1番詳しく書かれているのはそっちで間違いありません。

`Events - on_text_validate`

> Fired only in multiline=False mode when the user hits ‘enter’. 
> This will also unfocus the textinput.

私もそうでしたが、なぜ毎回エラー表示がなされているか不明だという方が多かった
のではないでしょうか。あ、これは前半部分の4つ目のウィジェットに関わる、コール
バックメソッドになりますね。

これがまさしくそういうことなのですが、'enter'を打った瞬間に着火(直訳)される
ので毎回エラー表示がなされていました。へぇー、将来的にはアンフォーカスする際に
も同じようになるのですね。これはありがたい。

あとは、WarningとNoteは同じようなことが書かれているので飛ばします。
プロパティの方に移ります。

`helper_text`

> Text for helper_text mode.

> helper_text is an StringProperty and defaults to ‘’.


`helper_text_mode`

> Helper text mode. Available options are: ‘on_error’, ‘persistent’, ‘on_focus’.

> helper_text_mode is an OptionProperty and defaults to ‘none’.


`max_text_length`

> Maximum allowed value of characters in a text field.

> max_text_length is an NumericProperty and defaults to None.


`required`

> Required text. If True then the text field requires text.

> required is an BooleanProperty and defaults to False.

`mode`

> Text field mode. Available options are: ‘line’, ‘rectangle’, ‘fill’.

> mode is an OptionProperty and defaults to ‘line’.


`error`

> If True, then the text field goes into error mode.

> error is an BooleanProperty and defaults to False.

上記は全て出てきたものなので、軽く見直すくらいで良いと思います。あとは
面白そうなプロパティがありますが、残念ながらタイムアップということで。

### class kivymd.uix.textfield.textfield.MDTextFieldRound(**kwargs)

ここも同じく、詳細はモジュールのドキュメントを見てよねということが書かれています。

> TextInput class. See module documentation for more information.

`icon_left`

> Left icon.

> icon_left is an StringProperty and defaults to ‘’.

`icon_right`

> Right icon.

> icon_right is an StringProperty and defaults to ‘’.

ここも、出てきたということでこちら側で引用しています。MDTextFieldRoundしか
使えないのかと言われると、そうでもなくMDTextFieldでも説明があったのでそちら
でも使えると思います。

## まとめ

長かった！感動した！

というバカなことも言っていますが、思わぬ長編となってしまいました。
いかがだったでしょうか。少しでも参考になれば嬉しいです。

もうほとんどコンポーネントは触り尽くした感がありますが、これを除いてはアプリを
製作できないと言っても過言ではないくらいのコンポーネントになります。使い方をここ
で掴みたいところです。

少し心残りなのは、MDTextFieldRoundでパスコードの入力時に文字をマスクしたい
のだけれども、どうやるんだというところになります。なんと、これ実は今日飛ばした
「Clickable icon for MDTextFieldRound」のところでありました。まさかのAPI
のところで記載してないという・・passwordプロパティ(値はTrue)を追記するだけなの
で良かったら追記してみて動作を見てみてください。

ということで今日はここまで！お疲れ様でした！

来週はというと、順番通りに行くとTimePicker篇となりますが、誰だお前?という状況
です。なんとこれ、以前で触れていたPicker篇の中にあるTimePickerが分かれている
だけになるのですね。これはやられました。まぁ、そこで触れていますので興味がある方は
以前のPicker篇をご覧ください。マニュアルはすでにないけど。

https://qiita.com/virty/items/a5b1d0752d735985b99e

というわけでやっと来週の取り組みに戻るわけですが、、お待ちかね、かどうかは不明
ですが、Toolbar篇の予定になります。これまで何十回とすでに出てきていますがよう
やくメインで取り扱えることとなりました。ではまた元気に来週お会いしましょうー。

それでは、ごきげんよう。

## 参照

> Components » TextField
> https://kivymd.readthedocs.io/en/latest/components/textfield/

> DeepL 翻訳ツール
> https://www.deepl.com/ja/translator