Python tkinter GUIプログラミング Canvasで箱をつなぐ線を描く

 今日も見に来てくださって、ありがとうございます。石川さんです。

 Canvasで箱を描いて、動かすことをやってきましたけど、箱と箱をつなぐためのコネクションのような線を描くことをやってみました。まあまあうまいことできたように思いますので、紹介させていただきます。

できあがりイメージ

 できあがりイメージは以下のとおりです。箱を描いて、まずは中心同士で直線を結ぶことにしました。箱を動かすと線がついてくる、というのが目標です。隠線処理は、次回のテーマとしたいと思います。

ソースコード

 ソースコードは以下のとおりです。先日お話したとおり、pylintでチェックしているのですけど、一部、リファクタリングのメッセージが残っています。それは次々回以降のテーマでお話したいと思います。(R0902: too-many-instance-attributesとR0913: too-many-arguments)

import tkinter as tk


class Entity():
    ''' Entity class '''
    def __init__(self, canvas, x, y, width=60, height=40):
        self.canvas = canvas
        self.x, self.y, self.width, self.height = x, y, width, height
        self.start_x = self.start_y = None
        self.connections = []

        self.id = self.canvas.create_rectangle(x, y, x + width, y + height,
                                               fill="lightblue", width=3)
        self.canvas.tag_bind(self.id, "<ButtonPress>", self.button_press)
        self.canvas.tag_bind(self.id, "<Motion>", self.move)
        self.canvas.tag_bind(self.id, "<ButtonRelease>", self.button_release)

    def button_press(self, event):
        ''' マウスのボタンが押されたときの処理 '''
        self.start_x = self.canvas.canvasx(event.x)
        self.start_y = self.canvas.canvasy(event.y)

    def move(self, event):
        ''' マウスが移動したときの処理 '''
        if self.start_x is None:
            return
        if event.state & 256:  # マウスボタン1が押されているときだけ(ドラッグ中のみ)
            can_x = self.canvas.canvasx(event.x)
            can_y = self.canvas.canvasy(event.y)
            coords = self.canvas.coords(self.id)
            coords[0] -= self.start_x - can_x
            coords[1] -= self.start_y - can_y
            coords[2] -= self.start_x - can_x
            coords[3] -= self.start_y - can_y
            self.canvas.coords(self.id, coords)
            self.start_x = can_x
            self.start_y = can_y
            self.x, self.y = coords[0:2]
            for connection in self.connections:
                connection.move(self)

    def button_release(self, event):  # pylint: disable=unused-argument
        ''' マウスのボタンが離されたとき '''
        self.start_x = self.start_y = None

    def get_center(self):
        ''' 中心座標を戻します '''
        return self.x + self.width//2, self.y + self.height//2

    def add_listener(self, connection):
        ''' コネクションのリスナーを登録します '''
        self.connections.append(connection)


class Connection():
    ''' Connection class '''
    def __init__(self, canvas, start_entity, end_entity):
        self.canvas = canvas
        self.start_e = start_entity
        self.end_e = end_entity
        start_entity.add_listener(self)
        end_entity.add_listener(self)

        self.make_figure()

    def make_figure(self):
        ''' コネクションを描きます '''
        start_point = self.start_e.get_center()
        end_point = self.end_e.get_center()
        self.id = self.canvas.create_line(start_point, end_point)

    def move(self, entity):
        ''' エンティティが移動したときの処理(エンティティから呼び出される)'''
        coords = self.canvas.coords(self.id)
        if entity == self.start_e:
            coords[0:2] = entity.get_center()
        elif entity == self.end_e:
            coords[2:4] = entity.get_center()
        self.canvas.coords(self.id, coords)


class Application(tk.Tk):
    ''' Application class '''
    def __init__(self):
        super().__init__()
        self.title("Connecter test")
        self.geometry("640x320")

        self.canvas = tk.Canvas(self, background="white")
        self.canvas.pack(fill=tk.BOTH, expand=True)

        entity1 = Entity(self.canvas, 40, 80)
        entity2 = Entity(self.canvas, 240, 160)
        Connection(self.canvas, entity1, entity2)
        entity3 = Entity(self.canvas, 420, 60)
        Connection(self.canvas, entity2, entity3)


def main():
    ''' main function '''
    application = Application()
    application.mainloop()


if __name__ == "__main__":
    main()

説明

 箱を動かす部分については、これまでに書いてきているので省略します。今回の一つ目のポイントは、Entityクラスです。10行目で接続されたコネクションを受け取るためのリストを作成しています。50~52行で定義されたadd_listener()メソッドで、リスナーとしてコネクションを登録できるようにしています。Entityクラスのmove()メソッドの39~40行で、登録されたコネクションのmove()メソッドが呼び出されます。

 2つ目のポイントは、Connectionクラスです。55~79行目です。コネクションが登録されたときに、箱の情報を取得するようにしています。指定された箱のadd_listener()メソッドを使って自分自身を登録します。登録することで、箱が移動したときに、こちらのmove()メソッドを呼び出してもらって、箱の動作にあわせてコネクションがついていくようになります。

まとめ

 箱に連動するコネクターが作れるようになりました。次回は接続部分の隠線処理について書きたいと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


reCaptcha の認証期間が終了しました。ページを再読み込みしてください。