PyQt 重写QRadioButton实现动态radioButton源码分享

PyQt 重写QRadioButton实现动态radioButton源码分享

PyQt 重写QRadioButton实现动态radioButton源码分享

  • 一、源码分享
    • 1、效果展示
    • 2、源码分享
      • 2.1、colorful_radio_button.py
      • 2.2、main.py
      • 2.3、库安装
  • 二、QRadioButton详解
    • 1、QRadioButton 概述
      • 主要特性:
    • 2、 基本用法
      • 2.1 、创建单选按钮
      • 2.2、 布局管理
    • 3、 核心方法详解
      • 3.1 、状态控制方法
      • 3.2 、文本和图标
      • 3.3、 信号与槽
    • 4、分组管理
      • 4.1、 自动分组(默认)
      • 4.2、 使用 QButtonGroup 手动分组
    • 5、样式定制
      • 5.1、 使用 QSS(Qt样式表)
      • 5.2 、自定义绘制(如示例中的 ColorfulRadioButton)
    • 6、实战技巧
      • 6.1、 动态创建单选按钮
      • 6.2、 获取选中值
      • 6.3 、启用/禁用控制
    • 7、常见问题与解决方案
      • 问题1:单选按钮不互斥
      • 问题2:文字显示不完整
      • 问题3:自定义样式不生效
    • 8、 与相关控件的对比
    • 9、性能优化建议
    • 10、扩展应用
      • 10.1 、与数据模型绑定
      • 10.2、 响应式单选按钮组
    • 11、总结

一、源码分享

1、效果展示

2、源码分享

2.1、colorful_radio_button.py

fromPyQt6.QtWidgetsimportQRadioButton,QWidgetfromPyQt6.QtCoreimportQPropertyAnimation,pyqtProperty,pyqtSignal,Qt,QRect,QPointfromPyQt6.QtGuiimportQPainter,QColor,QPenclassColorfulRadioButton(QRadioButton):# 信号:bottomLineWidth 变更通知bottomLineWidthChanged=pyqtSignal()def__init__(self,parent:QWidget=None):super().__init__(parent)# 成员变量初始化self._bottom_line_width=0self.color_checked=QColor("#0BFFB2")self.color_unchecked=QColor("#0C976B")self.left_padding=5self.text_x=0# 创建属性动画,绑定 bottomLineWidthself.anim_bottom_line_width=QPropertyAnimation(self,b"bottomLineWidth")self.anim_bottom_line_width.setDuration(300)# 选中切换触发动画self.toggled.connect(self._on_toggled)# 动画数值变化时重绘控件self.anim_bottom_line_width.valueChanged.connect(lambdav:self.update())# -------------------------- Q_PROPERTY 封装 --------------------------@pyqtProperty(int,notify=bottomLineWidthChanged)defbottomLineWidth(self)->int:returnself._bottom_line_width@bottomLineWidth.setterdefbottomLineWidth(self,val:int):ifself._bottom_line_width==val:returnself._bottom_line_width=val self.bottomLineWidthChanged.emit()# -------------------------- 外部接口 --------------------------defsetColor(self,checked_color:QColor,unchecked_color:QColor):"""设置选中/未选中颜色"""self.color_checked=checked_color self.color_unchecked=unchecked_color# -------------------------- 槽函数 --------------------------def_on_toggled(self,checked:bool):"""单选框选中状态切换,启动下划线动画"""text_x=self.text_x total_width=self.width()ifchecked:self.anim_bottom_line_width.setStartValue(text_x)self.anim_bottom_line_width.setEndValue(total_width)else:self.anim_bottom_line_width.setStartValue(total_width)self.anim_bottom_line_width.setEndValue(text_x)self.anim_bottom_line_width.start()# -------------------------- 重绘事件 --------------------------defpaintEvent(self,event):painter=QPainter(self)painter.setRenderHint(QPainter.RenderHint.Antialiasing)w=self.width()h=self.height()is_checked=self.isChecked()current_color=self.color_checkedifis_checkedelseself.color_unchecked# 外圆圈矩形border_rect=QRect(5,5,h-10,h-10)# 绘制外圈边框pen_width=4ifis_checkedelse2pen=QPen(current_color,pen_width,Qt.PenStyle.SolidLine)pen.setCapStyle(Qt.PenCapStyle.RoundCap)pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin)painter.setPen(pen)painter.setBrush(Qt.BrushStyle.NoBrush)painter.drawEllipse(border_rect)# 选中时绘制内部实心圆ifis_checked:round_w=border_rect.width()/2.0inner_rect=QRect()inner_rect.setX(int(border_rect.center().x()-round_w/2+1))inner_rect.setY(int(border_rect.center().y()-round_w/2+1))inner_rect.setWidth(int(round_w))inner_rect.setHeight(int(round_w))painter.setPen(Qt.PenStyle.NoPen)painter.setBrush(current_color)painter.drawEllipse(inner_rect)# 绘制文字text_rect=QRect(border_rect.x()+border_rect.width()+5,0,w,h)painter.setPen(QPen(current_color,3,Qt.PenStyle.SolidLine))painter.drawText(text_rect,Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter,self.text())# 记录文字起始X坐标self.text_x=text_rect.x()# 初始化下划线宽度ifself._bottom_line_width==0:self._bottom_line_width=self.text_x# 绘制底部变色下划线line_start=QPoint(self.text_x,h-2)line_end=QPoint(self._bottom_line_width,h-2)painter.drawLine(line_start,line_end)

2.2、main.py

importsysfromPyQt6.QtWidgetsimport(QApplication,QMainWindow,QWidget,QVBoxLayout,QHBoxLayout,QLabel,QSpacerItem,QSizePolicy)fromPyQt6.QtGuiimportQColor,QFontfromPyQt6.QtCoreimportQtfromcolorful_radio_buttonimportColorfulRadioButtonif__name__=="__main__":app=QApplication(sys.argv)# 主窗口黑色背景window=QMainWindow()window.setStyleSheet("background-color: #000000;")window.resize(1100,750)central_widget=QWidget()window.setCentralWidget(central_widget)main_layout=QVBoxLayout(central_widget)main_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)main_layout.setSpacing(35)# 顶部标题title_label=QLabel("请选择英雄:")title_font=QFont()title_font.setPointSize(48)title_label.setFont(title_font)title_label.setStyleSheet("color: white;")title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)main_layout.addWidget(title_label)main_layout.addSpacerItem(QSpacerItem(20,50,QSizePolicy.Policy.Minimum,QSizePolicy.Policy.Fixed))# 单选按钮外层容器radio_container=QWidget()h_layout=QHBoxLayout(radio_container)h_layout.setContentsMargins(0,0,0,0)h_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)# 垂直布局存放单选框,统一左对齐v_radio=QVBoxLayout()v_radio.setSpacing(24)v_radio.setAlignment(Qt.AlignmentFlag.AlignLeft)# 加宽容器,避免文字挤压截断radio_container.setMaximumWidth(700)font_hero=QFont()font_hero.setPointSize(24)# 封装创建按钮函数,统一设置最小宽高,保证文字完整显示defcreate_radio(text,color_check,color_uncheck,checked=False):radio=ColorfulRadioButton(text)radio.setColor(color_check,color_uncheck)radio.setFont(font_hero)# 关键:设置最小尺寸,防止文字被截断radio.setMinimumWidth(320)radio.setMinimumHeight(45)radio.setChecked(checked)returnradio# 1. 黑暗之女 安妮radio_annie=create_radio("黑暗之女 安妮",QColor("#0BFFB2"),QColor("#0C976B"))v_radio.addWidget(radio_annie)# 2. 远古恐惧 费德提克radio_fiddles=create_radio("远古恐惧 费德提克",QColor("#FFEE33"),QColor("#A89A00"))v_radio.addWidget(radio_fiddles)# 3. 符文法师 瑞兹(默认选中)radio_ryze=create_radio("符文法师 瑞兹",QColor("#FF22DD"),QColor("#A00088"),checked=True)v_radio.addWidget(radio_ryze)# 4. 复仇焰魂 布兰德radio_brand=create_radio("复仇焰魂 布兰德",QColor("#33AAFF"),QColor("#0055AA"))v_radio.addWidget(radio_brand)h_layout.addLayout(v_radio)main_layout.addWidget(radio_container)window.show()sys.exit(app.exec())

2.3、库安装

二、QRadioButton详解

1、QRadioButton 概述

QRadioButton 是 PyQt6 中用于创建单选按钮的控件,它允许用户从一组互斥的选项中选择一个。与 QCheckBox(复选框)不同,单选按钮通常成组出现,同一时间只能有一个被选中。

主要特性:

  • 互斥性:同一父容器中的单选按钮默认互斥
  • 状态管理:选中/未选中两种状态
  • 信号机制:状态变化时发射信号
  • 可定制性:支持样式表、自定义绘制等

2、 基本用法

2.1 、创建单选按钮

fromPyQt6.QtWidgetsimportQApplication,QWidget,QVBoxLayout,QRadioButton# 创建单选按钮radio1=QRadioButton("选项一")radio2=QRadioButton("选项二")radio3=QRadioButton("选项三")# 设置默认选中radio1.setChecked(True)

2.2、 布局管理

layout=QVBoxLayout()layout.addWidget(radio1)layout.addWidget(radio2)layout.addWidget(radio3)window=QWidget()window.setLayout(layout)window.show()

3、 核心方法详解

3.1 、状态控制方法

方法说明示例
setChecked(bool)设置选中状态radio.setChecked(True)
isChecked() -> bool获取选中状态if radio.isChecked():
toggle()切换选中状态radio.toggle()
setAutoExclusive(bool)设置自动互斥radio.setAutoExclusive(False)

3.2 、文本和图标

# 设置文本radio.setText("新的选项文本")# 设置图标fromPyQt6.QtGuiimportQIcon radio.setIcon(QIcon("icon.png"))# 设置图标大小radio.setIconSize(QSize(32,32))

3.3、 信号与槽

# 状态变化信号radio.toggled.connect(self.on_radio_toggled)defon_radio_toggled(self,checked):ifchecked:print(f"单选按钮被选中:{self.sender().text()}")

4、分组管理

4.1、 自动分组(默认)

同一父容器中的单选按钮自动形成互斥组。

4.2、 使用 QButtonGroup 手动分组

fromPyQt6.QtWidgetsimportQButtonGroup# 创建按钮组button_group=QButtonGroup()# 添加单选按钮到组button_group.addButton(radio1,1)# id=1button_group.addButton(radio2,2)# id=2button_group.addButton(radio3,3)# id=3# 获取选中按钮的IDselected_id=button_group.checkedId()# 信号:选中按钮变化button_group.buttonClicked.connect(self.on_button_clicked)

5、样式定制

5.1、 使用 QSS(Qt样式表)

# 基本样式radio.setStyleSheet(""" QRadioButton { color: white; font-size: 16px; spacing: 8px; } QRadioButton::indicator { width: 20px; height: 20px; } QRadioButton::indicator:unchecked { border: 2px solid #999; border-radius: 10px; background-color: white; } QRadioButton::indicator:checked { border: 2px solid #0078D7; border-radius: 10px; background-color: #0078D7; } """)

5.2 、自定义绘制(如示例中的 ColorfulRadioButton)

继承 QRadioButton 并重写paintEvent方法可以实现完全自定义的外观:

classCustomRadioButton(QRadioButton):defpaintEvent(self,event):painter=QPainter(self)# 自定义绘制逻辑# ...

6、实战技巧

6.1、 动态创建单选按钮

defcreate_radio_buttons(self,options):"""根据选项列表动态创建单选按钮"""fori,optioninenumerate(options):radio=QRadioButton(option)radio.setObjectName(f"radio_{i}")self.layout().addWidget(radio)

6.2、 获取选中值

defget_selected_option(self):"""获取当前选中的单选按钮文本"""forradioinself.findChildren(QRadioButton):ifradio.isChecked():returnradio.text()returnNone

6.3 、启用/禁用控制

# 禁用单个按钮radio.setEnabled(False)# 禁用整个组forradioinbutton_group.buttons():radio.setEnabled(False)

7、常见问题与解决方案

问题1:单选按钮不互斥

原因:可能设置了setAutoExclusive(False)或按钮不在同一父容器中
解决:确保按钮在同一布局中,或使用 QButtonGroup 显式分组

问题2:文字显示不完整

原因:控件尺寸不足
解决:设置最小尺寸或使用setSizePolicy

radio.setMinimumWidth(200)radio.setSizePolicy(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Fixed)

问题3:自定义样式不生效

原因:样式表优先级或选择器错误
解决:检查样式表语法,使用!important提高优先级

8、 与相关控件的对比

控件用途选择模式典型场景
QRadioButton单选互斥(多选一)性别选择、评分等级
QCheckBox多选独立(可多选)兴趣爱好、功能开关
QComboBox下拉选择单选(下拉列表)国家选择、分类筛选
QListWidget列表选择单选/多选文件列表、项目选择

9、性能优化建议

  1. 批量操作:大量单选按钮时使用setUpdatesEnabled(False)暂停重绘
  2. 内存管理:动态创建的按钮及时删除
  3. 信号连接:避免重复连接,使用disconnect()断开旧连接
  4. 样式缓存:相同样式的按钮共享样式表字符串

10、扩展应用

10.1 、与数据模型绑定

# 将单选按钮与数据模型关联classRadioButtonModel:def__init__(self):self.options=["选项A","选项B","选项C"]self.selected_index=0defcreate_radio_buttons(self,parent):buttons=[]fori,optioninenumerate(self.options):radio=QRadioButton(option,parent)radio.setChecked(i==self.selected_index)radio.toggled.connect(lambdachecked,idx=i:self.on_selected(idx)ifcheckedelseNone)buttons.append(radio)returnbuttonsdefon_selected(self,index):self.selected_index=indexprint(f"选中了:{self.options[index]}")

10.2、 响应式单选按钮组

创建根据用户选择动态显示/隐藏相关控件的交互界面。

11、总结

QRadioButton 是 PyQt6 中构建单选界面的核心控件。通过掌握其基本用法、分组管理、样式定制和信号机制,可以创建出功能完善、外观美观的单选界面。本文示例中的 ColorfulRadioButton 展示了如何通过自定义绘制实现独特的视觉效果,这为高级 UI 设计提供了思路。