Proper Use Of Inheritance in Tkinter using Button widget - python

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)

Related

Changing tab widgets from another tab in Tkinter

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()

How to reference correctly to frame names in tkinter

I am creating a card game in tkinter and need help with referencing to the frame names. My problem is that when I want to "refresh" the frame, I need to destroy and recreate it and this changes the progressive numbering of the frames.
Please take a look at the code below. The example shows that the third frame every time gets a new name as it gets recreated.
import tkinter as tk
class RootFrame(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.main_window = tk.Frame(self)
self.main_window.pack(side="top", fill="both", expand=True)
self.main_label = tk.Label(self.main_window, text="Main Window")
self.main_label.pack()
self.second_frame = SecondFrame(self.main_window, self)
self.second_frame.pack()
class SecondFrame(tk.Frame):
def __init__(self, parent, controller, *args, **kwargs):
super().__init__(*args, **kwargs)
self.controller = controller
label = tk.Label(self, text="Second Frame")
label.pack()
self.create_third_frame()
def create_third_frame(self):
self.third_frame = ThirdFrame(self, self.controller)
self.third_frame.pack()
def update_frame(self):
self.third_frame.destroy()
self.create_third_frame()
class ThirdFrame(tk.Frame):
def __init__(self, parent, controller, *args, **kwargs):
super().__init__(*args, **kwargs)
self.controller = controller
self.parent = parent
label = tk.Label(self, text="Third Frame")
label.pack()
refresh_button = tk.Button(
self, text="Resfresh", command=self.parent.update_frame)
refresh_button.pack()
print(self.winfo_name())
if __name__ == "__main__":
app = RootFrame()
app.mainloop()
The code above is used to illustrate the problem. Please run the code and you'll see the changing widget name in the terminal.
I use winfo_parent and winfo_name in my actual code to create different conditions for button bindings. For example, if the user clicks a widget1 in frame6 happens X and when I click a widget8 in frame2 happens Y. This works until I destroy() and recreate something and everything breaks.
I suppose that using winfo_name and winfo_parent for this kind of referencing is not the correct way to get around, but I really can't think of anything else.
I'm not sure exactly what you are asking, but you can assign a specific name to the widget:
def create_third_frame(self):
self.third_frame = ThirdFrame(self, self.controller, name='testframe')
self.third_frame.pack()
Then each time the name of the frame created will be consistent.
You can also reference the widget by name with Tk().nametowidget(), see this relevant answer here: Is it possible to search widget by name in Tkinter?
>>> from Tkinter import *
>>> win = Tk()
>>> button = Button( Frame( win, name = "myframe" ), name = "mybutton" )
>>> win.nametowidget("myframe.mybutton")
<Tkinter.Button instance at 0x2550c68>
I would recommend sticking with a OOP approach however, and just reference it with from your code like self.thirdframes where you might have a list or dict of ThirdFrame objects. This way your python code can easily reference the objects without going back to the tcl interpreter and parse the widget name. If you ever will only have one ThirdFrame, then just reference back to self.thirdframe whenever you need it.

Creating a new window on pressing a button in an existing window in Tkinter

I am having issues with a Tkinter GUI. I need to create a large application. I need to use classes for that to manage all modules together. For a unit check and getting help here, I have tried to provide a sample which is close to exact problem ( with a small example) here:
I am creating a 1st window with a Button labelled as "Test". What I want is that when I click the button "Test", a new second window will pop up with a text "Enter Value" and entry space, where I can enter the value. I have provided the code below. What is happening is that, I am able to get the new window, but the text "Enter Value" and entry Space is generated in the first window instead of the second and the second window remains blank. I am not understanding where I am making the wrong logic call. Help will be very much appreciated.
I know we do not need classes for GUI applications, however to manage my large application ( not shown here), I need to have classes and I will very much appreciate, if some Tkinter Guru can help me with the bug in my code.
gui view File (gui_view.py)
import tkinter as tk
from tkinter import Tk
class MyMainGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.Button1=tk.Button(self)
self.Button1["text"]= "Test"
self.Button1["command"]=self.controller.buttonPressed1
self.Button1.grid(row=2,column=0,rowspan=2)
class MySecondGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=1,rowspan=2)
#Entry Space
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=2,column=0,rowspan=2)
### gui Controller File (gui_controller.py)
import tkinter as tk
import gui_view # the VIEW file
class MainControl:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('550x200')
self.view = gui_view.MyMainGUI(self)
self.view.mainloop()
def newWindow(self):
self.newWin = tk.Toplevel(self.root)
self.newWin.geometry('300x400')
self.newDisplay = tk.Label(self.newWin, text='Test Mode')
self.viewNew = gui_view.MySecondGUI(self.newWin)
self.viewNew.mainloop()
self.newDisplay.pack()
def buttonPressed1(self):
self.newWindow()
if __name__ == "__main__":
c = MainControl()
#
ADDING A MODIFIED CODE WITH NEW PROBLEM.
I have now been able to generate a code which pops up a new window with entries, when I click the button "Test" in the first Window. However, I am having problems creating buttons in the scond window. The way I have it now, it pops an error to me saying "'MySecondGUI' object has no attribute 'buttonPressed2"
Help will be very much appreciated.
I have pasted my updated code below:
GUI_VIEW FILE ( gui_view.py)
import tkinter as tk
from tkinter import Tk
class MyMainGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.Button1=tk.Button(self)
self.Button1["text"]= "Test"
self.Button1["command"]=self.controller.buttonPressed1
self.Button1.grid(row=2,column=0,rowspan=2)
class MySecondGUI(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=5,rowspan=2)
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=8,column=0,rowspan=2)
self.Button2=tk.Button(self)
self.Button2["text"]= "Try Me"
self.Button2["command"] = self.buttonPressed2
self.Button2.grid(row=14,column=0,rowspan=2)enter code here
GUI MAIN CONTROLLER FILE
import tkinter as tk
import gui_view # the VIEW file
class MainControl:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('550x200')
self.view = gui_view_temp.MyMainGUI(self)
self.view.mainloop()
def newWindow(self):
self.viewNew = gui_view.MySecondGUI()
self.viewNew.geometry('300x400')
self.newDisplay = tk.Label(self.newWin, text='Test Mode')
self.viewNew.mainloop()
self.newDisplay.pack()
def buttonPressed1(self):
self.newWindow()
def buttonPressed2(self):
pass
if name == "main":
c = MainControl()
in MySecondGUI(tk.Frame):
def __init__(self, controller):
#Attach your frame to "secondGUI" (which is your second window)
tk.Frame.__init__(self, controller)
in class MainControl:
#I assume you're passing your parent window/frame as "controller"?
self.viewNew = MySecondGUI(self.newWin)
according to Python Tkinter Docs
https://docs.python.org/3.5/library/tkinter.html#mapping-basic-tk-into-tkinter
You should specify your parent window if you're not attach your widget to main window.
#Main window
root = tk.Tk()
#Second Window
newWin = tk.Toplevel(root)
#Attach to main window
tk.Label(text="This label attached to root").pack()
tk.Button(text="This button attached to root").pack()
#Second Window
tk.Label(newWin, text="This label attached to second window").pack()
tk.Button(newWin, text="This button attached to second window").pack()
also,
self.viewNew.mainloop()
#This will fail because you have to set everything up before mainloop()
#_tkinter.TclError: can't invoke "pack" command: application has been destroyed
self.newDisplay.pack()
Edit for update
You should put your
def buttonPressed2(self):
in class MySecondGUI, not in class MainControl.
class MySecondGUI(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=5,rowspan=2)
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=8,column=0,rowspan=2)
self.Button2=tk.Button(self)
self.Button2["text"]= "Try Me"
#self means "MySecondGUI" not "MainControl" here
self.Button2["command"] = self.buttonPressed2
self.Button2.grid(row=14,column=0,rowspan=2)
def buttonPressed2(self):
pass

Can you config a widget after it has been created dynamically?

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()

Python Tkinter Input Box

Good day.
I am trying to create my own input box for use in my project.
basically what i am trying to do is run my main form which will call the second. the user will provide some data on the second and when the press the ok/close button on the second for the data will be passed back to the first. similar in functionality to the inputbox.
here is what i have created, but being new to python i am not sure where i am going wrong/nor can i quick figure out when to put the return.
My Class is here
import tkinter as tk
class MainWindow():
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter a Grouping Name')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.focus_set()
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='OK', command=self.DestWin)
self.mySubmitButton.pack()
def DestWin(self):
self.top.destroy()
The method to call it is here
abc=configurator.MainWindow(root)
Not exactly sure what you are trying to achieve, but if you are trying to get values from one window to another, below you can find an extended example based on your code.
import tkinter as tk
class MainWindow():
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter a Grouping Name')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.focus_set()
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='OK', command=self.DestWin)
self.mySubmitButton.pack()
def DestWin(self):
# call callback function setting value in MyFrame
self.callback(self.myEntryBox.get())
self.top.destroy()
def set_callback(self, a_func):
self.callback = a_func
class MyFrame(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.pack()
self.myLabel1 = tk.Label(parent, text='Click OK to enter the group name')
self.myLabel1.pack()
self.mySubmitButton1 = tk.Button(parent, text='OK', command=self.get_group_name)
self.mySubmitButton1.pack()
def get_group_name(self):
mw = MainWindow(None)
# provide callback to MainWindow so that it can return results to MyFrame
mw.set_callback(self.set_label)
def set_label(self, astr = ''):
self.myLabel1['text'] = astr
root = tk.Tk()
mf = MyFrame(root)
root.mainloop()
The screenshot:
The text from the right window, when OK is pressed, will be shown in the left window. This is achieved through callbacks. MainWindow takes a callback function, and when you press OK, it is executed. The callback is set_label from MyFrame.
Hope this helps.

Categories