Why does only one button appear when creating them inside a class? - python

I have a class that contains a single button and its constructor packs it onto the root window. Why does only one button get packed onto the window when multiple objects of this class are created? Shouldn't every object have its own button?
import tkinter as tk
root = tk.Tk()
root.geometry("500x500")
class My_button:
button = tk.Button(root, width=10, height=5)
def __init__(self):
self.button.pack()
button1 = My_button()
button2 = My_button()
button3 = My_button()
root.mainloop()

You've defined button outside of __init__ so it is a class variable. That means it is shared by every instance of the class.
If you want each instance to have its own button, either inherit from tk.Button so that it is a button widget, or create the instance inside of __init__.
For more information see Class and Instance Variables in the official Python documentation.

Related

External window is empty with "tk.Toplevel" and "parent"

When I click on a label to open an external window (tertiary.py), the window opens normally but is empty inside. I don't get errors, but I don't see Tkinter widgets and miscellaneous items.
This is the secondary window where there is the label on which I click in order to open an external window. I add, for information purposes only, that this window is called secondary because it is displayed in a frame (with a vertical menu on the left) where the main window is called home = tk.Tk(). The secondary window opens and displays correctly in the frame.
Inside the secondary window there is the label thanks to which I want to open an external window (not in the frame) called tertiary
How can I solve?
secondary.py
import tkinter as tk
from tkinter import *
from tkinter import ttk
import other_options
from other_options import form_other_options
class Page2(tk.Frame):
def __init__(self, master, **kw):
super().__init__(master, **kw)
self.configure(bg='white')
bar1=Frame(self, width=2200, height=35, background="#960000", highlightbackground="#b40909", highlightthickness=0) #grigio chiaro #b40909
bar1.place(x=0, y=0)
other_options = Label(self, text="Other Options", bg="#960000", foreground='white', font='Ubuntu 10')
other_options.place(x=1025, y=7)
other_options.bind("<Button-1>", lambda event: other_options.form_other_options(self))
tertiary.py
from tkinter import *
from tkinter import ttk
import tkinter as tk
def form_other_options(parent):
other_options = tk.Toplevel(parent)
other_options.title("Other options")
other_options.geometry("1000x800")
other_options.config(bg="white")
other_options.state("normal")
other_options.transient(parent)
class checkbox():
def __init__(self, master, **kw):
super().__init__(master, **kw)
labelframe1 = LabelFrame(self, text="checkbox", width=600,height=190, bg="white", foreground='#e10a0a')
labelframe1.place(x=10, y=13)
Checkbutton1 = IntVar()
Button1 = Checkbutton(self, text = "option1",
variable = Checkbutton1,
onvalue = 1,
offvalue = 0,
height = 2,
width = 10)
Button1.pack()
other_options.mainloop()
The first reason why the window is empty is that you never create an instance of the class checkbox, so the code that creates the widgets never runs.
So, the first step is to make sure you create an instance of that class:
other_options = tk.Toplevel(parent)
...
cb = checkbox(other_options)
When you do that, you will discover other bugs in your code. The first positional argument when creating a tkinter widget must be another widget. If you want the widget to be inside a Toplevel, the parent needs to be the toplevel or a descendant. You're using self which isn't a widget at all.
A simple fix is to use master instead of self, since you're passing the toplevel window as the master parameter:
labelframe1 = LabelFrame(master, ...)
Button1 = Checkbutton(master, ...)
You also need to remove the call to other_options.mainloop(). Unless there is a very compelling reason to do otherwise, an entire application should only call mainloop() exactly once.
There are other problems in the code that, strictly speaking, aren't related to the question being asked. For example, you're importing tkinter twice: once as import tkinter as tk and once as from tkinter import *. You only need to import tkinter once, and PEP8 recommends to use the first form:
import tkinter as tk
You'll need to add the prefix tk. to every place where you call a tkinter function. This will help make your code easier to understand and helps prevent pollution of the global namespace.
You also should use PEP8 naming standards. Specifically, class names should begin with an uppercase character. This will also make your code easier to understand.
There's also no reason why your checkbox class should be indented. You should move it out of the function.
Also, instead of checkbox not inheriting from anything, and then creating a LabelFrame, you should just inherit from LabelFrame. That way you can use your class more like a real widget, and would enable you to use self rather than master when creating the other widgets.
class Checkbox(tk.LabelFrame):
def __init__(self, master, **kw):
super().__init__(master, **kw)
...
button1 = tk.Checkbutton(self, ...)
...
There are probably other problems, but those are the most obvious.

I get the error _tkinter.TclError: bad window path name ".!button" when I destroy the button

from tkinter import *
master=Tk()
class check:
def __init__(self,root):
self.root=root
self.b1=Button(root,text="Click me",command=self.undo)
self.b2=Button(root,text="Again",command=self.click)
def click(self):
self.b1.place(relx=0.5,rely=0.5)
def undo(self):
self.b1.destroy()
self.b2.place(relx=0.2,rely=0.2)
c=check(master)
c.click()
master.mainloop()
This is my code. I get _tkinter.TclError: bad window path name ".!button" error only when I use destroy method. But I want to delete previous button when another button appears.What should I do?
What are you doing? When you click the "Click me" button (and call the self.undo method, where the self.b1 button is destroyed) and then click the "Again" button (and call the self.click method, which tries to place already destroyed self.b1 button), you get the error, that the button does not exist. Of course, it doesn't because you have destroyed it.
It looks like you meant to hide the button. If you intended to do this, then you could just use .place_forget() method (there are also .pack_forget() and .grid_forget() methods for pack and grid window managers, respectively), that hides the widget, but not destroys it, and hence you would be able to restore it again when you need.
Here is your fixed code:
from tkinter import *
master = Tk()
class check:
def __init__(self, root):
self.root = root
self.b1 = Button(root, text="Click me", command=self.undo)
self.b2 = Button(root, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
c = check(master)
c.click()
master.mainloop()
I can also give you a piece of advice about the implementation:
1) You should write the code according to the PEP8 style; classes should be named in the CamelCase.
2) You should inherit your Tkinter app class(es) either from Tk (usage is shown below) Toplevel(the same as Tk, but use ONLY for child windows), Frame class (almost the same as for Tk, but you need to pack/grid/place that Frame in a window).
3) It's better to create the widgets in a separate function (it helps while developing complex and big apps).
4) It's recommended to write if __name__ == "__main__": condition before creating the window (if you do like this, you will be able to import this code from other modules, and the window won't open in that case).
Here is an example:
from tkinter import *
class Check(Tk):
def __init__(self):
super().__init__()
self.create_widgets()
self.click()
def create_widgets(self):
self.b1 = Button(self, text="Click me", command=self.undo)
self.b2 = Button(self, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
if __name__ == "__main__":
Check().mainloop()
After you destroyed button b1 in the undo(self) function tkinter cannot access it anymore and will be confused when you try to place is somewhere in the click(self) function.
To make button b1 only disappear visually you could place it outside of the window instead of destroying it. To do so replace
self.b1.destroy()
with
self.b1.place(relx=-5, rely=0)
This will move the button b1 far to the left, where it cannot be seen.
When calling the click(self) function, the button will reappear, because it will be moved inside the window again.

Python, accessing widget items from outside of a class

I have an auto generated code which generates a GUI that has various widgets in it. One of the widget is a ScrolledListBox. A part of the code is shown below:
class New_Toplevel_1:
def __init__(self, top=None):
self.Scrolledlistbox4.configure(background="white")
self.Scrolledlistbox4.configure(font="TkFixedFont")
self.Scrolledlistbox4.configure(highlightcolor="#d9d9d9")
self.Scrolledlistbox4.configure(selectbackground="#c4c4c4")
self.Scrolledlistbox4.configure(width=10)
I want to access the Scrolledlistbox4 from outside this class. So for example, I would like to write to write a function that updates the ScrolledListBox whenever I call it. I am relatively new to python and would like to know how can I accomplish this.
You need to first create a Scrolledlistbox4 object as an attribute:
self.scrolled_listbox = Scrolledlistbox4(...)
then you can do all configures in outermost scope like:
a = New_Toplevel_1()
a.scrolled_listbox.configure(background='white')
...
In below example "Outside Button" changes the text option of a class' button from the outside:
import tkinter as tk
class FrameWithButton(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.btn = tk.Button(root, text="Button")
self.btn.pack()
root = tk.Tk()
an_instance = FrameWithButton(root)
an_instance.pack()
def update_button():
global an_instance
an_instance.btn['text'] = "Button Text Updated!"
tk.Button(root, text="Outside Button", command=update_button).pack()
root.mainloop()

Efficient way to create a child window with access to main window disabled?

I'm looking for the most efficient way to create a child window and also forbid all access to the main window, in order that user will have to click on "OK" button inside the child window in order to recover the access to the main window.
Here is my code, using Toplevel class. It works, but is there a more efficient way to do ?
from tkinter import *
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
def SetEnableStatus(self, status):
for w in self.winfo_children():
if status == False:
w.grab_set()
else:
w.grab_release()
def CreateChildWindow(self):
subWindow = Toplevel(self)
def quit_subwindow():
subWindow.destroy()
self.SetEnableStatus(True) # Enable all widgets of main window
Button(subWindow, text='Exit', command=quit_subwindow).pack()
self.SetEnableStatus(False) # Disable all widgets of main window
It should be enough to call grab_set on the Toplevel object, and when you are done with it, you can simply destroy it, and call grab_set on self (but I am not 100% sure, even if the resulting program below confirms it).
In fact, if you create a button on your Tk root window and if you associate with this button for example a lambda function that prints something, then nothing will be printed, after "setting the grab" on the child window.
See the following example where basically all events are redirected to the Toplevel window, instead of to the Tk root window:
from tkinter import *
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
Entry(self).pack(side="left")
Button(self, text="whoami", command=lambda : print("A Tk root window.")).pack(side="left")
def CreateChildWindow(self):
subWindow = Toplevel(self)
def quit_subwindow():
subWindow.destroy()
self.grab_set()
Button(subWindow, text="whoami", command=lambda : print("A Toplevel child window.")).pack()
Button(subWindow, text='Exit', command=quit_subwindow).pack()
subWindow.grab_set()
win = MainWindow()
win.CreateChildWindow()
win.mainloop()
Check out the following article at effbot.org to know more about how to create dialogs:
http://effbot.org/tkinterbook/tkinter-dialog-windows.htm

How to close a Tkinter window by pressing a Button?

Write a GUI application with a button labeled "Good-bye". When the
Button is clicked, the window closes.
This is my code so far, but it is not working. Can anyone help me out with my code?
from Tkinter import *
window = Tk()
def close_window (root):
root.destroy()
frame = Frame(window)
frame.pack()
button = Button (frame, text = "Good-bye.", command = close_window)
button.pack()
window.mainloop()
With minimal editing to your code (Not sure if they've taught classes or not in your course), change:
def close_window(root):
root.destroy()
to
def close_window():
window.destroy()
and it should work.
Explanation:
Your version of close_window is defined to expect a single argument, namely root. Subsequently, any calls to your version of close_window need to have that argument, or Python will give you a run-time error.
When you created a Button, you told the button to run close_window when it is clicked. However, the source code for Button widget is something like:
# class constructor
def __init__(self, some_args, command, more_args):
#...
self.command = command
#...
# this method is called when the user clicks the button
def clicked(self):
#...
self.command() # Button calls your function with no arguments.
#...
As my code states, the Button class will call your function with no arguments. However your function is expecting an argument. Thus you had an error. So, if we take out that argument, so that the function call will execute inside the Button class, we're left with:
def close_window():
root.destroy()
That's not right, though, either, because root is never assigned a value. It would be like typing in print(x) when you haven't defined x, yet.
Looking at your code, I figured you wanted to call destroy on window, so I changed root to window.
You could create a class that extends the Tkinter Button class, that will be specialised to close your window by associating the destroy method to its command attribute:
from tkinter import *
class quitButton(Button):
def __init__(self, parent):
Button.__init__(self, parent)
self['text'] = 'Good Bye'
# Command to close the window (the destory method)
self['command'] = parent.destroy
self.pack(side=BOTTOM)
root = Tk()
quitButton(root)
mainloop()
This is the output:
And the reason why your code did not work before:
def close_window ():
# root.destroy()
window.destroy()
I have a slight feeling you might got the root from some other place, since you did window = tk().
When you call the destroy on the window in the Tkinter means destroying the whole application, as your window (root window) is the main window for the application. IMHO, I think you should change your window to root.
from tkinter import *
def close_window():
root.destroy() # destroying the main window
root = Tk()
frame = Frame(root)
frame.pack()
button = Button(frame)
button['text'] ="Good-bye."
button['command'] = close_window
button.pack()
mainloop()
You can associate directly the function object window.destroy to the command attribute of your button:
button = Button (frame, text="Good-bye.", command=window.destroy)
This way you will not need the function close_window to close the window for you.
from tkinter import *
window = tk()
window.geometry("300x300")
def close_window ():
window.destroy()
button = Button ( text = "Good-bye", command = close_window)
button.pack()
window.mainloop()
You can use lambda to pass a reference to the window object as argument to close_window function:
button = Button (frame, text="Good-bye.", command = lambda: close_window(window))
This works because the command attribute is expecting a callable, or callable like object.
A lambda is a callable, but in this case it is essentially the result of calling a given function with set parameters.
In essence, you're calling the lambda wrapper of the function which has no args, not the function itself.
from tkinter import *
def close_window():
import sys
sys.exit()
root = Tk()
frame = Frame (root)
frame.pack()
button = Button (frame, text="Good-bye", command=close_window)
button.pack()
mainloop()

Categories

Resources