PyQt5学习心得(四)入门提高——自定义控件:带搜索功能的输入下拉框

上一集将了一些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_())

上面的代码注释已经比较清楚了,我就不再进行详细讲解(主要还是我懒#.#)。运行后长下面这个样子:
PyQt5学习心得(四)入门提高——自定义控件:带搜索功能的输入下拉框_第1张图片
此时的自定义输入框和单行输入框没有任何区别,因为我们还没有给它赋予其他能力,下面我们就一一完善这个自定义控件的功能!

填充下拉框列表

在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)

再次运行后,可以看到如下界面:
PyQt5学习心得(四)入门提高——自定义控件:带搜索功能的输入下拉框_第2张图片 PyQt5学习心得(四)入门提高——自定义控件:带搜索功能的输入下拉框_第3张图片
此时点击下拉列表里的选项,只会出现选中状态,没有实际的响应,所以,接下来我们要加上选项的点击响应,在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)

然后再运行程序看看效果,输入3,下拉框里就只有333了。
PyQt5学习心得(四)入门提高——自定义控件:带搜索功能的输入下拉框_第4张图片

结语

写了这么多,由于能力有限,这个自定义控件可能还是没有讲得太明白,所以,如果对这个自定义控件感兴趣的童鞋们,只有自己亲自尝试了才知道效果如何!而且,这个控件还有很多需要优化的地方,所以需要大家自己去摸索和改进了!

最后,我留一个简单的自定义控件的小练习:继承QLabel类,实现一个http超链接的功能。
不能用如下最简单的方法如下:

label = QLabel(self)
label.setText("鹅厂")
label.setOpenExternalLinks(True)

PyQt5的简单入门就讲到这里了,因为我目前也就只是入门级别。接下来我可能会分享一些有关UI界面美化方面的内容,以及我在入门过程中记录的一些小技巧,谢谢大家阅读这篇文章!

你可能感兴趣的:(PyQt5)