I am creating a GUI using tkinter based on the structure described here. I have some tabs that look identical but with different variables. So I decided to define a class for tabs and add them to the main window. I am going to configure some widgets in one tab from another tab. In line 11, a function is defined that when a button in tab_2 is clicked, tab_1's button background color changes to green. Whereas its working, I have two question:
Is it possible not to define channel_1 as an attribute of main_window? I think there must be better way to do so, specifically, if the GUI is going to be used as module (then main_window will not be defined).
Is it possible to know which tab is open, so when button in each tab is clicked, configurations in the other one changes only?
import tkinter as tk
from tkinter import ttk
class Channel(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.btn = tk.Button(self.parent, text = 'click me', command = self.change_green)
self.btn.pack()
def change_green(self):
main_window.channel_1.btn.config(bg = 'green') # line 11
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.tab_control = ttk.Notebook(self.parent)
self.tab_1 = ttk.Frame(self.tab_control)
self.tab_2 = ttk.Frame(self.tab_control)
self.tab_control.add(self.tab_1, text = 'tab 1')
self.tab_control.add(self.tab_2, text = 'tab 2')
self.tab_control.pack(fill = 'both', expand = 1)
self.channel_1 = Channel(self.tab_1)
self.channel_2 = Channel(self.tab_2)
if __name__ == "__main__":
root = tk.Tk()
main_window = MainApplication(root) # <<<< here defined main_window
main_window.pack(side="top", fill="both", expand=True)
root.mainloop()
I would create class MyTab and keep its widgets in this class, not in channel. It can also keep access to other tab(s) to button in one tab can change color in other tab.
Using tab's parent (self.master) I can get active tab, list of all tabs and activate other tab.
import tkinter as tk
from tkinter import ttk
class MyTab(tk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
#self.master = master # super() already set it
self.btn = tk.Button(self, text='click me', command=self.change_green)
self.btn.pack()
self.other_tab = None # default value at start
def change_green(self):
if self.other_tab:
# change color in other tab
self.other_tab.btn.config(bg = 'green')
# get active tab ID
print('active tab ID:', self.master.select())
# get button in active tab
active_tab = root.nametowidget(self.master.select())
print('active tab - btn text:', active_tab.btn['text'])
# get all tabs
print('all tabs:', self.master.children.items())
# set other tab as active
self.master.select(self.other_tab)
class MainApplication(tk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
#self.master = master # super() already set it
self.tab_control = ttk.Notebook(self.master)
self.tab_1 = MyTab(self.tab_control)
self.tab_2 = MyTab(self.tab_control)
self.tab_1.other_tab = self.tab_2
self.tab_2.other_tab = self.tab_1
self.tab_control.add(self.tab_1, text = 'tab 1')
self.tab_control.add(self.tab_2, text = 'tab 2')
self.tab_control.pack(fill = 'both', expand = 1)
if __name__ == "__main__":
root = tk.Tk()
main_window = MainApplication(root)
main_window.pack(side="top", fill="both", expand=True)
root.mainloop()
Related
This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 1 year ago.
I have this code (sorry not really minimal):
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
# tk.Frame.__init__(self, parent, *args, **kwargs)
super().__init__(parent, *args, **kwargs)
self.reader = parent
# self.reader = tk.Toplevel(self)
self.reader.title("XXX")
self.reader.resizable(False, True)
self.reader.geometry("650x5")
self.reader.minsize(650,150)
self.panel = tk.Text(self.reader, wrap="word", width=12, font=('Courier',14))
self.viewer = tk.Text(self.reader, wrap="word", font=('Courier',14))
self.viewer.insert(tk.INSERT,'h\nm\nbbb\ntt\n\nbbb')
self.panel.insert(tk.INSERT,'hgggg\ngggm\nbbb\n\nbbbbtt\n\nbbb')
self.labelv1 = tk.IntVar()
# self.labelv1.set(int(self.panel.index('end-1c').split('.')[0]))
self.labelv2 = tk.IntVar()
# self.labelv2.set(int(self.viewer.index('end-1c').split('.')[0]))
self.pippo = tk.StringVar()
# self.pippo.set(("Panel : "+str(self.labelv1)+" Viewer : "+str(self.labelv2)))
self.label = tk.Label(self.reader, textvariable = self.pippo)
self.label.pack(side='top')
# self.update_labels()
self.counter = tk.IntVar()
self.counter.set(0)
# self.reader.bind_class('Text', '<Key>', self.update_labels()) #winfo_toplevel()
# self.winfo_toplevel().bind("<Key>", self.update_labels())
# self.reader.bind("<Key>", self.update_labels())
self.reader.bind("<Key>", lambda event: self.update_labels())
self.scrollbar = tk.Scrollbar(self.reader, command=(self.on_scrollbar))
self.scrollbar.pack(side="right", fill="y")
self.panel.pack(side="left", fill="both", expand=True)
self.viewer.pack(side="right", fill="both", expand=True)
# Changing the settings to make the scrolling work
# self.scrollbar['command'] = self.on_scrollbar
self.panel['yscrollcommand'] = self.on_textscroll
self.viewer['yscrollcommand'] = self.on_textscroll
def update_labels(self, *args):
print('OKK')
self.labelv1.set(int(self.panel.index('end-1c').split('.')[0]))
self.labelv2.set(int(self.viewer.index('end-1c').split('.')[0]))
self.pippo.set("Panel : "+str(self.labelv1.get())+" Viewer : "+str(self.labelv2.get()))
self.counter.set(self.counter.get() + 1)
print('updating labels : ' , self.counter.get(),' / ', self.pippo.get())
return
def on_scrollbar(self, *args):
"""
Scrolls both text widgets when the scrollbar is moved
"""
self.panel.yview(*args)
self.viewer.yview(*args)
def on_textscroll(self, *args):
'''Moves the scrollbar and scrolls text widgets when the mousewheel
is moved on a text widget'''
self.scrollbar.set(*args)
self.on_scrollbar('moveto', args[0])
if __name__ == "__main__":
master = tk.Tk()
MainApplication(master)
master.mainloop()
I don't understand why does:
self.reader.bind("<Key>", lambda event: self.update_labels())
that binds any pressed key inside the text windows to a function update_labels() that reports the number of line text inside the window itself
works, while
self.reader.bind("<Key>", self.update_labels())
doesn't. I read a lot of docs and examples, here on SO too, but wasn't able to figure-out why the latter doesn't work as it should.
The issue you have is because you are giving the output of the function and not the reference of it. To fix your issue you should do:
self.reader.bind("<Key>", self.update_labels)
and somewhere define:
def update_labels(self, event):
...
I've seen many examples of grab_set() being used for modal windows for tkinter but I can't get it to work for my application.
I am creating a second window as my 'Settings' window which is called from the Menu of the main application.
example:
import tkinter as tk
class Main(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self,*args, *kwargs)
button = tk.Button(self,text="second window", command=lambda:Settings())
button.pack()
class Settings(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self,*args, *kwargs)
button = tk.Button(self,text="quit", command=lambda: quit())
button.pack()
self.grab_set()
if __name__ == "__main__":
app = Main()
app.mainloop()
Right now I can still click the 'Settings' button to create as many instances of Settings as the pc would allow. How do I restrict clickability to the main application window until the second one is closed first?
Here is a super simple example of how you can open another window using Toplevel and how you can edit stuff on the main window from the Toplevel window.
Its very basic but it should be a good enough example to illustrate what is required in tkinter to open new window.
UPDATE: Added the grab_set() method as pointed out by Bryan in the comments.
The grab_set() method according to the documentation routes all events for this application to this widget.
Note: This would be along the lines of a Minimal, Complete, and Verifiable example. It is the smallest possible bit of code to get the point across while also being testable.
from tkinter import *
class GUI(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.my_frame = Frame(self.master)
self.my_frame.pack()
self.button1 = Button(self.master, text="Open New Window", command = self.open_toplevel_window)
self.button1.pack()
self.text = Text(self.master, width = 20, height = 3)
self.text.pack()
self.text.insert(END, "Before\ntop window\ninteraction")
def open_toplevel_window(self):
self.top = Toplevel(self.master)
#this forces all focus on the top level until Toplevel is closed
self.top.grab_set()
def replace_text():
self.text.delete(1.0, END)
self.text.insert(END, "Text From\nToplevel")
top_button = Button(self.top, text = "Replace text in main window",
command = replace_text)
top_button.pack()
if __name__ == "__main__":
root = Tk()
app = GUI(root)
root.mainloop()
Here is an example when using a separate class for the Toplevel:
from tkinter import *
class GUI(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.my_frame = Frame(self.master)
self.my_frame.pack()
self.button1 = Button(self.master, text="Open New Window",
command = open_toplevel_window)
self.button1.pack()
self.text = Text(self.master, width = 20, height = 3)
self.text.pack()
self.text.insert(END, "Before\ntop window\ninteraction")
class open_toplevel_window(Toplevel):
def __init__(self, *args, **kwargs):
Toplevel.__init__(self, *args, **kwargs)
self.grab_set()
def replace_text():
app.text.delete(1.0, END)
app.text.insert(END, "Text From\nToplevel")
top_button = Button(self, text = "Replace text in main window",
command = replace_text)
top_button.pack()
if __name__ == "__main__":
root = Tk()
app = GUI(root)
root.mainloop()
I figured out my problem
import tkinter as tk
class Main(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self,*args, *kwargs)
self.button = tk.Button(self,text="second window", command=lambda: SecondWindow())
self.button.pack()
class SecondWindow(tk.Toplevel):
def __init__(self,*args, **kwargs):
tk.Toplevel.__init__(self,*args, *kwargs)
self.button = tk.Button(self,text="quit", command=lambda: quit())
self.button.pack()
self.grab_set()
if __name__ == "__main__":
app = Main()
app.mainloop()
as per Sierra Mountain Tech and Bryan Oakley's suggestion. I have changed my Settings class to Toplevel and it does exactly what I want.
My acutal application has the two in different modules but yield the same results.
Try adding the following line after the line containing the grab_set method:
self.wait_window(self)
You need to allow focus with takefocus = True and you give the focus to it with focus_set()
def __init__(self, *args, **kwargs):
Toplevel.__init__(self, *args, **kwargs)
self.takefocus = True
self.focus_set()
So I have been looking for the answer to this but can't find any examples.
I want to know if you can create several buttons or labels or whatever widget in tkinter with all the same variable name and then be able to target that widget directly after its created.
Here is an example of some code that will create 5 buttons with the same variable name and if you press the button it will print the text on said button.
import tkinter as tk
btn_names = ["1st Button", "2nd Button", "3rd Button", "4th Button", "5th Button"]
class MyButton(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.createButtons()
def createButtons(self):
row_count = 0
for n in range(5):
someButton = tk.Button(self.parent, text=btn_names[n], command= lambda t=btn_names[n]: self.getText(t))
someButton.grid(row = row_count, column = 0)
row_count += 1
def getText(self, text):
print(text)
if __name__ == "__main__":
root = tk.Tk()
myApp = MyButton(root)
root.mainloop()
Now what I can't figure out is if it is possible to also make changes to said button. Like I now want to change the buttons background and foreground colors but I have no way of targeting the button I want to edit.
I can't just do this:
someButton.config(background = "black", foreground = "white")
as all the buttons are named someButton.
So is it possible to be able to edit a widget created in this manor after it has been created?
I'm not sure this is the best way to do it, but it is possible.
Instead of passing a command to your button when you originally create it, add a line where you configure the command to your lambda function and pass someButton as an argument. Then in your callback function, ensure you configure the button passed to change its background color.
import tkinter as tk
btn_names = ["1st Button", "2nd Button", "3rd Button", "4th Button", "5th
Button"]
class MyButton(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.createButtons()
def createButtons(self):
row_count = 0
for n in range(5):
someButton = tk.Button(self.parent, text=btn_names[n])
someButton.configure(command=lambda t=btn_names[n], btn = someButton: self.getText(t, btn))
someButton.grid(row = row_count, column = 0)
row_count += 1
def getText(self, text, btn):
print(text)
btn.configure(background = 'black')
if __name__ == "__main__":
root = tk.Tk()
myApp = MyButton(root)
root.mainloop()
I am writing a GUI in Tkinter using Python 2.11 using an OOP approach and trying to learn inheritance. I wrote a child class called ButtonField which is inherited from tk.Button. Inside ButtonField I have a method called pressMe which changes the color and text of the button when pressed.
My eventual goal is to have more buttons in the GUI and all the associated methods contained in the ButtonField class for cleaner and more manageable code.
When I press the Button the text "In Press Me Method" is displayed on the screen so the method is perhaps working but the button widget does not change text or background color.
import Tkinter as tk
class MainWindow(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.olFrame = tk.LabelFrame(text = 'Initial Frame', bg = 'grey')
self.olFrame.grid(column = 0, row = 0, sticky = 'w')
self.simpleButton = ButtonField(self.olFrame, text = "Press Me",bg= "green"
,command = ButtonField(self).pressMe)
self.simpleButton.grid(column = 0, row = 0)
class ButtonField(tk.Button):
def __init__(self, parent, *args, **kwargs):
tk.Button.__init__(self, parent, *args, **kwargs)
self.parent = parent
def pressMe(self):
print "In Press Me Method"
self.configure(text = "Pressed Now", background = "yellow")
#self.parent.configure(self, text = "Pressed Now", background = "yellow") #returns TclError: unknow option "-text"
root = tk.Tk()
root.geometry('500x400')
root.title('Test GUI')
root.configure(background = "black")
a = MainWindow(root)
root.mainloop()
Consider this line of code:
self.simpleButton = ButtonField(..., command = ButtonField(self).pressMe)
It is exactly the same as if you did this:
another_button = Button(self)
self.simpleButton = ButtonField(..., command=another_button.pressMe)
Because you are calling the function of a different button which is invisible, you don't see the changes. If you want the button to call its own function you will need to do it in two steps:
self.simpleButton = ButtonField(...)
self.simpleButton.configure(command=self.simpleButton.pressMe)
from tkinter import *
from tkinter import ttk
class MainGame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def tab_change(self, event):
tab_id = self.page.index('current')
print(tab_id, self.page.tab(tab_id, 'text'))
def initUI(self):
global canvas
self.parent.title('PythonPage')
self.pack(fill = BOTH, expand = 1)
self.page = ttk.Notebook(self, width = 646 ,height = 629)
self.page1 = Frame(self)
self.page2 = Frame(self)
self.page.add(self.page1, text = 'Tab1')
self.page.add(self.page2, text = 'Tab2')
self.page.bind('<ButtonPress-1>', self.tab_change)
self.page.pack(expand = 0, anchor = 'w', side = 'top')
root = Tk()
root.geometry('925x650')
main = MainGame(root)
root.mainloop()
tab_change can show their id and names, but not correctly.
When Tab1 is clicked, I clicked Tab2 but it still print 0 Tab1, it needs one more click to print 1 Tab2 .
Tab2 click to Tab1 is the same, it needs one more click to show the current selected tab.
I want to find why the tabs need double click? And how can I get selected tab correctly by a single click?
Change:
self.page.bind('<ButtonPress-1>', self.tab_change)
To:
self.page.bind('<ButtonRelease-1>', self.tab_change)
Because unless you have released the pressed button, the tab hasn't changed!