pyside6子类无法接收父类发射的正确值
以下是父类EXPANDABLE_MENU.py的代码:from CHROMELESS_WINDOW import *
# 伸缩菜单 父类
class ExpandableMenu(ChromelessWindow):
list_index = Signal(dict) # 发射 鼠标移动时的位置 {序号:[矩形起始y,矩形结束y,文字起始y,文字结束y]}
scrollbar_start_y = Signal(object)# 发射滚动条的起始y
text_gps_dict = Signal(dict) # 发射文本的gps
press_left = Signal(int) # 发射 按下左键时的文本序号
not_text_area = Signal(str) # # 发射 '非文本区域按下左键'
text_order = Signal(QPointF, object) # 调整文本顺序时发射 参数 鼠标轨迹,文本索引
def __init__(self, parent=None):
super().__init__(parent)
self.is_left_pressed = False # 左键按住标志
self.__left = None # 记录按下左键的位置
self.press_left_y = None # 按住左键时 滚动条的起始y
self.__pos = None # 设置 是否按住左键调整文本顺序 同时也时鼠标的移动轨迹
self.ctrl_left_name = None # 标记 调整文本顺序时的 名字
# 调用父类属性
self.min_button_tf, self.max_button_tf, self.button_close_tf = False, False, False # 设置三大组件失灵
self.drive_window = False# 为真时窗口可拖动
self.__list_index = None # 鼠标所在的位置的文本名字
self.parameters_settings()# 初始化各项参数
def parameters_settings(self, min_view: bool = True, # 设置 本窗口在任务栏不可见
adj_up: bool = False,
adj_down: bool = False,
adj_left: bool = False, # 左 为假时不可调整大小
adj_right: bool = True, # 右
window_x: int = 100,
window_y: int = 50,
window_width: int = 200,
text_number: int = 20, # 要显示文本数量
text_sum: int = 100, # 文本的总长度
text_size: int | float = 10, # 字体大小
text_lenght: int | float = 2, # 字体做坐标间距
text_window_start_y: int | float = 100, # 用于显示文本区域的起始x位置 非绘制文字的空白区域总高度
header_color: str = '#C4A737', # 标头颜色'#C4A737'
move_create_color: str = '#CEA5DA', # 跟随鼠标矩形的颜色
# column_width: dict = None, # 每一列的宽度{类序号:宽度}
scrollbar_color: str = '#444343', # 滚动条颜色
scrollbar_area_color: str = '#FAF8F8', # 滚动条区域的颜色
backcolor: str = None, # 背景颜色
):
if min_view:
self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool)# 设置窗口在任务栏中不可见
if backcolor:
self.setStyleSheet(f"background-color: {backcolor};") # 设置背景颜色
self.adj_up = adj_up
self.adj_down = adj_down
self.adj_left = adj_left
self.adj_right = adj_right
self.__window_x = window_x
self.__window_y = window_y
self.__window_width = window_width
self.__text_number = text_number # 要显示文本数量
self.__text_sum = text_sum # 文本的总长度
self.__text_size = text_size # 字体大小
self.__text_size_ratio = text_lenght # 字体间距比例
self.__text_lenght = text_lenght * self.__text_size # 字体纵坐标间距
# self.__column_width = {0: 50} if column_width is None else column_width# 每一列的宽度{类序号:宽度}
self.__scrollbar_color = scrollbar_color # 滚动条颜色
self.scrollbar_area_color = scrollbar_area_color # 滚动条区域颜色
screen = QApplication.primaryScreen().geometry()
self.__screenWidth, self.__screenHeight = screen.width(), screen.height()
self.__scrollbar_width = self.__screenWidth * 0.01 # 滚动条宽度
self.__scrollbar_ratio = self.__text_number / self.__text_sum # 滚动条占比
self.__text_window_start_y = text_window_start_y # 用于显示文本区域的起始x位置
self.__header_color = header_color # 标头颜色
self.move_create_color = move_create_color # 跟随鼠标矩形的颜色
self.text_start_index = 0 # 文本的开始索引
self.text_end_index = self.__text_number # 文本的结束索引
# 初始化 窗口
a = self.__text_lenght if header_color else 0
self.setGeometry(self.__window_x,
self.__window_y,
self.__window_width,
a + self.__text_number * self.__text_lenght + self.__text_window_start_y)
# 文本结束y和跟随鼠标的矩形y的距离
self.text_entity_lenght = self.__text_size * ((self.__text_size_ratio - 1) / 2)
if self.__text_window_start_y:# 空白区域
if header_color:
# 显示文本内容的区域 的起始y
self.__text_start_y = self.__text_size * self.__text_size_ratio + self.__text_window_start_y
# 滚动条起始y
self.__scrollbar_start_y = self.__text_start_y
self.__scrollbar_start_x = self.width() - self.__scrollbar_width# 滚动条起始x
# 滚动条长度
self.__scrollbar_height = self.__scrollbar_ratio * (self.height() - self.__scrollbar_start_y)
self.__scrollbar_end_y = self.__text_start_y + self.__scrollbar_height# 滚动条结束y
else:
self.__text_start_y = self.__text_window_start_y # # 显示文本内容的区域 的起始y
self.__scrollbar_start_y = self.__text_window_start_y # 滚动条起始y
self.__scrollbar_start_x = self.width() - self.__scrollbar_width# 滚动条起始x
# 滚动条长度
self.__scrollbar_height = self.__scrollbar_ratio * (self.height() - self.__text_window_start_y)
self.__scrollbar_end_y = self.__scrollbar_start_y + self.__scrollbar_height# 滚动条结束y
else:
if header_color:
self.__text_start_y = self.__text_size * self.__text_size_ratio# 显示文本内容的区域 的起始y
self.__scrollbar_start_y = self.__text_start_y # 滚动条起始y
self.__scrollbar_start_x = self.width() - self.__scrollbar_width # 滚动条起始x
# 滚动条长度
self.__scrollbar_height = self.__scrollbar_ratio * (self.height() - self.__scrollbar_start_y)
self.__scrollbar_end_y = self.__scrollbar_start_y + self.__scrollbar_height# 滚动条结束y
else:
self.__text_start_y = 0 # 显示文本内容的区域 的起始y
self.expandable_scrollbar_gps()# 初始化滚动条gps
self.scrollbar_max_start_y = self.height() - self.__scrollbar_height# 限制滚动论最大的起始y
self.__text_list = # 生成和文本列表同等长度的列表
self.expandable_text_gps_dict(start_index=0, end_index=self.__text_number)# 初始化跟随鼠标的矩形gps
def expandable_text_gps_dict(self, start_index, end_index):
self.mapping_text_gps_dict = {} # {序号:[矩形起始y,矩形结束y,文字起始y,文字结束y]}
for index, s in enumerate(self.__text_list):
end_y = self.__text_start_y + (index + 1) * self.__text_lenght # 矩形结束y
self.mapping_text_gps_dict = [self.__text_start_y + index * self.__text_lenght,
end_y,
end_y - self.text_entity_lenght - self.__text_size,
end_y - self.text_entity_lenght]
self.text_gps_dict.emit(self.mapping_text_gps_dict)
print(self.mapping_text_gps_dict)
def mouseMoveEvent(self, event):# 鼠标移动
super().mouseMoveEvent(event)
pos = event.position()
if self.is_left_pressed is False and pos.x() < self.__scrollbar_start_x:# 跟随鼠标的矩形
if pos.y() > self.__text_start_y:
for key_number, val_gps in self.mapping_text_gps_dict.items():
if val_gps < pos.y() < val_gps:
self.__list_index = key_number
# 发射 鼠标移动时的位置 {序号:[矩形起始y,矩形结束y,文字起始y,文字结束y]}
self.list_index.emit({self.__list_index: val_gps})
self.update()
break
else:
self.__list_index = None
elif self.is_left_pressed and pos.x() < self.__scrollbar_start_x:# 调整文本显示的位置
self.__pos = pos # 设置 是否按住左键调整文本顺序 同时也时鼠标的移动轨迹
self.text_order.emit(self.__pos, self.__text_list) # 参数 鼠标轨迹,文本索引
self.update()
elif self.is_left_pressed and pos.y() > self.__text_start_y and pos.x() > self.__scrollbar_start_x:
# 更新滚动条
new_y = pos.y() - self.__left # 计算鼠标移动和按下左键时的距离
new_y = self.press_left_y + new_y # 按住左键时滚动条的位置 + 鼠标移动的距离
if self.__text_start_y <= new_y <= self.scrollbar_max_start_y:
self.__scrollbar_start_y = new_y
text_start_y_ratio = (new_y - self.__text_start_y) / (self.height() - self.__text_start_y - self.text_entity_lenght)
self.text_start_index = int(text_start_y_ratio * self.__text_sum)
self.text_end_index = self.text_start_index + self.__text_number
self.expandable_text_gps_dict(self.text_start_index,
self.text_end_index)# {序号:[矩形起始y,矩形结束y,文字起始y,文字结束y]}
self.scrollbar_start_y.emit(new_y)# 发射滚动条的起始y
self.update()
def mousePressEvent(self, event: QMouseEvent):# 鼠标案件
super().mousePressEvent(event)
pos = event.position()
if event.button() == Qt.LeftButton:# 左键
self.ctrl_left_name = self.__list_index # 标记 调整文本顺序时的 名字
self.press_left_y = self.__scrollbar_start_y# 按住左键时 滚动条的 起始y位置
self.__left = pos.y() # 记录按下左键的位置
self.is_left_pressed = True# 设置按下标志
if pos.x() < self.__scrollbar_start_x and pos.y() > self.__text_start_y:
if self.__list_index:
self.press_left.emit(self.__list_index) # 发射 按下左键时的文本序号
elif pos.y() < self.__text_start_y:
self.not_text_area.emit('非文本区域按下左键') # 发射 '非文本区域'
elif event.button() == Qt.RightButton:# 右键
pass
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
pos = event.position()
if event.button() == Qt.LeftButton:
self.is_left_pressed = False# 清除按下标志
if self.__pos:
for key_number, val_gps in self.mapping_text_gps_dict.items():
if val_gps < pos.y() < val_gps:
self.__list_index = self.ctrl_left_name
# 发射 鼠标移动时的位置 {序号:[矩形起始y,矩形结束y,文字起始y,文字结束y]}
self.list_index.emit({self.__list_index: val_gps})
self.update()
break
else:
self.__list_index = None
self.__pos = None# 设置 是否按住左键调整文本顺序
pass
def wheelEvent(self, event):# 鼠标滚轮
super().wheelEvent(event)
angle_delta = event.angleDelta().y()# 垂直滚动的量
len_text = len(self.__text_list)
self.text_start_index, self.text_end_index = wheel_update_index(text_len=len_text,
max_number=self.__text_number,
start_index=self.text_start_index,
end_index=self.text_end_index,
angle_delta=angle_delta)
a = self.text_start_index / len_text # 开始索引占中长度的比例
b = self.height() - self.__text_start_y # 显示区域的长度
self.__scrollbar_start_y = a * b + self.__text_start_y # 更新滚动条起始y
self.expandable_text_gps_dict(start_index=self.text_start_index, end_index=self.text_end_index)
self.update()
def expandable_scrollbar_gps(self):
self.__scrollbar_start_y = 0 # 滚动条起始y
self.__scrollbar_start_x = self.width() - self.__scrollbar_width # 滚动条起始x
self.__scrollbar_height = self.__scrollbar_ratio * self.height() # 滚动条长度
self.__scrollbar_end_y = self.__scrollbar_start_y + self.__scrollbar_height # 滚动条结束y
def paintEvent(self, event):
qp = QPainter(self)
# 开启抗锯齿
qp.setRenderHint(QPainter.Antialiasing)# PyQt5/PySide6 通用写法
# 可选:同时开启其他渲染优化
qp.setRenderHint(QPainter.TextAntialiasing)# 文字抗锯齿
# qp.setRenderHint(QPainter.SmoothPixmapTransform)# 图片平滑缩放
# 绘制滚动条区域
entity_painter(qp, self.scrollbar_area_color, 0).drawRect(self.__scrollbar_start_x,
self.__text_start_y,
self.__scrollbar_width,
self.height() - self.__text_start_y)
qp.restore()
# 绘制滚动条 移动的轨迹
entity_painter(qp, self.__scrollbar_color, 0).drawRect(self.__scrollbar_start_x,
self.__scrollbar_start_y
, self.__scrollbar_width
, self.__scrollbar_height)
qp.restore()
# 绘制跟随鼠标的矩形
if self.__list_index is not None:
try:
entity_painter(qp, self.move_create_color, 0).drawRect(0,
self.mapping_text_gps_dict,
self.width() - self.__scrollbar_width,
self.__text_lenght)
qp.restore()
except KeyError:
pass
if self.__pos: # 绘制 调整文本顺序
# 绘制调整文本顺序时 矩形的移动轨迹
entity_painter(qp, self.move_create_color, 0).drawRect(0,
self.__pos.y() - self.__text_lenght / 2,
self.width() - self.__scrollbar_width,
self.__text_lenght)
qp.restore()
以下是子类a.py的代码:
from EXPANDABLE_MENU import *
import sys
from data_json import *
from MAPPING import *
# 我的自选
class MyMatchList(ExpandableMenu):
def __init__(self, parent=None):
super().__init__(parent)
screen = QApplication.primaryScreen().geometry()
self.match_width, self.match_height = screen.width(), screen.height()
self.match_font_size = self.match_height * 0.015 # 字体大小
self.text_x_lenght = self.match_font_size * 3 # 文本横向坐标的距离
self.match_text_start_y = self.match_height * 0.19# 显示文本区域的起始y,空白区域的结束y
self.match_window_width = int(self.match_width * 0.3) # 初始化 窗口宽度
self.match_font_type = "SimSun" # 字体类型
self.text_y_lenght = 2 # 文本纵向坐标的间距
# 创建与父类的链接
self.text_gps_dict.connect(self.js_text_gps)# 接收文本的纵向坐标gps
# 标头母版
self.header_mother_set = ['代码/名字', '价格', '涨幅', '大宗交易', '成交额', '流通市值']
# 数据母版
self.data_mother_set = ['SH600000_野马电池', '00.00', '++00.00', '20250909', '0.00亿', '000.00亿']
self.match_data_list = [] # 我的自选数据列表
self.match_window(window_x=0, window_y=0) # 初始化窗口 以及各项参数
def js_text_gps(self, gps: dict):
'''接收文本的纵向坐标gps'''
self.match_text_y_gps = gps
print(gps)
def match_window(self, window_x: int = 0, window_y: int = 0):
'''初始化窗口'''
match_json = Data_Http.mymatch_set_data()
# 文本列表
self.match_text_list = match_json['match'] if 'match' in match_json else None
# 窗口尺寸
self.match_window_width = int(match_json['window_size']) if 'window_size' in match_json else int(self.match_width * 0.3)
if self.match_text_list is None:
self.match_text_list = self.data_mother_set
# 窗口中可以显示的数据列数
self.display_column_number = mother_set_calculate(
data=self.match_text_list, # 数据
start_x=self.match_font_size * 2, # 预留 距离窗口左边的距离
window_width=self.match_window_width, # 窗口宽度
font_type=self.match_font_type, # 字体类型
font_size=self.match_font_size, # 字体大小
x_lenght=self.text_x_lenght # 文本横向距离
)
# 文本的横向gps 标头的横向gps
self.match_text_x_gps, self.match_header_gps = mother_set_mapping_gps(
header=self.header_mother_set, # 标头
data=self.match_text_list, # 数据
start_x=self.match_font_size * 2, # 预留 距离窗口左边的距离
font_type=self.match_font_type, # 字体类型
font_size=self.match_font_size, # 字体大小
x_lenght=self.text_x_lenght # 文本横向距离
)
# 调用父类
self.parameters_settings(window_x=window_x,
window_y=window_y,
window_width=self.match_window_width,
text_number=20, # 要显示文本数量
text_sum=len(self.match_text_list), # 文本的总长度
text_size=self.match_font_size, # 字体大小
text_lenght=self.text_y_lenght, # 字体y坐标间距
text_window_start_y=self.match_text_start_y # 用于显示文本区域的起始x位置
)
def resizeEvent(self, event): # 监控窗口大小
super().resizeEvent(event)# 确保父类逻辑执行
# old_size = event.oldSize()# 旧尺寸 (QSize)
new_size = event.size() # 新尺寸 (QSize)
if self.match_data_list:
pass
# 接收实时数据的接口
def mymatchlist_data_API(self, data: list = None) -> None:
self.match_data_list = data # 我的自选数据列表
len_data = len(data)
self.parameters_settings(window_x=100,
window_y=50,
window_width=200,
text_number=20, # 要显示文本数量
text_sum=len_data, # 文本的总长度
text_size=self.match_font_size, # 字体大小
text_lenght=2, # 字体做坐标间距
text_window_start_y=self.match_text_start_y # 用于显示文本区域的起始x位置
)
self.update()
def paintEvent(self, event):
super().paintEvent(event)
qp = QPainter(self)
#
# 绘制标头
for index, i in enumerate(self.header_mother_set):
text_begin(self, qp, '#BC29E5', "SimSun", self.match_font_size,
self.match_header_gps,
self.match_text_start_y - self.match_font_size * ((self.text_y_lenght - 1) / 2),
f'{i}')
qp.restore()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.processEvents()# 处理未完成的事件
window = MyMatchList()
window.show()
sys.exit(app.exec())
print(gps)语句的打印值为{},但是我通过在父类的发射信号处self.text_gps_dict.emit(self.mapping_text_gps_dict)添加
打印语句print(self.mapping_text_gps_dict),发现父类可以打印处正确的值,当父类的值改变时print(gps)会跟随打印出
{}而父类的打印语句正确无误,这说明父类发射了正确的值,子类MyMatchList也接收到了信号只不过收到的是空字典,这是怎么回事
你的问题在于子类 `MyMatchList` 在接收父类 `ExpandableMenu` 的 `text_gps_dict` 信号时收到了空字典 `{}`。核心原因是 **信号连接时机太晚**,父类在初始化过程中已经发射了信号,但此时子类尚未连接该信号。以下是详细分析和解决方案:
### 问题分析
1. **父类初始化流程**:
class ExpandableMenu(ChromelessWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.parameters_settings()# 这里会调用 expandable_text_gps_dict() 并发射信号
- 父类在 `__init__` 中直接调用 `parameters_settings()`
- `parameters_settings()` 内部会调用 `expandable_text_gps_dict()` 发射 `text_gps_dict` 信号
2. **子类初始化流程**:
class MyMatchList(ExpandableMenu):
def __init__(self, parent=None):
super().__init__(parent)# 父类构造器先执行(此时已发射信号)
self.text_gps_dict.connect(self.get_gps)# 子类在此后才连接信号
- 当 `super().__init__` 执行时,父类已经发射了信号
- 子类的信号连接 (`connect`) 发生在父类初始化**之后**,因此错过了初始信号
---
### 解决方案
#### 方法1:延迟父类的信号发射(推荐)
修改父类,在 `parameters_settings()` 中**移除初始化发射**,改为单独调用:
# 修改父类 EXPANDABLE_MENU.py
class ExpandableMenu(ChromelessWindow):
def parameters_settings(self, ...):
# ... 其他代码保持不变 ...
# 移除下面这行:
# self.expandable_text_gps_dict(start_index=0, end_index=self.__text_number)
# 新增方法供子类调用
def init_text_gps(self):
self.expandable_text_gps_dict(start_index=0, end_index=self.__text_number)
在子类中显式初始化位置字典:
class MyMatchList(ExpandableMenu):
def __init__(self, parent=None):
super().__init__(parent)
self.text_gps_dict.connect(self.get_gps)# 先连接信号
# 手动初始化文本位置字典
self.init_text_gps()# 调用父类新增的方法
def set_data(self, data: list = None):
# ... 原有代码 ...
self.init_text_gps()# 数据更新后重新初始化位置
#### 方法2:在子类中主动触发更新
如果不想修改父类,可在子类连接信号后主动请求数据:
class MyMatchList(ExpandableMenu):
def __init__(self, parent=None):
super().__init__(parent)
self.text_gps_dict.connect(self.get_gps)
# 主动触发父类更新
self.expandable_text_gps_dict(0, self._ExpandableMenu__text_number)
---
### 关键点说明
1. **执行顺序问题**:
- Python 构造器执行顺序:父类 `__init__` → 子类 `__init__`
- 父类在构造时发射的信号,子类尚未连接,因此丢失
2. **代码修复原则**:
- 父类构造时自动发射信号(子类错过)
+ 子类准备好后手动触发信号发射(确保连接有效)
3. **访问父类私有变量**:
若需访问父类私有变量如 `__text_number`,使用:
value = self._ExpandableMenu__text_number# 双下划线变量名重整规则
---
### 验证步骤
1. 在子类 `get_gps` 槽函数添加调试打印:
def get_gps(self, gps):
print("Received GPS:", gps)# 此时应收到非空字典
2. 确保每次数据更新后调用 `init_text_gps()`
通过调整信号发射时机,即可解决子类接收空字典的问题。
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
页:
[1]