Subclassing tkinter.Menu results in default menus - python

I'm trying to create a tkinter GUI application while organizing my code into classes for the main application, the frame, and the menu. For the menu, I'm subclassing tkinter.menu, but it's giving me a bunch of default menus and not including the menus and commands I've created. I've stripped it down as best I can to the following working example that illustrates the problem:
First I subclassed tkinter.TK to create the main widget, containing the main window and the menu. For illustration, I included one empty method to add as a command in the File menu. Next, I subclassed tkinter.Frame to create the main frame. For illustration, I added a text box to the frame. Finally, I subclassed tkinter.Menu to create the main menu and added the submenu "File" and a command "Open".
import tkinter
class Application(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
self.main = MainWindow(master=self)
self.menu = MainMenu(master=self)
self.main.pack(fill=tkinter.BOTH, expand=True)
def open(self):
pass
class MainWindow(tkinter.Frame):
def __init__(self, master):
tkinter.Frame.__init__(self)
self.master = master
self.textbox = tkinter.Text()
self.textbox.pack(fill=tkinter.BOTH, expand=True)
class MainMenu(tkinter.Menu):
def __init__(self, master=None):
tkinter.Menu.__init__(self, master=None)
self.master = master
self.file = tkinter.Menu(self)
self.add_cascade(label="File", menu=self.file)
self.file.add_command(label="Open", command=master.open)
if __name__ == "__main__":
Application().mainloop()
The result is a bunch of menus I didn't create, including a File menu without my "Open" command:
Here's a screen cap of the results.
Where am I going wrong?

Replace:
self.menu = MainMenu(master=self)
with:
self['menu'] = MainMenu(master=self)
# or self["menu"] = MainMenu(master=self)
# or self.config(menu=MainMenu(master=self))
# or self.configure(menu=MainMenu(master=self))
or add:
self['menu'] = self.menu
# or self["menu"] = self.menu
# or self.config(menu=self.menu)
# or self.configure(menu=self.menu)
anywhere after:
self.menu = ...
to have your menu assigned as the menu to your Toplevel-like widget.

Related

Don't know how to apply mainloop() in my app

I have programmed a 2600 lines application which worked great when running from the IDE. Now I have created an executable via Pyinstaller and now the GUI does not start. The application starts and disappears quickly. I do not get any errors (anymore, solved them), however this problem remains. I think it has to do with missing the mainloop() in my application, which I don't know how to apply in this particular case. Usually its like this:
root = tk.Tk()
root.mainloop()
In my case I created a class for my window adding a menubar and label as statusbar (the latter not shown in my code below). This makes me assigning this class being a Tk() to main_window. Where do I put the mainloop() without getting an error?
I already tried:
main_window.mainloop()
since main_window is the window where all frames put on to, but then I get the following error in the IDE:
main_window.mainloop() AttributeError: 'AppWindow' object has no attribute 'mainloop'
How do I apply the mainloop() into my application without getting the above mentioned error? Or how do I get my GUI to be working in a different manner? Both answers are welcome.
Here is the necessary code to know:
import tkinter as tk
class AppWindow():
def __init__(self, master):
self.master = master
master.title("Basic Application")
master.geometry("1060x680")
master.grid_propagate(False)
#create drop down menu
self.menubar = tk.Menu(master) # main menubar
#Add filemenu
self.filemenu = tk.Menu(self.menubar, tearoff=0) #sub menu
self.filemenu.add_separator() #create a bar in the menu
self.filemenu.add_command(label="Quit", command=master.destroy) #Add submenu item
self.menubar.add_cascade(label="File", menu=self.filemenu) #Add submenu to menubar
self.master.config(menu=self.menubar) #Show menu
class FrameOne(tk.Frame):
def __init__(self, parent):
super().__init__()
self["borderwidth"]=5
self["relief"]="ridge"
self.create_widgets() #Function which creates all widgets
self.position_widgets() #Function which position all widgets
def create_widgets(self): #Function which creates all widgets
pass
def position_widgets(self): #Function which position all widgets
pass
#Create a window as defined in the AppWindow class
main_window = AppWindow(tk.Tk())
#Create a Frame as defined in class FrameOne
first_frame = FrameOne(main_window)
first_frame.grid(row=0, column=0) #Positioning Frame on Window
main_window.mainloop() #THIS PROVIDES AN ERROR | GUI DOES NOT START WITHOUT
mainloop is a method on the tkinter root window and on tkinter itself. Like the error says, it's not a method of your AppWindow class.
In your case you should do it like this:
root = tk.Tk()
main_window = AppWindow(root)
root.mainloop()

How to get a variable out a a tkinter window?

There are some similar questions already on this site. The difference is that I'm coding the Tkinter window as a class. Then making an instance in another file. For example:
import file
gui = file.Window()
gui.mainloop()
# The program continues, using the variables chosen by the user in the previous window.
while file is another file that contains the window code:
import tkinter as tk
class Window(tk.Tk):
def __init__(self, master):
# some code
def mainWidgets(self):
self.body = Body(self)
self.body.grid(row=0, column=0)
class Body(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.widgets()
def widgets(self):
# Labels entries and selections
self.boton_enviar = tk.Button(self, text='Send', command=self.send)
self.boton_enviar.grid()
def send(self):
variables = self.variables
# What code do I add here?
self.quit()
So I want to close the window when clicking the 'Send' button but I need to retrieve some variables to use in the main program. How can I do this ?

Can't Show Tkinter Root Window Again After Using withdraw()

In my program, I am creating a window from my root tkinter window, and hiding the root using the .withdraw() function. When I try to show the root window again by calling the root class, it does not show and my program exits. Here's a rough outline of my code describing the problem:
class MainGUI:
def __init__(self, master):
self.master = master
#....Create and .grid() all GUI Widgets....
# Button for switching to other window
button = Button(text="CLICKME", command=lambda: self.other_window())
# Call and define show function at the end of __init__
self.show()
def show(self):
self.master.update()
self.master.deiconify()
# Create other window and withdraw self on button click
def other_window(self):
OtherGUI(self.master)
self.master.withdraw()
class OtherGUI:
def __init__(self, master):
# Function for returning to main window, calls MainGUI class
# to create window and withdraws self.
def main_window():
MainGUI(self.master)
self.master.withdraw()
master = self.master = Toplevel(master)
#....Create and .grid() all GUI Widgets....
# Button for switching back to main window
button = Button(text="CLICKME", command=lambda: self.main_window())
Using print functions in the MainGUI, I was able to see that when trying to switch back to the main window, show() is actually called, and the entire class does appear to be entered.
This puzzles me as I've only really learn how to do this from other forum posts, and using root.update() and .deiconify() seemed to be the solution for most people, however I have no idea why this isn't working.
Does anyone have an idea as to where I'm going wrong here?
The example you presented will not work for several reason.
#really you should build your gui as an inherited class as it makes things much easier to manage in tkinter.
class MainGUI:
def __init__(self, master):
self.master = master
button = Button(text="CLICKME", command=lambda: self.other_window())
# no need for lambda expressions here.
# missing geometry layout... grid(), pack() or place()
self.show()
# self.show does nothing here because your show method is improperly indented.
# your other_window method is also not properly indented.
def show(self):
self.master.update()
self.master.deiconify()
def other_window(self):
OtherGUI(self.master)
self.master.withdraw()
class OtherGUI:
def __init__(self, master):
# this function should be its own method.
def main_window():
MainGUI(self.master)
self.master.withdraw()
master = self.master = Toplevel(master)
# this is not how you should be defining master.
button = Button(text="CLICKME", command=lambda: self.main_window())
# missing geometry layout... grid(), pack() or place()
# your button command is using a lambda to call a class method but your define it as a function instead.
Here is a simpler version of what you are attempting that will work:
import tkinter as tk
class MainGUI(tk.Tk):
def __init__(self):
super().__init__()
tk.Button(self, text="Open Toplevel", command=self.open_toplevel_window).pack()
def open_toplevel_window(self):
OtherGUI(self)
self.withdraw()
class OtherGUI(tk.Toplevel):
def __init__(self, master):
super().__init__()
tk.Button(self, text="Close top and deiconify main", command=self.main_window).pack()
def main_window(self):
self.master.deiconify()
self.destroy()
MainGUI().mainloop()
As you can see here when you inherit from the tkinter classes that control the main window and toplevel windows it becomes easier to manage them and less code to perform a task.

Connecting objects with tkinter

I’m trying to master tkinter in Python-3. I read some tutorials and have a working window made of 4 classes. (MenuBar class, ToolBar class, Main class and StatusBar class). These classes are instantiated from one main class (MainWindow).
I can run the script with no errors, but I’m stuck with the interaction between the objects. I would like to run a method from the 'main' object when I select something in the 'menubar' object. What’s the correct way to do this, or should I change the architecture of the script to make this possible? - Lets say I want to execute change_main from within the menubar.
I have been looking at static and class methods, but I don't know how to access the instances. I would like to be able to access the 'main' instance from within the 'menubar' instance. All instances are created in the MainWindow class.
EDITED: CODE UPDATED
I added a controller method in the MainWindow class and passed the mainwindow object to the menubar object. In the menubar object the controller method is called by command=controller.controller_test as mentioned by stovfl. The controller_test method calls the change_main method in the main object. Directly calling parent.main.change_main did not work as the main object is instantiated after the menubar object.
import tkinter as tk
class MainWindow(tk.Frame):
def __init__(self, parent):
parent.title("Application Title")
parent.geometry("500x400")
self.menubar = MenuBar(parent, controller=self)
self.toolbar = ToolBar(parent)
self.main = Main(parent)
self.statusbar = StatusBar(parent, 'status shown here...')
def controller_test(self):
self.main.change_main()
class MenuBar(tk.Frame):
def __init__(self, parent, controller=None):
menu = tk.Menu(parent)
parent.config(menu=menu)
submenu = tk.Menu(menu, tearoff=False)
menu.add_cascade(label='File', menu=submenu)
submenu.add_command(label='Test', command=controller.controller_test)
class ToolBar(tk.Frame):
def __init__(self, parent):…
class StatusBar(tk.Frame):
def __init__(self, parent, message=''):…
class Main(tk.Frame):
def __init__(self, parent):
self.main = tk.Frame(parent, bg='black')
self.main.pack(fill='both', expand=True)
label.pack()
def change_main(self, event=None):
self.main.pack_forget()
self.main = tk.Frame(self.parent, bg='blue')
self.main.pack(fill='both', expand=True)
if __name__ == '__main__':
root = tk.Tk()
MainWindow(root)
root.mainloop()

{PYTHON + TKINTER} "Type cast" problem: How to retrieve Custom frame objects from a Pmw.NoteBook?

I am writing a GUI Python application.
I am using Tkinter + PythonMegaWidgets for semplicity reasons.
Going straight to the point, I need to extend Tkinter.Frame baseclass, by adding some custom member functions which provides extra-functionalities.
These "Custom Frames" will be added to single tabs of a Pmw.NoteBook object.
Official related docs can be found at: http://pmw.sourceforge.net/doc/NoteBook.html
Later, I need to retrieve "Custom Frame" instances from NoteBook, invoking custom member functions that I have added; here the problems begins...
despite the principle of Duck Typing, I can not access ANY of these "extra-methods" because the methods of Pmw.NoteBook class can only return Frame objects!.
I can not find any solution.
Below a piece of sample (more simplified) code which describes in detail my issue.
from Tkinter import *
from Pmw import NoteBook
# I define a custom frame, by extending Tkinter.Frame baseclass
class CustomPanel(Frame):
def __init__(self, master, _text):
Frame.__init__(self, master)
self.label = Label(self, text=_text)
self.localVariable = "Hello, world!" # I define a new local variable
self.label.pack()
self.pack()
# I define a custom member function, which I _ABSOLUTELY_ want to be accessible.
def customMethod(self):
print self.localVariable
# main frame of application
class MyFrame(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.noteBook = NoteBook(self) # I create a NoteBook object...
tab1 = self.noteBook.add("tab 1") # then, I add one (empty) tabs to it
panel1 = CustomPanel(tab1, "hello")
self.button = Button(self, text="Call CustomMethod()!", command=self.callCustomMethod) # I add a button test
self.noteBook.grid()
self.button.grid(row=1)
self.pack()
self.mainloop()
# I define click handler for button,
def callCustomMethod(self):
panel1 = self.noteBook.page(0) # I try to get frame contained in current tab
# pane11 is supposed to be a 'CustomPanel' object;
try:
panel1.customMethod() # ...then, custom method should be accessible
except AttributeError:
print 'AttributeError!'
# for illustration purpose only, I show that Panel1 is a 'Frame'(superclass) object only!
print panel1.__class__
frame = MyFrame() # create a MyFrame instance
Pressing the button, console output is:
AttributeError!
Tkinter.Frame
to anticipate objections:
1- Set panel1.class attribute, as showed below,
try:
panel1.__class__ = CustomPanel
panel1.customMethod() # ...then, custom method should be accessible
except AttributeError:
print 'AttributeError!'
DON'T work, because customMethod() could not access in any case to localVariable, which is declared in CustomPanel subclass only;
2- I can not even recall CustomPanel constructor, because this will RESET original member variables, which I want to retrieve with their original values.
Any help is appreciated.
IT
I don't think you need to change the class of the notebook tab, you can just add your custom frame inside that tab. Just tell the frame what your inner frame is, and remember to pack your inner frame inside the tab frame.
For example:
class MyFrame(Frame):
def __init__(self, master=None):
...
panel1 = CustomPanel(tab1, "hello")
panel1.pack(fill="both", expand=True)
tab1.inner_panel = panel1
...
def callCustomMethod(self):
tab1 = self.noteBook.page(0)
panel1 = tab1.inner_panel
...

Categories