Passing Python objects as arguments to tkinter's Entry validatecommand - python

I'm using Tkinter's "validatecommand" function to validate inputs from a entry box. I want to pass my class object so that the validation-function can request information from the object. However, it seems that the validatecommand function turns everything I pass into strings. Because of this the validation-function now has __main__.foo object at 0x042981B0 but as string. How can I instead pass the original __main__.foo?
It currently looks like this (pseudo-code):
class foo(object):
def start(program):
self.stuff = 5 #stuff changes while the program is running
tkinter_stuff(program)
def tkinter_stuff(program):
Entry = tkinter.Entry(validatecommand = (window.register(validate_entry), '%P', program))
def validate_entry(entry, program): #checks if current stuff + the amount of staff that would be added over this entry box is <= 20
if int(entry) + program.get_stuff() <= 20:
return True
return False
program = foo() #there are other classes that create their own program and overwrite the one the entry uses, so I can't rely on this one
program.start(program)
actual code:
import tkinter
class foo(object):
def __init__(self):
self.stuff = 5 #stuff changes while the program is running
def start(self, program):
tkinter_stuff(program)
def get_stuff(self):
return self.stuff
def tkinter_stuff(program):
window = tkinter.Tk(className = 'window')
window.geometry('50x50')
print(program, type(program))
Entry = tkinter.Entry(window, width = 10, validate = 'key', validatecommand = (window.register(validate_entry), '%P', program))
Entry.place(x = 10, y = 10)
window.update()
def validate_entry(entry, program): #checks if current stuff + the amount of staff that would be added over this entry box is <= 20
print(program, type(program))
if int(entry) + program.get_stuff() <= 20:
return True
return False
program = foo() #there are other classes that create their own program and overwrite the one the entry uses, so I can't rely on this one
program.start(program)

Try this:
import tkinter as tk
class Entry(tk.Entry):
def __init__(self, master=None, args=tuple(), validatecommand=None, **kwargs):
if validatecommand is not None:
self.args = args
self.callers_function = validatecommand[0]
validatecommand = (root.register(self.validatecommand), *validatecommand[1:])
super().__init__(master, validatecommand=validatecommand, **kwargs)
def validatecommand(self, *args):
return self.callers_function(*args, *self.args)
class Foo:
def __init__(self):
pass
def validate_entry(entry, program):
print(type(entry), type(program))
return True
program = Foo()
root = tk.Tk()
# Make sure it's not `root.register(validate_entry)`:
entry = Entry(root, validate="key", validatecommand=(validate_entry, "%P"),
args=(program, ))
entry.pack()
root.mainloop()
I just made a wrapper class that will call the validatecommand with the args that were specified when creating the Entry.

Related

tkinter: clear inheritanced widget

how to remove the page1's widgets when the next button is pressed, so that only the page2's widgets is shown.
and vice versa if the back button is pressed on page 2, so the widgets don't overlap
from tkinter import *
class Buttons(Button):
def __init__(self,master,**kwargs):
super().__init__(master=master,**kwargs)
self.look = {"fg":"ghost white","bg":"DarkBlue"}
self.config(self.look)
def makeButton(self,name,texts,wide,rows,cols,com):
self.name = name
self.texts = texts
self.wide = wide
self.rows = rows
self.cols = cols
self.com = com
self.name = Buttons(root,text=self.texts,width=self.wide,command=self.com)
self.name.place(x=self.rows,y=self.cols)
class make(Buttons):
def __init__(self, mainFrame):
super().__init__(mainFrame)
self.main_frame = Frame(mainFrame, width=400, height=300)
self.main_frame.place()
self.page1()
def page1(self):
self.makeButton("name1","Page1-widgets1",15,125,30,None)
self.makeButton("name2","Page1-widgets2",15,125,80,None)
self.makeButton("name3","Next",15,125,130,self.page2)
self.makeButton("name4","Exit",15,125,180,exit)
def page2(self):
self.makeButton("name5","Page2-widgets1",15,135,40,None)
self.makeButton("name6","Page2-widgets2",15,135,90,None)
self.makeButton("name7","Page2-widgets3",15,135,140,None)
self.makeButton("name8","Back",15,135,210,self.page1)
def main():
global root
root = Tk()
root.geometry('400x300+50+50')
script = make(root)
root.mainloop()
if __name__ == '__main__':
main()
was gonna comment but not enough reps, so gonna ask my question here. I copied your script and produced same error too. Firstly its not clear what you want to achieve with script so comments between definitions would help a lot!. When continue button is pressed you want current page to be destroyed and new one to be created right ?
enter code here
def frame_elements_remove(self, elements):
self.elements = elements
for self.element in self.elements:
self.element.destroy()
To destroy button it has to object. When I try to check for type of element in list ;
self.frame_elements = []
with
def mainPage(self):
self.frame_elements_remove(self.frame_elements)
self.makeButton("Button1", "Continue", 10, 10, self.page1)
self.makeButton("Button2", "Exit", 10, 80, quit)
self.controler = 1
self.frame_elements = [self.makeButton]
#just put here print for type check
print(type(self.frame_elements[0]))
it returns;
<class 'method'>
So its seems, its not destroyable because it is not object but a method.
Hope it helps!

Using decorator to change the return value of a method

I have a TextBox and a Button. When the user hits Enter, I want the specific button related to the TextBox to be clicked. TextBox and Button are classes. I am passing Button instance into the TextBox, so that we know one should be clicked. Button is a class with 2 methods click() and autoclick().
What I want is for autoclick() to check if Enter key is being pressed and make click() return True if it is. I tried using decorators and wrote this toy program to demonstrate exactly what I tried.
class Button:
def click(self):
return False
def autoclick(self, func, *args):
def wrapper():
func(*args)# This function needes events as it argumnet
return True
return wrapper
b = Button()
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if enterPressed:
if self.button is not None:
self.button.autoclick(self.button.click, events)()
tb = TextBox(button=b)
while True:
tb.box()
if b.click():
print("this needs to be printed when user hits enter")
I don't get any errors but it simply doesn't work.
I think I may know what you want to do now.
Try this out. This is how we usually use decorators.
class Button:
#autoclick # this is how decorators are used.
def click(self):
return False
def autoclick(self, func):
def wrapper(*args):
func(*args)# This function needes events as it argumnet
return True
if enterPressed:
return wrapper
else:
return func
I think the following part is needed to be modified
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if enterPressed:
if self.button is not None:
self.button.autoclick(self.button.click, events)()
to something like:
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if enterPressed:
if self.button is not None:
self.button.click = self.button.autoclick(self.button.click) # this line is changed
UPDATE
I guess maybe you are supposed to mean something like the following:
class Button:
def _click(self): # Maybe this method have events as parameters?
return False
def click(self):
return self._click()
def autoclick(self):
def wrapper(*args):
self._click(*args) # This function needs events as it arguments
self.click = self._click
return True
self.click = wrapper
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if enterPressed:
if self.button is not None:
self.button.autoclick()
Or maybe this one? :
class Button:
def _click(self): # Maybe this method have events as parameters?
return False
def click(self):
return self._click()
def autoclick(self):
def wrapper(*args):
result = self._click(*args) # This function needs events as it arguments
if enterPressed:
return True
return result
self.click = wrapper
class TextBox:
def __init__(self, button=None):
self.button = button
def box(self):
if self.button is not None:
self.button.autoclick()

Tkinter button highlight feature stops working after command is called

I have been working on my first GUI in tkinter - I am using Windows. My goal right now is to have buttons that accomplish these goals:
The buttons are highlighted when moused over.
The button remains highlighted if clicked.
Only one button can be "selected" (click-highlighted) at a time.
I initially thought that I had accomplished this! But I realize now that my work is not complete.
Here is what I am seeing:
I mouse over button A. It becomes highlighted! (GOOD)
I click on button A. It stays highlighted! (GOOD)
I mouse over button B. It becomes highlighted! (GOOD)
I click on button B. It stays highlighted! The highlight from A is removed! (GOOD)
I mouse over button A. It does not highlight. (BAD)
I am calling the default_coloring class function on button A when I click on button B. However, this appears to turn off the highlighting functions of button A, and the button no longer functions correctly according to the three rules I listed at the top.
How do I ensure that the buttons continue to function normally, even after the command is called? Am I approaching this the wrong way?
import tkinter as tk
blue = '#0000BB'
white = '#FFFFFF'
class HoverButton(tk.Button):
def __init__(self, master, position = None, **kw):
tk.Button.__init__(self,master=master,**kw)
self.defaultBackground = self["background"]
self.defaultForeground = self["foreground"]
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
self.bind("<Button-1>", self.hover_click)
self.state = 0
self.position = position
def on_enter(self, e):
if self.state == 0:
self['background'] = self['activebackground']
self['foreground'] = self['activeforeground']
def on_leave(self, e):
if self.state == 2:
self.state = 0
if self.state == 0:
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
def hover_click(self, e):
self.state += 1
self.state = self.state % 3
if self.state == 2:
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
def default_coloring(self):
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
class AddOnFrame(tk.Frame):
def __init__(self, master):
self.selectedbutton = None
super().__init__(master)
games = ['A','B','C']
self.objs = list()
self['bg'] = blue
for i in range(3):
self.objs.append(HoverButton(self,position = i, text = games[i].upper(), activebackground = white,activeforeground = blue,fg = white, bg = blue, borderwidth=0, relief = 'flat', highlightbackground = white))
self.objs[i]['command'] = lambda c=i: self._hover_button_clicked(self.objs[c])
self.objs[i].grid(row = i, column = 0, sticky = tk.W + tk.E)
self.blanklabel = tk.Label(self, text = '', background = white)
self.blanklabel.grid(row = 0, column = 1,rowspan = 10, sticky = tk.N + tk.E + tk.W + tk.S)
self.grid_columnconfigure(1, weight=1, minsize=10)
self.grid_columnconfigure(2, weight=1, minsize=500)
self.grid_columnconfigure(3, weight=1, minsize=500)
self.grid_columnconfigure(4, weight=1, minsize=500)
self.pack(expand = True)
def _hover_button_clicked(self, HoverButton):
self.lastbutton = self.selectedbutton
if self.lastbutton != None:
self.objs[self.lastbutton].default_coloring()
self.selectedbutton = HoverButton.position
window = tk.Tk()
window.geometry('1750x950')
window['bg'] = blue
window.title('Testing')
lf = AddOnFrame(window)
lf['bg'] = blue
window.mainloop()
I think I found the main source of the problem. When another button is clicked, you restore color of the last clicked button, but you do not reset its state. Change your default_coloring function to:
def default_coloring(self):
self.state = 0
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
But you should also prevent default_coloring if same button is pressed again:
def _hover_button_clicked(self, HoverButton):
self.lastbutton = self.selectedbutton
if (self.lastbutton != None) and (self.lastbutton != HoverButton.position):
self.objs[self.lastbutton].default_coloring()
self.selectedbutton = HoverButton.position
After cursory inspection, this sequence seems to be the problem:
When a button is clicked, the AddOnFrame._hover_button_clicked
method is invoked.
AddOnFrame.selectedbutton is initially None, which means the
if-statement in AddOnFrame._hover_button_clicked will not be
executed the first time. This is why the buttons seem to work the
first time you click them, but not after that.
However, the next time it is invoked (the next time a button is
pressed), AddOnFrame.selectedbutton is not None, and will never
be None again, meaning that from now on, every click will result in
a call to that HoverButton's default_coloring method.
default_coloring is invoked as soon as a button is clicked, which
results in a quick flash from the active color to the default color,
and the button does not stay highlighted.
The quick fix:
Basically, don't do the default_coloring stuff. It seems to be hurting you more than it's helping. Not really sure why you're doing it in the first place (all that stuff with setting the command, the lambda, the whole _hover_button_clicked method) since the buttons seem to be setting their colors back to the default just fine when on_leave or hover_click are invoked. You can fix your problem by changing the body of your HoverButton.default_coloring function to this:
def default_coloring(self):
return
The real fix would be some restructuring of your code.
EDIT I'm offering this to help you simplify things:
import tkinter as tk
colors = {
"white": "#FFFFFF",
"blue": "#0000BB"
}
class HoverButton(tk.Button):
def __init__(self, *args, **kwargs):
tk.Button.__init__(self, *args, **kwargs)
self.is_selected = False
self.is_highlighted = False
self["borderwidth"] = 0
self["relief"] = tk.FLAT
self["font"] = ("United Sans Cd Bk", 30)
self["activeforeground"] = colors["blue"]
self["activebackground"] = colors["white"]
self["highlightbackground"] = colors["white"]
self.recolor()
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
self.bind("<Button-1>", self.on_click)
def recolor(self):
self["background"] = [colors["blue"], colors["white"]][self.is_highlighted]
self["foreground"] = [colors["white"], colors["blue"]][self.is_highlighted]
def on_enter(self, *args):
self.is_highlighted = True
self.recolor()
def on_leave(self, *args):
if self.is_selected:
return
self.is_highlighted = False
self.recolor()
def on_click(self, *args):
self.is_selected = not self.is_selected
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Window")
self.geometry("256x256")
self.resizable(width=False, height=False)
self["background"] = colors["blue"]
button_labels = ["A", "B", "C"]
self.buttons = []
for row, button_label in enumerate(button_labels):
button = HoverButton(text=button_label)
button.grid(row=row, column=0, sticky=tk.W)
self.buttons.append(button)
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())

Python tkinter Doubling Checkboxes

I'm currently working on a Synthesizer inside Python for a school project and currently have a really troublesome issue. I have a Stack of Checkboxes which mark when a note is played and on which pitch inside a sequencer. My Problem is that whenever I open two Oscillators inside my synthesizer and put in the Values inside the checkboxes, the checkboxes duplicate their value over the multiple windows, but don't do it for the actual sequence, as in I have two lists with correct values, but the values aren't properly displayed inside the window.
To Replicate the Problem hit "new Oscillator" then click on "Arpeggio", do this a second time and click any Checkbox on the Arppeggio Windows
I know this might be a confusing explanation, but I'm going to link the complete code in the bottom so you can try it out and might know what I'm talking about.
The problem occurs inside the "Arpeggio" Class
import numpy # used for Waveform Calculation
from functools import partial # used for Command Combining
from tkinter import * # used for GUI
from tkinter import ttk # used for GUI
from tkinter import filedialog # used for GUI
np = numpy # Simplifying Libraries
tk = ttk # Simplifying Libraries
fd = filedialog # Simplifying Libraries
root = Tk()
#StartupFunction
def StartUp():
print("")
print("Starting Startup")
app = App(root) # Initializing GUI
print("Finished Startup")
main()
("Exiting Startup")
#Main Program Function
def main():
print("Executing Main")
root.mainloop()
print("Finished Main")
return 0
class Oscillator():
pass
class App(tk.Frame):
OscillatorWindowList = []
OscillatorList = []
SoundInputArrayList = []
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
root.title("PySynth")
root.geometry("984x300")
root.resizable(False, True)
root.maxsize(984,720)
btnNewOscillator = Button(root,text="New Oscillator",command=self.NewOscillator,relief=RIDGE,bg="#2d2d2d",fg="white")
btnNewOscillator.place(x = 8, y = 8+128)
def NewOscillator(self):
print("AddingOscillator")
self.OscillatorList.append(Oscillator())
self.SoundInputArrayList.append(SoundInputArray(root,len(self.OscillatorList)-1,len(self.OscillatorList)-1))
print(self.OscillatorList)
self.OscillatorWindowList.append(OscillatorGUI(root,self.OscillatorList[len(self.OscillatorList)-1],len(self.OscillatorList)))
def EXIT(self):
root.destroy()
#$SoundInputArray
class SoundInputArray():
actv = []
CheckbuttonList = []
CheckButtonFreq = []
i=0
ButtonCount = 32
VolumeSlider = None
btnArpeggio = None
Arpeggio = None
hasArpeggio = False
LFO = None
ArpeggioList = [(0,0)]
def __init__(self,master,oscillatorCount,number):
btnArpeggio = Button(master,text="Arpeggio",command=self.OpenArpeggio,relief=RIDGE,bg="#2d2d2d",fg="white")
btnArpeggio.place(x = 8, y = (1+oscillatorCount)*48 +128 )
def OpenArpeggio(self):
if self.Arpeggio == None:
self.Arpeggio = Arpeggio()
def GetArpeggio(self):
return self.Arpeggio
#$Arpeggio
class Arpeggio():
SoundValueList = None
def __init__(self):
GUI = Toplevel(root)
GUI.title("Arpeggio")
GUI.geometry("480x320")
GUI.resizable(False, False)
GUI.configure(bg="#171717")
self.SoundValueList = np.arange(0,16)
self.DrawUI(GUI)
self.ClearList()
def DrawUI(self,frame):
Button(frame,text="display", command= self.PrintSound, width=11,bg="#171717",).place(x = 4, y = 4)
Button(frame,text="empty", command= self.ClearList).place(x = 96, y = 4)
y = 1
x = 1
checkbuttonList = []
for y in range(1,13):
for x in range(0,16):
updatecommand = partial(self.UpdateList,x,y)
checkbuttonList.append(Checkbutton(frame, variable=self.SoundValueList[x], onvalue=y, offvalue=0,command = updatecommand))
checkbuttonList[len(checkbuttonList)-1].place(x = x*24 + 96, y= y*24 + 8)
def ClearList(self):
for i in range(0,16):
self.SoundValueList[i] = 0
def UpdateList(self,x,value):
if (self.SoundValueList[x] == value):
self.SoundValueList[x] = 0
else:
self.SoundValueList[x] = value
self.PrintSound()
def PrintSound(self):
print(self.SoundValueList)
def GetList(self):
print(self.SoundValueList)
return self.SoundValueList
StartUp() # Initiate Program

Dynamically Created Button Appears Over Another One - Python Tkinter

I have a problem. I dynamically created buttons using a class. Each button is stored in a list, so I can use them later by indexing them. I am having trouble placing/displaying the buttons, though. When I create one button, it shows up perfectly. When I create another one, for some reason it appears over the first one. Help to fix this would be appreciated. Thanks!
Here is the code:
import tkinter as tk
window = tk.Tk()
window.geometry('800x600')
placeX = 20
placeY = 20
bl = []
def direction(type_):
pass
class SideBar():
def __init__(self, name):
global window
global placeX
global placeY
global bl
self.name = name
self.placeX = placeX
self.placeY = placeY
self.bl = bl
self.bl.append(self.name)
print(self.bl)
def create_folder(self, index):
self.bl[index] = tk.Button(window, text = self.name, command = lambda: direction(self.name))
self.bl[index].config(height = 3, width = 6)
self.bl[index].place(x = self.placeX, y = self.placeY)
self.placeY += 100
Computer = SideBar('Computer')
Documents = SideBar('Documents')
Computer.create_folder(0)
Documents.create_folder(1)
window.mainloop()
I think the problem is somewhere in the create_folder function.
You probably meant to use a class variable as opposed to an instance attribute. A class variable holds the data shared among all instances of a class, in fact, it can have a value as long as there's a class definition. Whereas an instance attribute can have values specific to a singular instance of a class, typically in the format self.attribute.
The way you are trying to use self.placeY fits the typical use of class variable. Remove:
self.placeY = placeY
add:
class SideBar():
...
placeY = placeY #assign global placeY's value to Sidebar.placeY
...
finally, replace:
self.placeY += 100
with:
SideBar.placeY += 100
You are creating two different instances of a class. Both have their own local variable. Create one instance and use something like this:
import tkinter as tk
window = tk.Tk()
window.geometry('800x600')
placeX = 20
placeY = 20
bl = []
def direction(type_):
pass
class SideBar():
def __init__(self):
global window
global placeX
global placeY
global bl
self.name = []
self.placeX = placeX
self.placeY = placeY
self.bl = []
self.bl.append(self.name)
def create_folder(self, index, name):
self.name.append(name)
self.bl.append(tk.Button(window, text = self.name[-1], command = lambda: direction(self.name)))
self.bl[-1].config(height = 3, width = 6)
self.bl[-1].place(x = self.placeX, y = self.placeY)
self.placeY += 100
side_bar = SideBar()
#Documents = SideBar('Documents')
side_bar.create_folder(0, 'Computer')
side_bar.create_folder(1, 'Documents')
window.mainloop()

Categories