Why does the following code from my in initUI, a method called by __ init __, not add an Option menu to the window? I thought this code would make a window with a OptionMenu in it.
game_menu_var = tk.IntVar()
game_menu_var.set(1)
self.game_menu = tk.OptionMenu(self, game_menu_var, 1, 2 , 3)
self.game_menu.pack(side="left")
full code:
'''
A GUI for wm
'''
import tkinter as tk
import _wm
class WMGUI(tk.Frame):
'''
A GUI for wm
'''
def __init__(self, parent=None, *, title='WM'):
if parent is None:
parent = tk.Tk()
tk.Frame.__init__(self, parent)
self.parent = parent
self.initUI(title)
def initUI(self, title):
"""
do not call from outside of class
"""
self.parent.title(title)
# make game_menu
game_menu_var = tk.IntVar()
game_menu_var.set(1)
self.game_menu = tk.OptionMenu(self, game_menu_var, 1, 2 , 3)
self.game_menu.pack(side="left")
You need to use the pack() method on your Frame in init, otherwise the argument self within your OptionMenu doesn't refer to an existing Frame.
Try this:
class WMGUI(tk.Frame):
'''
A GUI for wm
'''
def __init__(self, parent=None, *, title='WM'):
if parent is None:
parent = tk.Tk()
tk.Frame.__init__(self, parent)
self.parent = parent
self.pack() #packs the Frame
self.initUI(title)
def initUI(self, title):
"""
do not call from outside of class
"""
self.parent.title(title)
# make game_menu
game_menu_var = tk.IntVar()
game_menu_var.set(1)
self.game_menu = tk.OptionMenu(self, game_menu_var, 1, 2 , 3)
self.game_menu.pack(side="left")
Alternatively, the parent widget is self.parent, so you could make that the master of self.game_menu:
self.game_menu = tk.OptionMenu(self.parent, game_menu_var, 1, 2 , 3)
Related
I have created a tkinter application which uses a framework very similar to the one shown in this video https://www.youtube.com/watch?v=jBUpjijYtCk
Essentially there is a main class which uses other classes below it as pages. These pages are created as soon as the program is ran.
My problem is that I need data from one of these pages to be sent to another page so that it can display the appropriate data depending on the data that is sent from the previous page.
I have simplified my program down to showcase the issue
from tkinter import *
class TkinterApp(Tk):
def __init__(self):
Tk.__init__(self)
container = Frame(self)
container.grid(row=1, column=0, sticky="nesw")
self.frames = {}
for p in (ClassA, ClassB):
frame = p(parent = container, controller = self)
self.frames[p] = frame
frame.grid(row=0, column=0, sticky="nesw")
self.ShowFrame(ClassA)
def ShowFrame(self, page_name):
page = self.frames[page_name]
page.tkraise()
def PassText(self, text):
self.frames[ClassB].GetData(text)
class ClassA(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
button1 = Button(self, text="button1", command=lambda: self.SubmitInfo("button1"))
button1.grid(row=0, column=0)
def SubmitInfo(self, data):
self.controller.ShowFrame(ClassB)
self.controller.PassText(data)
class ClassB(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
self.label = Label(self, text=self.data)
self.label.grid(column=0, row=0)
def GetData(self, data):
self.data = data[0]
app = TkinterApp()
app.mainloop()
Desired outcome:
the text shown on the ClassB page being "button1"
Current outcome:
AttributeError: 'ClassB' object has no attribute 'data'
to my knowledge, this is happening as the class TkinterApp is causing ClassB to be ran before the value of data exists.
Any help would be greatly appreciated
In line 44, remove Label widget parameter text=self.data
Add .configure in line 49, self.label.configure(text=self.data)
snippet:
class ClassB(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
self.label = Label(self) #<-- line 44
self.label.grid(column=0, row=0)
def GetData(self, data):
self.data = data[0]
self.label.configure(text=self.data) #Add in <---line 49
I would like to set a label value to the value of the current optionmenu value. If the latter changes I want the former to change too. My issue is that this gui elements are defined in separate classes (and I want them to be like that), but I do not know how to connect them together. Without classes I know I can use the OptionMenu's command method to set the value of the Label. But putting them into Frame containers I am stuck.
Here is a simplistic and functioning code what I want to resolve:
from tkinter import *
root = Tk()
opt=['Jan', 'Feb', 'March']
class MyOptMenu(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
self.var = StringVar(self)
self.var.set(opt[0])
self.om = OptionMenu(self, self.var, *opt)
self.om.pack(side=TOP)
self.var.trace('w', self.getValue)
def getValue(self, *args):
return(self.var.get())
class MyLabel(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack()
self.labstring = StringVar(self)
self.lab = Label(self, textvariable = self.labstring, bg='white')
self.lab.pack(side=TOP)
self.labstring.set('hello')
a = MyOptMenu(root)
b = MyLabel(root)
root.mainloop()
Could you give me some help how to proceed. Many thanks.
According to #j_4321's suggestion, here I post the solution that resolved my issue. I provide explanation in comments in between code lines.
from tkinter import *
root = Tk()
opt=['Jan', 'Feb', 'March']
var = StringVar(root) # initialization of a common StringVar for both OptionMenu and Label widgets
class MyOptMenu(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
var.set(opt[0]) # give an initial value to the StringVar that will be displayed first on the OptionMenu
self.om = OptionMenu(self, var, *opt)
self.om.pack(side=TOP)
var.trace('w', self.getValue) # continuously trace the value of the selected items in the OptionMenu and update the var variable, using the function self.getValue
def getValue(self, *args):
return(var.get()) # return the current value of OptionMenu
class MyLabel(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack()
self.lab = Label(self, textvariable = var, bg='white') # use the same StringVar variable (var) as the OptionMenu. This allows changing the Label text instantaneously to the selected value of OptionMenu
self.lab.pack(side=TOP)
a = MyOptMenu(root)
b = MyLabel(root)
root.mainloop()
I have modified the answer given here as written below. The code is basically creating pushbuttons with a counter as pushButton_0, pushButton_1..
Here, I know that when I press to self.addButton I am creating widgets named like self.pushButton_0, self.pushButton_1 etc. So, my question is, how I'm supposed to use this pushbuttons? Because when I'm trying to do something like self.pushButton_0.clicked.connect(self.x), it' s telling me that "there is no attribute named 'pushButton_0'".
Thanks!
from PyQt4 import QtGui, QtCore
import sys
class Main(QtGui.QMainWindow):
def __init__(self, parent = None):
super(Main, self).__init__()
self.GUI()
def GUI(self):
self.count = 0
# main button
self.addButton = QtGui.QPushButton('button to add other widgets')
self.addButton.clicked.connect(self.addWidget)
# scroll area widget contents - layout
self.scrollLayout = QtGui.QFormLayout()
# scroll area widget contents
self.scrollWidget = QtGui.QWidget()
self.scrollWidget.setLayout(self.scrollLayout)
# scroll area
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.scrollWidget)
# main layout
self.mainLayout = QtGui.QVBoxLayout()
# add all main to the main vLayout
self.mainLayout.addWidget(self.addButton)
self.mainLayout.addWidget(self.scrollArea)
# central widget
self.centralWidget = QtGui.QWidget()
self.centralWidget.setLayout(self.mainLayout)
# set central widget
self.setCentralWidget(self.centralWidget)
def addWidget(self):
self.scrollLayout.addRow(Test(self))
self.count = self.count + 1
print(self.count)
class Test(QtGui.QWidget):
def __init__( self, main):
super(Test, self).__init__()
self.Main = main
self.setup()
def setup(self):
print(self.Main.count)
name = "pushButton_"+str(self.Main.count)
print(name)
self.name = QtGui.QPushButton('I am in Test widget '+str(self.Main.count))
layout = QtGui.QHBoxLayout()
layout.addWidget(self.name)
self.setLayout(layout)
app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()
After hours, I found the problem!
You have to declare the signal while creating the pushbutton!
To fix this, I rewrote the setup function as below;
def setup(self):
print(self.Main.count)
name = "pushButton_"+str(self.Main.count)
print(name)
self.name = QtGui.QPushButton('I am in Test widget '+str(self.Main.count))
self.name.clicked.connect(self.x) # self.x is any function
layout = QtGui.QHBoxLayout()
layout.addWidget(self.name)
self.setLayout(layout)
So know, you will run function x whenever you push the new created pushbuttons!
I am trying to create a GUI using the first answer here. I am running into some issues because I don't fully understand how everything should be connected.
I am using a class to create a grid of boxes. Each box uses bind to call some function when clicked.
Where do I create the Many_Boxes class? see all the way at the bottom for an example of what I'm trying to do.
In the Many_Boxes class that is in the Main class I have a actions that I bind to a function. Where do I put that function? How do I call that function? What if I want to call that function from the Nav class?
I have:
class Nav(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
class Main(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.hand_grid_dict = self.create_hand_grid()
class Many_Boxes:
<<<<<<<<< bunch of code here >>>>>>>>>>>>>>
self.hand_canvas.bind("<Button-1>", lambda event: button_action(canvas_hand)) <<<<<< WHAT DO I NAME THIS??? self.parent.....?
class MainApplication(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.navbar = Nav(self)
self.main = Main(self)
self.navbar.pack(side="left", fill="y")
self.main.pack(side="right", fill="both", expand=True)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).grid(row=1, column=1)
root.mainloop()
Where do I put this:
def create_grid(self):
for x in y:
your_box = self.Many_Boxes(.......)
If I'm understanding your question correctly, there is no need to create a class just to create the boxes. All you have to do is replace the Many_Boxes class with your create_hand_grid function, and define the button_action function:
import tkinter as tk
class Navbar(tk.Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
tk.Label(self.parent, text='Navbar').pack()
tk.Button(self.parent, text='Change Color', command=self.change_color).pack()
def change_color(self):
# access upwards to MainApp, then down through Main, then ManyBoxes
self.parent.main.many_boxes.boxes[0].config(bg='black')
class ManyBoxes(tk.Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.boxes = []
self.create_boxes()
def button_action(self, e):
print('%s was clicked' % e.widget['bg'])
def create_boxes(self):
colors = ['red', 'green', 'blue', 'yellow']
c = 0
for n in range(2):
for m in range(2):
box = tk.Frame(self, width=100, height=100, bg=colors[c])
box.bind('<Button-1>', self.button_action)
box.grid(row=n, column=m)
self.boxes.append(box)
c += 1
class Main(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.many_boxes = ManyBoxes(self)
self.many_boxes.pack()
class MainApp(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.navbar = Navbar(self)
self.navbar.pack(fill=tk.Y)
self.main = Main(self)
self.main.pack(fill=tk.BOTH, expand=True)
if __name__ == "__main__":
root = tk.Tk()
MainApp(root).pack()
root.mainloop()
I've filled in create_hand_grid and button_action so you can copy-paste the code and see it work.
I am stuck trying to dynamically display a specific image on a tk page based on button clicked on a previous page. PageOne has 5 images with 5 buttons below each. Clicking on specific button should take the user to the second page and display image 3 if the 3rd button is clicked.
I have figured out how to pass a value of 1 to 5 depending on which button is clicked, and the images are saved pic1.gif,...pic5.gif so to return the correct image I just need to append the value to the file location.
I am struggling to figure out how to refresh PageTwo when it is accessed.
Any help would be greatly appreciated, thanks.
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
self.attributes("-fullscreen", False)
self.geometry('{}x{}'.format(1000, 1000))
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(PageOne)
def show_frame(self, c):
frame = self.frames[c]
frame.tkraise()
class PageOne(tk.Frame):
praiseid = 0
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def PraiseClick(button_id):
PageOne.praiseid = button_id
controller.show_frame(PageTwo)
users= [1,2,3,4,5]
for i in users:
location = str('C:/Users/XXXXXXX/Documents/pic'+str(i)+'.gif')
icon = tk.PhotoImage(file=location)
IDlabel = tk.Label(self,image=icon)
IDlabel.image = icon
IDlabel.place(x=i*100,y=200,width=100,height=100)
for j in users:
praisebutton = tk.Button(self,text="Click",width=10,command=lambda x=j: PraiseClick(int(x)))
praisebutton.place(x=j*100,y=300,width=100,height=44)
backbutton = tk.Button(self, text="Go to Start Page",
command=lambda: controller.show_frame(StartPage))
backbutton.place(x=100,y=50,width=200,height=44)
class PageTwo(tk.Frame):
def get_id(self):
return(PageOne.praiseid)
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.location = 'C:/Users/XXXXXXX/Documents/pic'+str(self.get_id())+'.gif'
icon = tk.PhotoImage(file=self.location)
self.IDlabel = tk.Label(self,image=icon)
self.IDlabel.image = icon
self.IDlabel.place(x=0,y=200,width=100,height=100)
The selected image from the PageOne is not drawn on the PageTwo because the drawing function is localized in the __init__() function and no event is raise when the PageTwo is redraw by calling the function tkraise().
Problem 1 - generate an event when calling tkraise() of the PageTwo.
Here, the OOP of Python will be the answer by overriding the function
tkraise() in the class PageTwo.
class PageTwo(tk.Frame):
...
def tkraise(self):
print('PageTwo.tkraise()')
tk.Frame.tkraise(self)
# then call the drawing icon
self.refresh_icon() # see Problem 2
Problem 2 - localize the drawing of the icon in a function of the class PageTwo.
To take into account of the new selected icon, create a function
refresh_icon() in the class PageTwo and call it from both
__init__() and tkraise() functions.
class PageTwo(tk.Frame):
...
def refresh_icon(self):
self.location = 'C:/Users/XXXXXXX/Documents/pic'+str(self.get_id())+'.gif'
icon = tk.PhotoImage(file=self.location)
self.IDlabel = tk.Label(self,image=icon)
self.IDlabel.image = icon
self.IDlabel.place(x=0,y=200,width=100,height=100)
Add at the end of the __init__() function.
class PageTwo(tk.Frame):
...
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
...
self.refresh_icon()
Bonus 1 - to prevent an Exception in case of missing image, add a check before.
Create a file_exists() function, then check before loading in
PhotoImage().
def file_exists(filepath):
try:
fp_file = open(filepath)
return (True)
except IOError:
return (False)
And in the function refresh_icon() of the class PageTwo:
self.location = 'C:/Users/XXXXXXX/Documents/pic'+str(self.get_id())+'.gif'
if (file_exists(self.location)):
icon = tk.PhotoImage(file=self.location)
...
Bonus 2 - clear the current IDlabel in case of image not loaded.
when creating a new Label in the PageTwo, the variable
self.IDlabel will store the new Image without deleting the old one.
Before creating a new one, call the destroy() function.
Add a declaration of the variable self.IDlabel and assign it to None in the __init__() function. Then call destroy() in the
class PageTwo(tk.Frame):
...
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
...
self.refresh_icon()
self.IDlabel = None
...
def refresh_icon(self):
self.location = 'C:/Users/XXXXXXX/Documents/pic'+str(self.get_id())+'.gif'
if (self.IDlabel): # check label and destroy it
self.IDlabel.destroy()
if (file_exists(self.location)):
...