event.returnValue=false和return false的用处

本文记录了一个在360兼容模式和IE10下,页面跳转异常的问题及其解决方案。通过将returnfalse更改为event.returnValue=false,避免了在输入无效页码时页面跳转的错误行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试页面的时候,发现一个bug,在360的兼容模式和Ie10(Ie7、8、9没试)下,return false 之后的链接跳转会继续执行,查了资料,将其改为event.returnValue=false就不会了,特此记录

代码如下:

function btnChangePage() {
    var numPage = $('#custompage');
    var pageTotal = numPage.attr('data-pagetotal');
    var thisVal = numPage.val();
    var caseurl = '';
    var re = /^[0-9]+.?[0-9]*$/;
    if(!re.test(thisVal) || (parseInt(thisVal) < 1) || (parseInt(thisVal) > pageTotal)) {
        alert('请输入有效的页码!');
        return false;
        //window.event.returnValue = false;
    }
    var url = numPage.attr('data-url_format');

    window.location.href = url+"&page_name="+thisVal;

}

当在上述所说的浏览器中输入小于1或者大于分页总数的页数, window.location.href = url+”&page_name=”+thisVal;会执行,然后页面跳转。
下面是改正的解释,说的不对的地方,希望予以改正。

  1. event.returnValue的作用就是:当捕捉到事件(event)时,判断为false,则阻止当前事件继续运行,window.event.returnValue = false;之后的语句将都不会执行。
  2. return false 不是阻止事件继续向顶层元素传播,而是阻止浏览器对事件的默认处理。

解释的不明白的,找找其他资料?

using System.Collections; using System.Collections.Generic; using DataModel; using DG.Tweening; using TMPro; using UnityEngine; using UnityEngine.PlayerLoop; using UnityEngine.UI; /// <summary> /// 照明 /// </summary> public class LightingTipClick : TipsClickCommon { //照明的位置 [SerializeField] private Text Name; //开关图表 [SerializeField] private Image Switch; //开关文本 [SerializeField] private TextMeshProUGUI SwitchText; //关闭图标 [SerializeField] private Sprite LightingTipSpriteClose; //打开图标 [SerializeField] private Sprite LightingTipSpriteOpen; [SerializeField] private Button send_btn; [SerializeField] private TMP_Dropdown tmp_Dropdown; private void Start() { Hide(); this.transform.localScale = Vector3.zero; this.transform.name = Name.text; tmp_Dropdown.GetComponentInChildren<TMP_Dropdown>(); } private void Update() { if (tmp_Dropdown.value == 0) { send_btn.interactable = false; } else { send_btn.interactable = true; } } EquipmentProxy equipmentProxy = new EquipmentProxy(); ControlEquipmentProxy controlEquipmentProxy = new ControlEquipmentProxy(); MessageShow messageShow = new MessageShow(); public override void Show(Object3dElement obj) { base.Show(obj); this.transform.GetChild(1).DOScaleX(1, 0.5f); if (this.transform.parent != null) { this.transform.parent.SetAsLastSibling(); } else { this.transform.SetAsLastSibling(); } Name.text = obj.objectName; SwitchText.text = "-"; equipmentProxy.GetRealTimeData(obj.id, (data, a) => { foreach (string key in data.Keys) { if (key.ToUpper().Equals("DGZT")) { string _json = Utils.CollectionsConvert.ToJSON(data[key]);
03-29
请帮我修改这段代码 当电压检测值2出现连续两次及其以上的数据低于1.9V时,判定为中频放大器故障。当电压检测值2出现数据高于1.95V时判定为中频放大器正常 当电压检测值3出现数据高于1.72V时,判定为低频放大器故障。当电压检测值3出现数据低于1.6V时,判定为低频放大器正常。当三个电压检测值同时低于1.8时,判定为电源故障,反之为电源正常。如果没故障时界面分别显示绿色的中频放大器正常低频放大器正常电源正常且保持整齐摆放。如果发生故障,蜂鸣器响一声提示故障具体的故障点文字变化为红色。操作界面不用显示阈值设置相关信息的文本。我的代码为% voltageDataProcessor_complete.m - 完整的电压数据实时处理与显示程序 function voltageDataProcessor_complete() % 选择输入文件 [fileName, filePath] = uigetfile('*.dat', '选择DAT文件'); if fileName == 0 fprintf('未选择文件,程序退出。\n'); return; end % 完整文件路径 fullPath = fullfile(filePath, fileName); % 创建图形窗口 fig = figure('Name', '电压数据实时处理与显示', ... 'Position', [100, 100, 1000, 700], ... % 调整窗口大小以适应所有元素 'CloseRequestFcn', @closeFigureCallback, ... 'Color', [0.95, 0.95, 0.95]); % 创建3个子图(垂直排列) ax(1) = subplot(3, 1, 1); p1 = plot(ax(1), NaN, NaN, 'b'); title('电压检测值1'); xlabel('采样点'); ylabel('电压(V)'); grid on; ax(2) = subplot(3, 1, 2); p2 = plot(ax(2), NaN, NaN, 'g'); title('电压检测值2 (中频放大器)'); xlabel('采样点'); ylabel('电压(V)'); grid on; ax(3) = subplot(3, 1, 3); p3 = plot(ax(3), NaN, NaN, 'r'); title('电压检测值3 (低频放大器)'); xlabel('采样点'); ylabel('电压(V)'); grid on; % 初始化数据存储变量 voltage1 = []; voltage2 = []; voltage3 = []; % 文件读取状态 fileID = -1; lastPos = 0; % 滤波参数设置 filterOrder = 4; % 滤波器阶数 cutoffFreq = 0.1; % 截止频率(归一化) % 创建低通滤波器 [b, a] = butter(filterOrder, cutoffFreq, 'low'); % 创建状态面板 statusPanel = uipanel(fig, 'Title', '系统状态', ... 'Position', [0.02, 0.02, 0.96, 0.15], ... 'BackgroundColor', [0.95, 0.95, 0.95]); % 状态文本 statusText = uicontrol(statusPanel, 'Style', 'text', ... 'Position', [20, 80, 600, 20], ... 'String', '准备开始...', ... 'FontSize', 10, ... 'BackgroundColor', [0.95, 0.95, 0.95], ... 'HorizontalAlignment', 'left'); % 频率文本 freqText = uicontrol(statusPanel, 'Style', 'text', ... 'Position', [20, 50, 300, 20], ... 'String', '频率: 计算中...', ... 'FontSize', 10, ... 'BackgroundColor', [0.95, 0.95, 0.95], ... 'HorizontalAlignment', 'left'); % 故障显示文本 midFaultText = uicontrol(statusPanel, 'Style', 'text', ... 'Position', [350, 50, 200, 20], ... 'String', '中频放大器: 正常', ... 'FontSize', 10, ... 'FontWeight', 'bold', ... 'BackgroundColor', [0.95, 0.95, 0.95], ... 'ForegroundColor', [0, 0.6, 0], ... % 绿色字体表示正常 'HorizontalAlignment', 'left'); lowFaultText = uicontrol(statusPanel, 'Style', 'text', ... 'Position', [350, 20, 200, 20], ... 'String', '低频放大器: 正常', ... 'FontSize', 10, ... 'FontWeight', 'bold', ... 'BackgroundColor', [0.95, 0.95, 0.95], ... 'ForegroundColor', [0, 0.6, 0], ... % 绿色字体表示正常 'HorizontalAlignment', 'left'); % 阈值显示文本 thresholdText = uicontrol(statusPanel, 'Style', 'text', ... 'Position', [600, 50, 300, 40], ... 'String', sprintf('阈值设置:\n中频故障: <1.95V 正常: >2.0V\n低频故障: >1.65V 正常: <1.6V'), ... 'FontSize', 9, ... 'BackgroundColor', [0.95, 0.95, 0.95], ... 'HorizontalAlignment', 'left'); % 停止按钮 stopBtn = uicontrol(statusPanel, 'Style', 'pushbutton', ... 'Position', [600, 20, 100, 30], ... 'String', '停止监控', ... 'Callback', @stopMonitoring, ... 'FontSize', 10, ... 'BackgroundColor', [0.9, 0.4, 0.4]); % 存储应用数据 appData = struct(... 'voltage1', voltage1, ... 'voltage2', voltage2, ... 'voltage3', voltage3, ... 'fileID', -1, ... 'lastPos', 0, ... 'fullPath', fullPath, ... 'running', true, ... 'p1', p1, ... 'p2', p2, ... 'p3', p3, ... 'ax', ax, ... 'statusText', statusText, ... 'freqText', freqText, ... 'midFaultText', midFaultText, ... 'lowFaultText', lowFaultText, ... 'b', b, ... 'a', a, ... 'maxDataPoints', 10000, ... 'maxDisplayPoints', 1000, ... 'midFaultStatus', false, ... % 中频放大器故障状态 'lowFaultStatus', false, ... % 低频放大器故障状态 'previousMidStatus', false, ... % 上一次中频放大器状态 'previousLowStatus', false ... % 上一次低频放大器状态 ); setappdata(fig, 'appData', appData); % 打开文件 try fileID = fopen(fullPath, 'r'); if fileID == -1 error('无法打开文件: %s', fullPath); end % 更新应用数据 appData.fileID = fileID; setappdata(fig, 'appData', appData); set(statusText, 'String', '文件已打开,开始监控...'); catch ME set(statusText, 'String', ['错误: ' ME.message]); return; end % 启动定时器 timerObj = timer(... 'ExecutionMode', 'fixedRate', ... 'Period', 0.5, ... % 更新间隔0.5秒 'TimerFcn', {@updatePlots, fig}, ... 'ErrorFcn', @timerErrorCallback, ... 'BusyMode', 'drop' ... ); start(timerObj); % 存储定时器对象 appData.timerObj = timerObj; setappdata(fig, 'appData', appData); % 关闭回调函数 function closeFigureCallback(~, ~) appData = getappdata(fig, 'appData'); if isfield(appData, 'timerObj') && isvalid(appData.timerObj) stop(appData.timerObj); delete(appData.timerObj); end if isfield(appData, 'fileID') && appData.fileID > 2 fclose(appData.fileID); end delete(fig); end % 停止监控回调 function stopMonitoring(~, ~) appData = getappdata(fig, 'appData'); appData.running = false; setappdata(fig, 'appData', appData); set(statusText, 'String', '监控已停止'); if isfield(appData, 'timerObj') && isvalid(appData.timerObj) stop(appData.timerObj); end end % 定时器错误回调 function timerErrorCallback(~, event) set(appData.statusText, 'String', ['定时器错误: ' event.Data.message]); end end % 更新图形函数 function updatePlots(~, ~, figHandle) if ~ishandle(figHandle) return; end appData = getappdata(figHandle, 'appData'); if ~appData.running return; end try % 获取当前文件大小 fseek(appData.fileID, 0, 'eof'); currentFileSize = ftell(appData.fileID); % 检查文件是否有新内容 if currentFileSize > appData.lastPos % 移动到上次结束位置 fseek(appData.fileID, appData.lastPos, 'bof'); % 读取新数据 newData = textscan(appData.fileID, '%s', 'Delimiter', '\n'); newLines = newData{1}; % 处理新数据 newVoltage1 = []; newVoltage2 = []; newVoltage3 = []; for i = 1:length(newLines) line = strtrim(newLines{i}); % 跳过空行 if isempty(line) continue; end % 提取数字 [num, success] = extractNumber(line); if success % 根据行号分配到对应的电压数组 switch mod(i-1, 3) + 1 case 1 newVoltage1 = [newVoltage1; num]; case 2 newVoltage2 = [newVoltage2; num]; case 3 newVoltage3 = [newVoltage3; num]; end end end % 更新数据 if ~isempty(newVoltage1) appData.voltage1 = [appData.voltage1; newVoltage1]; appData.voltage2 = [appData.voltage2; newVoltage2]; appData.voltage3 = [appData.voltage3; newVoltage3]; % 限制数据缓冲区大小 if length(appData.voltage1) > appData.maxDataPoints keepPoints = appData.maxDataPoints; appData.voltage1 = appData.voltage1(end-keepPoints+1:end); appData.voltage2 = appData.voltage2(end-keepPoints+1:end); appData.voltage3 = appData.voltage3(end-keepPoints+1:end); end % 更新图形 updatePlot(appData.p1, appData.voltage1, appData.maxDisplayPoints); updatePlot(appData.p2, appData.voltage2, appData.maxDisplayPoints); updatePlot(appData.p3, appData.voltage3, appData.maxDisplayPoints); % 计算并显示通道3的频率 if length(appData.voltage3) > 100 fs = 100; % 假设采样频率为100Hz frequency = calculateFrequency(appData.voltage3, fs); set(appData.freqText, 'String', sprintf('频率: %.2f Hz', frequency)); title(appData.ax(3), sprintf('电压检测值3 (主要频率: %.2f Hz)', frequency)); end % 应用滤波并更新滤波线 if length(appData.voltage1) >= 10 updateFilteredLine(appData.ax(1), appData.voltage1, appData.b, appData.a, appData.maxDisplayPoints); updateFilteredLine(appData.ax(2), appData.voltage2, appData.b, appData.a, appData.maxDisplayPoints); updateFilteredLine(appData.ax(3), appData.voltage3, appData.b, appData.a, appData.maxDisplayPoints); end % ====== 故障检测逻辑 ====== % 保存之前的状态 appData.previousMidStatus = appData.midFaultStatus; appData.previousLowStatus = appData.lowFaultStatus; % 检测中频放大器状态(电压检测值2) if ~isempty(appData.voltage2) lastValue2 = appData.voltage2(end); if lastValue2 < 1.95 % 故障条件 % 设置故障状态显示 set(appData.midFaultText, 'String', '中频放大器: 故障', 'ForegroundColor', [1, 0, 0]); appData.midFaultStatus = true; elseif lastValue2 > 2.0 % 正常条件 set(appData.midFaultText, 'String', '中频放大器: 正常', 'ForegroundColor', [0, 0.6, 0]); appData.midFaultStatus = false; end % 注意:1.95V到2.0V之间不改变状态 end % 检测低频放大器状态(电压检测值3),但受中频放大器状态影响 if ~isempty(appData.voltage3) lastValue3 = appData.voltage3(end); if appData.midFaultStatus % 中频放大器故障,强制低频放大器为正常 set(appData.lowFaultText, 'String', '低频放大器: 正常', 'ForegroundColor', [0, 0.6, 0]); appData.lowFaultStatus = false; else % 中频放大器正常,按自身规则判断 if lastValue3 > 1.65 set(appData.lowFaultText, 'String', '低频放大器: 故障', 'ForegroundColor', [1, 0, 0]); appData.lowFaultStatus = true; elseif lastValue3 < 1.6 set(appData.lowFaultText, 'String', '低频放大器: 正常', 'ForegroundColor', [0, 0.6, 0]); appData.lowFaultStatus = false; end % 注意:1.6V到1.65V之间不改变状态 end end % 检查状态变化,触发报警 if ~appData.previousMidStatus && appData.midFaultStatus % 中频放大器从正常变为故障 playBeep(); set(appData.statusText, 'String', '检测到中频放大器故障!'); end if ~appData.previousLowStatus && appData.lowFaultStatus % 低频放大器从正常变为故障 playBeep(); set(appData.statusText, 'String', '检测到低频放大器故障!'); end % 更新全局状态文本(如果没有故障,显示正常) if ~appData.midFaultStatus && ~appData.lowFaultStatus set(appData.statusText, 'String', '系统运行正常'); end % ====== 故障检测结束 ====== % 更新文件位置 appData.lastPos = currentFileSize; setappdata(figHandle, 'appData', appData); end end % 刷新图形 drawnow limitrate; catch ME set(appData.statusText, 'String', ['错误: ' ME.message]); appData.running = false; setappdata(figHandle, 'appData', appData); end end % 蜂鸣声函数 function playBeep() % 创建一个短暂的音频信号 fs = 8192; % 采样率 t = 0:1/fs:0.2; % 0.2秒 f = 1000; % 音调频率 beepSignal = sin(2*pi*f*t); % 播放声音(使用soundsc自动调整幅度) soundsc(beepSignal, fs); end % 数字提取函数 function [num, success] = extractNumber(line) % 尝试多种方法提取数字 success = false; num = NaN; % 方法1: 使用正则表达式提取第一个数字 tokens = regexp(line, '[-+]?\d*\.?\d+', 'match'); if ~isempty(tokens) num = str2double(tokens{1}); if ~isnan(num) success = true; return; end end % 方法2: 使用sscanf提取 [num, count] = sscanf(line, '%f'); if count >= 1 num = num(1); success = true; return; end % 方法3: 尝试去掉非数字字符 cleanLine = regexprep(line, '[^0-9\.\-+]', ''); if ~isempty(cleanLine) num = str2double(cleanLine); if ~isnan(num) success = true; end end end % 更新绘图函数 function updatePlot(plotHandle, data, maxDisplayPoints) if isempty(data) return; end totalPoints = length(data); % 确定显示的起始点结束点 if totalPoints > maxDisplayPoints startIdx = totalPoints - maxDisplayPoints + 1; endIdx = totalPoints; dataToShow = data(startIdx:end); else startIdx = 1; endIdx = totalPoints; dataToShow = data; end % 更新图形数据 set(plotHandle, 'XData', startIdx:endIdx, 'YData', dataToShow); % 更新坐标轴范围 ax = get(plotHandle, 'Parent'); xlim(ax, [startIdx, endIdx]); % 动态X轴范围 % 自动调整Y轴范围 minY = min(dataToShow); maxY = max(dataToShow); range = max(0.1, maxY - minY); ylim(ax, [minY - 0.1*range, maxY + 0.1*range]); end % 更新滤波线函数 function updateFilteredLine(ax, data, b, a, maxDisplayPoints) % 检查是否已存在滤波线 lines = findobj(ax, 'Type', 'line'); filteredLine = []; % 查找滤波线(红色虚线) for i = 1:length(lines) if strcmp(get(lines(i), 'LineStyle'), '--') && ... isequal(get(lines(i), 'Color'), [1, 0, 0]) filteredLine = lines(i); break; end end % 应用滤波 if length(data) > 10 filteredData = filtfilt(b, a, data); else filteredData = data; end totalPoints = length(data); % 确定显示的起始点结束点 if totalPoints > maxDisplayPoints startIdx = totalPoints - maxDisplayPoints + 1; endIdx = totalPoints; dataToShow = filteredData(startIdx:end); else startIdx = 1; endIdx = totalPoints; dataToShow = filteredData; end if isempty(filteredLine) % 创建新的滤波线 hold(ax, 'on'); plot(ax, startIdx:endIdx, dataToShow, 'r--', 'LineWidth', 1.5); hold(ax, 'off'); % 添加图例 if isempty(legend(ax)) legend(ax, {'原始数据', '滤波后数据'}, 'Location', 'best'); end else % 更新现有滤波线 set(filteredLine, 'XData', startIdx:endIdx, 'YData', dataToShow); end end % 频率计算函数 function freq = calculateFrequency(data, fs) % 确保数据长度足够 if length(data) < 100 freq = 0; return; end % 去直流分量 data = data - mean(data); % 使用自相关法检测周期 [acf, lags] = xcorr(data, 'coeff'); acf = acf(lags >= 0); lags = lags(lags >= 0); % 寻找主峰位置(跳过第一个点) [peaks, locs] = findpeaks(acf(2:end)); if isempty(peaks) freq = 0; return; end % 找到最高峰 [~, idx] = max(peaks); peakIdx = locs(idx) + 1; % 补偿跳过的第一个点 % 计算周期频率 period = lags(peakIdx) / fs; freq = 1 / period; % 验证频率在合理范围内 if freq > fs/2 || freq < 0.1 % 如果自相关法失败,使用FFT作为备选 n = length(data); fftResult = fft(data); P2 = abs(fftResult/n); P1 = P2(1:floor(n/2)+1); P1(2:end-1) = 2*P1(2:end-1); f = fs*(0:(n/2))/n; [~, idx] = max(P1(2:end)); % 跳过DC分量 freq = f(idx+1); end end
07-02
将以下代码做成PDF教案,详述每段代码的作用,包括整个程序的架构 import tkinter as tk from tkinter import filedialog, messagebox from tkinter import ttk import pandas as pd import json import os import sys class DataAnalysisApp: def __init__(self, root): self.root = root self.root.title("数据分析助手") # 配置文件路径 self.config_file = self.get_resource_path("app_config.json") # 加载配置 self.config = self.load_config() # 设置窗口大小位置 window_width = self.config.get("window_width", 1000) window_height = self.config.get("window_height", 600) screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int(screen_width/2 - window_width/2) center_y = int(screen_height/2 - window_height/2) self.root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}') # 设置窗口最小尺寸 self.root.minsize(800, 400) # 创建菜单栏 self.menu_bar = tk.Menu(self.root) # 文件菜单 self.file_menu = tk.Menu(self.menu_bar, tearoff=0) self.file_menu.add_command(label="打开", command=self.open_file) self.file_menu.add_separator() self.file_menu.add_command(label="退出", command=self.root.quit) self.menu_bar.add_cascade(label="文件", menu=self.file_menu) # 添加公式菜单 self.formula_menu = tk.Menu(self.menu_bar, tearoff=0) self.formula_menu.add_command(label="自定义公式", command=self.open_formula_window) self.menu_bar.add_cascade(label="公式", menu=self.formula_menu) self.root.config(menu=self.menu_bar) # 创建主框架 self.main_frame = ttk.Frame(self.root) self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 创建可拖拽的分隔窗口 self.paned_window = ttk.PanedWindow(self.main_frame, orient=tk.HORIZONTAL) self.paned_window.pack(fill=tk.BOTH, expand=True) # 创建左侧数据显示区域 self.left_frame = ttk.Frame(self.paned_window) # 创建右侧控制面板 self.right_frame = ttk.Frame(self.paned_window) # 添加框架到分隔窗口 self.paned_window.add(self.left_frame, weight=1) self.paned_window.add(self.right_frame, weight=0) # 设置分隔位置 if "paned_position" in self.config: self.paned_window.after(100, lambda: self.paned_window.sashpos(0, self.config["paned_position"])) # 创建算法选择区域 self.algorithm_frame = ttk.LabelFrame(self.right_frame, text="算法选择", padding=10) self.algorithm_frame.pack(fill=tk.X, pady=(0, 10)) # 添加算法选择下拉框 self.algorithm_var = tk.StringVar() self.algorithms = [ "描述性统计", "相关性分析", "数据分布分析", "时间序列分析", "分组统计分析", "缺失值分析", "CPK分析" ] self.algorithm_combo = ttk.Combobox( self.algorithm_frame, textvariable=self.algorithm_var, values=self.algorithms, state="readonly" ) self.algorithm_combo.pack(fill=tk.X, pady=(5, 0)) self.algorithm_combo.set("请选择分析方法") # 添加运行按钮 self.run_button = ttk.Button( self.algorithm_frame, text="运行分析", command=self.run_analysis ) self.run_button.pack(fill=tk.X, pady=(10, 0)) # 创建结果显示区域 self.result_frame = ttk.LabelFrame(self.right_frame, text="分析结果", padding=10) self.result_frame.pack(fill=tk.BOTH, expand=True) # 添加结果文本框 self.result_text = tk.Text( self.result_frame, wrap=tk.WORD, width=30, height=20, font=('Arial', 10) # 设置字体 ) # 为结果文本框添加滚动条 self.result_scrollbar = ttk.Scrollbar( self.result_frame, orient="vertical", command=self.result_text.yview ) # 正确放置滚动条文本框 self.result_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.result_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 配置文本框的滚动 self.result_text.configure(yscrollcommand=self.result_scrollbar.set) # 配置文本标签样式 self.result_text.tag_configure('header', font=('Arial', 11, 'bold')) self.result_text.tag_configure('subtitle', font=('Arial', 10, 'bold')) self.result_text.tag_configure('warning', foreground='orange') self.result_text.tag_configure('error', foreground='red') # 设置为只读 self.result_text.config(state='disabled') # 创建框架来容纳Treeview滚动条 self.tree_frame = ttk.Frame(self.left_frame) self.tree_frame.pack(fill=tk.BOTH, expand=True) # 创建并配置Treeview样式 style = ttk.Style() style.configure("Treeview", rowheight=22, # 稍微减小行高 font=('Arial', 9), # 更改字体大小 background="#FFFFFF", fieldbackground="#FFFFFF", foreground="#000000", borderwidth=1, relief='solid' ) # 配置标题样式,更接近Excel style.configure("Treeview.Heading", font=('Arial', 9, 'bold'), relief='flat', borderwidth=1, background='#F0F0F0', # Excel风格的标题背景色 foreground='#000000' ) # 设置选中颜色为Excel风格的蓝色 style.map('Treeview', background=[('selected', '#E1E9F5')], # Excel选中的浅蓝色 foreground=[('selected', '#000000')] # 选中时保持黑色文字 ) # 设置Treeview网格线颜色 style.configure("Treeview", background="white", fieldbackground="white", foreground="black", bordercolor="#DDD", # 网格线颜色 lightcolor="#DDD", # 亮边框颜色 darkcolor="#DDD" # 暗边框颜色 ) # 创建Treeview控件用于显示数据 self.tree = ttk.Treeview(self.tree_frame) # 创建垂直滚动条 self.vsb = ttk.Scrollbar(self.tree_frame, orient="vertical", command=self.tree.yview) self.vsb.pack(side=tk.RIGHT, fill=tk.Y) # 创建水平滚动条 self.hsb = ttk.Scrollbar(self.tree_frame, orient="horizontal", command=self.tree.xview) self.hsb.pack(side=tk.BOTTOM, fill=tk.X) # 设置Treeview的滚动 self.tree.configure(yscrollcommand=self.vsb.set, xscrollcommand=self.hsb.set) # 放置Treeview self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 显示行标题 self.tree["show"] = "headings" # 创建状态栏 self.status_bar = ttk.Label(self.root, text="就绪", anchor=tk.W) self.status_bar.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=3) # 添加数据存储变量 self.current_data = None def run_analysis(self): if self.current_data is None: messagebox.showwarning("警告", "请先加载数据") return selected_algorithm = self.algorithm_var.get() if selected_algorithm == "请选择分析方法": messagebox.showwarning("警告", "请选择分析方法") return try: # 创建不包含前两列的数据副本 analysis_data = self.current_data.iloc[:, 2:].copy() if analysis_data.empty: messagebox.showwarning("警告", "没有可分析的数据列") return self.result_text.config(state='normal') self.result_text.delete(1.0, tk.END) if selected_algorithm == "CPK分析": self._run_cpk_analysis(analysis_data) elif selected_algorithm == "描述性统计": self._run_descriptive_analysis(analysis_data) elif selected_algorithm == "相关性分析": self._run_correlation_analysis(analysis_data) elif selected_algorithm == "数据分布分析": self._run_distribution_analysis(analysis_data) elif selected_algorithm == "时间序列分析": self._run_time_series_analysis(analysis_data) elif selected_algorithm == "分组统计分析": self._run_group_analysis(analysis_data) elif selected_algorithm == "缺失值分析": self._run_missing_value_analysis(analysis_data) self.result_text.config(state='disabled') except Exception as e: self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, f"⚠ 分析过程出错:\n{str(e)}", 'error') self.result_text.config(state='disabled') def _run_descriptive_analysis(self, data): """描述性统计""" numeric_cols = data.select_dtypes(include=['int64', 'float64']).columns non_numeric_cols = data.select_dtypes(exclude=['int64', 'float64']).columns # 处理数值列 if not numeric_cols.empty: numeric_stats = data[numeric_cols].describe() self.result_text.insert(tk.END, "═══ 数值型数据统计 ═══\n\n", 'header') # 格式化数值统计结果 for col in numeric_cols: stats = numeric_stats[col] self.result_text.insert(tk.END, f"▶ {col}\n", 'subtitle') self.result_text.insert(tk.END, f" • 数量: {stats['count']:.0f}\n") self.result_text.insert(tk.END, f" • 均值: {stats['mean']:.2f}\n") self.result_text.insert(tk.END, f" • 标准差: {stats['std']:.2f}\n") self.result_text.insert(tk.END, f" • 最小值: {stats['min']:.2f}\n") self.result_text.insert(tk.END, f" • 25%分位: {stats['25%']:.2f}\n") self.result_text.insert(tk.END, f" • 中位数: {stats['50%']:.2f}\n") self.result_text.insert(tk.END, f" • 75%分位: {stats['75%']:.2f}\n") self.result_text.insert(tk.END, f" • 最大值: {stats['max']:.2f}\n") self.result_text.insert(tk.END, "\n") # 处理非数值列 if not non_numeric_cols.empty: self.result_text.insert(tk.END, "═══ 非数值型数据统计 ═══\n\n", 'header') for col in non_numeric_cols: value_counts = data[col].value_counts() unique_count = data[col].nunique() total_count = len(data[col]) self.result_text.insert(tk.END, f"▶ {col}\n", 'subtitle') self.result_text.insert(tk.END, f" • 总数据量: {total_count}\n") self.result_text.insert(tk.END, f" • 唯一值数量: {unique_count}\n") self.result_text.insert(tk.END, " • 前5项频率分布:\n") # 显示前5个值的频率分布 for val, count in value_counts.head().items(): percentage = (count / total_count) * 100 self.result_text.insert(tk.END, f" - {val}: {count} ({percentage:.1f}%)\n") self.result_text.insert(tk.END, "\n") def _run_correlation_analysis(self, data): """相关性分析""" numeric_data = data.select_dtypes(include=['int64', 'float64']) if numeric_data.empty: self.result_text.insert(tk.END, "⚠ 没有找到可以进行相关性分析的数值型数据", 'warning') else: result = numeric_data.corr() self.result_text.insert(tk.END, "═══ 相关性分析结果 ═══\n\n", 'header') # 格式化相关性矩阵 for col1 in result.columns: self.result_text.insert(tk.END, f"▶ {col1} 的相关性:\n", 'subtitle') for col2 in result.columns: if col1 != col2: # 不显示自身的相关性 corr = result.loc[col1, col2] # 添加相关性强度的描述 strength = "" if abs(corr) > 0.7: strength = "强" elif abs(corr) > 0.4: strength = "中等" else: strength = "弱" self.result_text.insert(tk.END, f" • 与 {col2}: {corr:.3f} ({strength}相关)\n") self.result_text.insert(tk.END, "\n") def _run_distribution_analysis(self, data): """数据分布分析""" numeric_cols = data.select_dtypes(include=['int64', 'float64']).columns if numeric_cols.empty: self.result_text.insert(tk.END, "⚠ 没有找到可以分析的数值型数据", 'warning') return self.result_text.insert(tk.END, "═══ 数据分布分析 ═══\n\n", 'header') for col in numeric_cols: # 修改变量名,避免与参数名冲突 col_data = data[col].dropna() # 计算分布相关指标 skewness = col_data.skew() kurtosis = col_data.kurtosis() # 计算分位数 quantiles = col_data.quantile([0.1, 0.25, 0.5, 0.75, 0.9]) self.result_text.insert(tk.END, f"▶ {col}\n", 'subtitle') self.result_text.insert(tk.END, f" • 偏度: {skewness:.3f}\n") self.result_text.insert(tk.END, f" • 峰度: {kurtosis:.3f}\n") self.result_text.insert(tk.END, " • 分位数分布:\n") self.result_text.insert(tk.END, f" - 10%: {quantiles[0.1]:.2f}\n") self.result_text.insert(tk.END, f" - 25%: {quantiles[0.25]:.2f}\n") self.result_text.insert(tk.END, f" - 50%: {quantiles[0.5]:.2f}\n") self.result_text.insert(tk.END, f" - 75%: {quantiles[0.75]:.2f}\n") self.result_text.insert(tk.END, f" - 90%: {quantiles[0.9]:.2f}\n\n") def _run_time_series_analysis(self, data): """时间序列分析""" # 查找日期列 date_cols = data.select_dtypes(include=['datetime64']).columns if date_cols.empty: self.result_text.insert(tk.END, "⚠ 没有找到日期类型的列\n", 'warning') return self.result_text.insert(tk.END, "═══ 时间序列分析 ═══\n\n", 'header') for date_col in date_cols: self.result_text.insert(tk.END, f"▶ {date_col} 时间分布\n", 'subtitle') # 基本时间范围 time_min = data[date_col].min() time_max = data[date_col].max() time_range = time_max - time_min self.result_text.insert(tk.END, f" • 时间范围: {time_range.days} 天\n") self.result_text.insert(tk.END, f" • 起始时间: {time_min:%Y-%m-%d}\n") self.result_text.insert(tk.END, f" • 结束时间: {time_max:%Y-%m-%d}\n\n") # 按月份分布 monthly_counts = data[date_col].dt.month.value_counts().sort_index() self.result_text.insert(tk.END, " • 月份分布:\n") for month, count in monthly_counts.items(): self.result_text.insert(tk.END, f" - {month}月: {count}条记录\n") self.result_text.insert(tk.END, "\n") def _run_group_analysis(self, data): """分组统计分析""" # 获取可能的分组列(分类数据) category_cols = data.select_dtypes(include=['object', 'category']).columns numeric_cols = data.select_dtypes(include=['int64', 'float64']).columns if category_cols.empty or numeric_cols.empty: self.result_text.insert(tk.END, "⚠ 需要同时包含分类数据数值数据\n", 'warning') return self.result_text.insert(tk.END, "═══ 分组统计分析 ═══\n\n", 'header') for cat_col in category_cols: self.result_text.insert(tk.END, f"▶ 按 {cat_col} 分组统计\n", 'subtitle') # 计算每个分组的基本统计量 for num_col in numeric_cols: group_stats = data.groupby(cat_col)[num_col].agg([ 'count', 'mean', 'std', 'min', 'max' ]) self.result_text.insert(tk.END, f" • {num_col} 统计:\n") for group_name, stats in group_stats.iterrows(): self.result_text.insert(tk.END, f" - {group_name}:\n") self.result_text.insert(tk.END, f" 数量: {stats['count']:.0f}\n") self.result_text.insert(tk.END, f" 均值: {stats['mean']:.2f}\n") self.result_text.insert(tk.END, f" 标准差: {stats['std']:.2f}\n") self.result_text.insert(tk.END, f" 最小值: {stats['min']:.2f}\n") self.result_text.insert(tk.END, f" 最大值: {stats['max']:.2f}\n") self.result_text.insert(tk.END, "\n") def _run_missing_value_analysis(self, data): """缺失值分析""" self.result_text.insert(tk.END, "═══ 缺失值分析 ═══\n\n", 'header') # 计算每列的缺失值 missing_stats = data.isnull().sum() total_rows = len(data) # 只显示有缺失值的列 missing_cols = missing_stats[missing_stats > 0] if missing_cols.empty: self.result_text.insert(tk.END, "✓ 数据中没有发现缺失值\n", 'subtitle') return self.result_text.insert(tk.END, "▶ 缺失值统计\n", 'subtitle') for col, missing_count in missing_cols.items(): missing_percentage = (missing_count / total_rows) * 100 self.result_text.insert(tk.END, f" • {col}:\n") self.result_text.insert(tk.END, f" - 缺失数量: {missing_count}\n") self.result_text.insert(tk.END, f" - 缺失比例: {missing_percentage:.2f}%\n") # 添加缺失值模式分析 self.result_text.insert(tk.END, "\n▶ 缺失值模式\n", 'subtitle') total_missing = data.isnull().sum().sum() self.result_text.insert(tk.END, f" • 总缺失值数量: {total_missing}\n") self.result_text.insert(tk.END, f" • 总缺失率: {(total_missing/(total_rows*len(data.columns))):.2f}%\n") def _run_cpk_analysis(self, data): """CPK分析""" numeric_cols = data.select_dtypes(include=['int64', 'float64']).columns if numeric_cols.empty: self.result_text.insert(tk.END, "⚠ 没有找到可以进行CPK分析的数值型数据", 'warning') return # 创建输入对话框获取规格限 spec_dialog = tk.Toplevel(self.root) spec_dialog.title("输入规格限") spec_dialog.geometry("400x500") # 增加窗口大小 # 使对话框成为模态窗口 spec_dialog.transient(self.root) spec_dialog.grab_set() # 创建主框架,并添加滚动条 main_frame = ttk.Frame(spec_dialog) main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建Canvas滚动条 canvas = tk.Canvas(main_frame) scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=canvas.yview) # 创建内容框架 content_frame = ttk.Frame(canvas) # 配置Canvas canvas.configure(yscrollcommand=scrollbar.set) # 创建规格限输入框 specs = {} row = 0 # 添加标题标签 title_label = ttk.Label(content_frame, text="请输入各列的规格上下限:", font=('Arial', 10, 'bold')) title_label.grid(row=row, column=0, columnspan=3, pady=10, padx=5, sticky='w') row += 1 for col in numeric_cols: # 列名标签 col_label = ttk.Label(content_frame, text=f"{col}:", font=('Arial', 9)) col_label.grid(row=row, column=0, pady=5, padx=5, sticky='w') # USL输入框标签 usl_frame = ttk.Frame(content_frame) usl_frame.grid(row=row, column=1, padx=5, sticky='w') usl_var = tk.StringVar() ttk.Entry(usl_frame, textvariable=usl_var, width=12).pack(side=tk.LEFT, padx=2) ttk.Label(usl_frame, text="USL").pack(side=tk.LEFT, padx=2) row += 1 # LSL输入框标签 lsl_frame = ttk.Frame(content_frame) lsl_frame.grid(row=row, column=1, padx=5, sticky='w') lsl_var = tk.StringVar() ttk.Entry(lsl_frame, textvariable=lsl_var, width=12).pack(side=tk.LEFT, padx=2) ttk.Label(lsl_frame, text="LSL").pack(side=tk.LEFT, padx=2) specs[col] = {'usl': usl_var, 'lsl': lsl_var} row += 1 # 添加分隔线 ttk.Separator(content_frame, orient='horizontal').grid( row=row, column=0, columnspan=3, sticky='ew', pady=5) row += 1 # 添加按钮框架 button_frame = ttk.Frame(content_frame) button_frame.grid(row=row, column=0, columnspan=3, pady=10) ttk.Button(button_frame, text="计算CPK", command=lambda: calculate_cpk()).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="取消", command=spec_dialog.destroy).pack(side=tk.LEFT, padx=5) # 放置Canvas滚动条 canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 将content_frame放入canvas canvas_window = canvas.create_window((0, 0), window=content_frame, anchor='nw') # 配置canvas滚动区域 def configure_scroll_region(event): canvas.configure(scrollregion=canvas.bbox('all')) # 配置canvas宽度 def configure_canvas_width(event): canvas.itemconfig(canvas_window, width=event.width) # 绑定事件 content_frame.bind('<Configure>', configure_scroll_region) canvas.bind('<Configure>', configure_canvas_width) # 绑定鼠标滚轮 def on_mousewheel(event): canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") canvas.bind_all("<MouseWheel>", on_mousewheel) def calculate_cpk(): """计算CPK""" try: # 确保文本框可编辑 self.result_text.config(state='normal') self.result_text.delete(1.0, tk.END) for col in numeric_cols: try: # 获取规格限 usl = float(specs[col]['usl'].get()) lsl = float(specs[col]['lsl'].get()) # 获取数据 values = data[col].dropna() # 计算统计量 mean = values.mean() std = values.std() # 计算CPUCPL cpu = (usl - mean) / (3 * std) cpl = (mean - lsl) / (3 * std) # 计算CPK cpk = min(cpu, cpl) # 计算过程能力评级 rating = "未知" if cpk >= 1.67: rating = "极佳" elif cpk >= 1.33: rating = "良好" elif cpk >= 1.0: rating = "合格" else: rating = "不合格" # 显示结果 self.result_text.insert(tk.END, f"▶ {col}\n", 'subtitle') self.result_text.insert(tk.END, f" • 均值: {mean:.3f}\n") self.result_text.insert(tk.END, f" • 标准差: {std:.3f}\n") self.result_text.insert(tk.END, f" • USL: {usl:.3f}\n") self.result_text.insert(tk.END, f" • LSL: {lsl:.3f}\n") self.result_text.insert(tk.END, f" • CPU: {cpu:.3f}\n") self.result_text.insert(tk.END, f" • CPL: {cpl:.3f}\n") self.result_text.insert(tk.END, f" • CPK: {cpk:.3f}\n") self.result_text.insert(tk.END, f" • 过程能力评级: {rating}\n\n") except ValueError: self.result_text.insert(tk.END, f"⚠ {col}: 输入数值无效\n", 'warning') except Exception as e: self.result_text.insert(tk.END, f"⚠ {col}: 计算出错 - {str(e)}\n", 'error') # 设置文本框为只读 self.result_text.config(state='disabled') # 关闭对话框 spec_dialog.destroy() except Exception as e: messagebox.showerror("错误", f"计算过程出错:{str(e)}") # 确保发生错误时也设置文本框为只读 self.result_text.config(state='disabled') def open_file(self): file_path = filedialog.askopenfilename( title="选择文件", filetypes=(("Excel files", "*.xlsx;*.xls"), ("All files", "*.*")) ) if file_path: try: # 使用pandas读取Excel数据 self.current_data = pd.read_excel(file_path) data = self.current_data # 清除现有的Treeview数据 self.tree.delete(*self.tree.get_children()) # 设置Treeview的列标题 self.tree["columns"] = list(data.columns) for col in data.columns: # 更精确的列宽计算 max_width = max( len(str(col)) * 7, # 进一步减小系数 data[col].astype(str).str.len().max() * 7 ) width = min(max(max_width, 50), 150) # 更紧凑的列宽范围 self.tree.column(col, anchor=tk.W, width=width, minwidth=40, # 更小的最小宽度 stretch=True ) self.tree.heading(col, text=col, anchor=tk.W, ) # 插入数据到Treeview for i, (index, row) in enumerate(data.iterrows()): tags = ('evenrow',) if i % 2 == 0 else ('oddrow',) self.tree.insert("", "end", values=list(row), tags=tags) # 配置更细微的交替行颜色 self.tree.tag_configure('oddrow', background='#FAFAFA') # 更浅的灰色 self.tree.tag_configure('evenrow', background='#FFFFFF') # 纯白色 # 更新状态栏 self.status_bar.config( text=f"已加载 {len(data)} 行数据,{len(data.columns)} 列 | {file_path}" ) # 清除之前的分析结果 self.result_text.config(state='normal') self.result_text.delete(1.0, tk.END) self.result_text.config(state='disabled') self.algorithm_var.set("请选择分析方法") except Exception as e: messagebox.showerror("错误", f"无法读取文件: {e}") self.status_bar.config(text="读取文件失败") def load_config(self): """加载配置文件""" config_dir = os.path.expanduser("~/.data_analysis_app") self.config_file = os.path.join(config_dir, "config.json") # 确保配置目录存在 if not os.path.exists(config_dir): os.makedirs(config_dir) if os.path.exists(self.config_file): try: with open(self.config_file, 'r', encoding='utf-8') as f: return json.load(f) except: return {} return {} def save_config(self): """保存配置到文件""" config = { "window_width": self.root.winfo_width(), "window_height": self.root.winfo_height(), "paned_position": self.paned_window.sashpos(0) } try: with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(config, f, indent=4) except Exception as e: print(f"保存配置失败: {e}") def on_sash_moved(self, event): """分隔条移动后的处理""" self.save_config() def on_closing(self): """窗口关闭时的处理""" self.save_config() self.root.destroy() def open_formula_window(self): """打开公式编辑窗口""" formula_window = tk.Toplevel(self.root) formula_window.title("自定义公式") formula_window.geometry("600x400") # 使窗口居中 window_width = 600 window_height = 400 screen_width = formula_window.winfo_screenwidth() screen_height = formula_window.winfo_screenheight() x = int((screen_width - window_width) / 2) y = int((screen_height - window_height) / 2) formula_window.geometry(f"{window_width}x{window_height}+{x}+{y}") # 创建主框架 main_frame = ttk.Frame(formula_window, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # 创建说明标签 ttk.Label(main_frame, text="在这里输入您的自定义公式:", font=('Arial', 10)).pack(anchor=tk.W) # 创建公式名称输入框 name_frame = ttk.Frame(main_frame) name_frame.pack(fill=tk.X, pady=(10,5)) ttk.Label(name_frame, text="公式名称:").pack(side=tk.LEFT) formula_name = ttk.Entry(name_frame) formula_name.pack(side=tk.LEFT, fill=tk.X, expand=True) # 创建公式输入区域 formula_frame = ttk.LabelFrame(main_frame, text="公式内容", padding="5") formula_frame.pack(fill=tk.BOTH, expand=True, pady=(5,10)) # 创建文本编辑器滚动条的容器 text_container = ttk.Frame(formula_frame) text_container.pack(fill=tk.BOTH, expand=True) # 创建文本编辑器 formula_text = tk.Text(text_container, wrap=tk.WORD, font=('Consolas', 11)) # 创建垂直滚动条 v_scrollbar = ttk.Scrollbar(text_container, orient=tk.VERTICAL, command=formula_text.yview) v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 创建水平滚动条 h_scrollbar = ttk.Scrollbar(formula_frame, orient=tk.HORIZONTAL, command=formula_text.xview) h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X) # 配置文本框的滚动 formula_text.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set) formula_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 添加示例文本 example_text = """# 示例公式: # 可以使用 Python 语法编写公式 # 数据可通过 data 变量访问 def calculate(data): # 示例:计算某列的平均值 result = data['列名'].mean() return result # 更多示例: # 1. 计算多列的平均值 # result = data[['列1', '列2', '列3']].mean() # 2. 条件筛选 # result = data[data['列名'] > 100].mean() # 3. 自定义计算 # result = (data['列1'] + data['列2']) / 2 # 4. 分组统计 # result = data.groupby('分组列')['值列'].mean() # 5. 数据转换 # result = data['列名'].apply(lambda x: x * 2) """ formula_text.insert('1.0', example_text) # 创建按钮框架 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=(0,5)) def save_formula(): """保存公式""" name = formula_name.get().strip() formula = formula_text.get('1.0', tk.END).strip() if not name: messagebox.showwarning("警告", "请输入公式名称") return if not formula: messagebox.showwarning("警告", "请输入公式内容") return try: # 保存公式到文件 formulas_file = "custom_formulas.json" formulas = {} # 读取现有公式 if os.path.exists(formulas_file): with open(formulas_file, 'r', encoding='utf-8') as f: formulas = json.load(f) # 添加或更新公式 formulas[name] = formula # 保存到文件 with open(formulas_file, 'w', encoding='utf-8') as f: json.dump(formulas, f, indent=4, ensure_ascii=False) messagebox.showinfo("成功", "公式保存成功!") formula_window.destroy() except Exception as e: messagebox.showerror("错误", f"保存公式失败:{str(e)}") def test_formula(): """测试公式""" if self.current_data is None: messagebox.showwarning("警告", "请先加载数据") return formula = formula_text.get('1.0', tk.END).strip() if not formula: messagebox.showwarning("警告", "请输入公式内容") return try: # 创建一个本地命名空间 local_dict = {} # 执行公式代码 exec(formula, globals(), local_dict) if 'calculate' not in local_dict: raise ValueError("未找到 calculate 函数") # 执行计算 result = local_dict['calculate'](self.current_data) # 显示结果 messagebox.showinfo("测试结果", f"计算结果:{result}") except Exception as e: messagebox.showerror("错误", f"公式测试失败:{str(e)}") # 添加按钮 ttk.Button(button_frame, text="测试公式", command=test_formula).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="保存公式", command=save_formula).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="取消", command=formula_window.destroy).pack(side=tk.RIGHT, padx=5) def get_resource_path(self, relative_path): """获取资源文件的绝对路径""" try: # PyInstaller创建临时文件夹,将路径存储在_MEIPASS中 base_path = sys._MEIPASS except Exception: base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) if __name__ == "__main__": root = tk.Tk() app = DataAnalysisApp(root) root.mainloop()
06-17
import sys import time import random import pandas as pd from datetime import datetime from PyQt5.QtCore import QThread, pyqtSignal, QMutex, QWaitCondition, Qt from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTableWidget, QTableWidgetItem, QProgressBar, QLabel, QMessageBox, QTextEdit, QListWidget, QSplitter, QListWidgetItem, QAbstractItemView) # 线程安全的数据存储 class ThreadSafeDataStore: def __init__(self): self.data = [] self.mutex = QMutex() self.condition = QWaitCondition() def add_data(self, item): """添加数据(线程安全)""" self.mutex.lock() self.data.append(item) self.mutex.unlock() def get_all_data(self): """获取所有数据(线程安全)""" self.mutex.lock() data_copy = self.data[:] self.mutex.unlock() return data_copy def clear(self): """清空数据(线程安全)""" self.mutex.lock() self.data.clear() self.mutex.unlock() # 数据采集线程 class DataCollectorThread(QThread): data_collected = pyqtSignal(dict) progress_updated = pyqtSignal(int) status_changed = pyqtSignal(str) def __init__(self, data_store): super().__init__() self.data_store = data_store self._is_paused = False self._is_running = False self.pause_condition = QWaitCondition() self.mutex = QMutex() self.sample_count = 0 # 样本计数器 def run(self): """线程主循环""" self._is_running = True self.sample_count = 0 while self._is_running: # 暂停处理 self.mutex.lock() if self._is_paused: self.status_changed.emit("采集已暂停") self.pause_condition.wait(self.mutex) self.mutex.unlock() # 模拟数据采集 self.sample_count += 1 timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") value = random.uniform(0, 100) temperature = random.uniform(20, 30) data_point = { "timestamp": timestamp, "sample_id": self.sample_count, "value": value, "temperature": temperature } # 保存数据并发送信号 self.data_store.add_data(data_point) self.data_collected.emit(data_point) self.progress_updated.emit(self.sample_count) self.status_changed.emit(f"采集样本 #{self.sample_count}") # 控制采集速度 time.sleep(0.5) def pause(self): """暂停采集""" self.mutex.lock() self._is_paused = True self.mutex.unlock() def resume(self): """恢复采集""" self.mutex.lock() self._is_paused = False self.pause_condition.wakeAll() self.mutex.unlock() def stop(self): """停止线程""" self._is_running = False self.resume() # 确保线程能退出 self.wait() def reset_counter(self): """重置样本计数器""" self.sample_count = 0 # 数据处理线程 class DataProcessorThread(QThread): processed_data = pyqtSignal(dict) status_changed = pyqtSignal(str) def __init__(self, data_store): super().__init__() self.data_store = data_store self._is_running = False def run(self): """线程主循环""" self._is_running = True while self._is_running: # 获取所有数据 data = self.data_store.get_all_data() if data: # 模拟数据处理:计算统计指标 values = [d['value'] for d in data] avg_value = sum(values) / len(values) max_value = max(values) min_value = min(values) result = { "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "samples": len(data), "avg_value": avg_value, "max_value": max_value, "min_value": min_value } # 发送处理结果 self.processed_data.emit(result) self.status_changed.emit(f"处理了 {len(data)} 个样本") # 控制处理频率 time.sleep(1) def stop(self): """停止线程""" self._is_running = False self.wait() # 主窗口 class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("多线程数据采集与分析系统") self.setGeometry(100, 100, 1000, 700) # 测试计数器 self.test_counter = 1 # 历史测试数据存储 self.history_tests = {} # 创建共享数据存储 self.data_store = ThreadSafeDataStore() # 初始化线程 self.collector_thread = DataCollectorThread(self.data_store) self.processor_thread = DataProcessorThread(self.data_store) # 初始化UI self.init_ui() # 连接信号 self.connect_signals() def init_ui(self): """初始化用户界面""" # 主布局 main_widget = QWidget() main_layout = QVBoxLayout(main_widget) # 使用分割器使界面更灵活 splitter = QSplitter(Qt.Vertical) # 顶部控制面板 top_widget = QWidget() top_layout = QVBoxLayout(top_widget) # 控制面板 - 使用两行布局 control_layout = QVBoxLayout() row1_layout = QHBoxLayout() row2_layout = QHBoxLayout() # 第一行按钮 self.start_btn = QPushButton("开始采集") self.pause_btn = QPushButton("暂停采集") self.stop_btn = QPushButton("停止采集") # 第二行按钮 self.restart_current_btn = QPushButton("重新本次采集") self.restart_btn = QPushButton("重新采集") self.export_btn = QPushButton("导出选中数据") # 初始状态设置 self.pause_btn.setEnabled(False) self.stop_btn.setEnabled(False) self.restart_current_btn.setEnabled(False) self.restart_btn.setEnabled(False) self.export_btn.setEnabled(False) # 添加到布局 row1_layout.addWidget(self.start_btn) row1_layout.addWidget(self.pause_btn) row1_layout.addWidget(self.stop_btn) row2_layout.addWidget(self.restart_current_btn) row2_layout.addWidget(self.restart_btn) row2_layout.addWidget(self.export_btn) control_layout.addLayout(row1_layout) control_layout.addLayout(row2_layout) # 状态显示 status_layout = QHBoxLayout() self.status_label = QLabel("就绪") self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) status_layout.addWidget(self.status_label, 3) status_layout.addWidget(self.progress_bar, 7) top_layout.addLayout(control_layout) top_layout.addLayout(status_layout) # 中部数据显示区域 center_widget = QWidget() center_layout = QHBoxLayout(center_widget) # 左侧:历史测试列表 history_layout = QVBoxLayout() history_layout.addWidget(QLabel("历史测试记录:")) self.test_list = QListWidget() self.test_list.setSelectionMode(QAbstractItemView.ExtendedSelection) # 允许多选 history_layout.addWidget(self.test_list) # 右侧:数据表格 data_layout = QVBoxLayout() # 原始数据表格 self.data_table = QTableWidget() self.data_table.setColumnCount(4) self.data_table.setHorizontalHeaderLabels(["测试时间", "测试次数", "指标1", "指标2"]) self.data_table.setMinimumHeight(200) # 处理结果表格 self.result_table = QTableWidget() self.result_table.setColumnCount(5) self.result_table.setHorizontalHeaderLabels(["处理时间", "样本数", "平均值", "最大值", "最小值"]) self.result_table.setMinimumHeight(150) data_layout.addWidget(QLabel("原始数据:")) data_layout.addWidget(self.data_table) data_layout.addWidget(QLabel("处理结果:")) data_layout.addWidget(self.result_table) center_layout.addLayout(history_layout, 1) center_layout.addLayout(data_layout, 3) # 底部:日志区域 bottom_widget = QWidget() bottom_layout = QVBoxLayout(bottom_widget) bottom_layout.addWidget(QLabel("操作日志:")) self.log_view = QTextEdit() self.log_view.setReadOnly(True) self.log_view.setMinimumHeight(100) bottom_layout.addWidget(self.log_view) # 组装分割器 splitter.addWidget(top_widget) splitter.addWidget(center_widget) splitter.addWidget(bottom_widget) splitter.setSizes([100, 400, 150]) # 设置各部分大小比例 main_layout.addWidget(splitter) self.setCentralWidget(main_widget) # 连接按钮信号 self.start_btn.clicked.connect(self.start_test) self.pause_btn.clicked.connect(self.toggle_pause) self.stop_btn.clicked.connect(self.stop_test) self.export_btn.clicked.connect(self.export_data) self.restart_current_btn.clicked.connect(self.restart_current) self.restart_btn.clicked.connect(self.restart_all) # 连接列表选择信号 self.test_list.itemSelectionChanged.connect(self.show_selected_test) def connect_signals(self): """连接线程信号到槽函数""" self.collector_thread.data_collected.connect(self.add_data_row) self.collector_thread.progress_updated.connect(self.update_progress) self.collector_thread.status_changed.connect(self.update_status) self.processor_thread.processed_data.connect(self.add_result_row) self.processor_thread.status_changed.connect(self.log_message) def start_test(self): """开始采集""" self.log_message("开始数据采集...") # 清空数据 self.data_store.clear() self.data_table.setRowCount(0) self.result_table.setRowCount(0) self.progress_bar.setValue(0) # 重置样本计数器 self.collector_thread.reset_counter() # 启动线程 if not self.collector_thread.isRunning(): self.collector_thread.start() if not self.processor_thread.isRunning(): self.processor_thread.start() # 更新UI状态 self.start_btn.setEnabled(False) self.pause_btn.setEnabled(True) self.stop_btn.setEnabled(True) self.restart_current_btn.setEnabled(True) self.restart_btn.setEnabled(True) self.export_btn.setEnabled(False) self.pause_btn.setText("暂停采集") def restart_current(self): """重新本次采集 - 清空当前数据但保持采集状态""" if not self.collector_thread.isRunning(): self.log_message("警告:采集未运行,无法重新本次采集") return self.log_message("重新本次采集...") # 清空当前数据 self.data_store.clear() self.data_table.setRowCount(0) self.result_table.setRowCount(0) self.progress_bar.setValue(0) # 重置样本计数器 self.collector_thread.reset_counter() self.log_message("已清空当前采集数据,继续采集...") def restart_all(self): """重新采集 - 停止当前采集并重新开始""" self.log_message("重新采集...") # 停止当前采集 if self.collector_thread.isRunning(): self.stop_test() # 开始新的采集 self.start_test() def toggle_pause(self): """切换暂停/继续状态""" if not self.collector_thread.isRunning(): self.log_message("警告:采集未运行,无法暂停") return if self.collector_thread._is_paused: self.collector_thread.resume() self.pause_btn.setText("暂停采集") self.log_message("继续数据采集...") else: self.collector_thread.pause() self.pause_btn.setText("继续采集") self.log_message("数据采集已暂停...") def stop_test(self): """停止采集""" self.log_message("停止数据采集...") # 停止线程 if self.collector_thread.isRunning(): self.collector_thread.stop() if self.processor_thread.isRunning(): self.processor_thread.stop() # 保存本次测试数据 test_name = f"测试{self.test_counter}" test_data = self.data_store.get_all_data() self.history_tests[test_name] = test_data # 添加到历史测试列表 item = QListWidgetItem(test_name) item.setData(Qt.UserRole, test_name) # 存储测试名称 self.test_list.addItem(item) self.log_message(f"已保存本次测试数据: {test_name}") self.test_counter += 1 # 更新UI状态 self.start_btn.setEnabled(True) self.pause_btn.setEnabled(False) self.stop_btn.setEnabled(False) self.restart_current_btn.setEnabled(False) self.restart_btn.setEnabled(True) self.export_btn.setEnabled(True) self.pause_btn.setText("暂停采集") # 重置按钮文本 data_count = len(test_data) self.log_message(f"采集完成,共收集 {data_count} 个样本") def show_selected_test(self): """显示选中的测试数据""" selected_items = self.test_list.selectedItems() if not selected_items: return # 只显示第一个选中的测试 test_name = selected_items[0].data(Qt.UserRole) test_data = self.history_tests.get(test_name, []) # 更新原始数据表格 self.data_table.setRowCount(0) for row, data_point in enumerate(test_data): self.data_table.insertRow(row) self.data_table.setItem(row, 0, QTableWidgetItem(data_point["timestamp"])) self.data_table.setItem(row, 1, QTableWidgetItem(str(data_point["sample_id"]))) self.data_table.setItem(row, 2, QTableWidgetItem(f"{data_point['value']:.2f}")) self.data_table.setItem(row, 3, QTableWidgetItem(f"{data_point['temperature']:.2f}")) # 更新处理结果表格(需要重新计算) if test_data: values = [d['value'] for d in test_data] avg_value = sum(values) / len(values) max_value = max(values) min_value = min(values) self.result_table.setRowCount(0) self.result_table.insertRow(0) self.result_table.setItem(0, 0, QTableWidgetItem(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) self.result_table.setItem(0, 1, QTableWidgetItem(str(len(test_data)))) self.result_table.setItem(0, 2, QTableWidgetItem(f"{avg_value:.2f}")) self.result_table.setItem(0, 3, QTableWidgetItem(f"{max_value:.2f}")) self.result_table.setItem(0, 4, QTableWidgetItem(f"{min_value:.2f}")) def export_data(self): """导出选中的测试数据到Excel文件""" selected_items = self.test_list.selectedItems() if not selected_items: QMessageBox.warning(self, "导出失败", "请选择要导出的测试集") return # 收集所有选中的测试数据 all_data = [] for item in selected_items: test_name = item.data(Qt.UserRole) test_data = self.history_tests.get(test_name, []) # 为每个数据点添加测试名称 for data_point in test_data: data_point_with_test = data_point.copy() data_point_with_test["test_name"] = test_name all_data.append(data_point_with_test) if not all_data: QMessageBox.warning(self, "导出失败", "选中的测试集没有可导出的数据") return try: today = datetime.now() filename = f"data_export_{today.strftime('%Y%m%d_%H%M%S')}.xlsx" # 创建DataFrame并导出 df = pd.DataFrame(all_data) # 重新排序列 column_order = ["test_name", "timestamp", "sample_id", "value", "temperature"] df = df[column_order] df.to_excel(filename, index=False) self.log_message(f"已导出 {len(selected_items)} 个测试集的数据到: {filename}") QMessageBox.information(self, "导出成功", f"已成功导出 {len(selected_items)} 个测试集的数据到:\n{filename}") except Exception as e: self.log_message(f"导出失败: {str(e)}") QMessageBox.critical(self, "导出错误", f"导出数据时发生错误:\n{str(e)}") def add_data_row(self, data): """添加数据到表格""" row = self.data_table.rowCount() self.data_table.insertRow(row) self.data_table.setItem(row, 0, QTableWidgetItem(data["timestamp"])) self.data_table.setItem(row, 1, QTableWidgetItem(str(data["sample_id"]))) self.data_table.setItem(row, 2, QTableWidgetItem(f"{data['value']:.2f}")) self.data_table.setItem(row, 3, QTableWidgetItem(f"{data['temperature']:.2f}")) # 自动滚动到最后一行 self.data_table.scrollToBottom() def add_result_row(self, result): """添加处理结果到表格""" row = self.result_table.rowCount() self.result_table.insertRow(row) self.result_table.setItem(row, 0, QTableWidgetItem(result["timestamp"])) self.result_table.setItem(row, 1, QTableWidgetItem(str(result["samples"]))) self.result_table.setItem(row, 2, QTableWidgetItem(f"{result['avg_value']:.2f}")) self.result_table.setItem(row, 3, QTableWidgetItem(f"{result['max_value']:.2f}")) self.result_table.setItem(row, 4, QTableWidgetItem(f"{result['min_value']:.2f}")) # 自动滚动到最后一行 self.result_table.scrollToBottom() def update_progress(self, count): """更新进度条""" self.progress_bar.setValue(count % 100) def update_status(self, status): """更新状态标签""" self.status_label.setText(status) def log_message(self, message): """添加日志消息""" timestamp = datetime.now().strftime("%H:%M:%S") self.log_view.append(f"[{timestamp}] {message}") def closeEvent(self, event): """窗口关闭时确保线程停止""" if self.collector_thread.isRunning(): self.collector_thread.stop() if self.processor_thread.isRunning(): self.processor_thread.stop() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) 将这段代码的数据采集存储逻辑同步到后一段代码 import sys from PyQt5.QtGui import QPixmap, QPainter from PyQt5.QtWidgets import ( QApplication, QMainWindow, QDockWidget, QVBoxLayout, QWidget, QLabel, QPushButton, QTreeWidget, QTreeWidgetItem, QTextEdit, QFileDialog, QMessageBox, QTextBrowser ) from PyQt5.QtCore import Qt, QTimer, QUrl, QThread, QDir from ui.Pages.capturePage.LeftRegion1 import StatusRegion from ui.Pages.capturePage.RightRegion import videoRegion, videoRegion1 from ui.Pages.capturePage.CenterUpRegion import CentralUpRegion from ui.Pages.capturePage.CenterDownRegion1 import CentralDownpRegion class CapturePage(QMainWindow): def __init__(self): super().__init__() self.init_ratio() self.initUI() self.slot_connect() def init_ratio(self): """初始化区域比例""" self.horizontal_ratio = [0.2, 0.8] # 左右区域 self.vertical_ratio = [0.5, 0.5] # 中部上下 self.right_ratio = [0.5, 0.5] # 右侧上下 def slot_connect(self): """连接信号槽""" self.status_ui.device_status_changed.connect(self.central_down_widget.get_device_status) self.status_ui.device_stream.connect(self.central_down_widget.handle_data_from_device) def initUI(self): self.takeCentralWidget() self.setDockOptions(self.dockOptions() | QMainWindow.DockOption.AllowTabbedDocks) self.setDockNestingEnabled(True) self.setWindowTitle("数据采集界面") self.setGeometry(100, 100, 1600, 1000) # 初始窗口大小 # 左侧区域 self.status_ui = StatusRegion() self.addDockWidget(Qt.LeftDockWidgetArea, self.status_ui) # 中间区域 self.central_up_widget = CentralUpRegion() # 右侧区域 self.video1_ui = videoRegion() self.central_down_widget = CentralDownpRegion(self.central_up_widget, self.video1_ui) self.video2_ui = videoRegion1() self.addDockWidget(Qt.RightDockWidgetArea, self.video1_ui) self.addDockWidget(Qt.RightDockWidgetArea, self.video2_ui) # 嵌套布局 self.splitDockWidget(self.status_ui, self.central_up_widget, Qt.Horizontal) self.splitDockWidget(self.central_up_widget, self.central_down_widget, Qt.Vertical) self.splitDockWidget(self.video1_ui, self.video2_ui, Qt.Vertical) # 背景图 self.background = QPixmap(r"C:\Users\86153\Downloads\【慧首UI】开发\素材\caiji.jpg") def paintEvent(self, event): painter = QPainter(self) painter.drawPixmap(self.rect(), self.background) def resizeEvent(self, event): """保持窗口缩放时区域比例不变""" super().resizeEvent(event) # 横向分配(左:状态,中:中心区域) total_width = self.width() left_width = int(total_width * self.horizontal_ratio[0]) center_width = int(total_width * self.horizontal_ratio[1]) self.resizeDocks([self.status_ui, self.central_up_widget], [left_width, center_width], Qt.Horizontal) # 中部纵向分配(上、下) center_total_height = self.central_up_widget.height() + self.central_down_widget.height() up_height = int(center_total_height * self.vertical_ratio[0]) down_height = int(center_total_height * self.vertical_ratio[1]) self.resizeDocks([self.central_up_widget, self.central_down_widget], [up_height, down_height], Qt.Vertical) # 右侧纵向分配(video1, video2) right_total_height = self.video1_ui.height() + self.video2_ui.height() video1_height = int(right_total_height * self.right_ratio[0]) video2_height = int(right_total_height * self.right_ratio[1]) self.resizeDocks([self.video1_ui, self.video2_ui], [video1_height, video2_height], Qt.Vertical) if __name__ == '__main__': app = QApplication(sys.argv) main_window = CapturePage() main_window.show() sys.exit(app.exec_())
最新发布
07-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值