kivyMDチュートリアル其の弍什弍 Components - Menu篇

みなさん、おはこんばんは。
天気がグズつく日が多くなってきましたが、いかがお過ごしでしょうか。
Virtyはそれほどよい体調で過ごしてはいませんね・・・
\# よい体調であったことがあまり記憶にありません

KivyMDを触ってからまもなく半年が過ぎようとしているところです。えぇぇ、季節が
すぎるの早くないぃですかぁぁと思ってしまいましたが、毎週毎週記事を欠かさず書け
てきたのは、これまで見ていただいた方のおかげかと思っているところです。本当に
感謝、感激、高○英○(さん)であります。\# 意味不明

また、しらずしらずkivyMDのComponentsが半分を過ぎていたことも今日知り意気揚々
としています。ですが、浮つくことなく、粛々とこれまで通り記事を生成して参る、所存、
で、あります。\# 急にかしこまるという \# モノマネでもないよ

そこで、先週との伏線回収となるのですが、今日は順番通りいくとMDSwiperとなります。
しかし先週言ってたことというと、以下の通りとなります。

> ということで今週はここまでということで！来週はMenu篇となります。
> あれっ、MDSwiperが先なのでは？と思われた方はその通りなのですが、色々事情が
> あってですね・・・まぁでもそれは来週話すとします。

> kivyMDチュートリアル其の弍什壱 Components - List篇より
> https://qiita.com/virty/items/3871b85bc67179d67b30

この理由としては、なんとも単純なワケがありましてもうお察しかもしれませんが、なんと
動かなかっただけなのですねw なので、動かなければ直しなさいよというご指摘があれば
んんんんーと黙りこくってしまう(黙ってない)だけなのですが、そんな技術力は私にはない
わけなのです。
\# そもそもそんな技術力があれば記事を書くこともなかった

これも少し調査をしまして、行き着いた結果としてはそもそも機能自体まだ用意されていなく、
KivyMDの次バージョンで直ってるみたいだぞという、まぁStackOverFlowで見たものをその
まま言っているだけなのですが。。んで、伝聞をここで載せるだけというのは小学生かもしくは
幼稚園児くらいしか許されません。なので裏付けを取るべく、以下のような記述を公式マニュアル
から読み取れましたので、引用しておきます。

> 0.104.2
> ・ Added MDSwiper component

> [参照]
> Changelog
> https://kivymd.readthedocs.io/en/latest/changelog/

なんと・・・、これは現バージョンでは動かないはずだ・・・

あ、この記事で採用している現バージョンはずっと以下から変更せず、0.104.1になります。
\# なつかしい

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

んで、Changelogからも実際に動かしてみたことからもComponentsの後半にかけては、結構
動かないものもポロポロ出てきました。具体的な対応方針としては、来週の冒頭で案内する形
となりますが一度0.104.1で出来るところまで進めるかと思います。なので、いきなりバージ
ョンを上げましたーみたいなことはないのでご安心のほどを。

ということで、長かったな、MDSwiperをスキップした理由が上記の通りとなります。まぁ、
よく分からんとなった方は単に動かなかっただけだよー、心配することないよーということ
だけ分かってもらえればと思います。

ということで、今日はMenu篇となります。

## Menu

まぁ、恒例行事であるリンクはスキップするのですけれども(固定化)。こちらに関しては、
なじみのあるもので見たことあるという方のほうが多いのではないでしょうか。Components
でも似たようなものは以前でもありましたね。はいせーの、Dropdown Itemですね(誰も
言ってなさそう)。そこでも、ここ(Menu篇)をお楽しみにと言っていましたがそちらが気になる
という方は、以下のページをご覧ください。

https://qiita.com/virty/items/0a039e24cdce85585698

こちらの方でもさっき言ってた影響はなくはありません。というか無茶苦茶おおありで、なんと
Usageより下にあるコードは全て動きません。まぁ、これも来週の対応方針で触れることなので
一旦ここではスルーすることとします。なんにせよ、スルーしっぱなしということはありません
のでご安心のほどを。

とまぁ、歴代以上前置きが長くなってしまいましたが、ここからはマニュアルのほうに戻ります。
概要としては以下のように記載があります。

> Menus display a list of choices on temporary surfaces.

ずっと、表示されることがないということですかね。確かにこういうウィジェットは表示されたり
消えたりします。まぁ、これも見た方が早いと思うのでコードと結果を見てみましょうか。

### Usage

```python:xxii
from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.menu import MDDropdownMenu

KV = '''
MDScreen:

    MDRaisedButton:
        id: button
        text: "PRESS ME"
        pos_hint: {"center_x": .5, "center_y": .5}
        on_release: app.menu.open()
'''


class Test(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = Builder.load_string(KV)
        menu_items = [
            {
                "text": f"Item {i}",
                "viewclass": "OneLineListItem",
                "on_release": lambda x=f"Item {i}": self.menu_callback(x),
            } for i in range(5)
        ]
        self.menu = MDDropdownMenu(
            caller=self.screen.ids.button,
            items=menu_items,
            width_mult=4,
        )

    def menu_callback(self, text_item):
        print(text_item)

    def build(self):
        return self.screen
```

ここもいつものように、分けて触れ込みたいと思います。

#### import文

今回におけるimportするパッケージは以下の通りとなります。

```python
from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.menu import MDDropdownMenu
```

今回はクラス側にてMDDropdownMenuが必要なので、指定をしています。あとは、おなじみ
ですかね。

#### kv側

同様に、kvでどのようなものが使われているか引用します。

```python
KV = '''
MDScreen:

    MDRaisedButton:
        id: button
        text: "PRESS ME"
        pos_hint: {"center_x": .5, "center_y": .5}
        on_release: app.menu.open()
'''
```

ここの定義はそれほど、触れるまでもないかもしれませんね。MDRaiseButton1つだけの
超シンプルな定義です。その中のon_releaseコールバックメソッドにapp側のopenメソ
ッドが指定されていますね。これはクラス側の方を見てみるしかないようです。

#### Testクラス側

問答無用にTestクラス側を引用してみます。

```python
class Test(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = Builder.load_string(KV)
        menu_items = [
            {
                "text": f"Item {i}",
                "viewclass": "OneLineListItem",
                "on_release": lambda x=f"Item {i}": self.menu_callback(x),
            } for i in range(5)
        ]
        self.menu = MDDropdownMenu(
            caller=self.screen.ids.button,
            items=menu_items,
            width_mult=4,
        )

    def menu_callback(self, text_item):
        print(text_item)

    def build(self):
        return self.screen
```

まずは、initメソッドから。ここではなにやら、menu_itemsというものがありますね。
これはなんでしょうか。1番中を注視してみると、どうやらディクショナリ型で定義している
ようですね。んで、その外にこの[]括弧があるので複数のディクショナリを格納するリストの
ようです。[リスト{複数のディクショナリ}]という形になりますかね。\# 余計ややこしいか


中身はというと、textやらviewclass、on_releaseプロパティなどそれぞれ定義をされて
います。あとは忘れてはいけませんが、rangeとあるので5回分ListItemの要素となるものを
作っていることもあります。

これはなぜこのような形をしているのかと考えると、プロパティを入れ込むにあたってはこの
形をとらざるを得なかったのでしょう。というかkv側で定義するときもpython側で色々生成
するとなると、この形を取るしかなさそうな気もします。

あとはここだけを見ると、何か見覚えありませんでしたでしょうか。そうです(誰もうんとは
言っていないと思う)、「Themes - Icon Definitions篇」になります。そこでもview-
classは出てきましたが、詳細な説明はしていませんでしたけどね・・・まぁ、一応参考程度
に以下を見てもらうと嬉しいです。今回はMenuの配下としてviewclassプロパティにOne-
LineListItemを指定しています。

https://qiita.com/virty/items/97499128a205113431d5

そしてここからはMDDropdownMenuインスタンスの生成に移りますが、ここではcallerプロ
パティにkv側で定義しておいたbutton、itemsには先程のmenu_items、width_multに4を
指定しています。width_multだと・・・？まぁこれは単なる横幅と考えてもらえればですね。
詳しくはAPIのところで触れておきます。

あと、少し忘れていましたがkv側でのコールバックメソッド(app.menu.open())はこのself
.menuすなわちMDDropdownMenuが持っているopenメソッドのことになります。

あとは、menu_itemsの中で指定していたコールバックメソッド(menu_callback)は渡されて
いたテキストを単にprintしているだけになりますね。まぁ、これも動いてないのだけれどもと
いう前提はあるのですが・・・

あとは、なじみのあるscreenをリターンしているbuildメソッドなどがあります。

### 結果

さて、論より証拠、結果の方を見てみましょう。

![110.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/3ea1b73b-3be4-98ec-964f-164bc94a912b.png)

まぁ、載せなくともいいような感じはありますが・・

![111.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/c63392e8-836b-cdde-a1b7-c691d2c40d62.png)

んで、やっとメニューが開きました。全然問題はありませんね。
printメソッドの機能以外は・・

そこでさっき言ってたwidth_multプロパティを倍数の8にするとどうなるか見てみました。

![112.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82826/83b336e6-82cf-f07f-17f3-048af5f3830e.png)

広いですねぇー。タブレッドサイズならとは思いましたが、少しくどいような気もします。

## API

えぇ、もうAPIに入るの？と言われそうなくらい早く逃げるようにAPIに入っていますが、
動かない以上はどうしようもありません(無責任)。まぁ、安心してください。アップデート
後に触れるつもりではありますから。

###  class kivymd.uix.menu.MDDropdownMenu(**kwargs)

`on_release`

> The method that will be called when you click menu items.

今回のサンプルでは、menu_itemsのそれぞれに指定したon_releaseプロパティなら指定
していましたが、これはどっちなんだろう。直接指定することはできるのか・・まぁ、今度
精査することとします。

`items`

> See data.

> items is a ListProperty and defaults to [].

まぁ、これは触れるまでもありませんが、リスト型であるのは忘れてはいけません。中身に
ディクショナリを持つのは構いませんが。

`width_mult`

> This number multiplied by the standard increment (56dp on mobile, 
> 64dp on desktop, determines the width of the menu items.

> If the resulting number were to be too big for the application Window, 
> the multiplier will be adjusted for the biggest possible one.

> width_mult is a NumericProperty and defaults to 1.

どうやら、デスクトップとモバイルで使用するdpが異なるのですね。デスクトップやスマホの
タイプによってサイズが異なることも注意が必要そうです。

`caller`

> The widget object that caller the menu window.

> caller is a ObjectProperty and defaults to None.

今回はボタンを選択しましたね。今度触れるときは違うウィジェットを選択するかもしれません。

## まとめ

はい、いかがだったでしょうか。
本当なら、ボタンを増やして色々挙動を見るというのもやってみたかったのですが今回は
タイムアップということで、すみません。まぁ、今度触れるしで許してもらえればと思い
ます。

これだけだと、何か物足りないな〜というのは私も同じ気持ちになっています。今度触れる
ときのお楽しみということで、今日はこの辺で締めくくりたいと思います。

来週は予定通りいくとNavigation Drawerなのですが、これも動かない以上どうしようも
ないので次回は結構飛ばしてPickers篇となります。これも忘れてはいけませんが、今後の
対応についても冒頭にて触れるようにします。

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

## 参照

> Components » Menu
> https://kivymd.readthedocs.io/en/latest/components/menu/