上一集将了一些UI界面开发常用的控件,但是在有些时候,这些常用的控件无法满足我们的需求,比如:我想实现一个输入框,并且带有下拉和搜索功能,就没有一个现成的控件可以满足这个需求。那么,我们要如何实现这个功能呢?这时候,就需要一个自定义控件来实现我们的需求了!
说到自定义控件,其实在第二集的自定义信号里就已经涉及了,当时我们自定义了一个可以响应点击消息的MyLabel控件,大家可以点击【传送门】去温故而知新。
在做一个自定义控件之前,一定要根据需求来分解目标,针对上面的需求,我们将自定义控件分解为2个控件的合成:第一个控件很容易想到,我们要输入文本,肯定是选用单行文本框(QLineEdit);第二个控件呢?带有下拉功能的控件,我第一时间想到的就是下拉框(QComboBox),但是在写完代码之后发现,当搜索功能开启后,出现了焦点被下拉框获取的问题。这个问题我想了好久也没想出好的解决办法,于是我又想到另一个列表控件(QListView)。于是把代码折腾一番,终于把焦点的问题解决了,但是缺陷是无法通过上下方向键来选择列表项里的内容,只能用鼠标点击选择。其实,这个缺陷其实也是可以解决的,只是因为我没时间(lǎn de xiě),所以就一直没去解决这个缺陷!如果有哪位大神可以帮忙解决了这个缺陷,请务必留言给我,谢谢!
然后,我就开始写代码了!因为这个自定义控件的功能主要还是输入文字,所以就继承了QLineEdit类,下面是自定义类的初始化代码:
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
class CustomEdit(QLineEdit):
"""
带下拉框的文本输入框类(文本下拉输入框),输入文字可以搜索内容
"""
def __init__(self, parent, size=(50, 50, 100, 20), name='edit',
drag=False, text_list=True, search=True, qss_file=''):
"""
初始化控件
:param parent: 控件显示的父对象
:param size: 输入框控件尺寸
:param name: 输入框控件名称
:param drag: 拖放标识位
:param text_list: 是否开启下拉框功能标识位
"""
super(CustomEdit, self).__init__(None, parent)
# 输入框的尺寸
self.w = size[2]
self.h = size[3]
self.setGeometry(*size)
# 输入框名称
self.setObjectName(name)
# 输入框是否支持拖放
self.drag_flag = False
if drag:
self.setAcceptDrops(True)
self.setDragEnabled(True) # 开启可拖放事件
else:
self.setAcceptDrops(False)
self.setDragEnabled(False) # 关闭可拖放事件
self.click_flag = False # 点击状态,为False时显示下拉框
self.text_list = None # 下拉框对象
self.p_text = "" # placehold text,输入框背景上的灰色文字
self.qss_file = qss_file # 下拉框样式文件
self.org_data = [] # 用来进行搜索的数据list
# 如果text_list为True,则初始化下拉框
if text_list:
# 下拉列表模型为StringListModel
self.list_model = QtCore.QStringListModel()
# 初始化下拉列表对象为QListView类型
self.text_list = QListView(parent)
# 下拉列表名称,以list_开头
self.text_list.setObjectName("list_%s" % name)
# 设置下拉列表初始化尺寸
self.text_list.setGeometry(size[0], size[1]+size[3], size[2], size[3])
# 隐藏下拉列表
self.text_list.hide()
# 初始化下拉列表数据
self.text_list.data = []
class MainWindow(QMainWindow):
"""主窗口,继承了QMainWindow类"""
def __init__(self, name, title):
"""初始化类的成员变量"""
super(MainWindow, self).__init__()
self.w = 0
self.h = 0
self.init_ui(name, title) # 初始化UI界面
def init_ui(self, name, title):
"""初始化UI界面"""
self.w = 140
self.h = 100
self.setObjectName(name) # 设置主窗口对象的名称
self.setWindowTitle(title) # 设置主窗口显示的标题
self.resize(self.w, self.h) # 设置主窗口尺寸
self.custom_edit = CustomEdit(self, size=(10, 10, 120, 24),
name='custom_edit', search=False)
self.custom_edit.setPlaceholderText('我是自定义输入框')
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
n = 'CustomUI'
t = '自定义控件'
ex = MainWindow(n, t)
ex.show()
sys.exit(app.exec_())
上面的代码注释已经比较清楚了,我就不再进行详细讲解(主要还是我懒#.#)。运行后长下面这个样子:
此时的自定义输入框和单行输入框没有任何区别,因为我们还没有给它赋予其他能力,下面我们就一一完善这个自定义控件的功能!
在CustomEdit类里添加如下代码:
def fill_data(self, text_data_list):
"""
初始化文本下拉输入框控件数据
:param text_data_list: 下拉框数据,必须为纯文本list
"""
# 设置下拉列表数据
self.list_model.setStringList(text_data_list)
self.text_list.setModel(self.list_model)
self.text_list.data = text_data_list
# 填充list数据
if text_data_list:
self.text_list.data = text_data_list
self.org_data = text_data_list[:] # 深拷贝一份数据,用来搜索
self.text_list.resize(self.w, self.h * 0.6 * len(self.text_list.data))
self.setText(text_data_list[0])
else:
self.setText('')
self.text_list.hide()
edit_clicked = QtCore.pyqtSignal() # 定义clicked信号
def mouseReleaseEvent(self, event):
"""
鼠标按键松开时的事件处理
:param event: 鼠标按键松开事件
"""
# 左键松开
if event.button() == QtCore.Qt.LeftButton:
# 下拉列表存在数据,并且未点击过,则显示下拉框
if self.text_list.data and not self.click_flag:
self.text_list.show()
# 下拉列表存在,并且点击过,则隐藏下拉框
elif self.click_flag:
self.text_list.hide()
self.click_flag = not self.click_flag
self.edit_clicked.emit() # 发送clicked信号
在init_ui函数里添加如下代码:
data_list = [i * ('%s' % i) for i in range(15)]
self.custom_edit.fill_data(data_list)
再次运行后,可以看到如下界面:
此时点击下拉列表里的选项,只会出现选中状态,没有实际的响应,所以,接下来我们要加上选项的点击响应,在CustomEdit类的__init__函数的初始化下拉框的代码部分增加如下代码:
# 点击下拉框列表元素,绑定消息响应函数
self.func = None
self.text_list.clicked.connect(lambda: self.on_select_data(self.func))
在CustomEdit类中新增成员函数on_select_data:
def on_select_data(self, func):
"""
选择下拉框数据时的消息响应函数
"""
text = ''
idx = self.text_list.currentIndex()
if self.text_list.data:
text = self.text_list.data[idx.row()]
self.text_list.hide()
self.setText(text)
self.setFocus()
self.setCursorPosition(len(text))
# 选择数据后就隐藏了下拉框,设置点击标识位为False,下次点击才会显示下拉框
self.click_flag = False
if func:
func()
此时再运行程序,就可以选择列表里的选项,并且把选择的值设置到输入框中了!
接下来要实现的功能是:在输入框输入字符,然后下拉列表里只显示包含这个字符的项。
实现步骤如下:
1、在CustomEdit类的__init__函数里增加textChanged的消息连接
if search:
self.textChanged.connect(self.on_search_data)
2、实现on_search_data函数功能
def on_search_data(self):
"""
文本变化时的消息响应,搜索下拉框列表里的数据
"""
# 输入框中文本和List选择文本不一样的时候,才显示下拉框
cur_text = self.text()
list_text = ''
idx = self.text_list.currentIndex()
if self.text_list.data:
list_text = self.text_list.data[idx.row()]
if cur_text != list_text: # and len(cur_text) > 1:
self.text_list.show()
# 根据消息响应传入的data来搜索
self.text_list.data = self.org_data[:]
for _text in self.org_data:
if cur_text not in _text:
self.text_list.data.remove(_text)
# 刷新下拉框
self.list_model.setStringList(self.text_list.data)
self.text_list.setModel(self.list_model)
if not self.click_flag:
self.text_list.hide()
self.click_flag = True
if self.text_list.data: # 有数据的时候就调整下拉框大小
self.resize_text_list()
else:
self.text_list.hide() # 没有数据就隐藏下拉框
3、创建控件时,将seach设置为True
self.custom_edit = CustomEdit(self, size=(10, 10, 120, 24),
name='custom_edit', search=True)
写了这么多,由于能力有限,这个自定义控件可能还是没有讲得太明白,所以,如果对这个自定义控件感兴趣的童鞋们,只有自己亲自尝试了才知道效果如何!而且,这个控件还有很多需要优化的地方,所以需要大家自己去摸索和改进了!
最后,我留一个简单的自定义控件的小练习:继承QLabel类,实现一个http超链接的功能。
不能用如下最简单的方法如下:
label = QLabel(self)
label.setText("鹅厂")
label.setOpenExternalLinks(True)
PyQt5的简单入门就讲到这里了,因为我目前也就只是入门级别。接下来我可能会分享一些有关UI界面美化方面的内容,以及我在入门过程中记录的一些小技巧,谢谢大家阅读这篇文章!