コンテンツにスキップ

PySideでタグクラウドっぽいWidgetを作る

前回 作ったFlowLayoutを利用して、今度は実践として
ウェブページなどでよく見かけるタグクラウドっぽいWidgetを作ってみようかと思います。

全コードはこちら
FlowLayout部分は前回と同じなので、説明は省きます。

基本機能

今回作るタグクラウドでは、まずこのようなシンプルな表示のみをする機能と、

一覧のタグを追加・削除することができる機能、
そして現在のタグの一覧を取得できるようにします。

Tag部分

 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
class Tag(QFrame):

    deleteTag = Signal(object)

    def __init__(self, name="", color: QColor = QColor(200, 200, 200), deletable=True, parent=None):
        super().__init__(parent)

        css = f"background-color: rgb({color.red()}, {color.green()}, {color.blue()});border-radius: 5px;"
        self.setStyleSheet(css)
        self.label = QLabel(name)
        layout = QHBoxLayout()
        self.setLayout(layout)
        layout.addWidget(self.label)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setContentsMargins(10, 5, 10, 5)

        if deletable:
            self.button = QPushButton()
            icon = QIcon()
            icon.addPixmap(QPixmap("D:/work/py37/PySide/icons/batu.png"), QIcon.Normal, QIcon.On)
            self.button.setIcon(icon)
            self.button.setFlat(True)
            layout.addWidget(self.button)
            self.button.clicked.connect(self.delete)

    @property
    def name(self):
        return self.label.text()

    def delete(self):

        self.deleteTag.emit(self)

最初はタグ部分。
ベースはQFrameを使用します。
背景色や、エッジを少し丸くするのは、QSSをFrameに対して指定することで作成しています。
そして、このFrameに対してHBoxLayoutでLabelとボタンを配置するようにします。

タグのマージンは、LayoutHBoxLayoutとQFrame自体に存在しているのですが
両方入っているとコントロールしにくいので、レイアウト自体のマージンは0にしたうえで
QFrameのマージンでいい感じになるように調整しています。

そして、タグを削除するとき用にDeleteのシグナルを追加します。
シグナルでは、削除するタグ(自分自身)を渡すようにします。
これは、このTag側で削除してしまうと、レイアウトの更新的によろしくないので
このシグナルを受け取ったTagCrowd側で削除します。

タグの色は引数で変えられるようにしておきます。

TagCrowd

 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
39
40
41
42
43
44
45
46
47
48
49
50
51
class TagCrowd(QWidget):

    def __init__(self, addNewTag=True, deletetable=True, parent=None):
        super().__init__(parent)

        self.layout = FlowLayout(3, 5, 5)
        self.setLayout(self.layout)

        self.addNewTag = addNewTag
        self.deletable = deletetable

        if addNewTag:
            self.button = QPushButton(self)
            self.button.clicked.connect(self.showAddEdit)
            icon = QIcon()
            icon.addPixmap(QPixmap("D:/work/py37/PySide/icons/plus.png"), QIcon.Normal, QIcon.On)
            self.button.setIcon(icon)
            self.button.setFlat(True)

        self.tags = []

    def getTagNames(self):

        return [x.name for x in self.tags]

    def showAddEdit(self):

        editor = PopupEdit(self)
        editor.send.connect(self.addTag)
        editor.show()

    def deleteTag(self, widget):

        self.tags = [x for x in self.tags if x.name != widget.name]
        widget.deleteLater()

    def addTag(self, name):

        self.layout.clear()
        currentTags = [x.name for x in self.tags]

        if name not in currentTags:
            tag = Tag(name, deletable=self.deletable)
            tag.deleteTag.connect(self.deleteTag)
            self.tags.append(tag)

        for i in self.tags:
            self.layout.addWidget(i)

        if self.addNewTag:
            self.layout.addWidget(self.button)

続いてタグクラウド本体。
構造はシンプルで、先ほどのTagウィジェットをFlowLayoutを利用して配置します。
FlowLayout側で配置しているオブジェクトの管理や増減をするか迷ったのですが
現在のタグ一覧を取得したり、削除したり追加したいする挙動はTagCrowd側でやりたかったので
addTag時にレイアウトへの追加は毎回クリア→追加するようにします。

タグ追加ボタンは、FlowLayoutの最後尾に追加するようにしたいので、
addTagでクリア→Tagオブジェクトを追加した最後にFlowLayoutに対してaddWidgetしています。

削除は、Tagのシグナルで削除されたときにdeleteTagを呼ぶようにします。
そして該当タグを tags から削除して、自分自身を deleteLater で削除します。
(deleteLaterしてから Tagオブジェクトを削除するとレイアウトがぶっ壊れるので注意)

 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
class PopupEdit(QDialog):

    send = Signal(str)

    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowFlags(Qt.Popup)
        self.setAttribute(Qt.WA_TranslucentBackground)

        layout = QVBoxLayout(self)

        self.edit = QLineEdit(self)
        layout.addWidget(self.edit)
        self.setLayout(layout)
        # 現在のマウス位置にGUIを出す
        size_x = 200
        size_y = 50
        pos = QCursor().pos()
        self.setGeometry(pos.x() - size_x,
                         pos.y() - size_y,
                         size_x,
                         size_y)

        self.edit.returnPressed.connect(self.Submit)
        self.edit.setFocus()

    def Submit(self):
        # Enterしたら文字をEmitして閉じる
        self.send.emit(self.edit.text())
        self.close()

addTagするときのDialogは、QCompleterでLineEditに予測変換を入れるの時のコードを流用して、Popupで表示+閉じるときにEmitする簡単な入力を作りました。

使ってみる

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SampleUI(QDialog):

    def __init__(self, parent=None):
        super().__init__(parent)

        self.ui = QUiLoader().load(f"{CURRENT_PATH}/scroll.ui")

        layout = QVBoxLayout(self)
        self.setLayout(layout)
        layout.addWidget(self.ui)

        self.crowd = TagCrowd()
        self.ui.scrollArea.setWidget(self.crowd)

        self.crowd.addTag('aaa')
        self.crowd.addTag('bbb')
        self.crowd.addTag('ccc')

        self.ui.pushButton.clicked.connect(self.showTags)

    def showTags(self):
        print(self.crowd.getTagNames())

sroll.uiは前に使ったものにボタンを追加しています。

まとめ

FlowLayoutと合わせてよく使いそうなタグクラウドウィジェットができました。
あとは、これにタグ追加シグナルやタグ削除シグナルを追加したり
個別にタグの色を変更できるようにするといったことを追加すると、さらに便利になるのでは?と思います。


最終更新日: 2021-08-10 17:19:27

Tag