如何使用 Tkinter 通过两个刻度滑块交互式控制信号的幅度和频率

问题描述 投票:0回答:1

刚开始使用Python,我想对三角信号进行建模,并能够使用比例/滑块小部件以交互方式控制其幅度和频率,并且尽可能

Tkinter

不恰当的是,我的(更新的)代码生成了两个信号(幅度和频率),这两个信号由滑块独立控制。这并不奇怪,因为我没有将这两者联系起来。然而,互联网上没有例子以说教的方式解释如何做到这一点(

matplotlib
,特别是
.set_ydata
允许这种交互性-参见这里-但我真的很想详细了解绑定过程,如果这可以通过
Tkinter
)实现。

所以,问题是:如何绑定2个滑块,以便它们控制单个信号,也就是说,当我们移动一个变量时,另一个变量保持在其最后的位置?

感谢您的帮助或任何建议

代码:

# import modules
from tkinter import ttk
import tkinter as tk

# create window
root = tk.Tk() # object root
root.title('Oscilloscope')
root.geometry("1200x600+200+100")

# exit button
btn_exit = tk.Button(root, text='Exit', command=root.destroy, height=2, width=15)
btn_exit.place(x=1100, y=500, anchor=tk.CENTER)

# canvas
canvas = tk.Canvas(root, width = 800, height = 400, bg = 'white')
canvas.place(x=600, y=250, anchor=tk.CENTER)
for x in range(0, 800, 50): canvas.create_line(x, 0, x, 400, fill='darkgray', dash=(2, 2)) # vertical dashed lines every 50 units
for x in range(0, 400, 50): canvas.create_line(0, x, 800, x, fill='darkgray', dash=(2, 2)) # horizontal dashed lines every 50 units
canvas.create_line(400, 0, 400, 800, fill='black', width=2) # vertical line at x = 400
canvas.create_line(0, 200, 800, 200, fill='black', width=2) # horizontal line at y = 200
canvas.create_rectangle(3, 3, 800, 400, width=2, outline='darkgrey')

# parameters triangular signal
amplitude = 200
frequency = 10
nb_pts = 20 # must be necessary 2 fold the frequency for a triangular signal
offset = 200

# function for drawing the triangular signal
def draw_triangular(canvas, amplitude, frequency, offset, nb_pts):
    xpts = 1000 / (nb_pts-1)
    line = []
    for i in range(nb_pts):
        x = i * xpts
        y = amplitude * ((2 * (i * frequency / nb_pts) % 2 - 1)) + offset
        line.extend((x, y))
    canvas_line = canvas.create_line(line, fill="red", width=3)
    canvas.after(50, canvas.delete, canvas_line)

# vertical widget scale for amplitude ###############################################################################################
def amplitude_value(new_value): # show value
    label_amplitude.configure(text=f"Amplitude {new_value}")

def select_amplitude():
    sel = "Value = " + str(value.get(amplitude))
value_amp = tk.IntVar()
frm = ttk.Frame(root, padding=10)
frm.place(x=100, y=250, anchor=tk.CENTER)
scale_amplitude = tk.Scale(frm, variable=value_amp, command=amplitude_value,
                           from_ = 200, to = -200, length=400, showvalue=0, tickinterval=50, orient = tk.VERTICAL)
scale_amplitude.pack(anchor=tk.CENTER, padx=10)
label_amplitude = ttk.Label(root, text="Amplitude", font=("Arial"))
label_amplitude.place(x=110, y=480, anchor=tk.CENTER)

def update_amplitude():
    amplitude = scale_amplitude.get()
    draw_triangular(canvas, amplitude, frequency, offset, nb_pts) 
    root.after(50, update_amplitude)
    return amplitude

# horizontal widget scale for frequency ###########################################################################################
def frequency_value(new_value): # show value
    label_frequency.configure(text=f"Frequency {new_value}")

def select_frequency():
    sel = "Value = " + str(value.get(frequency))
value_freq = tk.IntVar()
frm = ttk.Frame(root, padding=10)
frm.place(x=600, y=520, anchor=tk.CENTER)
scale_frequency = tk.Scale(frm, variable=value_freq, command=frequency_value,
                           from_ = -50, to = 50, length=800, showvalue=0, tickinterval=10, orient = tk.HORIZONTAL)
scale_frequency.pack(anchor=tk.CENTER, padx=10)
label_frequency = ttk.Label(root, text="Frequency", font=("Arial"))
label_frequency.place(x=600, y=560, anchor=tk.CENTER)

def update_frequency():
    frequency = scale_frequency.get()
    draw_triangular(canvas, amplitude , frequency, offset, nb_pts)
    root.after(50, update_frequency)
    return frequency

# reset function
def reset_values():
    value_amp.set(0)
    amplitude_value(0)
    value_freq.set(0)
    frequency_value(0)

# reset button
btn_reset = tk.Button(root, text='Reset', command=reset_values, height=2, width=15)
btn_reset.place(x=1100, y=400, anchor=tk.CENTER)

update_amplitude()
update_frequency()

root.mainloop() 

编辑

感谢 acw1668 的回答,两个滑块现在可以交互,并且信号现在完全可控,如下面的新版本代码中所更新。

仍然存在一个小问题:当我们设置幅度滑块(例如 100)然后逐渐增加(逐个单位)频率滑块时,我们观察到:

  1. 大多数频率的工厂屋顶外观,但 F = 5、8、10、20、25 和 50 除外,其外观完全水平,并且
  2. 信号的虚拟水平轴相对于零水平轴轻微向上移动,对于 A = 100 和 F = 50,在 y 轴上达到 +25。

我怀疑这与以下事实有关:在给定的非零频率下,信号不会从零幅度开始,并且这会通过 y 方程影响基于三角形几何的绘图。 因此,下一个问题是:如何将新变量

x0
集成到
draw_triangular
函数中,以便在选择幅度时能够从零幅度开始信号?换句话说,所选振幅从第一个三角形的左下角开始,而不是从顶部开始。

谢谢大家

更新代码

# import modules
from tkinter import ttk
import tkinter as tk
import numpy as np

# create window
root = tk.Tk() # object root
root.title('Oscilloscope')
root.geometry("1200x600+200+100")

# exit button
btn_exit = tk.Button(root, text='Exit', command=root.destroy, height=2, width=15)
btn_exit.place(x=1100, y=500, anchor=tk.CENTER)

# canvas
canvas = tk.Canvas(root, width = 800, height = 400, bg = 'white')
canvas.place(x=600, y=250, anchor=tk.CENTER)
for x in range(0, 800, 50): canvas.create_line(x, 0, x, 400, fill='darkgray', dash=(2, 2)) # vertical dashed lines every 50 units
for x in range(0, 400, 50): canvas.create_line(0, x, 800, x, fill='darkgray', dash=(2, 2)) # horizontal dashed lines every 50 units
canvas.create_line(400, 0, 400, 800, fill='black', width=2) # vertical line at x = 400
canvas.create_line(0, 200, 800, 200, fill='black', width=2) # horizontal line at y = 200
canvas.create_rectangle(3, 3, 800, 400, width=2, outline='darkgrey')

# parameters triangular signal
nb_pts = 200
offset = 200

# updated draw_triangular()
def draw_triangular(canvas, amplitude, frequency, offset, nb_pts):
    canvas.delete("line")  # clear current plot
    xpts = 800 / (nb_pts-1)
    line = []
    for i in range(nb_pts):
        x = i * xpts
        y = amplitude * ((2 * np.abs(frequency * i / nb_pts) % 2 - 1)) + offset
        line.extend((x, y))
    canvas.create_line(line, fill="red", width=3, tag="line")

# function to be called when any of the scales is changed
def on_scale_changed(*args):
    amplitude = value_amp.get()
    frequency = value_freq.get()
    draw_triangular(canvas, amplitude, frequency, offset, nb_pts)

# vertical widget scale for amplitude ###############################################################################################
def select_amplitude():
    sel = "Value = " + str(value.get(amplitude))
value_amp = tk.IntVar()
frm = ttk.Frame(root, padding=10)
frm.place(x=100, y=250, anchor=tk.CENTER)
scale_amplitude = tk.Scale(frm, variable=value_amp, command=on_scale_changed,
                           from_ = 200, to = -200, length=400, showvalue=1, tickinterval=50, orient = tk.VERTICAL)
scale_amplitude.pack(anchor=tk.CENTER, padx=10)
label_amplitude = ttk.Label(root, text="Amplitude", font=("Arial"))
label_amplitude.place(x=110, y=480, anchor=tk.CENTER)

# horizontal widget scale for frequency #############################################################################################
def select_frequency():
    sel = "Value = " + str(value.get(frequency))
value_freq = tk.IntVar()
frm = ttk.Frame(root, padding=10)
frm.place(x=600, y=480, anchor=tk.CENTER)
scale_frequency = tk.Scale(frm, variable=value_freq, command=on_scale_changed,
                           from_ = 0, to = 50, length=800, showvalue=1, tickinterval=5, orient = tk.HORIZONTAL)
scale_frequency.pack(anchor=tk.CENTER, padx=10)
label_frequency = ttk.Label(root, text="Frequency", font=("Arial"))
label_frequency.place(x=600, y=530, anchor=tk.CENTER)

# reset function
def reset_values():
    value_amp.set(0)
    value_freq.set(0)
    canvas.delete("line")
# reset button
btn_reset = tk.Button(root, text='Reset', command=reset_values, height=2, width=15)
btn_reset.place(x=1100, y=400, anchor=tk.CENTER)

root.mainloop() 
python tkinter widget waveform
1个回答
0
投票

您可以简单地将两个刻度的

command
选项绑定到同一个函数,并获取该函数内的 amplitudeFrequency 的值,并使用这些值调用
draw_triangular()

...

# updated draw_triangular()
def draw_triangular(canvas, amplitude, frequency, offset, nb_pts):
    canvas.delete("line")  # clear current plot
    xpts = 1000 / (nb_pts-1)
    line = []
    for i in range(nb_pts):
        x = i * xpts
        y = amplitude * ((2 * (i * frequency / nb_pts) % 2 - 1)) + offset
        line.extend((x, y))
    canvas.create_line(line, fill="red", width=3, tag="line")

...

# function to be called when any of the scales is changed
def on_scale_changed(*args):
    amplitude = value_amp.get()
    frequency = value_freq.get()
    draw_triangular(canvas, amplitude, frequency, offset, nb_pts)

...

scale_amplitude = tk.Scale(frm, variable=value_amp, command=on_scale_changed,
                           from_ = 200, to = -200, length=400, showvalue=0, tickinterval=50, orient = tk.VERTICAL)

...

scale_frequency = tk.Scale(frm, variable=value_freq, command=on_scale_changed,
                           from_ = -50, to = 50, length=800, showvalue=0, tickinterval=10, orient = tk.HORIZONTAL)

...

# reset function
def reset_values():
    value_amp.set(0)
    value_freq.set(0)
    canvas.delete("line")

...

请注意,在这种情况下,您不需要调用以下两个after循环

update_amplitude()  # don't call it
update_frequency()  # don't call it
© www.soinside.com 2019 - 2024. All rights reserved.