PyQt5技術分享:製作一個美觀的Dock欄

@MGod吾發表於2020-11-01
一週一小步,一年一大步!歐!耶!
這周我完成了軟體專案的一個重要的部件--dock欄,閒話少說,先上成品!!!

dock演示

1.建立透明視窗

要實現這樣一個小視窗當然需要先建立一個QWidget類,並對QWidget的背景,視窗大小,邊框等等做一些小設定,這裡的背景用QPinter動態描繪上邊框和背景色(具體的paintEvent程式碼的也是從某大師那裡抄的,具體哪個,我給忘了,,,)

class Dock_Win(QWidget):
    def __init__(self, parent=None):
        super(Dock_Win, self).__init__(parent)
        self.bg_color = QColor(170, 248, 248, 230)  # 設定背警色
        self.fill_color = QColor(0,250,255,50) # 陰影顏色
        self.initUI()
     
     def initUI(self):
       # 設定視窗透明,無邊框
       self.setAttribute(Qt.WA_TranslucentBackground)  # 透明背景
       self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog)
       # 設定視窗的大小
       self.resize(462, 110)

    # 設定視窗圓角+邊框陰影
    def paintEvent(self, event):
        # 設定陰影
        painter_path = QPainterPath()
        painter_path.setFillRule(Qt.WindingFill)

        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.fillPath(painter_path, QBrush(Qt.white))
        for i in range(10):
            i_path = QPainterPath()
            i_path.setFillRule(Qt.WindingFill)
            ref = QRectF(10 - i, 10 - i, self.width() - (10 - i) * 2, self.height() - (10 - i) * 2)
            i_path.addRoundedRect(ref, 20, 20)
            self.fill_color.setAlpha(int(150 - i ** 0.5 * 50))
            painter.setPen(self.fill_color)
            painter.drawPath(i_path)

        # 圓角
        self.painter_rect = QPainter(self)
        self.painter_rect.setRenderHint(QPainter.Antialiasing)  # 抗鋸齒
        self.painter_rect.setBrush(self.bg_color)
        self.painter_rect.setPen(Qt.transparent)

        self._rect = self.rect()
        self._rect.setLeft(15)
        self._rect.setTop(15)
        self._rect.setWidth(self._rect.width() - 15)
        self._rect.setHeight(self._rect.height() - 15)
        self.painter_rect.begin(self)
        self.painter_rect.drawRoundedRect(self._rect, 15, 15)
        self.painter_rect.end()

在這裡插入圖片描述
這樣我們就得到了一個時尚而不失優雅的背景視窗

2.為視窗新增移動事件

無邊框的視窗在美觀的同時,也失去了邊框所帶來的移動便利,沒有了邊框的視窗,你左擊並移動滑鼠,跟在一個空白視窗上亂畫沒啥區別,所以要對主視窗的mouseMoveEvent()滑鼠移動事件進行重寫

# 設定視窗移動事件
    # 當滑鼠左擊並且移動時觸發視窗移動事件
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.is_move = True
            self.move_xy = event.globalPos() - self.pos()  # 獲取滑鼠的移動事件
            # self.parent_rect = self.parent().pos()
            self.setCursor(QCursor(Qt.OpenHandCursor))  # 設定滑鼠為抓手

    def mouseMoveEvent(self, event):
        if self.is_move:
            # 移動dock欄(dock欄的座標位置+滑鼠的偏移量-dock欄的邊框位置)
            self.move(event.globalPos() - self.move_xy)

    def mouseReleaseEvent(self, event):
        self.is_move = False
        self.setCursor(QCursor(Qt.ArrowCursor))  # 設定滑鼠為正常

這樣視窗就被你牢牢的把握在手心了

3.新增元件

光有視窗肯定是不夠的,肯定要有選項啊,這裡的選項我嘗試了很多種,有QPushButton,有QListWidget,但由於知識儲備量不夠,都一一失敗了(尬``),最後還不如QLabel來的痛快,這裡直接定製一個QLabel

# 重寫QLabel,讓其能夠支援點選事件
class QLabel_Item(QLabel):
    cliecked = pyqtSignal()
    def __init__(self,index,QIcon_no,QIcon_on,text=None,parent=None):
        super(QLabel_Item, self).__init__(parent)
        self.icon_no = QIcon_no # 關閉時的圖示
        self.icon_on = QIcon_on # 開啟時的圖示
        self.is_clieck = True   # 預設滑鼠單擊為單擊事件
        if index != 1:
            self.is_open = False    # 預設為關閉狀態
            self.setScaledContents(True)  # 設定圖示鋪滿
            self.setPixmap(QPixmap(QIcon_no))  # 設定預設圖示
        else:
            self.is_open = True     # 預設第一個圖示為開啟狀態
            self.setScaledContents(True)  # 設定圖示鋪滿
            self.setPixmap(QPixmap(QIcon_on))  # 設定預設圖示
        if index != 3:
            if index > 3:
                _plus = 26
            else:
                _plus = 0
            self.setGeometry(36*index+46*(index-1)+_plus, 26, 46, 46)
            self.bottom_text = QLabel(text, parent)
            self.bottom_text.setFont(QFont('華文新魏', 10))
            self.bottom_text.setAlignment(Qt.AlignCenter)
            self.bottom_text.setGeometry(36*index+46*(index-1)+_plus, 74, 46, 16)
        else:
            self.setScaledContents(True)  # 設定圖示鋪滿
            self.setPixmap(QPixmap(QIcon_no))  # 設定預設圖示
            self.setGeometry(200, 20, 72, 72)
            self.setObjectName('play')
            self.setToolTip('點選播放')

    def set_no(self):   # 設定關閉狀態
        self.setPixmap(QPixmap(self.icon_no))  # 設定預設圖示
        # self.setStyleSheet('background-color:blue')
        self.bottom_text.setStyleSheet('text-decoration:normal;')   # 設定正常

    def set_on(self):   # 設定開啟狀態
        self.setPixmap(QPixmap(self.icon_on))  # 設定預設圖示
        # self.setStyleSheet(f'background-color:red')
        self.bottom_text.setStyleSheet('text-decoration:underline;')    # 設定下滑線

    # 正常情況下滑鼠單擊為觸發事件,點選並移動為移動事件

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.is_clieck = True
            self.move_xy = event.globalPos() - self.pos()  # 獲取滑鼠的移動事件
            self.parent_rect = self.parent().pos()

    def mouseMoveEvent(self, event):
        self.is_clieck = False
        # 移動dock欄(dock欄的座標位置+滑鼠的偏移量-控制元件本身座標)
        self.parent().move(self.parent_rect + event.globalPos() - self.move_xy - self.pos())
        self.setCursor(QCursor(Qt.OpenHandCursor))  # 設定滑鼠為抓手
        self.moved = True

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.setCursor(QCursor(Qt.ArrowCursor))  # 設定滑鼠為正常
            if self.is_clieck and not self.is_open:
                self.is_open = True
                self.cliecked.emit()
            elif self.is_clieck:
                self.is_open = False
                self.cliecked.emit()
  1. 因為QLabel本身是不支援點選事件的,所以要建立一個clieck訊號
  2. 為了讓滑鼠移動到圖示上和點選圖示時有更好的反饋效果,QLabel_Item()建立需要傳入開啟時的圖示,關閉時的圖示,底部文字和父視窗(使用絕對佈局時需要)
  3. 另圖示的滑鼠事件也是很重要的啊,滑鼠的點選,移動,出入都需要圖示進行一個很好的反饋效果
  4. 因為中間的播放按鈕不是用來進行主介面口切換的,所以要進行特殊關照(你懂的<-.<-)

注:因為QLabel和QLabel是同一類的,所以建立底部文字時,不能將圖示的QLabel設為父類,QLabel只能作為圖片,文字的容器

4.視窗的右擊選單

視窗的右擊選單可以用來對dock欄進行一些設定,這就需要對contextMenuEvent()進行重寫

    def contextMenuEvent(self, event):  # 連線選單事件
        # 設定右擊選單
        right_menu = QMenu(self)
        set_bg_color = QAction('背景色')
        right_menu.addAction(set_bg_color)
        # tkl_bg_color = QAction('天空藍')
        # lyh_bg_color = QAction('烈焰紅')
        # mmh_bg_color = QAction('檸檬黃')
        # sll_bg_color = QAction('深林綠')
        # jlz_bg_color = QAction('基佬紫')
        # other_bg_color = QAction('自定義顏色')
        # set_bg_color.addAction(tkl_bg_color)
        # set_bg_color.addAction(lyh_bg_color)
        # set_bg_color.addAction(mmh_bg_color)
        # set_bg_color.addAction(sll_bg_color)
        # set_bg_color.addAction(jlz_bg_color)
        # set_bg_color.addSeparator()  # 新增分隔線
        # set_bg_color.addAction(set_bg_color)

        # tkl_bg_color.triggered.connect(lambda: self.changeBgColor(tkl_bg_color.text()))
        # lyh_bg_color.triggered.connect(lambda: self.changeBgColor(lyh_bg_color.text()))
        # mmh_bg_color.triggered.connect(lambda: self.changeBgColor(mmh_bg_color.text()))
        # jlz_bg_color.triggered.connect(lambda: self.changeBgColor(jlz_bg_color.text()))
        # sll_bg_color.triggered.connect(lambda: self.changeBgColor(sll_bg_color.text()))
        set_bg_color.triggered.connect(lambda: self.changeBgColor(''))

        set_gh_color = QAction('光圈色')
        right_menu.addAction(set_gh_color)
        # right_menu.addMenu(set_gh_color)
        # tkl_gh_color = QAction('天空藍')
        # lyh_gh_color = QAction('烈焰紅')
        # mmh_gh_color = QAction('檸檬黃')
        # sll_gh_color = QAction('深林綠')
        # jlz_gh_color = QAction('基佬紫')
        # other_gh_color = QAction('自定義顏色')
        # set_gh_color.addAction(tkl_gh_color)
        # set_gh_color.addAction(lyh_gh_color)
        # set_gh_color.addAction(mmh_gh_color)
        # set_gh_color.addAction(sll_gh_color)
        # set_gh_color.addAction(jlz_gh_color)
        # set_gh_color.addSeparator()  # 新增分隔線
        # set_gh_color.addAction(other_gh_color)
        #
        # tkl_gh_color.triggered.connect(lambda: self.changeGhColor(tkl_gh_color.text()))
        # lyh_gh_color.triggered.connect(lambda: self.changeGhColor(lyh_gh_color.text()))
        # mmh_gh_color.triggered.connect(lambda: self.changeGhColor(mmh_gh_color.text()))
        # jlz_gh_color.triggered.coonnect(lambda: self.changeGhColor(jlz_gh_color.text()))
        # sll_gh_color.triggered.connect(lambda: self.changeGhColor(sll_gh_color.text()))
        set_gh_color.triggered.connect(lambda: self.changeGhColor(''))
        #

        exit_menu = QAction('退 出',right_menu)
        exit_menu.triggered.connect(right_menu.close)
        auto_hide = QAction('自動隱藏')
        right_menu.addAction(auto_hide)
        right_menu.addSeparator()   # 新增分割符
        right_menu.addAction(exit_menu)
        if not self.childAt(event.globalPos()-self.pos()):	# 當滑鼠右擊不在圖示範圍內時
            right_menu.exec_(event.globalPos())	# 傳入滑鼠的座標

可以看到我這裡原本預定了很多好看的顏色,嘗試過直接對self.bg_color()進行改變,重新整理介面,但不知道為什麼最後只有QColorDialog成功了,無奈,只能摸摸自己所剩無幾的頭髮,嘆了嘆氣,放棄了,如果有大神願為後輩指點一二,吾輩定當無比感謝!(拜託了,yyy)
背景色設定關聯的方法

因為專案的主視窗還在畫餅中,所以dock欄的些許功能尚未完善

最後原始碼獻上:https://github.com/MGod-monkey/ABAB_Book/blob/master/main_dock_UI.py

題外:
原本專案的登入介面已經做好的,而登入賬號用來桂電的VPN賬號,方便後續直接訪問校內資源,寫了篇關於呼叫桂電VPN介面的部落格,但奈何官方對這方面的部落格把控太嚴,無奈只能設為私密了(這裡沒有抱怨,而是稱讚審查大大的一絲不苟,兢兢業業···(此處省略一萬字))
(╥╯^╰╥)
這裡的UI還沒美化完全,後期會定製qss檔案專門美化的,而專案的程式不能因為部分而拖太久了
登入UI

相關文章