[PyQt] PySide2 教程 #9: 容器物件! DockWidget, ToolBox, StackWidget

容器物件(Conotainer Widgets),在這邊是指UI介面的容器,包括Dock、ToolBox工具列、Stack堆疊頁面等等。有了容器可以使我們再UI畫面中有更多層次並增加畫面豐富度

OverView

容器物件通常會搭配 第8章 所提到的ItemWidget一起始用,可以實作出非常實用的功能,例如QtCreater/Designer介面的左方工具列以及右方屬性欄都是利用Container+ItemWidget來製作的。

本章教學將會著重在Dock、ToolBox 以及StackWidget,而其他的container如TabWidgetGroupBox相對來說非常好上手,大家可以利用Document做了解,不看也沒關係,之後這兩個物件也會再StyleSheets的篇章中提起唷!

ToolBox

ToolBox物件是一個可縮放的單層容器,但是內容物可以是任意QWidget物件,因此只要在內部放入多層次的物件( 如TreeWidget ),也可以做出多層次的操作選單。

Implement

我們利用上一章的ListWidget來當作ToolBox的內容製作類似QtCreater左方的物件選單。跟許多其他的物件一樣,新增物件到ToolBox也是用 addItem的函式。

* 注意 *
這邊我使用了 removeItem(0) 這個函式,因為UI是用QtDesigner拉的會有一個default的Item,因此我先將他去除。

from PySide2.QtCore import Qt
from PySide2.QtWidgets import QListView, QListWidget, QListWidgetItem    # Setup ListWidget
    tool_list = QListWidget(self._window)
    tool_list.setCurrentRow(0)
    tool_list.setViewMode(QListView.ListMode)
    tool_list.setSpacing(3)
    tool_list.setItemAlignment(QtCore.Qt.AlignCenter)
    tool_list.setEnabled(True)    self.create_item('Play', './media/play.ico', tool_list)
    self.create_item('Pause', './media/pause.ico', tool_list)
    self.create_item('Stop', './media/stop.ico', tool_list)    # Setup ToolBox
    self._window.tool_box.removeItem(0)
    self._window.tool_box.addItem(tool_list, 'Music Tools')

除此之外,讓我再多新增一些基礎物件來讓工具列看起來更加的豐富。

不過我這邊hard code了PushButton的物件是不太優的作法,要取用按鈕會相當麻煩要使用ToolBox的 indexOf()函式計算物件順序,建議宣告前先用list或dict建好資料結構再for loop宣告唷!

from PySide2.QtWidgets import QWidget, QPushButton, QVBoxLayout# Add another item
w1 = QWidget(self._window)
layout = QVBoxLayout()

layout.addWidget(QPushButton('Hello Btn'))
layout.addWidget(QPushButton('GoodBye Btn'))
layout.addWidget(QPushButton('Hug Btn'))
layout.addWidget(QPushButton('Dont\'t Press Me'))
w1.setLayout(layout)

self._window.tool_box.addItem(w1, 'Button Tools')

Result

QToolBox Example

DockWidget

DockWidget是一個複雜的容器,一大特色就是脫離主畫面再放回去,也可以再畫面的上下左右自由嵌入,再製作動態的工具列上面非常實用,不需要時也可以先hide起來將他關閉,另外用signal觸發使其反還並顯示於畫面中。

Implement

Dock的特殊屬性有幾個:

  • 是否能彈出為獨立視窗:使用 setFloating()函式可以決定是否允許彈出
  • Feature設定 :這邊要使用Dock中的旗標來設定,利用 setFeature()
    函式來設定。可以定義包含floating, movable等等多個設定。
    e.g. setFeature(QDockWidget.QDockwidgetMovable | QDockWidget.QDockwidgetfloatable)
  • 允許嵌入的方位:使用 setAllowedAreas()來設定可以放置的方位
  • Dock Title也是一個Qwidget :所以可以客製化title如果喜歡的話 ❤️
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QDockWidget

def set_docker(self):
    dock = self._window.dock_tool
    dock.setFeatures(QDockWidget.AllDockWidgetFeatures)
    dock.setAllowedAreas(Qt.LeftDockWidgetArea |                        
                         Qt.RightDockWidgetArea)
    data_list = QListWidget(dock)
    data_list.addItems([
        "John Doe, Harmony Enterprises, 12 Lakeside, Ambleton",
        "Jane Doe, Memorabilia, 23 Watersedge, Beaton",
        "Tammy Shea, Tiblanka, 38 Sea Views, Carlton",
        "Tim Sheen, Caraba Gifts, 48 Ocean Way, Deal",
        "Sol Harvey, Chicos Coffee, 53 New Springs, Eccleston",
        "Sally Hobart, Tiroli Tea, 67 Long River, Fedula"])
    dock.setWidget(data_list)

    self._window.addDockWidget(Qt.RightDockWidgetArea, dock)

這邊我使用官方的Example作為參考並修改,值得一說的是官方的PySide2 example照抄是有問題的,主要是官方再python中用了C++的語法,也不知道是不是懶的改Document還是…

Result


QDockWidget Example

StackWidget

StackWidget在實務上滿常用到的,如果想要在同一個視窗中製作多個切換的畫面,又不想像TabWidget一樣有分頁標籤佔畫面就可以使用StackWidget。結構上,可以把StackWidget想成是一個Widget的array用index去選取目前要顯示的畫面。

Implement

我們先創建一個自定義的QWidget子物件,用來放入StackWidget中顯示的不同頁面。

"""text_widget.py"""from PySide2.QtWidgets import QWidget, QTextEdit, QGridLayout
from PySide2.QtGui import QFont


class TextPage(QWidget):
def __init__(self, parent=None, text=''):
super(TextPage, self).__init__(parent)
self._widget = None
self._help_edit = None
self._text = text

self.ui_setup()

@property
def widget(self):
return self._widget

@property
def text(self):
return self._text

@text.setter
def text(self, value):
self._text = value

def ui_setup(self):
self._widget = QWidget()
self._help_edit = QTextEdit()

text_font = QFont()
text_font.setFixedPitch(True)
text_font.setFamily('monospace')

style = 'color: rgb(6, 117, 0);' \
'background-color: rgb(230, 255, 230);' \
'border: no-border;'

self._help_edit.setFont(text_font)
self._help_edit.setStyleSheet(style)
self._help_edit.setText(self.help_info)

main_layout = QGridLayout()
main_layout.addWidget(self._help_edit, 0, 0, 1, 1)

self._widget.setLayout(main_layout)

接下來我們要將Widget放入StackWidget中,並且給予內容物。

  • 宣告內容物,還有用來管理Widget的dictionary
from container_widget.text_page import TextPage

PLAY_TEXT = 'Music has been described as a universal language.' \  
          'Regardless of age, race, gender or culture, ' \
          'most people feel some positive connection to it. But ' \
          'the impact of music isn’t just an emotional one.' \
          ' Music has been found to be an effective tool in' \
          'improving the quality of life for patients with' \
          ' physical and mental illnesses. Studies show music' \
          'therapy can ease anxiety, muscle tension, and' \
          ' the unpleasant side effects of cancer treatment; help' \
          'in pain relief and physical therapy and' \
          'rehabilitation; and provide safe emotional release and' \
          ' increased self-esteem.'
PAUSE_TEXT = 'A week after mandating masks at all state facilities, 
          'troubling numbers prompted Utah Gov. Gary Herbert' \
          'to require masks in regions of Utah that are home to' \
          'several of the state’s famous national parks' \
          ' July 2. He announced a pause in reopening in June.'
STOP_TEXT = 'WASHINGTON (Reuters) - US infectious disease expert' \ 
          ' Anthony Fauci on Wednesday called the White House ' \
          'effort to discredit him “bizarre” and urged an end to'\
          ' the divisiveness over the country’s response to' \
          ' the coronavirus pandemic, saying “let’s stop this' \ 
          ' nonsense.”'def set_stack(self):
    stack_widget = self._window.stack_widget
    self._pages['Play'] = TextPage(self._window, PLAY_TEXT)
    self._pages['Pause'] = TextPage(self._window, PAUSE_TEXT)
    self._pages['Stop'] = TextPage(self._window, STOP_TEXT)
  • 將pages加入Stack中並且,最重要的串接Signal / Slot,使得頁面可以透過ToolBox內的Listwidget做切換。
def set_stack(self):
stack_widget = self._window.stack_widget
self._pages['Play'] = TextPage(text=PLAY_TEXT)
self._pages['Pause'] = TextPage(text=PAUSE_TEXT)
self._pages['Stop'] = TextPage(text=STOP_TEXT)

for index, name in enumerate(self._pages):
print('pages {} : {} page'.format(index, name))
stack_widget.addWidget(self._pages[name].widget)

stack_widget.setCurrentIndex(0)
self._tool_list.currentItemChanged.connect(self.set_page)

Result

QStackWidget Example

Source Code

完整代碼請看 : Container Widget

結論

容器再UI的設計中基本上式不可或缺的,配合各種Signal / Slot可以實作出8成以上的互動顯示介面,熟悉各種容器還有ItemWidget的組合搭配,在畫面的框架編排設計上就都能請鬆搞定啦!

下一篇:[PyQt] PySide2 教程 #10: 專案!學以致用!

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注