python之关于QTimer.singleShot()的应用记录

本文介绍了一个使用PyQt5中QTimer.singleShot方法的示例,演示了如何在用户点击按钮后延迟500毫秒执行一次累加操作,并更新GUI界面上的显示。该应用展示了QTimer.singleShot在实现简单定时任务中的应用。
部署运行你感兴趣的模型镜像
# -*- coding: utf-8 -*-
'''
关于QTimer.singleShot()的应用记录
'''
import time
import numpy as np
import matplotlib.pyplot as plt
import winsound
import sys
import sklearn
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel
from PyQt5.QtCore import QTimer, Qt

num = 0


class my_num(QWidget):
    def __init__(self):
        super(my_num, self).__init__()
        self.InitUi()

    def InitUi(self):
        self.resize(600, 400)
        self.setWindowTitle('demo')
        self.btn_1 = QPushButton('累加', self)
        self.btn_1.setGeometry(60, 120, 150, 100)
        self.btn_1.clicked.connect(self.m_add)
        self.label = QLabel("<h1>0</h1>", self)  # 设置Label的字体大小,通过html的格式设置
        self.label.setGeometry(300, 120, 300, 100)
        self.label.setAlignment(Qt.AlignCenter)
        self.mt = QTimer(self)

    def my_timer(self):
        if self.btn_1.clicked:
            self.mt.singleShot(500, self.m_add)

    def m_add(self):
        global num
        num += 1
        print('num = {}'.format(num))
        self.label.setText("<h1>{}</h1>".format(num))
        self.my_timer()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = my_num()
    w.show()
    sys.exit(app.exec_())

我曾经跨过山和大海,也穿过人山人海,我曾经拥有着的一切,转眼都飘散如烟,我曾经失落失望失掉所有方向,直到看见平凡才是唯一的答案。
——韩寒《平凡之路》

 

您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

# -*- coding: UTF-8 -*- # """ @filename: main_window.py @author : Sun S Z @time : 2025/9/1 13:30 @software: PyCharm """ """ 主窗口模块 - 包含总体界面布局和框架 """ import os import matplotlib matplotlib.use('QtAgg') from PySide6.QtWidgets import ( QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem, QTabWidget, QLabel, QFileDialog, QMessageBox, QStatusBar, QProgressBar, QDockWidget, QApplication, QListWidget, QTextEdit ) from PySide6.QtGui import (QFont, QPixmap, QAction) from PySide6.QtCore import (Qt, QTimer, QDateTime) import matplotlib.pyplot as plt import seaborn as sns ## 导入相关类 # 数据处理 from data_processing.data_import.data_import import DataImporter from data_processing.preview.data_preview import DataPreview from data_processing.cleaning.data_cleaning import DataCleaningMain from data_processing.transformation.data_transformation import DataTransformationMain from data_processing.data_export.data_export import DataExporter # 统计分析 from statistical_analysis.descriptive.descriptive_layout import DescriptiveStatsLayout from statistical_analysis.t_test.t_test_layout import TTestLayout from statistical_analysis.variance.variance_layout import VarianceLayout from statistical_analysis.correlation.correlation_layout import CorrelationLayout from statistical_analysis.regression.regression_layout import RegressionLayout # 数据可视化 from data_visualization.bar_chart.bar_chart_layout import BarChartLayout from data_visualization.line_chart.line_chart_layout import LineChartLayout from data_visualization.scatter_chart.scatter_chart_layout import ScatterChartLayout from data_visualization.box_chart.box_chart_layout import BoxChartLayout from data_visualization.heatmap_chart.heatmap_chart_layout import HeatmapChartLayout # 系统设置相关 from system_settings.theme_settings import ThemeSettings from system_settings.language import LanguageSettings######################### from system_settings.fullscreen_mode import FullscreenMode from system_settings.variable_browser import VariableBrowser from system_settings.output_console import OutputConsole from system_settings.help_document import HelpDocument from system_settings.about_dialog import AboutDialog # 报告生成 from report_generation.generate_ui import ReportGeneratorUI # UI组件 from ui_components.top_bar import TopBar from ui_components.left_sidebar import Sidebar from ui_components.status_bar import StatusBar from ui_components.welcome_page import WelcomePage from ui_components.dock_widgets import DockWidgets from ui_components.data_required import DataRequired from ui_components.placeholder_page import PlaceholderPage # 工具类 from tool_support.chinese import FontSetting from tool_support.logger import ConsoleLogger from tool_support.helpers import AnalysisWorker from tool_support.navigation_handler import NavigationHandler############################# # 默认设置 from overall_framework.default_settings import AppSettings class ModernDataAnalysisApp(QMainWindow): """主应用程序窗口""" def __init__(self): super().__init__() self.settings = AppSettings(self) self.setWindowTitle("东方测控数据分析平台") # 窗口设置 - 屏幕适配 screen = QApplication.primaryScreen().availableGeometry() self.screen_width = screen.width() self.screen_height = screen.height() # 计算窗口大小和位置 window_width = max(int(self.screen_width * 0.9), 1200) window_height = max(int(self.screen_height * 0.9), 800) x = (self.screen_width - window_width) // 2 y = (self.screen_height - window_height) // 2 self.setGeometry(x, y, window_width, window_height) # 初始化数据 self.df = None self.df_history = [] # 数据操作历史 self.current_analysis = None self.analysis_results = {} self.recent_files = [] self.data_file_path = None # 当前数据集路径 self.modified = False # 数据修改标记 self.analysis_queue = [] # 分析任务队列 # 创建UI组件 self.create_main_content() # 创建主内容区域 self.status_bar = StatusBar(self) # 状态栏 self.dock_widgets = DockWidgets(self) # 停靠窗口 # 初始化导航处理器 self.navigation_handler = NavigationHandler(self) # 应用样式 self.apply_style() # 检查更新 QTimer.singleShot(3000, self.check_for_updates) # 显示欢迎页面 WelcomePage.show_welcome_page(self) def create_main_content(self): """创建主内容区域""" # 主内容区域 self.content_widget = QWidget() self.content_layout = QHBoxLayout(self.content_widget) self.content_layout.setContentsMargins(0, 0, 0, 0) self.content_layout.setSpacing(0) # 创建顶部标题栏 self.top_bar = TopBar(self).get_widget() # 创建左侧导航栏 self.sidebar = Sidebar(self).get_widget() self.navigation_tree = self.sidebar.findChild(QTreeWidget, "navigation") self.navigation_tree.itemClicked.connect(self.navigate_to) # 创建内容区域 self.content_area = QTabWidget() self.content_area.setDocumentMode(True) self.content_area.setTabsClosable(True) self.content_area.tabCloseRequested.connect(self.close_tab) self.content_layout.addWidget(self.content_area, 1) # 添加到主布局 self.main_layout = QVBoxLayout() self.main_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.setSpacing(0) self.main_layout.addWidget(self.top_bar) self.main_layout.addWidget(self.content_widget, 1) central_widget = QWidget() central_widget.setLayout(self.main_layout) self.setCentralWidget(central_widget) def navigate_to(self, item): """导航到选定页面,委托给导航处理器""" self.navigation_handler.handle_navigation(item) def add_tab(self, widget, title): """添加新选项卡""" self.content_area.addTab(widget, title) self.content_area.setCurrentIndex(self.content_area.count() - 1) def close_tab(self, index): """关闭选项卡""" if self.content_area.count() > 1: self.content_area.removeTab(index) def apply_style(self): """应用当前主题样式""" self.setStyleSheet(self.settings.get_stylesheet()) # 更新图表样式 if self.settings.parent.current_theme == "dark": plt.style.use('dark_background') sns.set_style("darkgrid") else: plt.style.use('default') sns.set_style("whitegrid") def check_for_updates(self): """检查更新""" self.status_bar.set_status_text("正在检查更新...") QTimer.singleShot(2000, lambda: self.status_bar.set_status_text("已是最新版本")) # 以下方法主要作为接口,供其他组件调用 def import_data(self, file_path=None): """导入数据文件""" return DataImporter.import_data(self.parent, file_path) def show_data_preview(self): """显示数据预览""" DataPreview.show_data_preview(self.parent) def show_data_required(self): """显示需要导入数据的消息""" DataRequired.show_data_required(self.parent) def log_to_console(self): """记录消息到控制台""" OutputConsole.log_to_console(self.parent) def update_variable_browser(self): """更新变量浏览器""" VariableBrowser.update_variable_browser(self.parent) def toggle_fullscreen(self): """切换全屏模式""" FullscreenMode.toggle_fullscreen(self.parent) def show_help(self): """显示帮助文档""" HelpDocument.show_help(self.parent) def show_about(self): """显示关于对话框""" AboutDialog.show_about(self.parent) def set_theme(self, theme_name): """设置应用主题""" ThemeSettings.set_theme(self.parent, theme_name) E:\PythonProject_likespssau_v5.0_9.1_split\.venv\Scripts\python.exe E:\PythonProject_likespssau_v5.0_9.1_split\main.py Traceback (most recent call last): File "E:\PythonProject_likespssau_v5.0_9.1_split\main.py", line 45, in <module> main() ~~~~^^ File "E:\PythonProject_likespssau_v5.0_9.1_split\main.py", line 38, in main window = ModernDataAnalysisApp() File "E:\PythonProject_likespssau_v5.0_9.1_split\overall_framework\main_window.py", line 118, in __init__ self.apply_style() ~~~~~~~~~~~~~~~~^^ File "E:\PythonProject_likespssau_v5.0_9.1_split\overall_framework\main_window.py", line 180, in apply_style if self.settings.parent.current_theme == "dark": ^^^^^^^^^^^^^^^^^^^^ AttributeError: 'AppSettings' object has no attribute 'parent' 进程已结束,退出代码为 1
09-04
import sys from PyQt5.QtWidgets import ( QMainWindow, QApplication, QMessageBox ) from PyQt5.QtCore import Qt, QTimer from pypinyin import lazy_pinyin, Style from opencc import OpenCC from functools import partial from 内科疾病模板_ui import Ui_NeiKeWindow # 替换为你自己的 UI 模块 from 连接池到疾病库 import POOL_D # 替换为你自己的数据库连接池模块 import traceback from PyQt5 import QtCore class NeiKeWindow(QMainWindow): def __init__(self): super().__init__() self.ui = Ui_NeiKeWindow() self.ui.setupUi(self) self.resize(600, 400) # 初始化简体转繁体转换器 self.cc = OpenCC('s2t') # 简体转繁体 # 单行输入框转换 self.line_edits = [self.ui.lineEdit_N_name] for line_edit in self.line_edits: line_edit.textChanged.connect(partial(self.convert_lineedit, line_edit)) # 多行文本框列表 self.text_edits = [ self.ui.textEdit_N_overview, self.ui.textEdit_disease_origin, self.ui.textEdit_N_symptom, self.ui.textEdit_N_check, self.ui.textEdit_N_identify, self.ui.textEdit_N_company, self.ui.textEdit_N_prevent, self.ui.textEdit_N_cure, self.ui.textEdit_N_food, self.ui.textEdit_N_encourage_food, self.ui.textEdit_N_taboo, ] # 绑定自动转换 + 自动调整高度 for text_edit in self.text_edits: text_edit.textChanged.connect(partial(self.convert_textedit, text_edit)) text_edit.textChanged.connect(partial(self.on_text_changed, text_edit)) # 初始化高度 for text_edit in self.text_edits: self.adjust_text_edit_height(text_edit) # 按钮绑定 self.ui.pushButton.clicked.connect(self.save) self.ui.pushButton_2.clicked.connect(self.clear) self.show() def convert_lineedit(self, line_edit): cursor_pos = line_edit.cursorPosition() original_text = line_edit.text() converted_text = self.cc.convert(original_text) if converted_text != original_text: line_edit.blockSignals(True) line_edit.setText(converted_text) line_edit.setCursorPosition(cursor_pos) line_edit.blockSignals(False) def convert_textedit(self, text_edit): cursor_pos = text_edit.textCursor().position() original_text = text_edit.toPlainText() converted_text = self.cc.convert(original_text) if converted_text != original_text: cursor = text_edit.textCursor() text_edit.blockSignals(True) text_edit.setPlainText(converted_text) cursor.setPosition(cursor_pos) text_edit.setTextCursor(cursor) text_edit.blockSignals(False) def on_text_changed(self, text_edit): self.adjust_text_edit_height(text_edit) def adjust_text_edit_height(self, text_edit): document = text_edit.document() size_hint = document.size() margins = text_edit.contentsMargins() new_height = int(size_hint.height()) + margins.top() + margins.bottom() + 4 # 额外空间 text_edit.setMinimumHeight(new_height) text_edit.setMaximumHeight(new_height) # 正确更新布局 QTimer.singleShot(0, text_edit.parentWidget().updateGeometry) QTimer.singleShot(0, self.updateGeometry) def save(self): disease_name = self.ui.lineEdit_N_name.text().strip() if not disease_name: QMessageBox.warning(self, "输入错误", "病名不能为空") return overview = self.ui.textEdit_N_overview.toPlainText() disease_origin = self.ui.textEdit_disease_origin.toPlainText() symptom = self.ui.textEdit_N_symptom.toPlainText() check = self.ui.textEdit_N_check.toPlainText() identify = self.ui.textEdit_N_identify.toPlainText() company = self.ui.textEdit_N_company.toPlainText() prevent = self.ui.textEdit_N_prevent.toPlainText() cure = self.ui.textEdit_N_cure.toPlainText() food = self.ui.textEdit_N_food.toPlainText() encourage_food = self.ui.textEdit_N_encourage_food.toPlainText() taboo = self.ui.textEdit_N_taboo.toPlainText() pinyin_text = ''.join(lazy_pinyin(disease_name, style=Style.FIRST_LETTER)) cla = 1 try: with POOL_D.connection() as conn: with conn.cursor() as cursor: cursor.execute("START TRANSACTION") essential_information = """ INSERT INTO 内科疾病 (病名,病名首拼,概述,病因,症状,检查,鉴别,并发症,预防,治疗,饮食,宜食,禁忌,疾病一级分类_id) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ cursor.execute(essential_information, ( disease_name, pinyin_text, overview, disease_origin, symptom, check, identify, company, prevent, cure, food, encourage_food, taboo, cla )) conn.commit() if cursor.rowcount > 0: QMessageBox.information(self, "成功", "保存成功!") else: QMessageBox.warning(self, "失败", "保存失败!") except Exception as e: QMessageBox.critical(self, "数据库错误", f"保存失败: {str(e)}") traceback.print_exc() def clear(self): self.ui.lineEdit_N_name.clear() for text_edit in self.text_edits: text_edit.clear() if __name__ == '__main__': from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QTranslator, QLibraryInfo # 启用高DPI缩放支持 QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) app = QApplication(sys.argv) # 加载中文语言包(繁体) translator = QTranslator() translator.load("qtbase_zh_TW", QLibraryInfo.location(QLibraryInfo.TranslationsPath)) app.installTranslator(translator) # 设置字体适配 DPI screen = app.screens()[0] dpi = screen.logicalDotsPerInch() scale_factor = dpi / 96.0 font_size = max(12, int(12 * scale_factor)) font = app.font() font.setPointSize(font_size) app.setFont(font) try: window = NeiKeWindow() window.show() sys.exit(app.exec_()) except Exception as e: print("程序异常退出:", e) traceback.print_exc() 把他融入到我的代码中
07-30
import copy import sys import threading import atexit from PyQt5.QtWidgets import * from PyQt5.QtCore import Qt, QRect, QPoint, QSize, pyqtSignal, QTimer, QEvent from PyQt5.QtGui import QFont, QColor from datetime import datetime from multiprocessing import Process, Queue, freeze_support import weakref import psutil import time class MT5ProcessWorker: """ 独立进程工作器(替代原MT5Worker) 通过队列与主进程通信 """ def __init__(self, exe_path, symbol): self.exe_path = exe_path self.symbol = symbol self.queue = Queue() # 每个worker独立队列 self._process = None self._shutdown_flag = False def start(self): self._process = Process( target=self._run, args=(self.exe_path, self.symbol, self.queue) ) self._process.daemon = True # 主进程退出时自动终止 self._process.start() atexit.register(self.stop) # 注册退出清理 def _run(self, exe_path, symbol, queue): """子进程运行方法""" try: import MetaTrader5 as mt5_local # 每个进程独立导入 import time # 初始化独立MT5实例 if not mt5_local.initialize(path=exe_path, timeout=5): queue.put(('error', f"初始化失败: {mt5_local.last_error()}")) return # 获取并发送精度信息 symbol_info = mt5_local.symbol_info(symbol) if not symbol_info: queue.put(('error', f"无效品种: {symbol}")) return # 获取服务器信息 account_info = mt5_local.account_info() if account_info and hasattr(account_info, 'server'): server_name = account_info.server else: server_name = "Unknown Server" print("警告: 无法获取服务器名称") # 添加调试输出 digits = symbol_info.digits queue.put(('connected', (server_name, digits))) # 返回服务器名和精度 print(f"发送连接成功消息: {server_name}") # 添加调试输出 # 报价循环 while not self._shutdown_flag: try: # 增加心跳检测 if not mt5_local.terminal_info().connected: break # 检查队列中的关闭命令 if not queue.empty(): msg_type, _ = queue.get_nowait() if msg_type == 'shutdown': break # 在循环开始处检查队列状态 if queue.qsize() > 10: # 防止队列堆积 queue.queue.clear() terminal = mt5_local.terminal_info() if terminal is None or not hasattr(terminal, 'connected'): queue.put(('disconnected', "MT5连接已断开")) break if not terminal.connected: queue.put(('disconnected', "连接已主动断开")) tick = mt5_local.symbol_info_tick(symbol) if tick and tick.time_msc > 0: queue.put(('price', (symbol, tick.bid, tick.ask))) time.sleep(0.1) # 减少CPU使用率 except AttributeError as ae: queue.put(('error', f"终端状态获取失败: {str(ae)}")) break except Exception as e: print(f"报价循环异常: {e}") except Exception as e: queue.put(('error', str(e))) finally: mt5_local.shutdown() queue.put(('disconnected', "MT5连接关闭")) # 确保关闭时发送通知 def stop(self): if self._process: try: # 发送关闭指令 self.queue.put(('shutdown', None)) self._process.join(2) # 增加等待时间 # 双重终止保障 if self._process.is_alive(): self._process.terminate() self._process.join(1) # 安全关闭队列 self.queue.close() self.queue.cancel_join_thread() except Exception as e: print(f"进程终止失败: {str(e)}") class PriceCard(QWidget): delete_requested = pyqtSignal(object) symbol_changed = pyqtSignal(str) set_as_reference = pyqtSignal(str, float, float) status_updated = pyqtSignal(str) # 新增状态信号 # 添加一个信号用于线程安全地更新UI update_platform_info = pyqtSignal(str, int) update_prices_signal = pyqtSignal(str, float, float) def __init__(self, parent=None, currency_list=None): super().__init__(parent) self.mt5_worker = None self.data_timer = QTimer() # 初始设置货币对列表 if currency_list is not None: self.currency_list = copy.deepcopy(currency_list) else: self.currency_list = ["EURUSD", "GBPUSD", "USDJPY", "XAUUSD"] self.current_bid = 0.0 self.current_ask = 0.0 self._is_connected = False # 新增连接状态标志 self._has_valid_prices = False # 是否接收过有效价格 self.is_reference = False self._valid = True # 新增有效性标志 self.abnormal_count = 0 self.setup_ui() self.setup_style() self.setup_connections() # 初始化MT5工作器 self.start_connection() # 连接信号到UI更新方法 self.update_platform_info.connect(self._safe_update_platform_info) self.update_prices_signal.connect(self._safe_update_prices) def _safe_update_platform_info(self, server_name, digits): """在线程安全的情况下更新平台信息""" self.platform_input.setText(f"已连接 · {server_name}") self.platform_input.setStyleSheet("color: #009900;") self._digits = digits self._is_connected = True def _safe_update_prices(self, symbol, bid, ask): """线程安全地更新价格显示""" if not hasattr(self, '_digits'): self._digits = 5 # 默认精度 format_str = f".{self._digits}f" try: if bid > 0 and ask > 0: self._has_valid_prices = True self.current_bid = bid self.current_ask = ask self.sell_label.setText(f"{bid:{format_str}}") self.buy_label.setText(f"{ask:{format_str}}") # 持续更新reference_label if self.is_reference: self.status_updated.emit(f"参照平台:{symbol} {bid}/{ask} [{datetime.now().strftime("%H:%M:%S")}]") else: self._has_valid_prices = False except Exception as e: print(f"更新价格异常: {e}") # 安全地断开信号连接 @staticmethod def safe_disconnect(signal: pyqtSignal): try: # 尝试断开所有连接 signal.disconnect() except TypeError: # 没有连接或已断开,忽略 pass def _process_queue(self): """非阻塞方式处理队列""" if not self.mt5_worker or not hasattr(self.mt5_worker, 'queue'): return try: max_messages = 10 for _ in range(max_messages): if self.mt5_worker.queue.empty(): break try: msg_type, data = self.mt5_worker.queue.get_nowait() self._handle_queue_message(msg_type, data) except Exception as e: print(f"处理队列消息异常: {e}") break except Exception as e: print(f"队列处理主循环异常: {e}") finally: # 继续轮询 QTimer.singleShot(50, self._process_queue) def setup_ui(self): self.setFixedSize(320, 200) layout = QVBoxLayout(self) layout.setContentsMargins(12, 8, 12, 8) layout.setSpacing(8) # 标题栏 title_layout = QHBoxLayout() title_layout.setContentsMargins(0, 0, 0, 0) title_layout.setSpacing(6) self.platform_input = QLineEdit("等待连接...") self.platform_input.setFont(QFont("微软雅黑", 9)) self.platform_input.setReadOnly(True) # 参考按钮 self.ref_btn = QPushButton("★") self.ref_btn.setFixedHeight(24) self.ref_btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) # 删除按钮 self.delete_btn = QPushButton("×") self.delete_btn.setFixedHeight(24) self.delete_btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) title_layout.addWidget(self.platform_input, stretch=1) title_layout.addWidget(self.ref_btn) title_layout.addWidget(self.delete_btn) layout.addLayout(title_layout) # 交易品种 self.symbol_combo = QComboBox() self.symbol_combo.addItems(self.currency_list) layout.addWidget(self.symbol_combo) # 价格显示 price_layout = QHBoxLayout() price_layout.setContentsMargins(0, 0, 0, 0) price_layout.setSpacing(10) self.sell_label = QLabel("--") self.buy_label = QLabel("--") for label in [self.sell_label, self.buy_label]: label.setFixedHeight(40) label.setAlignment(Qt.AlignCenter) label.setFont(QFont("Arial", 12, QFont.Bold)) price_layout.addWidget(label) layout.addLayout(price_layout) # MT5路径选择 path_layout = QHBoxLayout() self.path_input = QLineEdit() self.path_input.setPlaceholderText("MT5可执行文件路径") self.browse_btn = QPushButton("浏览") self.browse_btn.setMinimumWidth(100) self.browse_btn.setFixedHeight(24) path_layout.addWidget(self.path_input) path_layout.addWidget(self.browse_btn) layout.addLayout(path_layout) self.browse_btn.clicked.connect(self.browse_mt5_exe) self.delete_btn.clicked.connect(self.request_delete) self.symbol_combo.currentTextChanged.connect(self.symbol_changed.emit) self.ref_btn.clicked.connect(self.emit_reference_signal) def setup_style(self): self.base_style = """ QWidget { background: white; border-radius: 8px; border: 2px solid #e0e0e0; } QLineEdit, QComboBox { border: 1px solid #d0d0d0; border-radius: 4px; padding: 4px; } QStatusBar { background: #f8f8f8; border-top: 1px solid #e0e0e0; } """ self.normal_style = "" self.abnormal_style = """ QWidget { border: 2px solid #ff0000; background: #fff0f0; } """ self.ref_style = """ QWidget { border: 2px solid #00aa00; background: #f8fff8; } """ self.ref_btn.setStyleSheet(""" QPushButton { background: transparent; color: #888888; font-size: 12px; border-radius: 4px; border: 1px solid #e0e0e0; } QPushButton:checked { color: #FFFFFF; background: #00aa00; border-radius: 4px; } """) self.delete_btn.setStyleSheet(""" QPushButton { background: transparent; color: #888888; font-size: 12px; border-radius: 4px; border: 1px solid #e0e0e0; } QPushButton:hover { color: #FF0000; border-color: #FF0000; } """) self.sell_label.setStyleSheet(""" QLabel { background-color: #8B0000; color: white; border-radius: 4px; padding: 6px; min-width: 100px; } """) self.buy_label.setStyleSheet(""" QLabel { background-color: #000080; color: white; border-radius: 4px; padding: 6px; min-width: 100px; } """) # 为浏览按钮添加样式 self.browse_btn.setStyleSheet(""" QPushButton { color: black; /* 设置文字颜色为黑色,可按需调整 */ background-color: #f0f0f0; /* 设置背景色,可按需调整 */ border: 1px solid #ccc; /* 设置边框,可按需调整 */ padding: 4px 8px; } """) self.setStyleSheet(self.base_style) def isValid(self): return self._valid def deleteLater(self): if not self._valid: # 防止重复调用 return self._valid = False # 停止定时器 if hasattr(self, 'data_timer') and self.data_timer.isActive(): self.data_timer.stop() # 停止工作进程 if hasattr(self, 'mt5_worker') and self.mt5_worker: try: self.mt5_worker.stop() except Exception as e: print(f"停止MT5工作进程失败: {e}") self.safe_disconnect(self.delete_requested) self.safe_disconnect(self.set_as_reference) self.safe_disconnect(self.symbol_changed) self.safe_disconnect(self.status_updated) self.safe_disconnect(self.update_platform_info) self.safe_disconnect(self.update_prices_signal) super().deleteLater() def setup_connections(self): self.symbol_changed.connect(self.restart_connection) def emit_reference_signal(self): if self.ref_btn.isChecked(): self.set_as_reference.emit( self.symbol_combo.currentText(), self.current_bid, self.current_ask ) self.set_style(self.ref_style) self.is_reference = True else: self.clear_reference_style() self.set_as_reference.emit("", 0.0, 0.0) self.is_reference = False def clear_reference_style(self): self.setFixedSize(320, 200) self.setStyleSheet(f"{self.base_style}") if self.parent(): self.parent().updateGeometry() def set_style(self, style_sheet): self.setStyleSheet(style_sheet) self.update() def update_prices(self, symbol, bid, ask): # 使用信号更新UI,确保线程安全 self.update_prices_signal.emit(symbol, bid, ask) def update_abnormal_status(self, is_abnormal): if self.is_reference: return if is_abnormal: self.abnormal_count += 1 self.set_style(self.abnormal_style) else: self.abnormal_count = 0 self.set_style(self.normal_style) def browse_mt5_exe(self): path, _ = QFileDialog.getOpenFileName( self, "选择MT5可执行文件", filter="Executable Files (*.exe)" ) if path: self.path_input.setText(path) self.start_connection() def start_connection(self): try: if not self.path_input.text().strip(): return if hasattr(self, 'mt5_worker') and self.mt5_worker: self.mt5_worker.stop() self.mt5_worker = MT5ProcessWorker( self.path_input.text().strip(), self.symbol_combo.currentText() ) self.mt5_worker.start() # 启动数据接收定时器 if not self.data_timer.isActive(): self.data_timer.timeout.connect(self._process_queue) self.data_timer.start(50) # 添加临时状态显示,用于调试 self.platform_input.setText("正在连接...") self.platform_input.setStyleSheet("color: #FF8800;") except Exception as e: print(f"启动连接失败: {e}") self.platform_input.setText("连接失败") self.platform_input.setStyleSheet("color: #FF0000;") def _handle_queue_message(self, msg_type, data): try: if msg_type == 'connected': server_name, digits = data print(f"收到连接成功消息: {server_name}, 精度: {digits}") # 使用信号更新UI,确保在主线程中执行 self.update_platform_info.emit(server_name, digits) elif msg_type == 'disconnected': disconnect_msg = data self.on_disconnected(disconnect_msg) elif msg_type == 'price': symbol, bid, ask = data self.update_prices(symbol, bid, ask) elif msg_type == 'error': error_msg = data self.show_error(error_msg) except Exception as e: print(f"处理队列消息异常: {e}") def on_disconnected(self, message): self.platform_input.setText("等待连接...") self.platform_input.setStyleSheet("color: #666666;") self._is_connected = False self._has_valid_prices = False self.sell_label.setText("--") self.buy_label.setText("--") # 自动重连逻辑 if not self._valid: # 卡片已标记为无效时不重连 return QTimer.singleShot(3000, self.start_connection) def request_delete(self): # 立即从父布局中移除自己 if self.parent() and self.parent().layout(): layout = self.parent().layout() layout.removeWidget(self) # 发送删除请求 self.delete_requested.emit(weakref.ref(self)) # 停止数据接收 if hasattr(self, 'data_timer') and self.data_timer.isActive(): self.data_timer.stop() # 终止工作进程 if hasattr(self, 'mt5_worker') and self.mt5_worker: self.mt5_worker.stop() self.mt5_worker = None self.safe_disconnect(self.delete_requested) self.safe_disconnect(self.set_as_reference) self.safe_disconnect(self.symbol_changed) self.safe_disconnect(self.status_updated) self.safe_disconnect(self.update_platform_info) self.safe_disconnect(self.update_prices_signal) self._valid = False self.hide() # 立即隐藏 self.deleteLater() def restart_connection(self, new_symbol): if self.path_input.text().strip(): self.start_connection() def on_connected(self, server_name, digits): print(f"更新连接状态: {server_name}") # 添加调试输出 self.platform_input.setText(f"已连接 · {server_name}") self.platform_input.setStyleSheet("color: #009900;") self._digits = digits self._is_connected = True def show_error(self, message): QMessageBox.critical(self, "连接错误", f"{message}\n请检查:\n1. MT5客户端是否已登录\n2. 路径是否正确\n3. 品种是否有效") self.platform_input.setText("连接失败") self.path_input.clear() self._is_connected = False def update_currencies(self, new_currency=None): """更新交易品种下拉菜单""" # 检查品种是否已存在 if new_currency and new_currency not in self.currency_list: self.currency_list.append(new_currency) if hasattr(self, 'symbol_combo'): self.symbol_combo.clear() self.symbol_combo.addItems(self.currency_list) class CardScrollArea(QScrollArea): """自定义滚动区域,确保卡片完整显示""" def __init__(self, parent=None): super().__init__(parent) self.setWidgetResizable(True) self._block_signal = False # 滚动动画定时器 self.scroll_timer = QTimer(self) self.scroll_timer.timeout.connect(self._smooth_scroll_step) self.target_value = 0 self.start_value = 0 self.scroll_steps = 0 self.current_step = 0 # 安装事件过滤器以捕获滚动事件 self.verticalScrollBar().installEventFilter(self) def eventFilter(self, obj, event): """拦截滚动事件,实现卡片对齐""" # 兼容不同PyQt5版本的事件类型检查 slider_release_type = getattr(QEvent, 'SliderRelease', None) if slider_release_type is None: # 尝试使用Type枚举 slider_release_type = getattr(QEvent.Type, 'SliderRelease', None) if obj == self.verticalScrollBar() and event.type() == slider_release_type: if not self._block_signal: self._snap_to_card() return True return super().eventFilter(obj, event) def _snap_to_card(self): """对齐到最近的完整卡片""" if not self.widget(): return scroll_bar = self.verticalScrollBar() current_value = scroll_bar.value() viewport_height = self.viewport().height() layout = self.widget().layout() valid_items = [layout.itemAt(i).widget() for i in range(layout.count()) if layout.itemAt(i).widget()] positions = [] for widget in valid_items: y = widget.y() height = widget.height() top = y bottom = y + height if top <= current_value + viewport_height and bottom >= current_value: positions.extend([top, bottom]) if positions: closest_pos = min(positions, key=lambda x: abs(x - current_value)) self._start_smooth_scroll(closest_pos) def _start_smooth_scroll(self, target): """开始平滑滚动到目标位置""" scroll_bar = self.verticalScrollBar() self.start_value = scroll_bar.value() self.target_value = target self.current_step = 0 self.scroll_steps = 10 # 动画步数 self.scroll_timer.start(20) # 每20ms更新一次 def _smooth_scroll_step(self): """平滑滚动的每一步""" if self.current_step >= self.scroll_steps: self.scroll_timer.stop() return t = self.current_step / self.scroll_steps ease_t = t * t * (3 - 2 * t) # 缓入缓出函数 scroll_bar = self.verticalScrollBar() value = int(self.start_value + (self.target_value - self.start_value) * ease_t) self._block_signal = True scroll_bar.setValue(value) self._block_signal = False self.current_step += 1 def resizeEvent(self, event): """处理窗口大小变化事件,确保布局更新""" super().resizeEvent(event) # 使用单次定时器延迟布局更新,避免频繁计算 QTimer.singleShot(100, self._safe_layout_update) def _safe_layout_update(self): layout = self.widget().layout() if layout: layout.invalidate() self.updateGeometry() class FlowLayout(QLayout): def __init__(self, parent=None): super().__init__(parent) self._items = [] self._hspace = 10 # 水平间距 self._vspace = 10 # 垂直间距 self._is_layouting = False self._layout_cache = None self._last_width = -1 self._update_pending = False # 用于延迟布局更新 def addItem(self, item): self._items.append(item) self._layout_cache = None self.invalidate() self._schedule_layout_update() def _schedule_layout_update(self): """安排延迟布局更新,避免频繁计算""" if not self._update_pending: self._update_pending = True QTimer.singleShot(50, self._safe_layout_update) def _safe_layout_update(self): """安全地更新布局""" try: if self.parent(): self._do_layout(self.parent().rect()) finally: self._update_pending = False def count(self): return len(self._items) def itemAt(self, index): if 0 <= index < len(self._items): return self._items[index] return None def takeAt(self, index): if 0 <= index < len(self._items): self._layout_cache = None self._schedule_layout_update() return self._items.pop(index) return None def setGeometry(self, rect): super().setGeometry(rect) if self._last_width != rect.width(): self._layout_cache = None self._last_width = rect.width() self._do_layout(rect) def _do_layout(self, rect): if self._is_layouting: return self._is_layouting = True try: x = rect.x() y = rect.y() line_height = 0 available_width = rect.width() valid_items = [item for item in self._items if item.widget() and item.widget().isVisible()] layout_cache = [] for i, item in enumerate(valid_items): widget = item.widget() if not widget: continue # 获取内容边距 margins = widget.contentsMargins() total_margin_width = margins.left() + margins.right() total_margin_height = margins.top() + margins.bottom() # 获取组件的实际大小 size_hint = widget.sizeHint() widget_width = size_hint.width() + total_margin_width widget_height = size_hint.height() + total_margin_height # 计算水平间距 if i > 0: x += self._hspace # 判断是否需要换行 if x + widget_width > available_width and x != rect.x(): x = rect.x() y += line_height + self._vspace line_height = 0 # 设置固定大小并放置组件 widget.setFixedSize(widget_width, widget_height) geom = QRect(QPoint(x, y), QSize(widget_width, widget_height)) widget.setGeometry(geom) layout_cache.append((widget, geom)) # 更新当前行的宽度和最大高度 x += widget_width line_height = max(line_height, widget_height) # 保存布局缓存 self._layout_cache = layout_cache # 强制更新布局和渲染 self.update() if self.parent(): self.parent().update() self.parent().repaint() finally: self._is_layouting = False def sizeHint(self): return self.minimumSize() def minimumSize(self): width = 0 height = 0 line_width = 0 line_height = 0 for item in self._items: widget = item.widget() if not widget or not widget.isVisible(): continue # 考虑内容边距 margins = widget.contentsMargins() total_margin_width = margins.left() + margins.right() total_margin_height = margins.top() + margins.bottom() size = widget.sizeHint() widget_width = size.width() + total_margin_width widget_height = size.height() + total_margin_height # 计算水平间距 if line_width > 0: line_width += self._hspace # 判断是否需要换行 if line_width + widget_width > self.parent().width(): width = max(width, line_width) height += line_height + self._vspace line_width = widget_width line_height = widget_height else: line_width += widget_width line_height = max(line_height, widget_height) # 计算最后一行 width = max(width, line_width) height += line_height return QSize(width, height) def horizontalSpacing(self): return self._hspace def verticalSpacing(self): return self._vspace def setSpacing(self, spacing): self._hspace = spacing self._vspace = spacing self._layout_cache = None self.invalidate() def spacing(self): return self.horizontalSpacing() class MainWindow(QMainWindow): currency_added = pyqtSignal(str) def __init__(self): super().__init__() self._global_currencies = ["EURUSD", "GBPUSD", "USDJPY", "XAUUSD"] self._currency_lock = threading.Lock() # 添加锁保护 self._layout_lock = False self._pending_deletions = set() self.cards = [] self.reference_card = None self.threshold = 20 self.setup_ui() self.setWindowTitle("多平台报价监控系统 版本:V1.0 作者:wbb") self.resize(1280, 720) self.setup_monitoring() self._queue_lock = False self._layout_timer = QTimer() self._layout_timer.setSingleShot(True) self._layout_timer.timeout.connect(self._safe_layout_update) self.start_status_monitor() def setup_ui(self): main_widget = QWidget() self.setCentralWidget(main_widget) main_layout = QHBoxLayout(main_widget) # 整体水平布局 main_layout.setContentsMargins(10, 10, 10, 10) # 左侧垂直布局 (控制栏 + 卡片区) left_vertical_layout = QVBoxLayout() # 控制栏 control_bar = QHBoxLayout() self.threshold_input = QLineEdit("20") self.threshold_input.setFixedWidth(80) self.monitor_switch = QCheckBox("实时监控") self.monitor_switch.setChecked(True) control_bar.addWidget(QLabel("异常阈值(点):")) control_bar.addWidget(self.threshold_input) control_bar.addWidget(self.monitor_switch) # 状态栏 self.status_bar = QStatusBar() self.status_bar.setSizeGripEnabled(False) status_widget = QWidget() status_layout = QHBoxLayout(status_widget) status_layout.setContentsMargins(0, 0, 0, 0) self.reference_info_label = QLabel("未设置参照平台") self.reference_info_label.setStyleSheet("color: #666666; padding: 0 10px;") self.reference_info_label.setFixedWidth(320) status_layout.addWidget(self.reference_info_label) self.performance_info_label = QLabel("内存占用: -- MB | 存活卡片: --") self.performance_info_label.setStyleSheet("color: #666666; padding: 0 10px;") self.performance_info_label.setFixedWidth(280) status_layout.addWidget(self.performance_info_label) self.status_bar.addWidget(status_widget) control_bar.addWidget(self.status_bar) left_vertical_layout.addLayout(control_bar) # 左侧卡片区域 left_card_widget = QWidget() left_card_layout = QVBoxLayout(left_card_widget) left_card_layout.setContentsMargins(0, 0, 0, 0) scroll = CardScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setStyleSheet(""" QScrollArea { border: 1px solid #e0e0e0; border-radius: 4px; } """) self.scroll_content = QWidget() self.flow_layout = FlowLayout(self.scroll_content) self.scroll_content.setLayout(self.flow_layout) self.scroll_content.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.MinimumExpanding ) scroll.setWidget(self.scroll_content) left_card_layout.addWidget(scroll) left_vertical_layout.addWidget(left_card_widget, 1) # 设置伸缩因子为1,占据剩余空间 # 右侧垂直布局 (按钮 + 日志区) right_vertical_layout = QVBoxLayout() # 添加按钮水平布局 button_layout = QHBoxLayout() button_layout.setAlignment(Qt.AlignLeft) # 按钮靠左排列 # 添加左侧弹簧 button_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) self.add_card_btn = self.create_tool_button("+ 添加卡片", "#0078D4") self.add_currency_btn = self.create_tool_button("+ 添加品种", "#009966") self.clear_display_btn = self.create_tool_button("- 清除显示", "#D40000") # 调整按钮间距和边距 button_layout.setSpacing(10) button_layout.setContentsMargins(0, 0, 0, 10) # 底部留出间距 button_layout.addWidget(self.add_card_btn) button_layout.addWidget(self.add_currency_btn) button_layout.addWidget(self.clear_display_btn) # 添加右侧弹簧 button_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) right_vertical_layout.addLayout(button_layout) # 右侧日志区域 right_log_widget = QWidget() right_log_layout = QVBoxLayout(right_log_widget) right_log_layout.setContentsMargins(0, 0, 0, 0) # 异常信息区域 abnormal_title_widget = QWidget() abnormal_title_layout = QHBoxLayout(abnormal_title_widget) abnormal_title_layout.setContentsMargins(0, 0, 0, 0) abnormal_label = QLabel("异常平台") abnormal_label.setStyleSheet(""" font-weight: bold; padding: 4px 12px; background-color: #f0f0f0; border-radius: 4px; border-left: 1px dashed #cccccc; border-right: 1px dashed #cccccc; """) abnormal_label.setAlignment(Qt.AlignCenter) abnormal_title_layout.addWidget(abnormal_label) abnormal_title_layout.addStretch(1) right_log_layout.addWidget(abnormal_title_widget) self.abnormal_area = QTextEdit() self.abnormal_area.setReadOnly(True) self.abnormal_area.setStyleSheet(""" QTextEdit { background: #fff0f0; border: 1px solid #e0e0e0; border-radius: 4px; padding: 8px; font-family: Consolas; font-size: 12px; } """) right_log_layout.addWidget(self.abnormal_area, 2) # 异常信息区占2份 # 系统日志区域 log_title_widget = QWidget() log_title_layout = QHBoxLayout(log_title_widget) log_title_layout.setContentsMargins(0, 0, 0, 0) log_label = QLabel("系统日志") log_label.setStyleSheet(""" font-weight: bold; padding: 4px 12px; background-color: #f0f0f0; border-radius: 4px; border-left: 1px dashed #cccccc; border-right: 1px dashed #cccccc; """) log_label.setAlignment(Qt.AlignCenter) log_title_layout.addWidget(log_label) log_title_layout.addStretch(1) right_log_layout.addWidget(log_title_widget) self.log_area = QTextEdit() self.log_area.setReadOnly(True) self.log_area.setStyleSheet(""" QTextEdit { background: #f8f8f8; border: 1px solid #e0e0e0; border-radius: 4px; padding: 8px; font-family: Consolas, monospace; font-size: 12px; color: #333333; line-height: 1.4; } """) right_log_layout.addWidget(self.log_area, 1) # 系统日志区占1份 right_vertical_layout.addWidget(right_log_widget, 1) # 设置伸缩因子为1,占据剩余空间 # 将左右两个垂直布局添加到整体水平布局 main_layout.addLayout(left_vertical_layout, 2) # 左侧占2份 main_layout.addLayout(right_vertical_layout, 1) # 右侧占1份 # 连接信号 self.add_card_btn.clicked.connect(self.add_card) self.add_currency_btn.clicked.connect(self.show_add_currency_dialog) self.threshold_input.textChanged.connect(self.update_threshold) self.currency_added.connect(self.add_global_currency) self.clear_display_btn.clicked.connect(self.clear_logs) def clear_logs(self): """清空异常平台和系统日志的显示""" self.abnormal_area.clear() self.log_area.clear() def create_tool_button(self, text, color): btn = QPushButton(text) btn.setStyleSheet(f""" QPushButton {{ background: {color}; color: white; border-radius: 4px; padding: 8px 12px; min-width: 100px; }} QPushButton:pressed {{ color: #D0D0D0; background: {QColor(color).darker(150).name()}; }} """) return btn def setup_monitoring(self): self.check_timer = QTimer() self.check_timer.timeout.connect(self.check_all_platforms) self.check_timer.start(1000) def check_perf(self): try: # 获取内存占用 process = psutil.Process() mem = process.memory_info().rss / 1024 / 1024 # 计算存活卡片数量 valid_cards = len([ref for ref in self.cards if ref() is not None]) # 更新状态栏性能信息 self.performance_info_label.setText(f"内存占用: {mem:.2f} MB | 存活卡片: {valid_cards}") except Exception as e: print(f"更新性能信息错误: {e}") # 出错时保持原显示 valid_cards = len([ref for ref in self.cards if ref() is not None]) self.performance_info_label.setText(f"内存占用: -- MB | 存活卡片: {valid_cards}") def update_threshold(self): try: self.threshold = int(self.threshold_input.text()) self.log(f"异常阈值更新为: {self.threshold}点") except: self.threshold = 20 self.threshold_input.setText("20") def handle_reference(self, symbol, bid, ask): # 清除旧参考样式 if self.reference_card and self.reference_card() and self.reference_card().is_reference: self.reference_card().ref_btn.setChecked(False) self.reference_card().clear_reference_style() # 设置新参考卡片 if symbol: current_card = self.sender() if current_card: current_card.ref_btn.setChecked(True) current_card.set_style(current_card.ref_style) current_card.is_reference = True self.reference_card = weakref.ref(current_card) # 更新参考平台信息 timestamp = datetime.now().strftime("%H:%M:%S") self.reference_info_label.setText(f"参照平台: {symbol} {bid:.5f}/{ask:.5f} [{timestamp}]") self.log(f"设置参考平台: {symbol}") else: self.reference_card = None self.reference_info_label.setText("未设置参照平台") self.log("取消参考平台设置") def add_card(self): """添加新的价格卡片""" card = PriceCard(currency_list=self._global_currencies) card.delete_requested.connect(self.remove_card) card.set_as_reference.connect(self.handle_reference) card.status_updated.connect(self.update_status) card.symbol_changed.connect(self.update_currencies) self.flow_layout.addWidget(card) self.cards.append(weakref.ref(card)) # 延迟更新布局,避免频繁计算 self._schedule_layout_update() # 记录日志 self.log(f"添加新卡片,当前总数: {len(self.cards)}") def remove_card(self, card_ref): """安全移除卡片""" card = card_ref() if card: # 从布局中移除 if self.flow_layout.indexOf(card) >= 0: self.flow_layout.removeWidget(card) card.hide() # 如果是参考卡片,清除参考状态 if self.reference_card and self.reference_card() == card: self.handle_reference("", 0, 0) # 延迟删除,确保布局更新完成 QTimer.singleShot(500, card.deleteLater) # 从卡片列表中移除 self.cards = [ref for ref in self.cards if ref() is not None] # 记录日志 self.log(f"移除卡片,当前总数: {len(self.cards)}") # 延迟更新布局 self._schedule_layout_update() def show_add_currency_dialog(self): """显示添加交易品种对话框""" dialog = QDialog(self) dialog.setWindowTitle("添加交易品种") dialog.setFixedSize(300, 150) layout = QVBoxLayout(dialog) input_label = QLabel("请输入新的交易品种:") input_field = QLineEdit() input_field.setPlaceholderText("例如: EURUSD") button_layout = QHBoxLayout() ok_button = QPushButton("确定") cancel_button = QPushButton("取消") button_layout.addWidget(ok_button) button_layout.addWidget(cancel_button) layout.addWidget(input_label) layout.addWidget(input_field) layout.addLayout(button_layout) ok_button.clicked.connect(lambda: self._handle_add_currency(input_field.text(), dialog)) cancel_button.clicked.connect(dialog.reject) dialog.exec_() def _handle_add_currency(self, currency, dialog): """处理添加交易品种""" if currency.strip(): currency = currency.strip().upper() if currency not in self._global_currencies: self.currency_added.emit(currency) self.log(f"添加新交易品种: {currency}") else: self.log(f"交易品种 '{currency}' 已存在") dialog.accept() def add_global_currency(self, currency): """添加全局交易品种""" with self._currency_lock: if currency not in self._global_currencies: self._global_currencies.append(currency) # 更新所有卡片的品种列表 for ref in self.cards: card = ref() if card: card.update_currencies(currency) def update_currencies(self, symbol): """更新全局交易品种列表""" if symbol and symbol not in self._global_currencies: with self._currency_lock: self._global_currencies.append(symbol) # 更新其他卡片的品种列表 for ref in self.cards: card = ref() if card and card.symbol_combo.currentText() != symbol: card.update_currencies(symbol) def check_all_platforms(self): """检查所有平台价格差异""" if not self.monitor_switch.isChecked() or not self.reference_card or not self.reference_card(): return ref_card = self.reference_card() ref_symbol = ref_card.symbol_combo.currentText() ref_bid = ref_card.current_bid ref_ask = ref_card.current_ask if ref_bid <= 0 or ref_ask <= 0: return self.abnormal_area.clear() abnormal_count = 0 for ref in self.cards: card = ref() if card and card != ref_card and card.isValid() and card._has_valid_prices: # 确保检查相同的交易品种 if card.symbol_combo.currentText() == ref_symbol: card_bid = card.current_bid card_ask = card.current_ask # 计算点差差异 (假设5位小数的品种) bid_diff = (card_bid - ref_bid) * 10000 ask_diff = (card_ask - ref_ask) * 10000 # 判断是否异常 is_abnormal = abs(bid_diff) > self.threshold or abs(ask_diff) > self.threshold card.update_abnormal_status(is_abnormal) if is_abnormal: abnormal_count += 1 platform = card.platform_input.text().split("·")[1].strip() self.abnormal_area.append( f"平台: {platform}\n" f"参考价格: {ref_bid:.5f} / {ref_ask:.5f}\n" f"当前价格: {card_bid:.5f} / {card_ask:.5f}\n" f"差异: {bid_diff:+.2f} / {ask_diff:+.2f} 点\n" f"{'=' * 30}\n" ) # 更新状态栏 if abnormal_count > 0: self.statusBar().showMessage(f"发现 {abnormal_count} 个异常平台", 3000) def log(self, message): """记录系统日志""" timestamp = datetime.now().strftime("%H:%M:%S") log_entry = f"[{timestamp}] {message}\n" self.log_area.insertPlainText(log_entry) # 自动滚动到底部 self.log_area.moveCursor(self.log_area.textCursor().End) def update_status(self, message): """更新状态栏消息""" self.statusBar().showMessage(message, 3000) def _schedule_layout_update(self): """安排布局更新,避免频繁计算""" if not self._layout_timer.isActive(): self._layout_timer.start(100) def _safe_layout_update(self): """安全地更新布局""" if self.flow_layout: self.flow_layout.invalidate() self.flow_layout._layout_cache = None # 清除缓存 self.scroll_content.adjustSize() self.flow_layout._do_layout(self.scroll_content.rect()) def start_status_monitor(self): """启动状态监控定时器""" self.performance_timer = QTimer() self.performance_timer.timeout.connect(self.check_perf) self.performance_timer.start(2000) # 每2秒更新一次 def closeEvent(self, event): """关闭应用程序时的清理工作""" # 停止所有定时器 if hasattr(self, 'check_timer') and self.check_timer.isActive(): self.check_timer.stop() if hasattr(self, 'performance_timer') and self.performance_timer.isActive(): self.performance_timer.stop() if hasattr(self, '_layout_timer') and self._layout_timer.isActive(): self._layout_timer.stop() # 停止所有MT5工作进程 for ref in self.cards: card = ref() if card and card.mt5_worker: card.mt5_worker.stop() # 确认所有进程已停止 time.sleep(0.5) event.accept() if __name__ == "__main__": freeze_support() # 支持多进程打包 app = QApplication(sys.argv) # 设置应用程序样式 app.setStyle("Fusion") # 设置全局字体 font = QFont("微软雅黑", 9) app.setFont(font) # 设置全局样式 app.setStyleSheet(""" QMainWindow, QWidget { background-color: #f8f9fa; } QLabel { color: #333333; } QPushButton { border-radius: 4px; padding: 6px 12px; background-color: #007bff; color: white; border: none; } QPushButton:hover { background-color: #0069d9; } QPushButton:pressed { background-color: #0056b3; } QScrollBar:vertical { width: 12px; background: #f0f0f0; } QScrollBar::handle:vertical { background: #c0c0c0; border-radius: 6px; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } """) window = MainWindow() window.show() sys.exit(app.exec_()) 添加卡片后选择mt5路径后,mt5连接成功并开始报价更新,但是platform_input中显示没有更新,还是显示正在连接
05-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值