Generated Button appears in wrong window - python

I started to learn Python and I got a problem that is not discussed in any of the tutorials I've found.
Basically when the program got more complicated I've lost the ability to control where the new elements are appearing. It's difficult to explain it for me since English is not my native language so i made a mock-up program that shows what is wrong with my main program.
import Tkinter as tk
import ttk as ttk
clutter=['1PLACEHOLDER', '2PLACEHOLDER', '3PLACEHOLDER', 'PICK ME']
class GRAPHIC_INTERFACE(tk.Frame):
def __init__(self,*args):
tk.Frame.__init__(self, *args)
self.grid()
self.first_window()
def first_window(self):
self.button1=tk.Button(self, text="PLACEHOLDER")
self.button1.grid()
self.button2=tk.Button(self, text="CLICK ME", command=self.second_window)
self.button2.grid()
self.button3=tk.Button(self, text="PLACEHOLDER")
self.button3.grid()
#the additional button apears here
def second_window(self):
alpha=tk.Toplevel(self)
self.button4=tk.Button(alpha, text="PLACEHOLDER")
self.button4.grid()
self.button5=tk.Button(alpha, text="CLICK ME", command= self.third_window)
self.button5.grid()
def third_window(self):
beta=tk.Toplevel(self)
self.BOXXY=ttk.Combobox(beta, values= clutter, state='readonly')
self.BOXXY.bind("<<ComboboxSelected>>", self.misplaced_button) #after choosing the third option an aditional button is created
self.BOXXY.current(0)
self.BOXXY.grid()
self.button6=tk.Button(beta, text="PLACEHOLDER")
self.button6.grid()
#the additional button needs to appear here
def misplaced_button(self, *args):
Catie=self.BOXXY.get()
if Catie=='PICK ME':
self.button7=tk.Button(self, text="I am that problematic button")#this button needs to be in the third window
self.button7.grid()
else:
print "that was not the chosen one"
root=tk.Tk()
root.title("Mockup")
root.geometry("180x200")
app=GRAPHIC_INTERFACE(root)
root.mainloop()
At first I was thinking that i can control the placement of the widgets by giving them names (i.e alpha, beta) but apparently I was wrong.

If self.button7 is supposed to be in the third window, all you have to do is use the third window as the parent of the button.
You can accomplish this many ways: you can save the window as an attribute, you can pass the window in when calling the function, or you can compute the window based on which widow caught the event.
Here's a solution that puts the button in the toplevel that got the event:
def misplaced_button(self, event):
...
toplevel = event.widget.winfo_toplevel()
self.button7=tk.Button(toplevel,...)
...

Related

ttk.Combobox triggers <<ListboxSelect>> event in different tk.Listbox

What i am trying to do: I am trying to build a GUI using tkinter in Python (i am using version 3.7.7 on Windows) with a main window that has a Listbox and a second window that contains a Combobox.
In the Listbox there are different files that can be opened. When clicking on each entry some additional information are displayed in the main window (That part is not in my shorter example code below). So what i did was binding the Listbox to a function that loads and displays the information (function_1 in the code below) with the <<ListboxSelect>> event.
The second window can be opened with a button and should edit the selected file. In the Combobox in the second window are different options that change other elements of the second window depending on whats selected. Thats why i bind the <<ComboboxSelected>> event to a different function (function_2 in the code below).
My problem with this is: When i select a value in the Combobox of the second window, the first function that is binded to the Listbox of the first window is executed just after the correct function for the Combobox. This only happens the first time a value for the Combobox is selected, for each time the second window is created. Also when looking at the selected value of the Listbox it returns the selected value of the Combobox.
Edit: I just noticed this problem only happens when i previously selected an item in the Listbox. So like i said below it might have something to do that the Listbox is still active or something like that.
My code and an example:
import tkinter as tk
from tkinter import ttk
class MainGUI:
def __init__(self):
self.root = tk.Tk()
items = ['Test 1', 'Test 2', 'Test 3']
self.item_box = tk.Listbox(self.root)
self.item_box.insert('end', *items)
self.item_box.bind('<<ListboxSelect>>', self.function_1)
self.item_box.pack()
tk.Button(self.root, text='Open second window',
command=self.open_window).pack()
self.root.mainloop()
def function_1(self, event):
print('MainGUI function:', event)
print('Text in Listbox:', self.item_box.selection_get())
def open_window(self):
SecondGUI()
class SecondGUI:
def __init__(self):
self.window = tk.Toplevel()
self.window.grab_set()
items = ['A', 'B', 'C']
self.dropdown = ttk.Combobox(self.window, values=items)
self.dropdown.current(0)
self.dropdown.pack()
self.dropdown.bind('<<ComboboxSelected>>', self.function_2)
def function_2(self, event):
print('SecondGUI function:', event)
MainGUI()
Image of the GUI before and after the option B is clicked
The output after selecting Test 1, opening the second window with the button and selecting B looks like this:
MainGUI function: <VirtualEvent event x=0 y=0>
Text in Listbox: Test 1
SecondGUI function: <VirtualEvent event x=0 y=0>
MainGUI function: <VirtualEvent event x=0 y=0>
Text in Listbox: B
My guess: If i had to guess what the problem is, i would say the Combobox maybe had some sort of Listbox in it that sends the event, but as i wasn't able to find more about when and how these events are sent, i couldn't really find anything about it (Edit: Less likely). When looking at the picture, you can see that the entry in the first window is still selected until the entry in the second window is clicked, so my second guess would be that the Listbox is still active and takes the new selected value when it is clicked or something like that (Edit: more likely).
My question: Why does the first function execute when i use a different widget in a different window, how can i fix this, or are there any better way to do this in the first place?
TL;DR: A Listbox recieves an event when a Combobox is selected in a different window. Why?
Thanks in advance!
It may be due to that exportselection=False is not set for both the Listbox and Combobox. So when an item in the Combobox is selected, it will clear the selection of Listbox which triggers the <<ListboxSelect>> event.
Also you have used wrong function, self.item_box.selection_get(), to get the current selected item of Listbox. self.item_box.get(self.item_box.curselection()) should be used instead.
Below changes should fix the issue:
class MainGUI:
def __init__(self):
...
self.item_box = tk.Listbox(self.root, exportselection=False)
...
def function_1(self, event):
print('MainGUI function:', event)
print('Text in Listbox:', self.item_box.get(self.item_box.curselection()))
...
class SecondGUI:
def __init__(self):
...
self.dropdown = ttk.Combobox(self.window, values=items, exportselection=False)
...
...

Python tkinter restarting the mainwindow while its running

I have a complex program where I run multiple tasks in my tkinter mainloop. My program is working however I am having one annoying problem, I do not have an ability to stop a task unless its finished. I have developed a simple tkinter program to show this problem:
Full code :
import tkinter as tk
from tkinter import Button,Entry,Canvas,Label,ttk
class Application(tk.Frame):
def __init__(self,master=None):
super().__init__(master)
self.master = master
def Create_canvas(self,canvas_width,canvas_height):
global canvas#described as global because used outside class
canvas = tk.Canvas(self.master,bg='papaya whip',width=canvas_width,height=canvas_height)
def Application_Intro(self):
print("starting new app")
restart_program_button = tk.Button(canvas, text="Restart_program",font='Helvetica 12 bold', width=20, height=2, command =self.Restart)
start_program_button = tk.Button(canvas, text="Start_program",font='Helvetica 12 bold', width=20, height=2, command =self.Start_program)
canvas.create_text(960,20,text="MY PROGRAM",font='Helvetica 16 bold')
canvas.create_window(710,300,window = restart_program_button)
canvas.create_window(710,500,window = start_program_button)
canvas.pack()
self.master.mainloop()
def Start_program(self):
print("Program start")
self.master.after(1000,self.Start_program)
def Restart(self):
#self.master.destroy()
#self.master.quit()
print("HERE I WANT TO INTERRUPT START PROGRAM AND RETURN TO IDLE STATE")
#WHAT TO DO IN THIS FUNCTION TO GO BACK TO INITIAL MAINLOOP STATE??
return
master = tk.Tk()
app = Application(master=master)
app.Create_canvas(1920,1080)
app.Application_Intro()
The program above will create a simple GUI with 2 buttons. Initially, the program will IDLE and wait for user to start an Application. When application is started, it will call itself recursively ( checking various states and doing other complex operations in my real program), however, I Want to be able to interrupt this operation and stop it at any time. That is why I have created a button "RESTART" which should restart the program back to its initial state where I am waiting for user to start and application again. How can I return out of the Start_program function after the button is pressed?
I cant really find relevant information on the internet apart from master.destroy() or master.quit().
master.destroy() destroys my mainloop completely which is not what I want, and calling master.quit() does not even seem to do anything.
Adding an image url so you can understand it easier:
https://ibb.co/djMd5TW
It depends how much of the program you are willing to rewrite. IMO, the "best-case scenario" would be not deleting the main window and using as few TopLevels as possible. I would recommend instead just creating a series of frames so you can add and remove the entire content of the window very easily. These frames would be your own classes with their parent being tkinter.Frame and contain all the logic for that screen.
The reason I am suggesting this is because it appears you have ~700+ lines of methods which (I know from experience ;-) is a real pain to maintain (any more than a few hundred lines and I will start to use the method above). If you want an example of this method, please see the code at the bottom of this answer.
A slightly smaller change, I would recommend you replace your from tkinter import * with import tkinter as tk as the former creates a very cluttered namespace:
from tkinter import *: This imports the Tkinter library into the global namespace. This isn't best practice, because it fills your namespace with a lot of classes, which you might accidentally overwrite, but it's okay for very small scripts. [Creating a Tkinter Hello World - Python GUI programming with Tkinter]
Another small change I would recommend is to stop creating your own message windows (like that declaring "Login Success") and instead look at the tkinter.messagebox module (in that situation, you could use showinfo).
If you have lots of time on your hands, take a look at the IDLE source as it is a tkinter program that is (generally speaking) very well written (I believe Thonny also uses tkinter).
#/usr/bin/python3
import tkinter as tk
class Login(tk.Frame):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.create_widgets()
self.pack(expand=True, fill="both")
def create_widgets(self):
# your login screen widgets ...
# I use this method to stop __init__ becoming too big and also to
# give me the option to seperate widget creating from
# instantiation if I wish
tk.Button(self, text="Login", command=self.check_creds).pack()
def check_creds(self):
# mysql and other logic
# assuming everything is good to go:
self.destroy()
Welcome(self.master)
class Welcome(tk.Frame):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.create_widgets()
self.pack(expand=True, fill="both")
def create_widgets(self):
# again, your widgets are created here...
tk.Button(self, text="Log out", command=self.log_out).pack()
def log_out(self):
self.destroy()
Login(self.master)
root = tk.Tk()
Login(root)
root.mainloop()

Button doesn't show in the frame

I have created a couple of frames and now want to put some buttons and labels on them, but from the first button that I created, I am facing a problem. Nothing is showing in the tkinter window.
from tkinter import *
class STproject():
def __init__(self,app): #1
self.stframe=Frame(app,background='blue',height=90,width=350)
self.stframe.grid(row=0,column=0)
self.ndframe=Frame(app,background='red',height=90,width=350)
self.ndframe.grid(row=1,column=0)
self.rdframe=Frame(app,background='yellow',height=90,width=350)
self.rdframe.grid(row=2,column=0)
self.ndstframe=Frame(self.ndframe,background='black',width=145)
self.ndstframe.grid(row=0,column=0,rowspan=3,sticky='ns')
self.ndndframe=Frame(self.ndframe,background='white',height=45,width=205)
self.ndndframe.grid(row=1,column=1)
self.ndrdframe=Frame(self.ndframe,background='green',height=45,width=205)
self.ndrdframe.grid(row=2,column=1)
def buttons(self):
self.importbutton=Button(self.stframe,text='Import',width=4,height=2)
self.importbutton.grid(row=0,column=0)
root=Tk()
root.title('SteelBox Inc. Calculator')
application=STproject(root) #2
root.mainloop() #3
You have put the creating of the button in a separate function, but you never call it.
Add self.buttons() at the end of the __init__ and the button will appear.

Checkbutton responds to clicks on text

By default, tkinter's Checkbutton widget responds to clicks anywhere in the widget, rather than just in the check box field.
For example, consider the following (Python 2) code:
import Tkinter as tk
main = tk.Tk()
check = tk.Checkbutton(main, text="Click").pack()
main.mainloop()
Running this code results in a small window, with a single check box, accompanied by the word "Click". If you click on the check box, it is toggled.
However, this also happens if you click on the text of the widget, rather than the check box.
Is this preventable? I could make a separate widget to hold the text, but that still results in a small area around the check box that responds to clicks.
Two solutions come to mind:
Do as you suggest and create a separate widget for the checkbutton and for the label.
replace the bindings on the checkbutton with your own, and examine the x/y coordinates of the click and only accept the click if it happens in a small region of the widget.
This program creates a checkbutton and overrides the default event on it by binding a method to the checkbutton. When the button is clicked, the method checks a defined limit to allow the normal operation or to override. OP wanted to make sure that when text of the checkbutton is clicked, no default action is taken. That is essentially what this does.
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.checkvar = IntVar()
check = tk.Checkbutton(parent, text='Click', variable=self.checkvar)
check.bind('<Button-1>', self.checkCheck)
check.pack()
print(dir(check))
def checkCheck(self, event):
# Set limit based on your font and preference
limit = 18
print(event.x, event.y, self.checkvar.get())
if event.x > limit or event.y > limit:
self.checkvar.set(not self.checkvar.get())
else:
print("Normal behavior")
if __name__ == "__main__":
window = tk.Tk()
app = App(window)
window.mainloop()

Why is my tkinter button not displaying, have I installed everything in the module if not how can I install it?

#snakes and ladder
from tkinter import * #pygame is the module that has collections of functions that is used to create a window import everything from tkinter
import time
class window(Frame): #Frame comes from tkinter is what you think as a window
def __init__(self, master = None):#The master widget defines the settings upon initialisation
Frame.__init__(self, master) #This is called the frame class that has been built in python
self.master = master
def __init__window(self): #creation of the init window
self.master.title("Reagan Kambayi") #It changes the title of the title of our widget
self.pack(fill=BOTH, expand=1)#The pack function will pack this in our frame
#placing the button
stop = Button(self, master, message= "Stop")
#intialising the button that will start the stopwatch
stop.place(x=0, y=0)
screen = Tk() #It must be written with capitalised T because there will be an error and it holds the components of the tkinter module
screen.geometry("700x500")
app = window(screen) #The app variable is assigned to the window creation which has the tkinter module
screen.mainloop()
Ok, here we go.
from tkinter import * #pygame is the module that has collections of functions that is used to create a window import everything from tkinter
Pygame has nothing to do with tkinter and you're not importing pygame here, you're importing tkinter.
class window(Frame): #Frame comes from tkinter is what you think as a window
No, it isn't. A Frame widget is just that, a frame inside a window. It doesn't draw a window in and of itself. The parameter Frame in your example isn't even a Frame widget at all, it's value is Tk() which is the function called to draw the first window in tkinter.
def __init__(self, master = None):#The master widget defines the settings upon initialisation
I'm actually at a loss for what you're attempting to do here. master should equal Frame which equals screen which equals Tk() but if I'm correct you're overriding that and telling it to equal None?
Frame.__init__(self, master) #This is called the frame class that has been built in python
I don't think you're doing what you think you're doing here. This answer explains it better than I could.
def __init__window(self): #creation of the init window
If I'm reading your program correctly then window.__init__window() is never called, so none of this function ever actually happens.
self.pack(fill=BOTH, expand=1)#The pack function will pack this in our frame
You're attempting to call .pack() on self which is calling .pack() on Frame. Typically we wouldn't assign a value to self in this way (although this is valid), read this to find out what self should be used for.
#placing the button
stop = Button(self, master, message= "Stop")
This isn't placing the Button widget, this is assigning the Button widget to a variable. Also, Button widgets are declared as Button(parent, *attributes) and there is no message attribute built in. Meaning what you meant to call was Button(self.master, text="Stop").
#intialising the button that will start the stopwatch
stop.place(x=0, y=0)
This is where you're placing the button, but the function that contains this is never called, so it never happens.
app = window(screen) #The app variable is assigned to the window creation which has the tkinter module
What you're actually doing here is calling the class window, all this does in your current program is call window.__init__(), which in itself does essentially nothing.
This is meant with no offence but I think you're lacking a very basic understanding of tkinter and possibly even Pythonic OOP.
I believe what you're trying to do in your program is the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.root.title("Reagan Kambayi")
self.stop = Button(self.root, text="Stop")
self.stop.place(x=0, y=0)
root = Tk()
App(root)
root.mainloop()

Categories

Resources