设计tkinter控制按钮实时生成函数图像出现问题?

新手上路,请多包涵

各位大神好,我建立一个简单的由电池、开关和负载的电路,目的是通过开关实时操控来量测电路中电压、电流的变化。现在的代码如下:

import tkinter as tk
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.animation import FuncAnimation

t_max = 10.0  # 最大时间(秒)
fps = 20  # 帧率
t = np.linspace(0, t_max, int(fps * t_max))  # 时间序列

# 电路参数
V_battery = 15  # 电池电压(V)
R_load = 10  # 负载电阻(欧姆)

class CircuitSimulator:
    def __init__(self, t_values, initial_switch_state=True):
        self.t_values = t_values
        self.VoltageOverTime = np.full_like(t_values, V_battery)  # 初始时所有时间点电压均为电池电压
        self.CurrentOverTime = np.full_like(t_values, V_battery / R_load)  # 初始化电流数组
        self.switch_states = np.ones_like(t_values, dtype=bool) * initial_switch_state  # 记录每个时间点开关的状态
        self.previous_switch_state = initial_switch_state  # 记录上一个开关状态
        self.previous_switch_time_index = -1 # 初始化为-1,表示尚未进行过切换

    def calculate_circuit_response(self, current_time_index):
        # 检查当前时间点是否有开关切换发生
        if current_time_index > self.previous_switch_time_index:
            # 检查当前时间点的开关状态是否与前一时刻不同
            if self.switch_states[current_time_index] != self.switch_states[current_time_index - 1]:
                self.previous_switch_state = self.switch_states[current_time_index]
                next_switch_index = current_time_index + np.argmax(self.switch_states[current_time_index:] != self.switch_states[current_time_index])
                if not self.previous_switch_state:
                    # 开关断开
                    self.VoltageOverTime[current_time_index:next_switch_index] = 0
                    self.CurrentOverTime[current_time_index:next_switch_index] = 0
                else:
                    # 开关闭合
                    self.VoltageOverTime[current_time_index:next_switch_index] = V_battery * np.ones_like(self.VoltageOverTime[current_time_index:next_switch_index])
                    self.CurrentOverTime[current_time_index:next_switch_index] = V_battery / R_load * np.ones_like(self.CurrentOverTime[current_time_index:next_switch_index])
                # 更新上一次开关切换的时间索引
                self.previous_switch_time_index = next_switch_index

    def toggle_switch_at_time(self, switch_time_index):
        if 0 <= switch_time_index < len(self.switch_states):
            # 只改变指定时间点及其之后已定义的时间段内的开关状态
            end_of_simulation = min(switch_time_index + 1, len(self.switch_states))  # 防止越界
            self.switch_states[switch_time_index:end_of_simulation] = not self.switch_states[switch_time_index]
        else:
            raise IndexError(f"Invalid time index: {switch_time_index}. Maximum allowed index is {len(self.switch_states) - 1}")

# 创建一个新的窗口类,用于集成matplotlib图表与Tkinter GUI
class CircuitSimulationGUI(tk.Tk):

    def __init__(self):
        super().__init__()

        # 设置窗口标题
        self.title("电路运行状态监控")

        # 创建主容器
        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        # 初始化图表
        self.fig, self.axs = plt.subplots(2, 1, figsize=(10, 6))
        self.V_line, = self.axs[0].plot([], [], label='Circuit Voltage (V)')
        self.I_line, = self.axs[1].plot([], [], label='Circuit Current (A)', color='r')
        for ax in self.axs:
            ax.set_xlabel('Time (s)')
            ax.set_ylabel('Value')
            ax.legend()

        # 将matplotlib图表嵌入到Tkinter窗口中
        self.canvas = FigureCanvasTkAgg(self.fig, master=container)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

        # 创建开关按钮
        self.manual_switch_button = tk.Button(master=self, text="Close Circuit", command=self.toggle_manual_switch)
        self.manual_switch_button.pack(pady=10)

        # 初始化动画
        self.simulator = CircuitSimulator(t)
        self.ani = None  # 将动画实例化变量初始化为None

        # 初始化状态变量
        self.current_time_index = 0

        # 启动模拟
        self.start_simulation()

    def toggle_manual_switch(self):
        """
        处理手动开关按钮点击事件,切换当前时刻的开关状态并持续影响后续状态
        """
        # 获取当前时刻的索引
        current_index = int(self.current_time_index)

        # 切换开关状态
        self.simulator.toggle_switch_at_time(current_index)
        self.simulator.calculate_circuit_response(current_index)

        # 更新按钮文本和命令
        if self.manual_switch_button["text"] == "Close Circuit":
            self.manual_switch_button["text"] = "Open Circuit"
        else:
            self.manual_switch_button["text"] = "Close Circuit"

        # 更新整个图表,传递当前时间点的索引
        self.update_plot(current_index)
        self.canvas.draw_idle()


    def start_simulation(self):
        # 使用更现代且兼容的方式检测和控制动画状态
        if self.ani is None:
            self.ani = FuncAnimation(
                fig=self.fig,
                func=self.update_plot, #传递函数而非方法 
                frames=len(t),
                interval=200,
                blit=True
            )
            self.canvas.draw_idle()
        elif getattr(self.ani, '_is_running', False):  # 更改此处
            self.ani.event_source.stop()
            self.ani.event_source.start()
        else:
            self.ani.event_source.start()

    def update_plot(self, frame):
        self.simulator.calculate_circuit_response(frame)
        time = t[frame]
        
        V_circuit = self.simulator.VoltageOverTime[:frame+1]
        I_circuit = self.simulator.CurrentOverTime[:frame+1]

        self.V_line.set_data(t[:len(V_circuit)], V_circuit)
        self.I_line.set_data(t[:len(I_circuit)], I_circuit)
        self.axs[0].set_xlim(0, t_max)
        self.axs[1].set_xlim(0, t_max)
        self.axs[0].set_ylim(0, 20)
        self.axs[1].set_ylim(0, 2)
        print("Plot updated")  # 添加这行代码来确认图表是否被更新
        print("Plot Voltage:", V_circuit[-1], "V")
        return self.V_line, self.I_line

# 创建并启动GUI应用
if __name__ == "__main__":
    app = CircuitSimulationGUI()
    app.mainloop()

现在基本运行时没有问题,但是在逻辑上有些不对。
1、在点击模拟开关的按钮时,整体的电压电流从0时刻,而不是点击按钮时刻开始。
2、按钮无法实现电路的断开和闭合。

我曾经改动了在CircuitSimulator类中的calculate_circuit_response,CircuitSimulationGUI(tk.Tk)中的toggle_manual_switch以及update_plot,但都达不到理想的效果。也曾经到某四字字母网站求助,但是回答的都是GPT未经验证的答案。
希望获得效果如下图所示:

阅读 517
1 个回答

这种问题很赞

def toggle_manual_switch(self):
    """
    处理手动开关按钮点击事件,切换当前时刻的开关状态并持续影响后续状态
    """
    # 获取当前时刻的索引
    current_index = int(self.current_time_index)  # FIXME: Q1.这个值没有更新过


def update_plot(self, frame):
    self.simulator.calculate_circuit_response(frame)
    time = t[frame]
    # FIXME: A1.这里更新一下
    self.current_time_index = frame

修改值不持久问题。能满足逻辑,但思想好像和你的不太一致,又不太想花太多时间了解你的意图。

def calculate_circuit_response(self, current_time_index):
    # 检查当前时间点是否有开关切换发生
    if current_time_index > self.previous_switch_time_index:
        # 检查当前时间点的开关状态是否与前一时刻不同
        if self.switch_states[current_time_index] != self.switch_states[current_time_index - 1]:
            # self.previous_switch_state = self.switch_states[current_time_index]
            self.previous_switch_state = not self.previous_switch_state  # TODO: 这块的值反正需要修改一下的
            next_switch_index = current_time_index + np.argmax(
                self.switch_states[current_time_index:] != self.switch_states[current_time_index])
            if not self.previous_switch_state:
                # 开关断开
                self.VoltageOverTime[current_time_index:] = 0 # FIXME: VoltageOverTime 和 CurrentOverTime 的赋值有些问题,之前是只赋值一个点?
                self.CurrentOverTime[current_time_index:] = 0
            else:
                # 开关闭合
                self.VoltageOverTime[current_time_index:] = V_battery * np.ones_like(
                    self.VoltageOverTime[current_time_index:])
                self.CurrentOverTime[current_time_index:] = V_battery / R_load * np.ones_like(
                    self.CurrentOverTime[current_time_index:])
            # 更新上一次开关切换的时间索引
            self.previous_switch_time_index = next_switch_index

image.png

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏