目次
ソースコードはここにあります。
- はじめに
- Recycle View
- 一致する場所をマークアップする
- View Classの作成
- 完成
- さいごに
はじめに
本記事ではタイトルの通り、PythonのGUIライブラリーのKivyを使用して入力された文字に一致する項目を逐次表示する検索ボックスを作成していきます。
イメージとしてはGitHubのファイルの検索ボックスのような感じです。
Recycle View
RecycleViewについて説明します。
RecycleViewはMVCデザインパターンに基づいて設計されたクラスで、ListViewの後継だそうです。
基本的には
- MVCのM(model)に値するdataプロパティを設定する
- ビュークラスを指定する
- 子にRecycleBoxLayputなどのRecycleView用のレイアウトウィジェットを追加する
ことで使用できます。
例を見てみましょう。
test.py
from kivy.uix.recycleview import RecycleView
from kivy.app import App
class MyView(RecycleView):
def __init__(self,**kwargs):
super(MyView,self).__init__(**kwargs)
self.data = [{'text':'1'},{'text':'2'}] #1
self.viewclass = Button #2
class MyApp(App):
def build(self):
return MyView()
My.kv
MyView:
RecycleBoxLayout: #3
orientation: 'vertical'
default_size_hint: 1,None
default_size: None,dp(45)
size_hint_y: None
spacing: 5
height: self.minimum_height
padding: 10
解説:
まずはdataプロパティについて。dataプロパティは辞書のリストです。それぞれの辞書にはviewclass(ここではButton)のプロパティ名とその値を指定できます。ですので例えばBubbleをviewclassにした場合、{'arrow_pos':'left_top'}のようにしてarrow_posプロパティを指定できます。
次はviewclassについて。viewclassは読んで字のごとく、MVCのV(view)にあたる部分の表示をどのクラスでするか、ということを指定できます。
最後に子に追加するレイアウトウィジェットについて。今回はRecycleBoxLayoutを使用しました。これを追加することによってデータ表示の形式を決められます。
一致する場所をマークアップする
KivyではLabelクラス等のmarkupプロパティをTrueにすることで、テキストをマークアップできます。
今回は検索ワードと一致した場所を濃く表示するために使います。
それではこれを利用して一致部分を濃く表示する機能をもったRecycleViewのサブクラスを実装していきましょう。
MacthView.py
from kivy.uix.recycleview import RecycleView
class MatchView(RecycleView):
def __init__(self,**kwargs):
super(MatchView,self).__init__(**kwargs)
self.titles = ['aiueo','tyuukansikenyada','marasonnkirai']
self.match_data = {}
def match(self,word):
self.match_data = {}
for title in self.titles:
i = 0
if title.find(word) >= 0:
if not self.match_data.get(word):
self.match_data[word] = {}
if not self.match_data[word].get(title):
self.match_data[word][title] = {title:[]}
while True:
index = title.find(word,i)
if index >= 0:
i += index+len(word)
self.match_data[word][title][title].append(index)
else:
break
self.data=[]
for w in self.match_data:
for t in self.match_data[w]:
indexes = self.match_data[w][t][t][::-1]
for i in indexes:
t = t[:i]+'[color=#AAAAAA]'+w+'[/color]'+t[i+len(w):]
self.data.append({'text':t,'color':(0,0,0,0.2),'markup':True})
解説:
検索ワードと一致する場所を探すためのメソッド、matchを定義しています。
前半のfor文では{word:{title:{title:[index1,index2...]}}}といった形の辞書を作成し、self.match_dataに代入しています。
後半ではそれぞれのタイトルごとに一致した場所に[color]タグを挿入する事でほかの場所と色を変えています。
ただし前の方からタグを挿入してしまうと、たとえば
title = '123531234'
indexes = [0,5]
word = '1'
のような場合に
'[colo[color=#AAAAAA]1[/color]r=AAAAAA]1[/color]23531234'
というむちゃくちゃな文章になってしまいますので[::-1]とスライスをうまく使ってリストを逆順にすることでその問題を解決しています。(indexes = indexes.sort(reverse=True)でも同じことができます)
※10/26 追記 リスト内法表記を使うと良い、とのご指摘を受けましたのでリスト内法表記を使ったプログラムを掲載しておきます。
def match(self.word):
self.data = [{'text':title.replace(word,'[color=#AAAAAA]'+word+'[/color]'),
'color':(0,0,0,0.2),'markup':True}
for title in self.titles
if title.find(word.lower()) >= 0 or title.find(word.upper()) >= 0]
この書き方の方が高速です。多分。
View Classの作成
MatchView.pyを作成しましたが、viewclassの指定とレイアウトウィジェットの追加がまだでした。
以下のMatchView.kvにてそれらを追加していきます。
MatchView.kv
MatchView:
id: matchview
size_hint_y: 0.5
viewclass: 'MyLabel'
RecycleBoxLayout:
orientation: 'vertical'
default_size_hint: 1,None
default_size: None,dp(45)
size_hint_y: None
scaping: 5
height: self.minimum_height
padding: 10
これでviewclassとレイアウトウィジェットが追加できました。
しかし!このままでは各ラベルに境界線もなく文字がつらつら表示されているだけなので見た目が微妙です。
なので独自に表示用のラベルを定義してしまいましょう。
<MyLabel@Label>:
canvas:
Color:
rgba: 0,0,0,0.6
Line:
points: (self.x,self,y),(self.x+self.width,self.y)
これで見た目も良くなります。
因みにMyLabelで使った方法(キャンバスの下部にLineで線を引く)は結構よく使うので覚えておいて損はないかと思います。
それでは今まで定義してきたクラスやビューをひとまとめにして実行できるようにしてみましょう。
MatchView.pyに以下のコードを追加します。
from kivy.app import App
MacthViewApp(App):
pass
MatchViewApp().run()
そしてMatchView.kvを以下のように変更します。
Root
<MyLabel@Label>:
canvas:
Color:
rgba: 0,0,0,0.6
Line:
points: (self.x,self.y),(self.x+self.width,self.y)
<Root@BoxLayout>:
size: root.size
orientation: 'vertical'
padding: self.width * 0.2,self.height * 0.15
matchview: matchview
canvas:
Color:
rgba: 0.99,0.99,0.99,1
Rectangle:
size: self.size
pos: self.pos
TextInput:
size_hint_y: 0.1
font_size: self.height/2
on_text:
if self.text: root.matchview.match(self.text)
else: root.matchview.data = []
MatchView:
id: matchview
size_hint_y: 0.5
viewclass: 'MyLabel'
RecycleBoxLayout:
orientation: 'vertical'
default_size_hint: 1,None
default_size: None,dp(45)
size_hint_y: None
scaping: 5
height: self.minimum_height
padding: 10
Widget:
size_hint_y: 0.3
Button:
size_hint_y: 0.1
text: 'Go' #dumb
完成
さいごに
いかがでしたか?
今回は逐次表示検索ボックス(という名前かは分かりませんが)の作成という形でKivyについて解説させていただきました。
冒頭の通り、この記事は僕の初めての投稿です。おかしいな、と思うところがあったらぜひご指摘お願いします。