kivyMDチュートリアル其の肆什乃弍 Components - Tabs篇

晩秋の候、ますますご繁栄のこととお慶び申し上げます(急な畏まり)。
11月も突入するようになりました。またちょっと温かい気温になる日もあって、なんだか
よく分からない季節ですね。

はい、今週も始まりました。KivyMDのお時間です。
少し今週は毛色が異なり、先週のTabs篇の続きとなっています。コードの置き場所などは
先週と同じなのでご留意ください。先週からの差分を主に触れ込んでいきたいと思っており
ます。

今週のニュースはというと、先週からの続きですが衆議院選挙のことではないでしょうか。
大小問わず変化があった!ということやいつも通りでしょということなど色々思われるとは
推測していますが、いかかだったでしょうか。ちなみにですが、どの党に投票したかやこう
予想していた(後出しだし...)などは少しセンシティブに近いかもなのでここでは記しません。。

というわけで今週も元気にやっていきましょう！しつこいようですが、今日は先週のTabsの
続きとなっています。

## Tabs

さて、今日はどこから始まるかというと、先週は「Example with tab icon」までで
止まっていました。

https://qiita.com/virty/items/46268277e782bba1a0c5#example-with-tab-icon

サンプルを1つ紹介しただけで、全然進んでいませんね。ということなので、今日は「Example 
with tab text」から始めようと思います。また、マニュアルのサンプルを全て触れ込むのでは
なく、何個か気になる・触れた方がいいと思ったところをピックアップして触れ込んでいくので
ご留意頂けたらと思います。

### Example with tab (icon and) text

なにやら、冒頭にNoteがあり意味がありげです。見てみましょう。

:::note
The MDTabsBase class has an icon parameter and, by default, 
tries to find the name of the icon in the file kivymd/icon_definitions.py.

If the name of the icon is not found, the class will 
send a message stating that the icon could not be found.

if the tab has no icon, title or tab_label_text, 
the class will raise a ValueError.
:::

少し長い英文を見ると、依頼したくなるので依頼してみます。どこかの会社の上司みたいです。
依頼してみると、以下のように結果が返ってきました。

> MDTabsBase クラスには icon パラメータがあり、デフォルトでは 
> kivymd/icon_definitions.py というファイルからアイコンの名前を探そうとします。

> アイコンの名前が見つからない場合、クラスはアイコンが見つからないことを示すメッセージを送信します。

> タブにアイコン、タイトル、tab_label_text がない場合、クラスは ValueError を発生させます。

書いてあることはまんまです。ここにあることは結果の方で見てみてもよいかもしれません。
実際にコードなり結果なりを見てみた方が早いので、まずはコードの方に移ります。Usage
などはすでに先週でやったので、重要なところをピックアップする方針でやっていきます。

```python:xl/tabs_icon_and_text.py

(略)

KV = '''

(略)

<Tab>

    MDLabel:
        id: label
        text: "Tab 0"
        halign: "center"
'''
(略)

class Example(MDApp):

(略)

    def on_start(self):
        for i in range(20):
            self.root.ids.tabs.add_widget(Tab(title=f"Tab {i}", icon="android"))
            #self.root.ids.tabs.add_widget(Tab(title=f"Tab {i}", icon="hoge"))    # Note - #2 icon name not found
            #self.root.ids.tabs.add_widget(Tab())                                 # Note - #3 no icon, title or tab_label_text

    def on_tab_switch(
        self, instance_tabs, instance_tab, instance_tab_label, tab_text
    ):

(略)

        instance_tab.ids.label.text = tab_text

```

ということでピックアップしたものが上記となります。importに関しては、md_icons
オブジェクトがなくなったくらいなので、省略しています。

次にkvですが、ここもそれほど変わっていなく、前回のサンプルはボタンでしたがラベル
に置き換わっているだけです。ラベルの詳細については、以下リンクもしくはマニュアルの
該当ページをご覧頂けたらと思います。

https://qiita.com/virty/items/8475f11f314b76af21e1

最後にクラス側ですが、ここもそれほど大きく変わっていなく、on_startメソッドでは
アイコンのリストをイテレートしていたのが数値でイテレートしているだけになります。
なお、アイコンのことをまとめて表示させたいために、iconプロパティを置いて値として
全て"android"を指定しています。なので、ここの節のタイトルがおかしかったのもこう
いう理由がありました。
\# コメントアウトしたとこは順次結果の方で見ていきたいと思います

また、on_tab_switchメソッドですが、ここもそれほど変化なくアイコンをテキストに
置き換わっただけになります。

### 結果

ということで、簡素な触れ込みでしたが、実行結果の方に移りたいと思います。まずは
コメントアウトされていないところから。

![184.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/e0833ffb-b167-086d-85b7-a8c51ba65eb6.png)

問題ありませんね。見えてないだけですが、ちゃんとタブが20個生成されていることも
確認済みです。android君もちゃんと健在ですね。

次に以下のコメントアウトされているところを外して(最初の方はもちろんコメントアウト
します)、挙動の方を見ていきたいと思います。Noteでは見当たらないアイコンがあれば
メッセージを飛ばすとありました。

```python
#self.root.ids.tabs.add_widget(Tab(title=f"Tab {i}", icon="hoge"))    # Note - #2 icon name not
```

![185.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/11affd8b-6222-5428-b01a-669f3dcd65a0.png)

こちらは、書いてないので当たり前ですが、アプリを落ちるところまでの仕様ではあり
ません。またメッセージが以下のように出力されたので、以下に記載しておきます。

```
[ERROR  ] [<__main__.Tab object at 0x110a00150>] [UID] = [365]:
	Icon 'hoge' not found in md_icons
[WARNING] Deprecated property "<StringProperty name=text>" of object "<__main__.Tab object at 0x110a00150>" has been set, it will be removed in a future version
[ERROR  ] [<__main__.Tab object at 0x110a57450>] [UID] = [456]:
	Icon 'hoge' not found in md_icons
[ERROR  ] [<__main__.Tab object at 0x110aaa650>] [UID] = [547]:
	Icon 'hoge' not found in md_icons
[ERROR  ] [<__main__.Tab object at 0x110b007d0>] [UID] = [638]:
	Icon 'hoge' not found in md_icons
以下タブオブジェクトの数だけメッセージが続く
```

ちゃんとERRORメッセージとして出力されていますね。ちなみにですが、WARNINGメッセ
ージはここだけの話だけでなく、他のサンプルコードとかでも動かしたときに出力されて
います。内部の話なのか、オブジェクト生成のプロパティなのかはいざ知らず。。次バー
ジョンにての確認になりますかね。

最後に以下のコメントアウトを外して(しつこいようですが上の指定していたものはコメ
ントします)、挙動を見てみます。何も指定がなければ落ちるということが書かれていま
した。

```python
#self.root.ids.tabs.add_widget(Tab()) # Note - #3 no icon, title or tab_label_text
```

ここもちゃんとNoteで書かれていたことが期待通りに動きました。アプリは起動されず、
エラーメッセージが以下のように出力されました。

```
Traceback (most recent call last):
   File "tabs_icon_and_text.py", line 54, in <module>
     Example().run()
(略)

   File "/UsersDirectory/.pyenv/versions/3.7.6/lib/python3.7/site-packages/kivymd/uix/tab.py", line 885, in _update_text
     f"{self}: [UID] = [{self.uid}]:\n\t"
 ValueError: <__main__.Tab object at 0x10e68d150>: [UID] = [365]:
 	No valid Icon was found.
 	No valid Title was found.
 	Icon	= ''
 	Title	= ''
```

なにやら、さきほどのエラーでもそうですが、UIDが付きものということは留意しておき
たいですね。多分来週には忘れてそうだけど。

みなさんも良ければ、動かして挙動を見てもらえればと思います。なおさら分かりやすい
かと思います。

### Dynamic tab management

少しここまででバテバテですが、あと2つほど動かして今日は終わりにしたいと思います。

ざっとマニュアルを見た感じだと、なにやらタブを追加したり削除したり出来るみたいですね。
使用するかどうかは分かりませんが、他のコンポーネントも参考になるかもしれないので、
ちょっと覗いてみたいと思います。

ここもマニュアルから相違ありませんが、というか全く変わっていませんが、以下にサンプル
コードを重要なところだけピックアップして記載をしておきます。

```python:xl/tabs_dynamic_tab_management.py
(略)

from kivy.uix.scrollview import ScrollView

KV = '''

(略)

    MDTabs:
        id: tabs

<Tab>

    MDList:

        MDBoxLayout:
            adaptive_height: True

            MDFlatButton:
                text: "ADD TAB"
                on_release: app.add_tab()

            MDFlatButton:
                text: "REMOVE LAST TAB"
                on_release: app.remove_tab()

            MDFlatButton:
                text: "GET TAB LIST"
                on_release: app.get_tab_list()
'''

class Tab(ScrollView, MDTabsBase):
    '''Class implementing content for a tab.'''


class Example(MDApp):
    index = 0

(略)

    def on_start(self):
        self.add_tab()

    def get_tab_list(self):
        '''Prints a list of tab objects.'''

        print(self.root.ids.tabs.get_tab_list())

    def add_tab(self):
        self.index += 1
        self.root.ids.tabs.add_widget(Tab(text=f"{self.index} tab"))

    def remove_tab(self):
        if self.index > 1:
            self.index -= 1
        self.root.ids.tabs.remove_widget(
            self.root.ids.tabs.get_tab_list()[-1]
        )

(略)
```

はい、ということでもうほとんど抜粋してるのではというご意見は真っ当だと思います。
それだけここでは重要なところがあるということですね。

まずimport文ですが、ScrollViewオブジェクトを追加しております。〇〇Layoutで
はなく、このようにScrollViewも使用できるようになっているんですね。これはTab
クラスの方でも継承していることが見受けられます。

で、お次はkv側ですが、まずMDTabsウィジェットでon_tab_switchプロパティが無く
なっています。なので、タブを切り替えたときにコンテンツなどが変わらないことも分か
りますね。

また、Tabコンテンツの方ではボタンが3つ配置されていて、各イベントメソッドが指定
されています。

最後にExampleクラスですが、冒頭にindex変数が0の初期値で定義されていますね。
そして、on_startメソッドですが、ここは初期挙動としてadd_tabメソッドを指定
しています。

そしてadd_tabメソッドはというと、定義していたindexを1つインクリメントして
その番号をテキストとしてTabオブジェクト生成に使用されています。

今度逆のremove_tabメソッドですが、index変数が2以上のとき1つデクリメント
して、tabsクラスが持っているget_tab_listメソッドにて削除するようにして
います。ここで気をつけたいのが、tabsリストは逆順で取得してそれを削除している
ということですね。これをしないとindexとリストが一致しなく思わぬ挙動を招きます。

最後のget_tab_listメソッドですが、これはremove_tabメソッドでも同様ですが、
tabsクラスが持っているget_tab_listメソッドでtabs配下のtabをリスト化して
返してそのままprintで出力しています。

### 結果

少し長くなりましたが、一旦実行結果の方に入りたいと思います。

![186.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/10f36f94-1011-82fb-77e6-acf5267f8e2a.gif)

最初の初期表示については、先程コードを見た通りですがタブが1つだけありますね。
で、あとは追加したり、削除したりなどの挙動はマニュアルと差がありません。さら
に、GET TAB LISTボタンを押したときのメッセージは以下のように出力されました。

```
[<kivymd.uix.tab.MDTabsLabel object at 0x10d4fced0>, <kivymd.uix.tab.MDTabsLabel object at 0x10d656e50>, <kivymd.uix.tab.MDTabsLabel object at 0x1104df9d0>]
```

### Use on_ref_press method

やっと今日のお題は最後となりました。いけない、少し本音が出てしまったようだ。。

ここではtabsクラスが持っているイベントで、on_ref_pressメソッドをいじって
みるとどうなるということが書いてあります。色々ここでごちゃごちゃ言ってもしょ
うがないので、さっそくサンプルコードを見てみたいと思います。

```python:xl/tabs_use_onrefpress.py
(略)

from kivymd.font_definitions import fonts

(略)

KV = '''

(略)

    MDTabs:
        id: tabs
        on_ref_press: app.on_ref_press(*args)

(略)

'''

(略)

class Example(MDApp):

(略)

    def on_start(self):
        for name_tab in self.icons:
            self.root.ids.tabs.add_widget(
                Tab(
                    text=f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]{md_icons['close']}[/font][/ref]  {name_tab}"
                )
            )

    def on_ref_press(
        self,
        instance_tabs,
        instance_tab_label,
        instance_tab,
        instance_tab_bar,
        instance_carousel,
    ):

        '''


(略)

        :param instance_tab_bar: <kivymd.uix.tab.MDTabsBar object>
        :param instance_carousel: <kivymd.uix.tab.MDTabsCarousel object>
        '''

        # Removes a tab by clicking on the close icon on the left.
        for instance_tab in instance_carousel.slides:
            if instance_tab.text == instance_tab_label.text:
                instance_tabs.remove_widget(instance_tab_label)
                break

(略)

```

だいぶ端折った気はしますが、それでも多いですね。ここもざっと確認してみます。

まずimport文はfontsオブジェクトを指定しているところが変わったところになります。

その次にkv側ですが、MDTabsウィジェットでon_ref_pressプロパティが追加されて
いますね。

最後にExampleクラスですが、on_startメソッドの中でタブを追加しているところで、
タブのテキストを指定している形式が変わっていますね。今まではカンマ付きでアイコン
だとかを追加してtabオブジェクトを生成していましたが、今回はこのような形で追加を
しているということですね。これはAPI側で説明があれば詳細を見てみたいと思います。

その次に実体のon_ref_pressメソッドですが、引数にinstance_tab_barとinstance-
carouselがありますね。それぞれ字の如くtab_barとcarouselのインスタンスでしょう
か。このメソッド内ではinstance_tab_barは使用されていません。これらの詳細はAPI
のところに譲ることとします(自己タスク回し)。

中は何をやっているかというと、instance_carouselが持っているslides(リスト?)を
イテレートして、instance_tab_labelのテキストとイテレートしたオブジェクトのテキ
ストを見比べ、合致していたらtabsオブジェクトのremove_widgetメソッドにて削除して
います。なるほど、refというのはtabオブジェクトのテキストで指定していたrefと繋がる
わけですね。こうすることによって×ボタンで削除が出来ているわけになります。

### 結果

ここも結果を見ておきましょう。論より証拠を、という気持ちをずっと持っていきたいと思い
ます。

![187.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/51324399-5b8c-8db5-cba2-9146384bd42c.gif)


はい、ここも問題ありませんね。1つ1つ削除していますが、最後の1個は削除出来ない仕様と
なっています。

### Switching the tab by name

HP切れです。。ここはこうなんだなと見てみるとTabsレベルが上がると思います。
あとは、マテリアルデザインのリンクでもこちらに関して言及はないので、使用する方が
グッと下がるのではと投稿者は変な憶測を立てています。

## API - kivymd.uix.tab.tab

まとめ入る前に使用したAPIについてまとめておきます。

###  class kivymd.uix.tab.tab.MDTabsBase(**kwargs)

Tabクラスの継承先の基底クラスでしたね。冒頭に説明があるので、引用します。

> This class allow you to create a tab. You must create a new class
> that inherits from MDTabsBase. In this way you have total control 
> over the views of your tabbed panel.

それほど留意する点はないかと思われます。

`icon`

> This property will set the Tab’s Label Icon.

> icon is an StringProperty and defaults to ‘’.

当たり前ですが、Tabsの方では持っていません。こちらは必須プロパティではありません。

`title_icon_mode`

> This property sets the mode in wich the tab’s title and icon are shown.

> title_icon_mode is an OptionProperty and defaults to ‘Lead’.

他に何があるか気になるところですね。

`title`

> This property will set the Name of the tab.

(略)

> title is an StringProperty and defaults to ‘’.

こちらは、NoteとWarningの方が重要そうですが、ここでは引用をしません。
理由としては、内部仕様のことを記載しているのかと判断したためになります。

`text`

> This property is the actual title of the tab. use the property icon and title to set this property correctly.

> This property is kept public for specific and backward compatibility purposes.

> text is an StringProperty and defaults to ‘’.

> Deprecated since version 1.0.0: Use tab_label_text instead.

`tab_label_text`

> This property is the actual title’s Label of the tab. use the property icon and title to set this property correctly.

> This property is kept public for specific and backward compatibility purposes.

> tab_label_text is an StringProperty and defaults to ‘’.

なるほど、WARNINGメッセージが出てきたのも理由が分かりましたね。次バージョンの
ときでも覚えておきたいところです(覚えるとは言ってない)。

### class kivymd.uix.tab.tab.MDTabs(**kwargs)

ここからはMDTabsクラスについてです。以下のように説明があります。

> You can use this class to create your own tabbed panel.

#### Events

`on_tab_switch`

> Called when switching tabs.

`on_ref_press`

> The method will be called when the on_ref_press event 
> occurs when you, for example, use markup text for tabs.

ここでは、これらとadd/remove_widgetメソッドくらいしか使っていませんが、
他にもタブの幅や色など色々とカスタマイズすることも可能です。

## まとめ

さて、いかがだったでしょうか。

もうこれさえやっておけば、もうアプリを作れないとは言えないだろうという
トドメのコンポーネントだったような気もします(意味不明)。

結構アプリには欠かせない機能でもあるかもしれませんが、それほど難しく
ない機能でもあるで、気軽にチャレンジしてもよいかもしれません。私も最初
アプリ作るときはこれを触ってみようかな。

ということで、今週はここまでとします！来週はというと、一旦やることを先週
振り返ってみたところから言うと、順番前後するかもしれませんがFitImageを
やりたいと思います。お楽しみに〜。

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

## 参照

> Components » Tabs
> https://kivymd.readthedocs.io/en/latest/components/tabs/

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

## 番外編(初期投稿後に追記)

すみません、大事なことを忘れていました。

以前というか大分前に、Themes章を触れ込んでいました。なつかしい。
以前のコードを整理していると、ここでTabsを触っていたこともありまして、
慌ててこのページにて追記したわけになります。

具体的な内容としては以下のリンクになります。

https://kivymd.readthedocs.io/en/latest/themes/theming/#changing-the-theme-colors

https://kivymd.readthedocs.io/en/latest/themes/color-definitions/#kivymd.color_definitions.colors

以前なんで触れてなかったんだ！と思われる方もいらっしゃるかもしれないので、
少し説明しますと、簡単に言えば動かなかったということになりますねw

なので、触れ込みはないですが、このページを理解頂けましたら、なんてことは
ないので(他の知識も必要になると思うけど)すんなり頭に入ってくるかと思われ
ます。コードの方は追コミット(ラーメンみたい)しておきますね。

* changing_the_theme_colors.py
* color_definitions.py

ということでこれだけの追報告になります〜。それではまた来週！