Tkinter - 单击默认情况下未设置的按钮后无法获取单选按钮值

Posted

技术标签:

【中文标题】Tkinter - 单击默认情况下未设置的按钮后无法获取单选按钮值【英文标题】:Tkinter - Cannot get radiobutton values after clicking a button which is not set by default 【发布时间】:2022-01-14 11:02:22 【问题描述】:

我正在尝试从 CSV 文件中读取内容并创建一个设置窗口,我可以在其中设置单选按钮中的新值并保存它们。为此,我创建了一个循环,该循环从 CSV 中创建 N-1 行,其内容为:

Row0;T
Row1;M
Row2;-
Row3;C
Row4;O

问题是当我尝试通过单击不同的单选按钮来获取新值时,我没有得到该值。我尝试过使用 select、invoke 方法,创建一个打印所选内容的函数,但没有返回正确的值。

我认为问题出在第 44 行和第 88 行之间

import pandas as pd
import os
from tkinter import * 
import tkinter as tk
from tkinter import ttk
import platform

class Application(ttk.Frame):
    def __init__(self):
        root = Tk()
        root.geometry("555x490")
        root.resizable(0,0)
        self.notebook = ttk.Notebook(root)
        self.notebook.grid(sticky='news')
        self.notebook.pressed_index = None
        self.container = Frame(self.notebook)
        self.container.grid(sticky='news')
        self.notebook.add(self.container)

        self.canvas = Canvas(self.container, width=530, height=470)
        self.scroll = Scrollbar(self.container, command=self.canvas.yview)
        self.canvas.config(yscrollcommand=self.scroll.set, scrollregion=(0,0,100,1000))
        self.canvas.grid(row=0, column=0, sticky="news")
        self.scroll.grid(row=0, column=1, sticky='ns')

        self.frame = Frame(self.canvas, width=555, height=1000)
        self.canvas_window = self.canvas.create_window(555, 500, window=self.frame)

        self.container.bind("<Configure>", self.onFrameConfigure)                       #bind an event whenever the size of the container frame changes.
        self.canvas.bind("<Configure>", self.onCanvasConfigure)                       #bind an event whenever the size of the canvas frame changes.
            
        self.container.bind('<Enter>', self.onEnter)                                 # bind wheel events when the cursor enters the control
        self.container.bind('<Leave>', self.onLeave)                                 # unbind wheel events when the cursorl leaves the control

        self.onFrameConfigure(None)                                                 #perform an initial stretch on render, otherwise the self.scroll region has a tiny border until the first resize

        archive = pd.read_csv(os.getcwd()+"\\test.csv", sep=';', engine='python')
        y=0
        self.radioInbound = []
        self.radioInbound1 = []
        self.radioInbound2 = []
        self.radioInbound3 = []
        self.radioInbound4 = []
        for self.element in range(len(archive.index)):
            y+=20
            # El nombre de cada fila
            self.label = tk.Label(self.frame, text=(str(self.element+1)+' - '+archive.iloc[self.element,0])).grid(column=20, row=y)

            # Radio buttons para seleccionar el valor
            self.radioInbound.append(tk.IntVar())
            #print(self.radioInbound)
            if archive.iloc[self.element,1] == 'M':
                self.radioInbound[self.element].set(1)
            elif archive.iloc[self.element,1] == 'C':
                self.radioInbound[self.element].set(2)
            elif archive.iloc[self.element,1] == 'O':
                self.radioInbound[self.element].set(3)
            else: 
                self.radioInbound[self.element].set(4)
        # Pinta los 4 radiobutton por cada self.elemento existente en el inbound
            self.radioInbound1.append(tk.Radiobutton(self.frame, text='M', variable=self.radioInbound[self.element], value=1, command=self.selected()))
            self.radioInbound1[self.element].grid(column=280,row=y)

            self.radioInbound2.append(tk.Radiobutton(self.frame, text='C', variable=self.radioInbound[self.element], value=2, command=self.selected()))
            self.radioInbound2[self.element].grid(column=310,row=y)

            self.radioInbound3.append(tk.Radiobutton(self.frame, text='O', variable=self.radioInbound[self.element], value=3, command=self.selected()))
            self.radioInbound3[self.element].grid(column=340,row=y)         

            self.radioInbound4.append(tk.Radiobutton(self.frame, text='-', variable=self.radioInbound[self.element], value=4, command=self.selected()))
            self.radioInbound4[self.element].grid(column=370,row=y)
            #print(self.radioInbound1)
            
        # Mira el valor de cada celda para invocar el radiobutton correspondiente
            #if archive.iloc[self.element,1] == 'M':
            #    self.radioInbound1[self.element].select() ##hay que invocar al que esté en cada momento en el archivo csv
            #elif archive.iloc[self.element,1] == 'C':
            #    self.radioInbound2[self.element].select() 
            #elif archive.iloc[self.element,1] == 'O':
            #    self.radioInbound3[self.element].select()
            #else: 
            #    self.radioInbound4[self.element].select()
    
        #print(self.radioInbound)

        root.mainloop()
    def selected(self):
        #print(self.radioInbound[self.element].get())
        pass
        
    def onFrameConfigure(self, event):                                              
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))                 #whenever the size of the frame changes, alter the self.scroll region respectively.

    def onCanvasConfigure(self, event):
        '''Reset the canvas window to encompass inner frame when required'''
        canvas_width = event.width
        self.canvas.itemconfig(self.canvas_window, width = canvas_width)            #whenever the size of the canvas changes alter the window region respectively.

    def onMouseWheel(self, event):                                                  # cross platform self.scroll wheel event
        if platform.system() == 'Windows':
            self.canvas.yview_scroll(int(-1* (event.delta/120)), "units")
        elif platform.system() == 'Darwin':
            self.canvas.yview_scroll(int(-1 * event.delta), "units")
        else:
            if event.num == 4:
                self.canvas.yview_scroll( -1, "units" )
            elif event.num == 5:
                self.canvas.yview_scroll( 1, "units" )
    
    def onEnter(self, event):                                                       # bind wheel events when the cursor enters the control
        if platform.system() == 'Linux':
            self.canvas.bind_all("<Button-4>", self.onMouseWheel)
            self.canvas.bind_all("<Button-5>", self.onMouseWheel)
        else:
            self.canvas.bind_all("<MouseWheel>", self.onMouseWheel)

    def onLeave(self, event):                                                       # unbind wheel events when the cursorl leaves the control
        if platform.system() == 'Linux':
            self.canvas.unbind_all("<Button-4>")
            self.canvas.unbind_all("<Button-5>")
        else:
            self.canvas.unbind_all("<MouseWheel>")

app = Application()

【问题讨论】:

提供minimal reproducible example 【参考方案1】:

您在创建这些单选按钮的 for 循环中使用了相同的实例变量 self.element。所以在for循环之后,self.element会引用self.radioInbound的最后一项。

其实实例变量self.element 是不必要的。您需要在创建这些单选按钮时使用分配给command 选项的默认值lambda 将这些单选按钮的相应索引传递给self.selected()

### actually don't need to inherited from ttk.Frame ###
#class Application(ttk.Frame):
class Application:
    def __init__(self):
        ...
        # just use a local variable instead of instance variable
        for idx in range(len(archive.index)):
            y+=20
            # El nombre de cada fila
            self.label = tk.Label(self.frame, text=(str(idx+1)+' - '+archive.iloc[idx,0])).grid(column=20, row=y)

            # Radio buttons para seleccionar el valor
            self.radioInbound.append(tk.IntVar())
            #print(self.radioInbound)
            if archive.iloc[idx,1] == 'M':
                self.radioInbound[idx].set(1)
            elif archive.iloc[idx,1] == 'C':
                self.radioInbound[idx].set(2)
            elif archive.iloc[idx,1] == 'O':
                self.radioInbound[idx].set(3)
            else:
                self.radioInbound[idx].set(4)
            # Pinta los 4 radiobutton por cada idxo existente en el inbound
            ### used lambda with argument with default value ###
            self.radioInbound1.append(tk.Radiobutton(self.frame, text='M', variable=self.radioInbound[idx], value=1, command=lambda i=idx:self.selected(i)))
            self.radioInbound1[idx].grid(column=280,row=y)

            self.radioInbound2.append(tk.Radiobutton(self.frame, text='C', variable=self.radioInbound[idx], value=2, command=lambda i=idx:self.selected(i)))
            self.radioInbound2[idx].grid(column=310,row=y)

            self.radioInbound3.append(tk.Radiobutton(self.frame, text='O', variable=self.radioInbound[idx], value=3, command=lambda i=idx:self.selected(i)))
            self.radioInbound3[idx].grid(column=340,row=y)

            self.radioInbound4.append(tk.Radiobutton(self.frame, text='-', variable=self.radioInbound[idx], value=4, command=lambda i=idx:self.selected(i)))
            self.radioInbound4[idx].grid(column=370,row=y)
        ...

    ### added argument idx ###
    def selected(self, idx):
        print(idx, self.radioInbound[idx].get())

    ...

【讨论】:

以上是关于Tkinter - 单击默认情况下未设置的按钮后无法获取单选按钮值的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有按钮单击python tkinter的情况下进入另一个页面

单击时如何将按钮值从自定义小部件传递到 tkinter 中的主应用程序

在 tkinter python 中给单选按钮一个默认值

win7待机后无网络连接

tkinter 按钮回调中的 Lambda 函数

研究在 tkinter 中单击按钮后返回按钮文本的方法