Use OptionMenu as a Menu in Python using Tkinter - python

To increase my understanding and usability of python I have been building a text editor from scratch. What I'm trying to do now is allow the user to change the size of the font. I have this working using an OptionMenu but by using that I have to have a drop down on the interface. What I'm trying to do is somehow put the option menu into a menu on the top bar (with file_menu = Menu(root)) and make it look like a cascade. I don't have my exact code as I'm on mobile and away from my laptop, but I've been thinking about how to do this and I can't figure it out And wasn't able to find it searching earlier. Thanks in advance!
EDIT: I figured out how to do it, and I apologize for not having my original code. My original code was something like the answer posted below me, but I wanted to let the user select font sizes ranging from 8-40 so having
font_size.add_command(label='8', command=lambda: font_size_changer(8))
font_size.add_command(label='10', command=lambda: font_size_changer(10))
font_size.add_command(label='12', command=lambda: font_size_changer(12))
Just looked bad for how many I wanted. I ended up not doing the OptionMenu and IntVar() and went with a for loop like so:
def font_size_changer(clicked_size):
global current_font_size
current_font_size = clicked_size
text.config(font=(current_font, current_font_size, style_combo))
sizes_list = [8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
font_size = Menu(menu)
font_menu.add_cascade(label="Font Size", menu=font_size)
for x in range(0, len(sizes_list)):
font_size.add_command(label=str(sizes_list[x]), command=lambda: font_size_changer(sizes_list[x]))

Whilst you could use an OptionMenu to do this Tkinter actually has a native Menu object which we could use to achieve similar results much more cleanly and with less code.
This is actually not too tricky to pull off, see below for an example:
import tkinter as tk
class App:
def __init__(self, root):
self.root = root
self.label = tk.Label(self.root, text="Lorem Ipsum", font=("Comic Sans MS", 44))
self.menubar = tk.Menu(self.root)
self.menu = tk.Menu(self.root, tearoff=0)
self.menu.add_command(label="Small", command=lambda:self.label.config(font=("Comic Sans MS", 22)))
self.menu.add_command(label="Medium", command=lambda:self.label.config(font=("Comic Sans MS", 44)))
self.menu.add_command(label="Big", command=lambda:self.label.config(font=("Comic Sans MS", 66)))
self.menubar.add_cascade(label="Size", menu=self.menu)
self.root.config(menu=self.menubar)
self.label.pack()
root = tk.Tk()
App(root)
root.mainloop()
So let's break this down.
We create two Menu objects menubar and menu. We then store all of the commands inside of menu, this creates a fleshed out Menu object with a few entries in them.
Each command is setup to change the font size of label
We then add a cascade item to menubar selecting the predefined menu as our Menu object for the cascade.
root.config(menu=self.menubar is then used to "draw" the Menu object on our window.
This results in a cascading menu at the top of the screen where each option in the menu allows us to change the font size of a label object.

Related

Tkinter Button image not showing

I am using tkinter to make a simple drawing tool. I am trying to put an image, fill_bucket.png, on the button. However, when I run the program, the button is incredibly small, like it has no content, and there is not image.
From looking around, it seems like the issue is probably Python's garbage collection, however I have tried many ways of fixing this and nothing seems to work. The simplest thing I have tried is:
self.fill_bucket_photo = PhotoImage(file='./img/fill_bucket.png')
self.fillButton = Button(self.button_layout, image=self.fill_bucket_photo, compound='top', font=('Arial', 25), width=1, height=1, command=lambda: self.selectTool('fill'))
However this does not work. I have also tried this solution, which I found here:
label = Label(image=photo)
label.image = photo # keep a reference!
label.pack()
However when I do self.fillButton.image after assigning self.fillButton, I get an Attribute error for the image attribute.
Everything that I am doing is inside a class, and I can define normal buttons with text fine.
Additionally, doing
self.fill_bucket_photo = PhotoImage(file='./img/fill_bucket.png')
self.label = Label(image=self.fill_bucket_photo).grid(row=1, column=1)
shows the image normally.
Full example. On my computer, this code creates a window that contains one blank button with no image on it:
from tkinter import *
class App():
def __init__(self):
self.root = Tk()
self.root.resizable(False, False)
self.fill_bucket_photo = PhotoImage(file='./img/fill_bucket.png')
self.fillButton = Button(self.root, image=self.fill_bucket_photo, compound='top', font=('Arial', 25), width=40, height=40).pack()
self.root.mainloop()
app = App()
What are some possible solutions to this?

Creating a new window in tkinter that has the same widgets from the root window

I am wanting to create a tkinter window where when I click a button widget it opens a new window, showing all the widgets, exactly the same, from the root/original window. Essentially creating a second instance of the root window, where the application can have multiple users, using the same GUI, in different windows.
Any help is appreciated.
An example of one of my widgets:
summary_output = Text(
master=window,
height=8,
width=78,
bg="gray95",
borderwidth=2,
relief="groove",
font=("Arial", 12))
My window layout
window = Tk()
window.title("Data Viewer")
window.geometry("750x950")
window.configure(bg='white')
window.iconphoto(False, tk.PhotoImage(file='icon.png'))
I have this but cant seem to place the widgets from the root window:
def new_window():
newWindow = Toplevel(window)
newWindow.geometry("750x950")
newWindow.configure(bg='white')
newWindow.iconphoto(False, tk.PhotoImage(file='icon.png'))
upload_button.place(x=20, y=560)
mainloop()
Is their anyway to change the master to be any window?
Edit:
from tkinter import *
class StaticFrame(Frame):
def __init__(self,master,*args,**kwargs):
Frame.__init__(self,master,*args,**kwargs)
# All your widgets
Label(self,text='This is a reusable frame',font=(0,17)).place(x=0, y=0)
Button(self,text='Click me for nothing').pack()
Label(self,text='End of page').pack()
upload_button = Button(
self,
text="Edit Data",
fg="DodgerBlue4",
font=("Graph Type", 15),
height=1, width=12,
borderwidth=2,
relief="groove")
upload_button.place(x=20, y=50)
root = Tk() # First window
top = Toplevel(root) # Second window
root.geometry("750x968")
StaticFrame(root).pack() # Put the frame on the first window
StaticFrame(top).pack() # Put the frame on the second window
root.mainloop()
Result:
The concept used here is simple, create a "custom frame" that we will put onto these new windows, so that it will create the exact same frame, and widgets within it, inside different windows.
from tkinter import *
class StaticFrame(Frame):
def __init__(self,master,*args,**kwargs):
Frame.__init__(self,master,*args,**kwargs)
# All your widgets
Label(self,text='This is a reusable frame',font=(0,17)).pack()
Button(self,text='Click me for nothing').pack()
Label(self,text='End of page').pack()
root = Tk() # First window
top = Toplevel(root) # Second window
StaticFrame(root).pack() # Put the frame on the first window
StaticFrame(top).pack() # Put the frame on the second window
root.mainloop()
Very simple to code and has been explained with comments, if you do not know what classes and inheritance is then first do go through those. There are variety of other methods that come onto mind when I read this question, like even having an option database and storing the widgets in a list and recreating it based on its order, but this seems to be the easiest in a scratch.

How do I disable keyboard input into an Entry widget, disable resizing of tkinter window and hide the console window?

I am making a calculator using tkinter and I wish to do the following:
Disable keyboard input for the Entry widget so that the user can only input through the buttons.
Even after disabling keyboard input for the Entry widget, I wish to be able to change the background and foreground of the widget.
I wish to hide the console window because it is totally useless in the use of this calculator.
I don't want to let the user resize the root window. How do I disallow the resizing of the root window?
Here is my code so far...
from tkinter import *
root = Tk()
root.title("Calculator")
root.config(background="black")
operator = ""
textVar = StringVar()
def valInput(number):
global operator
operator+=str(number)
textVar.set(operator)
display = Entry(root, textvariable=textVar, font=("Arial", 14, "bold"), bg="lightblue", fg="black", justify="right")
display.grid(row=0, column=0, columnspan=4)
btn7 = Button(root, font=("Arial", 12, "bold"), bg="orange", fg="red", text="7", command= lambda : valInput(7))
btn7.grid(row=1, column=0)
"""
And more buttons...
"""
root.mainloop()
As you can see, I can input into the Entry widget using buttons but later on, after the calculator is complete, if the user inputs characters like abcd... it will cause problems and show errors. How do I disallow keyboard entry so that I can avoid these errors?
I want to make my calculator a bit colorful. I changed the color of the root window, the buttons and also the color of the Entry widget. Is there any way to change the color of the widget even after it is disabled?
I don't need the console window while using this calculator. How do I hide it?
If I resize the root window, the calculator becomes ugly, besides, resizing the window isn't necessary. So how do I prevent the user from resizing the window?
To be able to disable keyboard input in Entry(args)
Set the state to disabled:
display = Entry(root, state=DISABLED)
To be able to disable the feature of resizing the tkinter window (so that you can't drag and stretch it.
root.resizable(0,0)
To be able to make the command prompt window disappear. (I just want the tkinter window.
Rename the file with a .pyw extension (assuming you are using windows)
Don't use from tkinter import * it's really not recommended because it pollutes the main namespace with every public name in the module. At best this makes code less explicit, at worst, it can (and it will) cause name collisions.
Have the right reflexes, use import tkinter or import tkinter as tk instead
this should work, you have to use the disabledbackground option :
import tkinter as tk
root = tk.Tk()
display = tk.Entry(root,font=('Arial', 20, 'bold'), disabledbackground='lightblue', state='disabled')
display.pack()
root.resizable(0,0)
root.mainloop()

How do I change button size in Python?

I am doing a simple project in school and I need to make six different buttons to click on. The buttons must have different sizes, but I can't find how do do it. I have made the button by using:
def __init__(self, master):
super().__init__(master)
self.grid()
self.button1 = Button(self, text = "Send", command = self.response1)
self.button1.grid(row = 2, column = 0, sticky = W)
I imagine that something like:
self.button1.size(height=100, width=100)
would work, but it doesn't and I cannot find how to do it anywhere.
I am using Python 3.3.
Configuring a button (or any widget) in Tkinter is done by calling a configure method
"config"
To change the size of a button called button1 you simple call
button1.config( height = WHATEVER, width = WHATEVER2 )
If you know what size you want at initialization these options can be added to the constructor.
button1 = Button(self, text = "Send", command = self.response1, height = 100, width = 100)
I've always used .place() for my tkinter widgets.
place syntax
You can specify the size of it just by changing the keyword arguments!
Of course, you will have to call .place() again if you want to change it.
Works in python 3.8.2, if you're wondering.

Keep a menu open in Tkinter

I want to keep a menu cascade open, after a command button within the cascade is clicked. So it basically only closes when the user clicks anywhere else (like it would normally too). Can't seem to find a proper option or a method to open said menu in the callback. The invoke() function only works on buttons wihtin the cascade right? How would you go about that?
Yes, I know this was asked a long time ago, but I was curious if there was any way to accomplish this with tkinter, so I fiddled about for a while and figured out how to do it. I was unable to come up with a way to properly place the persistent menu where it was when it originally opened, but I have managed to make it persist in any location you request (I use upper-left corner of root window). And yes, I know this isn't a nice proper class based implementation, but I was just going for as simple a test as I could write without obscuring it with too many extraneous details.
try:
from tkinter import *
from tkinter.ttk import *
except:
from Tkinter import *
from ttk import *
root = Tk()
var = StringVar()
def menu_click(menu, item):
global root
var.set(item)
menu.post(root.winfo_rootx(), root.winfo_rooty())
root.option_add('*tearOff', False) # remove tearoff from all menus
Label(root, textvariable=var).pack() # just to give menu clicks some feedback
root.geometry('400x300')
menubar = Menu(root)
root['menu'] = menubar
menu_test = Menu(menubar)
menubar.add_cascade(menu=menu_test, label='Test')
menu_test.add_command(label='One', command=lambda: menu_click(menu_test, 'One'))
menu_test.add_command(label='Two', command=lambda: menu_click(menu_test, 'Two'))
menu_test.add_command(label='Three', command=lambda: menu_click(menu_test, 'Three'))
menu_cas = Menu(menu_test)
menu_test.add_cascade(menu=menu_cas, label='Four')
menu_cas.add_command(label='One', command=lambda: menu_click(menu_cas, 'Fourty One'))
menu_cas.add_command(label='Two', command=lambda: menu_click(menu_cas, 'Fourty Two'))
menu_cas.add_command(label='Three', command=lambda: menu_click(menu_cas, 'Fourty Three'))
root.mainloop()

Categories