PyQt5技术分享:制作一个美观的Dock栏

一周一小步,一年一大步!欧!耶!
这周我完成了软件项目的一个重要的部件--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栏的些许功能尚未完善

最后源码献上:代码入口

题外:
原本项目的登录界面已经做好的,而登录账号用来桂电的VPN账号,方便后续直接访问校内资源,写了篇关于调用桂电VPN接口的博客,但奈何官方对这方面的博客把控太严,无奈只能设为私密了(这里没有抱怨,而是称赞审查大大的一丝不苟,兢兢业业···(此处省略一万字))
(╥╯^╰╥)
这里的UI还没美化完全,后期会定制qss文件专门美化的,而项目的进程不能因为部分而拖太久了
登录UI

猜你喜欢

转载自blog.csdn.net/qq_45516773/article/details/109436722