Quick search

A Simple Paint App(翻訳済み)

このチュートリアルでは、最初にwidgetを作成する方法を教えます。Kivyアプリケーションをプログラミングする際、あなたが特定の目的ためにカスタム要素で新しいユーザーインターフェースを作成するための最も重要な知識を提供します。

Basic Considerations

アプリケーションを作成する前に、自分自身で重要な3つの項目を確かめる必要があります。

  • アプリケーション・プロセスはどのようなデータですか?

  • どのようにしてそのデータを視覚的に表現しますか?

  • ユーザーはどのようにデータと相互作用しますか?

たとえば、非常に単純な線画アプリケーションを記述したいのであれば、ユーザーは指でスクリーンに描画することをたぶん望みます。 それは、ユーザーがアプリケーションと 相互作用 する方法です。その一方で、アプリケーションは後からユーザーの指があった位置を記憶します。そのため、あなたは後からそれらの位置の間に線を描くことができます。つまり、指のあった点はあなたの データ であり、それらの間に描く線は 視覚的な表現 です。

Kivyでは、アプリケーションのユーザーインタフェースはwidgetから構成されています。画面に表示されるすべてが何らかの形でwidgetによって描かれています。しばしば、異なる文脈で書いたコードを再利用したいと思うでしょう。widgetはデータをカプセル化し、そのデータとユーザの対話を定義します。そしてその視覚的な表現を描画します。あなたは、入れ子のwidgetによって、単純なインターフェースから複雑なインタフェースまで、何でも構築することができます。これには、ボタン、スライダーや他の一般的なもの、として構築された多くのwidgetがあります。しかし、多くの場合、Kivyに同胞されているウィジェットの範囲を超えたcustom widget(例えば、医療を視覚化するためのウィジェット)が必要となります。

widgetをデザインするときは、この3つの質問に留意してください。最小限かつ再利用可能な方法で書き込むようにしてください(widgetはまさにそれが何をすべきか、それ以上のことはしません。もっと必要な場合は、より多くのwidgetを作成するか、小さなwidgetか他のwidgetを作成してください。単一責任の原則(Single Responsibility Principle )に従ってください)。

Paint Widget(ペイントwidget)

私たちはあなたの子供のころの夢の一つに、マルチタッチのペイントプログラムを作りたいと思っていたと確信しています。その夢を実現させるために私たちに手助けさせてください。次のセクションでは、Kivyを使ってペイントプログラムを書く方法を順を追って学習します。まずは Create an application(アプリケーションを作成する) を読んで基本的な内容を理解していることを確認してください。すでに読んでいる?すばらしいです!始めましょう!

Initial Structure(初期構造)

必要ですがとても基本的な構造のコードを書くことから始めましょう。ところで、このセクションで使用されているさまざまなコードは、Kivyに付属している「examples/guide/firstwidget」ディレクトリにもありますので、コピー&ペーストする必要はありません。ここに必要なコードの基本的な構造は以下のとおりです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from kivy.app import App
from kivy.uix.widget import Widget


class MyPaintWidget(Widget):
    pass


class MyPaintApp(App):
    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()

これは実際にはとても簡単です。このコードをpaint.pyとして保存します。paint.pyを実行すると黒い画面しか表示されません。ご覧のように、Button(:ref:`quickstart`を参照)などの組み込みwidgetを使用する代わりに、独自のwidgetを作成して描画を行います。widgetから継承するクラス(5-6行目)を作成することで描画を行いますが、独自クラスはまだ何もしませんが、通常のKivyウィジェット(11行目)のように扱います。「if __name__」 ...構造体(14行目)は、ファイルからインポートするときにif文でコードを実行できないようにするPythonの仕組みです。``import paint``を書くと、予期せぬことが起きないようにファイルに定義されたクラスを提供します。

注釈

「from kivy import *」のようなことをするのではなく、AppとWidgetを別々にインポートする必要があるのか​もしれません。短くすると、「`ネームスペースを汚染<http://en.wikipedia.org/wiki/Namespace_%28computer_science%29#Python>`_」し、アプリケーションの開始を潜在的には非常に遅くするという欠点があります。また、クラスと変数のネーミングにあいまいさをもたらす可能性があるため、一般的にPythonコミュニティではお勧めされていません。今回のやり方はより速くよりクリーンです。

Adding Behaviour(動作の追加)

widgetに実際の動作を追加しましょう。ユーザーの入力に反応させます。 以下のようにコードを変更します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from kivy.app import App
from kivy.uix.widget import Widget


class MyPaintWidget(Widget):
    def on_touch_down(self, touch):
        print(touch)


class MyPaintApp(App):
    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()

ここではユーザーの入力にどのように反応するのかを簡単に説明しています。MotionEvent (タッチ、クリックなど)が発生すると、タッチオブジェクトに関する情報がコンソールに表示されます。画面上では表示されませんが、プログラムを実行しているコマンドラインを確認すると、すべてのタッチに関するメッセージが表示されます。これはまたwidgetが視覚的表現を持つ必要がないことを示しています。

今はあまりユーザーエクスペリエンスではありません。実際に何かをウィンドウに描画するコードを追加しましょう:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        with self.canvas:
            Color(1, 1, 0)
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))


class MyPaintApp(App):

    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()
../_images/guide-3.jpg

これらの変更を加えてコードを実行すると、タッチするたびにタッチした場所に小さな黄色の円が描かれます。どのように機能しているのですか?

  • 9行目:ウィジェットの Canvas オブジェクトでPythonの「with」コマンドを使用します。これはwidgetが画面上に自分自身を表すために物を描く領域のようなものです。 「with」ステートメントを使用すると、適切に字下げされたすべての連続した描画コマンドがこのcanvasを変更します。 withステートメントは、描画後、内部状態を適切にクリーンアップできることも保証します。

  • 10行目:すでに推測しているかもしれません:これは、連続した描画操作の”Color を黄色に設定します(デフォルトの色形式はRGBですので、(1,1,0)は黄色です)。別の色が設定されるまでは黄色のままです。これでブラシを黄色にしてそれを別の色に塗りつぶすまでキャンバスに描画できます。

  • 11行目:描画する円の直径を指定します。円の値を複数回参照する必要があるので変数を使用することが望ましいです。円を大きくするか小さくするかで複数の場所で変数を変更する必要はありません。

  • 12行目:円を描画するには”Ellipse で、幅と高さが等しいシンプルな円を描画します。ユーザーがタッチする場所に円を描きたいので、タッチの位置を円に渡します。位置は円の境界ボックスのを指定するため、円をx方向とy方向の「2/d」(つまり左下隅)に移動する必要があることに注意してください。

簡単でしたね。 さらに良くなります! 以下のようにコードを更新します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Line


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        with self.canvas:
            Color(1, 1, 0)
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        touch.ud['line'].points += [touch.x, touch.y]


class MyPaintApp(App):

    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()
../_images/guide-4.jpg
以下は変更された内容です:
  • 3行目:~kivy.graphics.vertex_instructions.Ellipse の描画コマンドだけでなく、 Line 描画コマンドもインポートしました。 Line のドキュメンテーションを見ると、(x1、y1、x2、y2、...、xN、yN)のような2Dポイント座標のリストの引数「points」を受け取ることがわかります。

  • 13行目:これは興味深い箇所です。「touch.ud」は、タッチ用のカスタム属性を格納するPythoの辞書型(type <dict>)です。

  • 13行目:インポートしたLine命令を使用して、描画用にLineのupを設定します。これはon_touch_downで行われるので、新しいタッチのたびに新しい行が表示されます。withブロックの中の線を作成するコードにより、canvasは自動的にその線を知り描画します。後でその行を修正したいので、任意に選択して適切に名前を付けたキーの ‘line’の下でtouch.ud辞書に参照を格納します。最初のタッチ位置を作成する線を渡します。これは線の開始部分であるからです。

  • 15行目:ウィジェットに新しいメソッドを追加します。これは「on_touch_down」メソッドに似ていますが、新しい タッチが発生したときに呼び出される代わりに、(on_touch_downが既に呼び出されている)既存のタッチが移動したとき、つまりタッチの位置が変更されたときに呼び出されます。これは、更新された属性を持つ同じ MotionEvent オブジェクトであることに注意してください。これは信じられないぐらい便利だと分かったことであり、すぐにその理由がわかるでしょう。

  • 16行目:覚えておいてください:これは「on_touch_down」に入ったのと同じタッチオブジェクトなので、辞書型の変数touch.udに保存したデータに簡単にアクセスできます! 先にこのタッチのために設定した行に現在のタッチの位置を新しいポイントとして追加します。 「on_touch_move」ではタッチが移動したときにのみ呼び出されるので、延長する必要があることはわかっています。正確にはラインを更新する理由です。 touch.udに線を保存することで、私たち自身のタッチ・ツー・ラインの位置を保持する必要がなくなるのでずっと簡単になります。

ここまでは順調ですね。しかし今の状態はまったく美しくないです。それはスパゲッティのボローニャのようなものです。それぞれのタッチに独自の色を付けるのはどうですか?グレート、実装しましょう:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Line


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        color = (random(), random(), random())
        with self.canvas:
            Color(*color)
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        touch.ud['line'].points += [touch.x, touch.y]


class MyPaintApp(App):

    def build(self):
        return MyPaintWidget()


if __name__ == '__main__':
    MyPaintApp().run()
../_images/guide-5.jpg

変更点は以下の通りです:

  • 1行目:Pythonのrandom()関数をインポートして、[0.、1]の範囲でランダムな値を与えます。

  • 10行目:この場合ランダムなRGBカラーを表す3つのランダムな浮動小数点値をもつ新しいタプルを作成するだけです。 「on_touch_down」でこれを行うので、すべての新しいタッチが独自の色を取得します。`タプル<http://docs.python.org/2/tutorial/datastructures.html#tuples-and-sequences>`_ の使用によって混乱することはありません。 私たちは怠け者ですのでこのメソッド内のショートカットとして使用するためにタプルを色付けします。

  • 12行目:上と同じように、キャンバスの色を設定します。今度は、私たちが生成したランダムな値を使用して、Pythonのタプル展開構文を使用してcolorクラスに値を渡します(Colorクラスはただ1つではなく3つの個別のカラーコンポーネントを必要とするためです)。タプルを直接渡すと、 タプル自体に3つの値が含まれているかどうかに関係なく、1の値が渡されます)。

この時点でかなり良くなりまいた!多くのスキルと忍耐があれば、ちょっと素敵な絵を描くこともできるかもしれません!

注釈

デフォルトでは、:class:`~kivy.graphics.context_instructions.Color 命令はRGBモードを想定しており、3つのランダムな浮動小数点値を持つタプルを供給しているので、運が悪いときにには暗い色や黒の色が多くなることがあります。デフォルトでは背景色も暗いため、描画する線を(簡単に)見れないためにこのことは悪いことです。これを防ぐには素晴らしいトリックがあります:3つのランダムな値を持つタプルを作成する代わりに、「(random(), 1., 1.)」のようなタプルを作成します。次に、カラー命令に渡すときは、モードをHSVカラースペース:Color(*color, mode=’hsv’)に設定します。この方法を使用することで可能な色の数は少なくなりますが、得られる色は常に明るくなり色相のみが変化します。

Bonus Points(ボーナスポイント)

この時点で完成と言えるでしょう。widgetはそれぞれの役割をおこないます:タッチをトレースし、ラインを描画します。また線が始まる位置に円を描画することもできます。

しかし、ユーザーが新しく作画を開始したい場合はどうなりますか? 現在のコードでは、ウィンドウを消去する唯一の方法は、アプリケーション全体を再起動することです。幸いにももっと良い案があります。今まで描かれているすべての線と円を消去するクリアボタンを追加しましょう。現在次の2つのオプションがあります。

  • widgetの子としてボタンを作成できます。それは複数のwidgetを作成すると、すべてのwidgetが独自のボタンを取得することを意味します。注意しないとユーザーはボタンの上に描画できます。

  • 最初にボタンを1回だけアプリケーションクラスに設定して押したときにウィジェットをクリアします。

簡単な例ではさほど重要ではありませんが大規模なアプリケーションの場合、アプリで誰が何をしているかを考えなければなりません。ここでは2番目のオプションを使用して、アプリケーションクラスのbuild()メソッドでアプリケーションのウィジェットツリーを構築する方法を見ていきます。また、HSV色空間に変更します(上の注を参照)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line


class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        color = (random(), 1, 1)
        with self.canvas:
            Color(*color, mode='hsv')
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        touch.ud['line'].points += [touch.x, touch.y]


class MyPaintApp(App):

    def build(self):
        parent = Widget()
        self.painter = MyPaintWidget()
        clearbtn = Button(text='Clear')
        clearbtn.bind(on_release=self.clear_canvas)
        parent.add_widget(self.painter)
        parent.add_widget(clearbtn)
        return parent

    def clear_canvas(self, obj):
        self.painter.canvas.clear()


if __name__ == '__main__':
    MyPaintApp().run()
../_images/guide-6.jpg

ここで何が起きたのですか?

  • 4行目:Button クラスを使用できるようにimportステートメントを追加しました。

  • 25行目:ペイントwidgetと追加しようとしているボタンの親としてダミーのWidget()オブジェクトを作成します。これは、widgetのツリー階層を設定するための安価な方法です。私たちは、レイアウトを使用するだけでなく、他のすばらしい機能を使うこともできます。再び:このwidgetは、2つのwidgetを子として保持する以外は何もしません。

  • 26行目:私たちは「MyPaintWidget()」いつものように作成しますが、今度は直接返さずに変数名にバインドします。

  • 27行目:ボタンwidgetを作成します。 テキスト「Clear」が表示されたラベルが表示されます。

  • 28行目:ボタンのon_releaseイベント(ボタンが押されて離された時にに起動される)を、33行目と34行目で定義されている コールバック関数 の「clear_canvas」にバインドします。

  • 29行目と30行目:ダミーの親widgetのペインターとクリアボタンの両方の子を作成してwidgetの階層を設定します。これは、通常のコンピュータの用語では、「ペインター」と「クリアボタン」が同じツリー構造にあることを意味します。

  • 33行目と34行目:今まではボタンは何もしませんでした。存在していて目に見えて押すことができましたが、何も起こりません。ここでは、ボタンを押したときに コールバック関数 になる小さなゴミ捨て用の関数を作成します。この関数は、ペインターのキャンバスの内容をクリアして、再び真っ黒にします。

注釈

Kivy Widgetクラスは、設計上シンプルに保たれています。背景色や境界色などの一般的なプロパティはありません。代わりに例とドキュメントでは、今回の例で行ったような簡単な内容、キャンバスの色を設定し、図形を描画する簡単な方法を示します。簡単なスタートからより精巧なカスタマイズに移行できます。 Buttonのようなwiggetから派生したより高いレベルの組み込みwidgetは、background_colorのような便利なプロパティを持っていますが、そのプロパティがあるかないはwidgetによって異なります。 APIドキュメントを使用してwidgetが提供するものを確認し、さらに機能を追加する必要がある場合はサブクラスを使用します。

おめでとう!あなたは最初のKivy widgetを書いています。明らかに、簡単な紹介でした。発見すべきものはたくさんあります。私たちはあなたが学んだことがことを理解するために短い休憩を取ることを提案します。あなたがすべてを理解していてより多くの準備ができていると感じたらお読みください。