自定义下拉选择框,PYQT combobox

用过pycharm的老铁们对这个界面比较熟悉吧,有个好看的下拉框。

如果用PYQT自带的组件combobox, 默认值有多宽,这个下拉框就会有多宽,不方便自定义,不好带图标,且不美观,如图:

于是我想着换种方式做,这是我做的初级效果。当然,还可以更美观一点的,我是项目有什么需求做什么东东,以后新项目有新需求了,我再来更新。下图是我做的效果:

组件实现

from PyQt5.QtCore import QPoint, pyqtSignal
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QPushButton, QMenu, QAction

from Assets.styles.dark import DarkStyle
from Assets.styles.light import LightStyle
from lib.share import ShareInfo
from utils_.main import newIconPixmap


class PseudoComboBox(QPushButton):
    set_current_signal = pyqtSignal(str, int)

    def __init__(self, current_value=None, options=None, parent=None):
        super(PseudoComboBox, self).__init__(parent)
        self.style = LightStyle if ShareInfo.setting["theme"] == "light" else DarkStyle
        self.options = options if options is not None else []

        # 初始化默认值
        self.current_text = ""
        self.current_index = -1

        # 处理options不为空的情况
        if len(self.options) > 0:
            if current_value is None:
                # 情况1:current_value为None,显示第一个选项
                self.set_current_index(0)
            else:
                # 情况2:current_value不为None,根据current_value查找匹配项
                self.find_and_set_current(current_value)

        self.init_ui()

    def init_ui(self):
        self.setText(self.current_text or "")
        self.setProperty("class", "transparent medium")
        self.setStyleSheet("padding-right: 20px;")
        self.clicked.connect(self.show_options)

    def show_options(self):
        if not self.options:
            return

        menu = QMenu(self)
        for index, option in enumerate(self.options):
            # 问题所在:需要从option中提取显示文本,而不是直接传入option对象
            display_text = self._get_display_text(option)
            action = QAction(display_text, menu)  # 传入显示文本,而不是option对象
            # 使用闭包传递参数
            action.triggered.connect(
                lambda checked, idx=index, opt=option: self.action_selected(idx, opt)
            )
            menu.addAction(action)

        # 计算菜单显示位置
        x = self.mapToGlobal(self.pos()).x() - self.x()
        y = self.mapToGlobal(self.pos()).y() + self.height()
        menu.exec_(QPoint(x, y))

    def action_selected(self, index, option):
        self.current_index = index
        self.current_text = self._get_display_text(option)
        self.setText(self.current_text)
        # 发送信号,传递选项的显示文本和索引
        self.set_current_signal.emit(self.current_text, index)

    def _get_display_text(self, item):
        """从选项项中获取显示文本"""
        if isinstance(item, str):
            return item
        elif isinstance(item, dict):
            # 处理字典类型的选项
            return item.get('text', str(item))
        elif hasattr(item, 'text'):
            # 处理有text属性的对象
            return item.text
        elif hasattr(item, '__str__'):
            # 其他对象,调用其字符串表示
            return str(item)
        else:
            return str(item)

    def _get_item_value(self, item):
        """从选项项中获取实际值(如果需要)"""
        if isinstance(item, str):
            return item
        elif isinstance(item, dict):
            # 处理字典类型的选项
            return item.get('value', item.get('text', str(item)))
        elif hasattr(item, 'value'):
            return item.value
        elif hasattr(item, 'text'):
            return item.text
        else:
            return str(item)

    def set_current_index(self, index):
        """设置当前索引"""
        if 0 <= index < len(self.options):
            self.current_index = index
            option = self.options[index]
            self.current_text = self._get_display_text(option)
            self.setText(self.current_text)

    def find_and_set_current(self, current_value):
        """根据值查找并设置当前项"""
        found = False

        for i, option in enumerate(self.options):
            option_value = self._get_item_value(option)

            # 多种匹配方式
            if isinstance(current_value, str):
                # 字符串匹配
                if str(option_value) == current_value:
                    self.set_current_index(i)
                    found = True
                    break
            elif hasattr(current_value, 'value'):
                # 对象有value属性
                if option_value == current_value.value:
                    self.set_current_index(i)
                    found = True
                    break
            elif hasattr(current_value, 'text'):
                # 对象有text属性
                if self._get_display_text(option) == current_value.text:
                    self.set_current_index(i)
                    found = True
                    break
            else:
                # 其他类型,转换为字符串比较
                if str(option_value) == str(current_value):
                    self.set_current_index(i)
                    found = True
                    break

        # 如果没有找到匹配项,默认显示第一个选项
        if not found and len(self.options) > 0:
            self.set_current_index(0)

    def paintEvent(self, event):
        super(PseudoComboBox, self).paintEvent(event)
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        suffix_pixmap = newIconPixmap(self.style.arrow_down, 10, 10)
        painter.drawPixmap(self.width() - 15, 10, 10, 10, suffix_pixmap)

    def clear(self):
        self.options = []
        self.current_text = ""
        self.current_index = -1
        self.setText("")

    def addItem(self, item):
        self.options.append(item)

    def setCurrentText(self, text):
        """根据显示文本设置当前项"""
        for i, option in enumerate(self.options):
            display_text = self._get_display_text(option)
            if display_text == text:
                self.set_current_index(i)
                self.set_current_signal.emit(self.current_text, self.current_index)
                return

    def setCurrentIndex(self, index):
        """根据索引设置当前项"""
        if 0 <= index < len(self.options):
            self.set_current_index(index)
            self.set_current_signal.emit(self.current_text, self.current_index)

    def currentIndex(self):
        return self.current_index

    def currentText(self):
        return self.current_text

    def getCurrentValue(self):
        """获取当前选项的实际值(不是显示文本)"""
        if 0 <= self.current_index < len(self.options):
            return self._get_item_value(self.options[self.current_index])
        return None

    def count(self):
        return len(self.options)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值