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

ハロー、ワールド！みなさん開発してますか？(言い方としてはセコムのように)

はい、ということで今日も懲りもせずKivyMDのお時間が参りました。ちょっと使い方は
違うのですが、前に失敗して痛い目をして繰り返すという意味においては間違いではなか
ろうかと思います。

https://www.weblio.jp/content/%E6%87%B2%E3%82%8A%E3%82%82%E3%81%9B%E3%81%9A

今週のニュースはというと、やっぱり選挙のことではないでしょうか。このページも投稿
するころにはもう結果も出ているのではと推測するばかりです。そんなことはないかな。
結果の方も注目したいところです。ITエンジニアに勤務助成金とか出すよと言っている
党にはすぐさま投票したいばかりです。ほんと出ないかなぁ。

馬鹿なことを言っていますが、助成金を獲得できるためにも(無理)私たちはガリガリ
コードを書くしかありません。ということで今日も元気にやっていきましょう。今日は
タイトルにもある通り、Tabs篇となります。

## Tabs

一旦気持ちを震わせてはいましたが、マテリアルデザインのリンクは飛ばしましょう。
手を抜けるときに手を抜くのも、ITエンジニアのなせる技です。

ですが、マニュアルに記載のあるものは目が離せません。冒頭にはこう記載があります。

> Tabs organize content across different screens, data sets, and other interactions.

言わんとすることは分かる、というのは多くの方が思っていることではないでしょうか。
スマホアプリだけではなく、デスクトップアプリやwebアプリなど多くの方が触れたので
はないでしょうか。

なにやら、他にも書かれていますね。

> Module provides tabs in the form of icons or text.

ほう、アイコンとテキストをセットできる、というかそんなことは書いてないのですが
少し訳を改変しています。これは試してみるしかないようです。しかしなかなか訳すのが
難しいですね。主語がモジュールだけのように見える。。

### Usage

では、さっそく使用方法について入っていきたいと思います。

ここでも説明がありますね。見てみましょう。

> To create a tab, you must create a new class that inherits 
> from the MDTabsBase class and the Kivy container, in which 
> you will create content for the tab.

これまで見てきた方には「はいはい、みなまで言うな」と言いたくなるかもしれません。
少し注意したいところは、MDTabsBase以外にもkivyコンテナーを継承する必要がある
ということです。これも後で実際に見てみましょう。また、分からないよーとなった方は
DeepLとかGoogle翻訳などで翻訳してみてください。すぐ言わんとしてることは分かる
と思います。

コードも貼り付けられているので見てみましょう。

```python
class Tab(MDFloatLayout, MDTabsBase):
    '''Class implementing content for a tab.'''
    content_text = StringProperty("")
```

```python
<Tab>
    content_text

    MDLabel:
        text: root.content_text
        pos_hint: {"center_x": .5, "center_y": .5}
```

ともう1つがこれになります。

> All tabs must be contained inside a MDTabs widget:

```python
Root:

    MDTabs:

        Tab:
            title: "Tab 1"
            content_text: f"This is an example text for {self.title}"

        Tab:
            title: "Tab 2"
            content_text: f"This is an example text for {self.title}"

        ...
```

まず前半2つのコードについては、タブクラス及びレイアウト(ウィジェット)の書き方
になります。クラス側には、MDFloatLayoutと先程の説明にあったMDTabsBaseが継承
されていることが分かります。いや、MDFloatLayoutってなによとなった方はKivyコン
テナーと書かれていたことを思い出して頂ければと思います。ややこしいね。

で、後半はなんぞよということですが、後半はコンテンツのところになりますね。これも
説明文の後半(ややこしい)に書かれていたところが合致しています。ルートウィジェット
→ MDTabs → Tab(さきほどのもの)と分かりやすく1、2、フィニッシュが決まっています。
テストや資格などがあれば絶対に覚えておかないとダメなところですね。

### Example with tab icon

使用方法が分かれば、動かしてみたくなるというのが人間の本能というもの。まぁ、かな
らずしもそうではありませんが。まず初っ端のサンプルコードになります。アイコンを何個
か(同時にコンテンツへも)セットしておいて、切り替わるときにタブ・コンテンツ両方を切り
替えるというアプリになります。

```python:xl/tabs_tabicon.py
from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.icon_definitions import md_icons

KV = '''
MDBoxLayout:
    orientation: "vertical"

    MDToolbar:
        title: "Example Tabs"

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


<Tab>

    MDIconButton:
        id: icon
        icon: root.icon
        user_font_size: "48sp"
        pos_hint: {"center_x": .5, "center_y": .5}
'''


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


class Example(MDApp):
    icons = list(md_icons.keys())[15:30]

    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        for tab_name in self.icons:
            self.root.ids.tabs.add_widget(Tab(icon=tab_name))

    def on_tab_switch(
        self, instance_tabs, instance_tab, instance_tab_label, tab_text
    ):
        '''
        Called when switching tabs.

        :type instance_tabs: <kivymd.uix.tab.MDTabs object>;
        :param instance_tab: <__main__.Tab object>;
        :param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>;
        :param tab_text: text or name icon of tab;
        '''
        # get the tab icon.
        count_icon = instance_tab.icon
        # print it on shell/bash.
        print(f"Welcome to {count_icon}' tab'")


Example().run()
```

まず手始めなので、ここはしっかり見ていきます。いつものように、importや
kv、クラス側といったように見ていこうと思います。

#### import文

importで必要なものを以下に抜粋しておきます。

```python
from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.icon_definitions import md_icons
```

BuilderやMDAppオブジェクトに関してはいつもと同じです。今回においてはそれ
以外のMDTabsBaseとMDFloatLayout、md_iconsオブジェクトをインポートして
います。なぜこれらが必要なのかが分かる方は、この触れ込みを見る必要がないかも
しれません。

#### kv側

続いてkvレイアウトに入ります。Usageのところで既出ではありますが、ここでは
どのように使われているかを見てみます。おさらいでもありますし。

```python
KV = '''
MDBoxLayout:
    orientation: "vertical"

    MDToolbar:
        title: "Example Tabs"

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


<Tab>

    MDIconButton:
        id: icon
        icon: root.icon
        user_font_size: "48sp"
        pos_hint: {"center_x": .5, "center_y": .5}
'''
```

まぁ、まんまですよね。ここで気をつけたいところでいうと、MDFloatLayoutが
さっき出てきたけど今回はMDBoxLayoutを使うんだねーははーん、という方は惜し
いですね。そう思う方がどれだけいるのかということもありますが、思った方は再度
Usageのところと見比べてみてください。

また、Usageからの差分でMDToolbarやMDBoxLayoutなどが定義されていますが、
この辺りについては以下の過去のページもしくは公式マニュアルの該当ページをご覧
下さい。

https://qiita.com/virty/items/3c726e2a7faa694fb54d

https://qiita.com/virty/items/29d792d5ecf4ad0f86be

https://qiita.com/virty/items/29d792d5ecf4ad0f86be

また、気をつけたいところですが、Tabレイアウトの下にあるMDIconButtonはあく
までコンテンツであってタブが実際にButtonになっているかと言われるとそうでは
ないということが挙げられます。

#### クラス側

最後にクラス側となります。ここまで見ると全体像が浮かび上がってくるのではない
でしょうか。

```python
class Tab(MDFloatLayout, MDTabsBase):
    '''Class implementing content for a tab.'''


class Example(MDApp):
    icons = list(md_icons.keys())[15:30]

    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        for tab_name in self.icons:
            self.root.ids.tabs.add_widget(Tab(icon=tab_name))

    def on_tab_switch(
        self, instance_tabs, instance_tab, instance_tab_label, tab_text
    ):
        '''
        Called when switching tabs.

        :type instance_tabs: <kivymd.uix.tab.MDTabs object>;
        :param instance_tab: <__main__.Tab object>;
        :param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>;
        :param tab_text: text or name icon of tab;
        '''
        # get the tab icon.
        count_icon = instance_tab.icon
        # print it on shell/bash.
        print(f"Welcome to {count_icon}' tab'")


Example().run()
```

TabクラスはUsageからそれほど変化はありませんね。ただし、Usageの方では
コンテンツのテキストプロパティを配置していました。

で、メインのExampleクラスですが、まずmd_iconオブジェクトの15番目から
29番目のキーを取り出しiconsリストに格納しています。

また、あとは3つのメソッドがありますが、build・on_startメソッドなんかは
これまでも見てきた方も多いのではないでしょうか。

on_startメソッドは何をやっているかと言うと、先程のiconsリストをイテレート
して、テキストを取り出しそのままTabオブジェクトのiconに指定しています。こう
することでTabオブジェクトのタブ(ややこしい)をアイコンと指定できるようになり
ます。また、TabレイアウトのMDIconButtonもこのアイコンとリンクが出来ている
みたいです。これは後で詳細を見てもいいかもしれませんね。

最後にon_tab_switchですが、コールバックメソッドのようなものでタブをスイッチ
したときにファイアーされるメソッドみたいです。これはUsageの方で書いて欲しいな。

まず引数としてはself以外に、instance_tabsやinstance_tab、instance_tab_
labelとtab_textの4つがあります。これらはコメント文にある通りですが、おいおい
見ていければと思います。ここではinstance_tab、すなわちTabオブジェクトを持って
きています。さらにその中のアイコンプロパティを取り出し、ただただそれをprintして
いるだけになります。これも後で見てみましょう。

### 結果

1通りコードを見ることが出来たので、実行結果を見てみることにします。

![182.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/5486e215-b6fa-ca33-47b2-14a30bc468b8.gif)

うん、問題ありません。サンプルと相違なく動いています。

ちなみにですが、on_tab_switchメソッドの中でプリントしていた文言としては
ちゃんと出力されていれば以下のようになります。

```
Welcome to account-arrow-left-outline' tab'
Welcome to account-arrow-left' tab'
Welcome to account-alert-outline' tab'
Welcome to account-alert' tab'
```

アポストロフィーの打ち方が気になりますが、まぁそれはそれということで。

### 結果の続き

先程on_startメソッドの説明の中で、以下のように記載をしていました。

> また、TabレイアウトのMDIconButtonもこのアイコンとリンクが出来ている
> みたいです。これは後で詳細を見てもいいかもしれませんね。

すっ飛ばそうと思いましたが、どうしても気になることと、以前の何らかの篇で異なる
ことを言っていたような気がしたので、この辺りを詳しく見てみます。まず何をするか
と言うと、コードを直打ちしている方は以下の文を追加してみてください。
※ snackbar自体は先週触れているので気になる方は以下リンクもしくは
　マニュアルの該当ページをご覧ください

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

```diff_python

(略)

KV = '''
+ #:import Snackbar kivymd.uix.snackbar.Snackbar

(略)

<Tab>

    MDIconButton:
        id: icon
        icon: root.icon
        user_font_size: "48sp"
        pos_hint: {"center_x": .5, "center_y": .5}
+       on_release: Snackbar(text=str(root)).open()

(略)
```

単にMDIconButtonのon_releaseプロパティのコールバックメソッドで、Snackbar
オブジェクトを作りアプリ側で表示しているだけになります。何を表示しているかと
言うとrootがなんぞやということになります。これが準備できれば、アプリを起動
してコンテンツ側のアイコンを押してみると以下のようにsnackbarが表示出来ます。

![183.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/7cc82016-cfe8-0154-f50b-2a3ec73b5145.png)

なんと、on_tab_switchメソッドの中で説明があった第3引数のinstance_tabの
出力がされているではありませんか。なんらかの篇でroot.hogeのrootはルート
ウィジェットになります、みたいなことを言っている可能性があります。というか
記憶にあるな、、総ざらいしてページを訂正するのは後にして、ここにてそのこと
を訂正します。間違ったことを発信してしまったことはお詫び申し上げます。

## まとめ

はい、ということで中途半端な終わり方ですが、一旦ここにて締めくくろうかと
思います。いかがだったでしょうかというのは来週におあずけですね。

まぁ、でもこれだけでも使い方とかは分かるので、おっ、じゃあこれでなんか作っ
てみるかという方もいらっしゃるのではないでしょうか。リストなんかと作り方は
似ていて決して難しいものではないかと思われます。

来週にやろうかと思いましたが、切りがいいので、ここで今後の展望などを書いて
おきます。なんとコンポーネンツの触れ込みが全て終わりましたー！と言いたいと
ころなのですが、まだ少々残っているものがあるんです。。それを以下に記載します。

* AnchorLayout
* CircularLayout
* FitImage
* FileManager
* (*Picker)
* (DataTables)

とまぁ、追加分も含めてこれだけありました。結構多いですね。1番目と2番目に
ついては以前のようにまとめて触れ込めればと思います。FitImageはいつぞや
に追加されたものですね。何回か登場はしているけど。

あとは、現バージョンでは動かせない、実際にアプリで落とし込んでみたいなど
色々あるので後半3つはおいおいゆっくり見ていきたいなぁと思っています。これ
らが終わり次第、Behaviors章に入っていきます。

あとは十分なくらい知識を入れれたので、アプリもそろそろ作成しなきゃですね。
何にしよう、、まぁでも最初は簡単なアプリからスタートになりそうです。TODO
アプリとかかな。ありきたりだなぁ。。

ということで今週はここまでとしようと思います。来週はこのTabs篇の続きですね。
ではまた来週〜。

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

## 参照

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