I have a problem with my Checkbutton widget. Every time I select it the slider on the scale widget above moves by itself to 1, deselecting the Checkbutton widget will set the Scale widget to 0. Both widgets are not intended to be related with each other in any way yet for some reason changing values in one of them affect the other. Can anyone explain to me why this is happening and how can I avoid such problems in the future?
tk.Label(f7, text=("Jakość")).grid(row=3, column=0)
self.jakosc=tk.Scale(f7, orient='horizontal', variable=jakosc)
self.jakosc.grid(row=3, column=1)
self.rozpinany_sweter=tk.IntVar()
tk.Checkbutton(f7, text='Rozpinany',variable=rozpinany_sweter).grid(row=4, column=1)
In this example
the slider is set to 56, after checking the checkbox on the slider sets itself to 1.
EDIT: MCVE provided:
import tkinter as tk
from tkinter import ttk as ttk
RS=0
Q=0
class Aplikacja(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.grid()
self.create_widgets()
def create_widgets(self):
self.jakosc=tk.Scale(root, orient='horizontal', variable=Q)
self.jakosc.grid()
self.rozpinany_sweter=tk.IntVar()
tk.Checkbutton(root, variable=RS).grid()
root= tk.Tk()
app= Aplikacja(root)
root.mainloop()
The variable= parameter to Tk widgets MUST be a Tk variable (created by Tk.IntVar() or similar calls). Your code passes Q and RS, which are ordinary Python variables; the one Tk variable you create is pointless, because you never use it anywhere. Tk variables have a special ability not possessed by Python variables: they can have watchers attached to them, which allows widgets to automatically update themselves when the variable is modified.
The Python representation of a Tk variable is basically just the Tk name of the variable. Both Q and RS happen to have the same value, so they're both referring to the same variable on the Tk side - that's why your scale and checkbox appear to be linked.
Related
I bring up here a problem, that's been there for ages, but is obviously still not solved and older workarounds don't work on my Python 3.7.2 (64-bit on Win10).
I have this code:
import tkinter as tk
import tkinter.simpledialog
# message box to enter a value where to set the scale to
class EnterValueBox(tk.simpledialog.Dialog):
def body(self, master):
self.e = tk.Entry(self, width=10)
self.e.pack(pady=5)
return self.e # initial focus
def apply(self):
print(self.e.get())
# callback to open message box
def enterValue(event):
EnterValueBox(root, title="Enter Value 0..100")
# create window with scale widget
root = tk.Tk()
scale = tk.Scale(root, orient=tk.HORIZONTAL, from_=0, to=100)
scale.pack()
# unbind any button-3 events
scale.unbind("<ButtonPress-3>")
scale.unbind("<ButtonRelease-3>")
scale.unbind("<Button-3>")
# bind button-3 press event to open message box
scale.bind("<ButtonPress-3>", enterValue)
tk.mainloop()
It creates a window with a single scale widget. I want to bind ButtonPress-3 to open a little dialog to directly enter a new value. The code only prints that value to the shell, but the example shows, that the unbind is not working, because after printing the value, the dialog box is closed (when the user clicks OK) and then the default binding is executed, which sets the slider, where the user clicked in the trough of the slider widget.
I tried the workaround from Deleting and changing a tkinter event binding with a PatchedScale widget (instead of the PatchedCanvas shown there), but that didn't make any difference.
Any help would be greatly appreciated!
The default bindings are not on the widget, they are on the widget class. Calling unbind on a widget for which there is no widget-specific binding won't have any effect.
If you don't want the default binding to run after your widget-specific binding, the normal technique is to have your bound function return the string break.
def enterValue(event):
EnterValueBox(root, title="Enter Value 0..100")
return "break"
I tried to isolate the problem as best as i could.
Lets assume that there are 3 tk/ttk widgets.
Why doesn't the tk_spinbox unselect the selected listbox entry and why does the ttk_spinbox?
I dont want to unselect the items whenever the ttk_spinbox is pressed. Is there an workaround in order to get an equal behaviour for ttk_spinbox and tk_spinbox?
Here is the code:
import tkinter as tk
import tkinter.ttk as ttk
masterframe = tk.Tk()
listbox = tk.Listbox(masterframe, height=5, selectmode='multiple')
listbox.pack(padx=10, pady=10)
listbox.insert(tk.END, 'blubb_1')
listbox.insert(tk.END, 'blubb_2')
tk_spinbox = tk.Spinbox(masterframe,from_=10, to=20, increment=2)
tk_spinbox.pack(padx=10, pady=10)
ttk_spinbox = ttk.Spinbox(masterframe,from_=10, to=20, increment=2)
ttk_spinbox.pack(padx=10, pady=10)
masterframe.mainloop()
The "why" is simply, that's how they are designed to work. When you interact with a ttk spinbox, the spinbox value is automatically selected. This doesn't happen for the tk spinbox. By default only one thing can have the selection at a time, so the listbox loses the selection when the spinbox gains the selection.
If you don't want to have the listbox lose the selection you can set the exportselection option to False on the listbox and/or the ttk spinbox.
I'm fairly new to python and try to build a simple GUI following an object oriented approach. Therefor I let my widget classes inherit from tk.Frame and create an application controller to build the GUI.
My application contains the following two files:
mainModule.py
# coding: utf8
from testPackage import myGUI
import Tkinter as tk
root = tk.Tk() # Main window
my_gui = myGUI.MainApplication(root)
root.mainloop() # Hold window open until we close it
myGUI.py
# coding: utf8
import Tkinter as tk
# Application initializer
class MainApplication(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, master)
self.configure_gui()
self.pack() # <--- CODE IN QUESTION
self.create_widgets()
def configure_gui(self):
self.master.title("Any title")
self.master.geometry('800x600')
self.master.minsize(600, 100)
def create_widgets(self):
self.main_window = MainWindow(self)
self.main_window.pack()
# Main Data Window
class MainWindow(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, master)
self.main_window = tk.Label(self, text="This is a test")
self.main_window.pack(side="top", fill="x")
Initially running my code without self.pack() (marked as #Code in question) in the MainApplication class definition gave me only the root basic window without the Label created from MainWindow class.
As simple as the answer may be but why?
I used a few sources to get into the topic inbefore and some of them didn't use any geometry manager on the root window (or i'm to inexpierienced to see it. Source below for examples).
Source:
https://www.begueradj.com/tkinter-best-practices.html
http://python-textbok.readthedocs.io/en/latest/Introduction_to_GUI_Programming.html#putting-it-all-together
Tkinter example code for multiple windows, why won't buttons load correctly?
Only after reading following answers regarding widget creation I came aware of the missing part:
Best way to structure a tkinter application
creating a custom widget in tkinter
Figuring that pack() on initialization would be the same as
MainApplication(root).pack(side="top", fill="both", expand=True)
or
Example(root).place(x=0, y=0, relwidth=1, relheight=1)
for a grid based layout.
I guess it's a very simple or obvious element i don't see relating to inheritance but i'm not entirely sure why have to pack() the MainApplication Frame and furthermore why it seems to work for example without this step.
Because MainWindow inherits from Frame, it is itself a frame. If you never call pack, place, or grid on it, it will be invisible. This is no different than if you created a button or scrollbar or any other widget and then don't call one of those methods on it.
Since all of the other widgets are a children of this frame, they will be invisible since their parent is invisible.
Somewhat unrelated to the question being asked, self.pack() is a code smell. Generally speaking, a class should never call pack, place or grid on itself. This tightly couples itself to the caller (meaning, this widget has to know that the caller is using one of those methods).
In other words, if you decide that MainApplication wants to switch from pack to grid for all of its children, you can't just update MainApplication, you also have to update MainWindow. In this case, the root window has only one child so the problem is fairly small, but by doing it this way you are starting a bad practice that will eventually cause you problems.
The rule of thumb is that the function that creates a widget should be responsible for adding it to the screen. That means that it would be better to do it like this:
root = tk.Tk()
my_gui = myGUI.MainApplication(root)
my_gui.pack(fill="both", expand=True)
You would then need to remove self.pack() from MainApplication.__init__.
You also have some very misleading code that might be contributing to the confusion. Take a look at this code:
# Main Data Window
class MainWindow(tk.Frame):
def __init__(self, master):
...
self.main_window = tk.Label(self, text="This is a test")
self.main_window.pack(side="top", fill="x")
Notice how you have a class named MainWindow. Within that you have a label that is named self.main_window, but self.main_window is not a MainWindow. This is especially confusing since the function that creates MainWindow also creates an instance variable named self.main_window.
You might want to consider renaming this label to be something else (eg: self.label, self.greeting, etc).
I have discovered an unexpected difference in Entry widget validation between classic and ttk widgets in Python 3.5.
Using classic widgets:
from tkinter import *
def validate(reason):
print("--> validate:", reason)
return(True)
def change():
var.set("data")
root = Tk()
vc = root.register(validate)
var = StringVar()
Entry(root, textvariable = var, validate = "all", validatecommand = (vc, "%V")).pack()
Button(root, text = "Change", command = change).pack()
root.mainloop()
Using ttk widgets:
from tkinter import *
from tkinter.ttk import *
... same code as above
With classic widgets, when the "Change" button is pressed, the validate function is called with reason == "forced", which seems to comply with the Tk doc. With ttk widgets, when the "Change" button is pressed, the validate function is not called. Otherwise the validate function seems to have the equivalent behavior for both cases. Anybody have an idea if this is a bug or a feature?
It's a feature. According to the official ttk documentation:
DIFFERENCES FROM TK ENTRY WIDGET VALIDATION
The standard Tk entry widget automatically disables validation (by
setting -validate to none) if the -validatecommand or -invalidcommand
modifies the entry's value. The Tk themed entry widget only disables
validation if one of the validation scripts raises an error, or if
-validatecommand does not return a valid boolean value. (Thus, it is not necessary to re-enable validation after modifying the entry value
in a validation script).
In addition, the standard entry widget invokes validation whenever the
linked -textvariable is modified; the Tk themed entry widget does not.
I am trying to disable all of the (ttk) widgets in a frame, but it appears that the scale widget is giving me some trouble, as it throws the following exception:
_tkinter.TclError: unknown option "-state"
Some relevant code:
import tkinter as tk
from tkinter import ttk
def disable_widgets(parent):
for child in parent.winfo_children():
child.config(state = 'disabled')
root = tk.Tk()
# Frame full of widgets to toggle
frame_of_widgets = ttk.Frame(root)
frame_of_widgets.pack()
# Button to be disabled
button_to_disable = ttk.Button(frame_of_widgets)
button_to_disable.pack()
# Entry to be disabled
entry_to_disable = ttk.Entry(frame_of_widgets)
entry_to_disable.pack()
# Scale to be disabled
scale_to_disable = ttk.Scale(frame_of_widgets)
scale_to_disable.pack()
# Button that disables widgets in frame
disable_button = ttk.Button(root,text="Disable",command= lambda: disable_widgets(frame_of_widgets))
disable_button.pack()
root.mainloop()
It works for the button and entry, but not for the scale. I thought one of the benefits of ttk was making widgets more uniform with common methods and attributes, so I am guessing perhaps I am accessing all three of these widgets incorrectly?
For ttk widgets you use the state method. The state method for buttons and entry widgets are just a convenience function to mimic the standard button and entry widgets.
You can rewrite your function like this:
def disable_widgets(parent):
for child in parent.winfo_children():
child.state(["disabled"])
ttk states are mentioned in the ttk documentation here (though the description borders on useless): https://docs.python.org/3.1/library/tkinter.ttk.html#widget-states
another way:
scale_to_disable.configure(state='disabled') # 'normal'
You can consider that set the breakpoint at the configure of the class Scale (from tkinter.ttk import Scale) may get some helpful.
The following is part of the code to intercept the class Scale
class Scale(Widget, tkinter.Scale):
...
def configure(self, cnf=None, **kw):
if cnf:
kw.update(cnf)
Widget.configure(self, **kw)