Disabling tkinter ttk scale widget - python

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)

Related

Is it possible to center a tkinter.simpledialog window?

I'm including a conditional password capture in a larger script. The code looks like this:
if thing: # found token, don't need password
do stuff
else: # no token, get password in popup
try:
import tkinter as tk
import tkinter.simpledialog
tk.Tk().withdraw()
passwd = tkinter.simpledialog.askstring("Password", "Enter password:", show="*")
return passwd
functionally it's fine, but most of my users are on monitors at 3840x1600 resolution, so when the dialog pops up at the top left it's easy to miss.
Is there a brief way to override the simpledialog class to tell it to appear at a certain X/Y on the monitor, or is my only option to build a full mainloop()?
so when the dialog pops up at the top left it's easy to miss.
If you specify the optional argument parent, the dialogbox will appear in the middle of your window and gets the focus internally. Make sure your window is mapped via .update_idletasks() to get corresponding coordinates. You may consider also to make your window transparent instead of withdraw.
import tkinter as tk
import tkinter.simpledialog
def ask_pw():
pw = tkinter.simpledialog.askstring("Password",
"Enter password:",
show="*",
parent=root)
root = tk.Tk()
root.geometry('250x250+500+500')
root.update_idletasks()
ask_pw()
#root.withdraw()
root.mainloop()
As the dialog is placed relative to the position of its parent, so you can center its parent, i.e. root window in your case:
import tkinter as tk
from tkinter import simpledialog
root = tk.Tk()
# center root window
root.tk.eval(f'tk::PlaceWindow {root._w} center')
root.withdraw()
# set parent=root
passwd = simpledialog.askstring('Password', 'Enter password:', show='*', parent=root)
I couldn't find a way to override any of the classes to directly modify the underlying _QueryString class (also it is private so I didn't want to use that and I probably wouldn't have found an easy way with that anyways). So I just wrote a custom askstring function. The functionality is such that you need to provide the parent and the geometry and it will first schedule an inner function (could be a proper outer one but it should be fine) to get the last widget in the widgets of that parent and after the scheduled time it should be the new dialog so it should get that. (Check in place to check if that widget is derived from dialog.Dialog which is from what the _QueryString inherits). Then just change the geometry (because up the inheritance chain there is Toplevel which obviously has a geometry method):
import tkinter as tk
import tkinter.simpledialog as dialog
def askstring(title, prompt, geometry='', **kwargs):
def change_geometry():
if not geometry:
return
widget = kwargs['parent'].winfo_children()[-1]
if isinstance(widget, dialog.Dialog):
widget.geometry(geometry)
if 'parent' in kwargs:
kwargs['parent'].after(10, change_geometry)
return dialog.askstring(title, prompt, **kwargs)
root = tk.Tk()
root.withdraw()
askstring('Title', 'Prompt', geometry='300x200+200+200', parent=root)
Useful:
source code for tkinter.simpledialog which is what I used to solve this (also from previous experience)

How to configure map for all ttk widgets except TButton?

I am creating a GUI with Tkinter and ttk, and I'm trying to create a custom map to make ttk widgets blue on hover. I could apply that to all widgets with passing "." as the first argument to ttk.Style().map(...).
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
style = ttk.Style()
style.map(".", background=[("active", "blue")])
button = ttk.Button(root, text="An example button")
button.pack()
scrollbar = ttk.Scrollbar(root)
scrollbar.pack()
root.mainloop()
But now I want to exclude TButton from this query. That is, I need to make all widgets but TButton blue on hover. How can I do that?
Passing ".!TButton" as well as "!TButton" instead of "." has no effect.
There is a root style whose name is '.'. To change some feature's default appearance for every widget, you can configure this style. For example, let's suppose that you want all text to be 12-point Helvetica (unless overriden by another style or font option).
So we can override it by simply adding another style:
style.map('TButton',background=[('active','#f0f0f0')])
Keep a note that you might want to do this for every style that you wish to set to default too.
Take a read here for more info.

How to (re)enable tkinter ttk Scale widget after it has been disabled?

I'm trying to reenable my Scale widget in Python tkinter after disabling it but it doesn't work. I tried multiple options but none are working.
s.state(["normal"]);
s.configure(state='normal');
The error that I'm getting is saying:
_tkinter.TclError: unknown option "-state"
Since you use ttk widget, the state that you needed to reenable your widget is !disabled.
According to ttk states:
A state specification or stateSpec is a list of state names, optionally prefixed with an exclamation point (!) indicating that the bit is off.
try:
import tkinter as tk
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
root = tk.Tk()
scale = ttk.Scale(root)
scale.pack()
# disable scale
scale.state(['disabled'])
# enable scale
scale.state(['!disabled'])
root.mainloop()

Entry widget validation difference between classic and ttk widgets

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.

Checkbutton and Scale conflict

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.

Categories