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

一周一小步,一年一大步!欧!耶!
这周我完成了软件项目的一个重要的部件–dock栏,闲话少说,先上成品!!!

1.创建透明窗口

要实现这样一个小窗口当然需要先创建一个QWidget类,并对QWidget的背景,窗口大小,边框等等做一些小设置,这里的背景用QPinter动态描绘上边框和背景色(具体的paintEvent代码的也是从某大师那里抄的,具体哪个,我给忘了,,,)

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
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()鼠标移动事件进行重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 设置窗口移动事件
# 当鼠标左击并且移动时触发窗口移动事件
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

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 重写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()进行重写

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
52
53
54
55
56
57
58
59
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