Python Tkinter GUI File Menu Not Displaying though GUI is operational - python-3.x

I'm relatively new to Python and I'm sure this is an error with the structure of my code, but I cannot seem to get the filemenu to display in my GUI. Can someone tell me what errors I have made with the filemenu inclusion? Also, I am sorry, but the spacing after copying and pasting is a little off. The class indentation level is proper on my side. I am using Python 3.71
Any other comments on better or more Pythonic ways to accomplish what I have here are also welcome and thank you for your help in advance!
from tkinter import *
from tkinter import ttk
import tkinter.scrolledtext as tkst
import os
import tkinter as tk
from functools import partial
from PIL import Image, ImageTk
class UserGui(tk.Tk):
def __init__(self,parent):
self.parent=parent
self.widgets()
def widgets(self):
self.parent.configure(bg='white')
self.frame1_style = ttk.Style()
self.frame1_style.configure('My.TFrame', background='white')
self.frame2_style = ttk.Style()
self.frame2_style.configure('My2.TFrame',background='white')
self.parent.title("TGUI")
self.frame1 = ttk.Frame(self.parent, style='My.TFrame') #Creating Total Window Frame 1
self.frame1.grid(row=0, column=0, sticky=(N, S, E, W))
self.frame2 = ttk.Frame(self.parent, width=100, height=20, style='My2.TFrame')
self.frame2.grid(row=0, column=6, padx=20, pady=5)
#Menu Creation
self.menu1 = tk.Menu(self.parent, tearoff=0)
self.parent.config(menu=self.menu1)
self.fileMenu = tk.Menu(self.menu1, tearoff=0)
self.fileMenu.add_command(label="Open", command=self.donothing)
self.fileMenu.add_command(label="Save", command=self.donothing)
self.fileMenu.add_separator()
self.fileMenu.add_command(label="Exit", command=self.parent.quit)
self.fileMenu.add_cascade(label="File", menu=self.menu1)
self.editMenu = tk.Menu(self.menu1, tearoff=0)
self.editMenu.add_command(label="Cut", command=self.donothing)
self.editMenu.add_command(label="Copy", command=self.donothing)
self.editMenu.add_command(label="Paste", command=self.donothing)
self.editMenu.add_cascade(label="Edit", menu=self.menu1)
def donothing(self):
filewin = Toplevel(self.parent)
button = Button(filewin, text="Do nothing button")
button.pack()
def main():
root=tk.Tk()
ug=UserGui(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit 1,2,3: I have corrected the add_cascade option for menu with menu=self.menu1 and I still do not have a file menu displaying.

EDIT: I'm sorry I didn't notice the Python-3 tag in time, it's all the same except when inherriting you would call super().__init__ instead of the Frame.__init__ directly. That would make it more Py3-like. Even so, this should still work.
Weirdly, pushing the menu.config down to the run function worked for me - even though it looks like it should work the way you did it.
def main():
root=tk.Tk()
ug=UserGui(root)
root.config(menu=ug.fileMenu)
root.mainloop()
if __name__ == '__main__':
main()
Oterwise there are some things you can work on to make it more OOP like and readable. THis is how I usually handle making GUIs. The idea is to split the GUI's into Frames that then do simmilar things. I.e. your app could have left and right Frame where the RightFrame would hold the textbox ad the left Frame would actually have 2 sub frames - one for the names and dropdowns and the other for the buttons. That way each individual functionality is handled by the Frames themselves and it's not all in one giant class, the elements in those Frames are placed relative to the Frame's grid itself, while all the Frames are placed in the MainFrame's grid. This lets you split a lot of code into modules as well and helps with maintainability.
The sub-frames emit "global" events (events bothering other frames) by propagating them through the MainFrame, that's why they all carry a self.parent - their parent frame, and a self.root - the MainFrame. The MainFrame is also the Frame in which I like to put something like self.data which itself is a class on its own (outside Tkinter) that handles all the data input/output and logic so that you don't clutter the GUI code logic with data calculations and logic. Ideally the Data class would handle data errors and GUI would only then have to handle any errors in logic (such as selecting two impossible-to-combine options from the dropdown menus.
from tkinter import *
from tkinter import ttk
class SubFrame(Frame):
def __init__(self, parent, text="Top Right"):
Frame.__init__(self)
self.pack()
self.parent = parent
self.root = parent.root
self.label=Label(self, text=text).pack()
class RightFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=RIGHT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Right Frame").pack()
class LeftFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=LEFT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Left Frame").pack()
#again Frames which would have a parent class RightFrame and root MainFrame
self.subFrame1 = SubFrame(self)
self.subFrame2 = SubFrame(self, text="Top Right SubFrame 2")
class MainFrame(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry("1100x600")
self.title("Working Example")
self.leftFrame = LeftFrame(self)
self.rightFrame = RightFrame(self)
#self.data = MagicalDataHandlingClass()
def run():
app = MainFrame()
app.mainloop()
EDIT answer to comments that are too long to fit
The call to Frame.__init__(...) is made because the class definition looks like class LeftFrame(Frame). Usually to declare a class what you would write is just class LeftFrame. When you add the bit in the () what is happening is called inheritance. When you inherit from a class (called parent), your class (called child) inherits all of the methods and attributes of parent. But much like you have to initialize your class to get an object, i.e. lf = LeftFrame(...) the parent class has to be initialized too. In Python this initialization is done by calling the special dunder __init__(...) function. So that call to Frame.__init__(...) happens because you need to tell the parent class what are all the values it needs to work properly. In Python 3 however it is recommended that instead of instantiating the parent by name like that you use the super function like super().__init__(....). This happens for a lot of complicated reasons most of which you probably don't have to worry about for a while yet (such as what if you inherit from multiple classes at the same time, what if you inherit from a class that inherited from a different one, etc...). I wouldn't try to feel overwhelmed by understanding the complete power of super() if you're just starting because 99% of the time in Python 3 just doing super().__init__(...) will do exactly what you want even if you don't understand. If you feel like getting in over your head Raymond Hettinger has a good writeup of Super is Super and why exactly it's so much better than old way.

I will post this answer for completeness considering #JasonHarper has not copied it to an answer format and I want others to be able to benefit from the post.
The key was the object that I was calling the add_cascade on the child Menu widget object instead of the main Menu widget object called self.menu1. The key was changing:
self.fileMenu.add_cascade(label="File", menu=self.menu1)
to :
self.menu1.add_cascade(label="File", menu=self.fileMenu)
This was the proper way of adding the fileMenu Menu object to the total Menu widget object of self.menu1.

Related

Python scrollable frame and canvas window sizing

I have been trying to find a way to size a frame inside of a canvas window for quite a while to no avail. I finally came across some posts that helped me begin to understand the problem, and eventually dug up a post that gave the solution below:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
root=tk.Tk()
canvas = tk.Canvas(root, background="blue")
frame = tk.Frame(canvas, background="red")
canvas.pack(expand=True, fill="both")
canvas.create_window((0,0), window=frame, anchor="nw", tags="frame")
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
This completely solves my problem....if I don't have the GUI in a function, which I need to. I have multiple different GUI's that would need to implement this solution. I have come across other solutions that use OOP, but I haven't yet wrapped my head around OOP. I've also found a way to make the above code work inside of a program myself:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
def test():
window=tk.Tk()
global canvas
canvas = tk.Canvas(master=window)
frame=tk.Frame(master=canvas, background='red')
canvas.pack(expand=True, fill=tk.BOTH)
canvas.create_window((0,0), window=frame, anchor=tk.NW, tags = 'frame')
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
test()
However, this requires the use of a global variable, which I would rather avoid. Is there anything I'm missing that would help me resize the frame inside of the canvas window? If you have any pointers to where I might even find this information that would also be helpful.
The event object that is passed in has a reference to the widget that received the event. So, just replace your global canvas with e.widget, or initialize a local variable named canvas:
def onCanvasConfigure(e):
canvas = e.widget
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
If it helps, here's an object-oriented version of your application code. Other than the implementation differences, it should behave the same way as the functional version.
import tkinter as tk
class App(tk.Tk): # create a base app class that inherits from Tk
def __init__(self):
super().__init__() # initialize Tk
self.canvas = tk.Canvas(master=self)
self.frame = tk.Frame(master=self.canvas, background='red')
self.canvas.pack(expand=True, fill=tk.BOTH)
self.canvas.create_window(
(0,0),
window=self.frame,
anchor=tk.NW,
tags='frame',
)
self.canvas.bind('<Configure>', self.on_canvas_configure)
def on_canvas_configure(self, event):
self.canvas.itemconfig(
'frame',
height=self.canvas.winfo_height(),
width=self.canvas.winfo_width(),
)
if __name__ == '__main__':
root = App() # instantiate the App class
root.mainloop() # start the app
Since everything here is contained within the App class, you can avoid globals (thanks to self!)

Tkinter Label class not appearing when used within a class

I am creating a basic GUI with multiple, but similar, label structures. However, when I created a class to help minimize the text, and placed it within a label frame, the label frame does not appear. This only happens when I use the class within a class, and if I use the regular label and label frame classes everything works out well. I'm trying to figure out as to why this is the case.
My code:
main.py
from tkinter import *
def main():
main_window = Tk()
app = First(main_window)
main_window.mainloop()
class GPULabel(Label):
def __init__(self, master, varText):
varText = varText
super().__init__()
self["text"] = varText
self["anchor"] = "w"
self["width"] = 25
class First:
def __init__(self, root):
self.root = root
self.root.title('First Window')
self.myFrame = LabelFrame(self.root, text="frame")
self.myFrame.pack()
label1 = GPULabel(self.myFrame, "label")
lable1.pack()
if __name__ == '__main__'
main()
This opens a window but it is completely empty. However, if I swap to a regular Label(self.myFrame...) then the window pops up correctly. Why is that? And is there a way to make my original method work?

Change widget focus by clicking widget [duplicate]

This question already has an answer here:
how to accept/ignore QKeyEvent
(1 answer)
Closed 3 years ago.
I have two PyQt5 widgets and both need keyboard input. Initially, one widget has the setFocusPolicy set to QtCore.Qt.StrongFocus, but when both widgets has this property activated, both of them get the input. I would like to initially set the input in one of them and if the user clicks in the other widget, the focus would be changed to the clicked widget or vice versa.
MRE:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtWidgets import QOpenGLWidget, QWidget
import sys
class Renderizador(QOpenGLWidget):
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("OpenGL")
super().keyPressEvent(event)
class Diedrico(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
def paintEvent(self, event):
qp = QPainter(self)
qp.setPen(QPen(Qt.black))
qp.drawRect(0, 0, 1000, 1000) # Marco
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("Widget")
super().keyPressEvent(event)
class UiVentana(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(UiVentana, self).__init__(parent)
self.resize(1500, 1015)
self.setFixedSize(1500, 1015)
self.statusBar().setSizeGripEnabled(False)
self.widget_central = QtWidgets.QWidget(self)
self.Renderizador = Renderizador(self.widget_central)
self.Renderizador.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
self.Renderizador.setFocusPolicy(QtCore.Qt.StrongFocus)
visor = QtWidgets.QWidget(self.widget_central)
visor.setGeometry(1010, 510, 470, 460)
self.scene = QtWidgets.QGraphicsScene(visor)
self.view = QtWidgets.QGraphicsView(self.scene)
self.diedrico = Diedrico(visor)
self.diedrico.setFixedSize(470, 460)
self.view.setFocusPolicy(QtCore.Qt.StrongFocus)
self.scene.addWidget(self.diedrico)
self.setCentralWidget(self.widget_central)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("Ui")
super().keyPressEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
ui = UiVentana()
ui.show()
sys.exit(app.exec_())
Based on your code, I'm not getting key events from the Diedrico widget, but only from the Renderizador and UiVentana instancies; with your implementation it seems very unlikely that you get key events from both the Renderizador and Diedrico widgets, since their parent hierarchy is completely different and they actually are members of different "physical" windows.
If you really get the Widget and OpenGL outputs from a single key event, it might be a bug, but, frankly, I doubt that, and that's mostly because there are some issues in your implementation, mostly due to confusing parent/child relationship.
If that's not the case (meaning that you're getting the key events from Renderizador and UiVentana), the explanation is simple: a QOpenGLWidget, as any basic QWidget class, doesn't process nor "consume" a key event; as soon as you call the base implementation with super() the event is propagated to its parents (UiVentana, in your case). If you want to stop the event propagation, just return without calling the base implementation of keyPressEvent.
Finally, some notes about your example.
When you want to add a widget to a scene, it must have a parent that is already embedded in the scene or it shouldn't have a parent at all (as in a top level widget). In your code you created the Diedrico widget setting its parent to a child of the "widget_central", which at that point is a widget without no parent (meaning that it would be a top level widget, as in a "window"): no matter what you do afterwards (setting it as the central widget), the topmost parent has not been embedded, and you can't add any of its child to a scene.
Qt itself warns about this when executing your code:
StdErr: QGraphicsProxyWidget::setWidget: cannot embed widget 0x911ae78 which is not a toplevel widget, and is not a child of an embedded widget
Then, you created the view, but you never actually add it to the main window or any of its children. You can see the Diedrico instance only because of the aforementioned problem: the widget is added to the main widget because of the parent set in the class initialization, but it's never added to the scene.
Be very careful when initializing widgets and setting parents, expecially when you're going to embed them into a QGraphicsScene: QWidgets added to a scene are actually QGraphicsProxyWidgets, not "actual" widgets, and some special care is required when dealing with them.

How can I create a multiple buttons in tkinter that change the background image when hovering over them?

I am coding a GUI for a Mathematics Formula Calculator. I want to create multiple buttons that change the background image when hovering over them, and I don't really know how to go about doing that...
I have already tried creating a class for the button itself so that I can modify the behaviour of it, it did not work...
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master,**kw)
self.defaultbackground = tk.PhotoImage(file = "GeometricBackground.png")
self.currentbackground = tk.PhotoImage(file = "")
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, currentbackground):
image = tk.Label(self, image = currentbackground)
image.pack()
def on_leave(self):
image = tk.Label(self, image = self.defaultbackground)
image.pack()
root = tk.Tk()
classButton = HoverButton(root, currentbackground = "MainMenu.png")
classButton.grid()
root.mainloop()
I was really hoping this would cut it, but I got this error message when it executed:
_tkinter.TclError: unknown option "-currentbackground"
Any help would be appreciated :)
There are several issues with your code:
The error you get is because you are trying to pass the currentbackground option to your HoverButton but given the way your class is defined:
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master,**kw)
currentbackground ends into the kw dictionary you pass in argument to the standard tkinter Button class which has no currentbackground option, hence the unknown option error. To fix it, you can define the options specific to your class like
def __init__(self, master, defaultbackground="", currentbackground="", **kw):
tk.Button.__init__(self, master=master, **kw)
so that defaultbackground and currentbackground won't end in the kw dictionary.
When an event occurs, the function you bound to this event is executed with one argument, the "event" object that contains information about the event (like the pointer coordinates, the widget in which the event happened ...) so you need to add this argument when you define on_enter() and on_leave().
You are creating a label to put the image inside then packing this label in the button. This is overly complicated (and probably will result in the button not reacting to click events). The button class has an image option to set the background image of the button, so you can change the image with button.configure(image=<image>).
Inserting all those changes in the code gives
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, defaultbackground="GeometricBackground.png", currentbackground="", **kw):
tk.Button.__init__(self, master=master, **kw)
self.defaultbackground = tk.PhotoImage(file=defaultbackground)
self.currentbackground = tk.PhotoImage(file=currentbackground)
self.configure(image=self.defaultbackground)
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, event):
self.configure(image=self.currentbackground)
def on_leave(self, event):
self.configure(image=self.defaultbackground)
root = tk.Tk()
classButton = HoverButton(root, currentbackground="MainMenu.png")
classButton.grid()
root.mainloop()

Is there a way to stop a tkinter class object from opening until the button is opened

I have a little problem with my code. I am writing multiples classes with different GUI interfaces as a project. However, every time I import those classes the GUI window automatically opens the window and I want the window to open only when a button is clicked.
from FinalProject import addFlight
from FinalProject import reserveFlight
class ex:
def __init__(self,win):
self.win = win
...
...
def mainButtons(self):
look = Button(self.win, text="Add New Flight",command=lambda: self.reserveMenu(1))
look.place(relx="0.2", rely="0.3")
res = Button(self.win, text="Book A Flight",command=lambda: self.reserveMenu(2))
res.place(relx="0.4", rely="0.3")
...
...
def reserveMenu(self, options):
if options == 1:
self.flight = Toplevel(self.win)
self.flMenu = addFlight.AddFlights(self.flight)
self.flMenu.addingFlight()
# call(["python","addFlight.py"])
if options == 2:
pass
# self.flight = Toplevel(self.win)
# self.flMenu = reserveFlight.ReserveFlights(self.flight)
# self.flMenu.reserve()
# call(["python","reserveFlight.py"])
...
...
The "reserveMenu" function works fine but is there way to suppress those import statements or at least prevent the windows from opening until the button is clicked.
I know there are other methods of opening my python code but this HAS to be done using CLASSES. Trust me I have found way easier methods of doing this. FYI, there is more code but I only copied the more important parts.
Instead of using a method you could define your reserve option windows as classes, ReserveAdd, ReserveBook, that inherit from tkinter.Toplevel. And all a button would do is to call them. Here's an example:
import tkinter as tk
root = tk.Tk()
class ReserveAdd(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.master = master
tk.Label(self, text="This is ReserveAdd window.").pack()
class ReserveBook(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.master = master
tk.Label(self, text="This is ReserveBook window.").pack()
def res_one():
ReserveAdd(root)
def res_two():
ReserveBook(root)
tk.Button(root, text="Reserve Option 1", command=res_one).pack()
tk.Button(root, text="Reserve Option 2", command=res_two).pack()
root.mainloop()
In the above example Reserve Option 1 calls an instance of ReserveAdd class whereas Reserve Option 2 calls an instance of a ReserveBook class.
I'd define a single method for buttons but that's not exactly the scope here.

Resources