I am very new to Python and have to write a simple GUI program, which I chose to do in tkinter for simplicity.
The GUI I want should be very similar to the dialog one often experiences when installing a program on Windows (where would you like to install, what modules would you like, etc). Basically when it is run in python3.3, I want a window to appear with some options taking up most of the window and 'next', 'back' and 'cancel' buttons at the bottom; by clicking the 'next' button, the current window is closed and a new one opens which looks the same, except that it has different options (or perhaps it is the same window but its contents have been destroyed, I'm not sure which would be better). The rough layout I want is shown in this image
I have looked around for example codes which do something similar to this but have not been able to find any. I have seen this answer, but it's not quite what I want. I have used this tutorial to learn what I know about tkinter but I can't find an answer in it.
Here is my extremely poor attempt at a simplified version of what I want to do: When I run the code it creates a window with two buttons. The 'Quit' button works fine; however, when I click the 'Next' button it closes the window and opens a new one as desired but it also opens another window.
from tkinter import *
from tkinter import ttk
def win1():
mainframe = ttk.Frame(root, padding = '3 3 12 12')
mainframe.grid(column = 0, row = 0, sticky = (N, W, E, S))
mainframe.columnconfigure(0, weight = 1)
mainframe.rowconfigure(0, weight = 1)
ttk.Button(mainframe, text = 'Next', command = win2).grid(
column = 1, row = 1, sticky = W)
ttk.Button(mainframe, text = 'Quit', command = quit).grid(
column = 1, row = 2, sticky = W)
root.mainloop()
def quit():
root.destroy()
def win2():
quit()
new = Toplevel()
new.title('Window 2')
new = ttk.Frame(root, padding = '3 3 12 12')
new.grid(column = 0, row = 0, sticky = (N, W, E, S))
new.columnconfigure(0, weight = 1)
new.rowconfigure(0, weight = 1)
ttk.Button(mainframe, text = 'Next', command = win2).grid(
column = 1, row = 1, sticky = W)
root = Tk()
win1()
This gives the following error message (which I don't understand):
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.3/tkinter/__init__.py", line 1478, in __call__
return self.func(*args)
File "<stdin>", line 23, in win2
File "/usr/lib/python3.3/tkinter/ttk.py", line 733, in __init__
Widget.__init__(self, master, "ttk::frame", kw)
File "/usr/lib/python3.3/tkinter/ttk.py", line 553, in __init__
tkinter.Widget.__init__(self, master, widgetname, kw=kw)
File "/usr/lib/python3.3/tkinter/__init__.py", line 2078, in __init__
(widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: this isn't a Tk applicationNULL main window
Apart from the fact that it doesn't actually do what I want, I feel like I am going about this the completely wrong way (defining the windows in a function, etc), and will run into a lot of trouble when I want to make it more complicated. Would anyone be able to rewrite my code in a better way and in a way that helps me build to more complicated programs, offer a resource for learning what I need to make the program I want or even offer advice? Thanks.
Your problem here is closing the root Tk window when you call quit(). Don't do that. Once you close that you have unloaded Tk and it can't process the Window system messages properly for you. Instead, if you want to create an application that is a series of dialogs, hide the root window by withdrawing it and create each dialog as a new toplevel with the hidden root toplevel as the parent.
I amended your example, with my hopefully correct understanding of what you are trying to achieve.
from tkinter import *
from tkinter import ttk
def win1():
mainframe = ttk.Frame(root, padding = '3 3 12 12')
mainframe.grid(column = 0, row = 0, sticky = (N, W, E, S))
mainframe.columnconfigure(0, weight = 1)
mainframe.rowconfigure(0, weight = 1)
ttk.Button(mainframe, text = 'Next', command = win2).grid(
column = 1, row = 1, sticky = W)
ttk.Button(mainframe, text = 'Quit', command = quit).grid(
column = 1, row = 2, sticky = W)
root.mainloop()
def quit():
root.destroy()
def win2():
root.withdraw()
new = Toplevel()
new.title('Window 2')
mainframe = ttk.Frame(new, padding = '3 3 12 12')
mainframe.grid(column = 0, row = 0, sticky = (N, W, E, S))
mainframe.columnconfigure(0, weight = 1)
mainframe.rowconfigure(0, weight = 1)
ttk.Button(mainframe, text = 'Next', command = win2).grid(
column = 1, row = 1, sticky = W)
root = Tk()
win1()
Hope this helps.
Related
I want to change the text displaying in frame after my mainloop() has been called. I have created loginfo function to append text in my string but nothing happens. The GUI gets started and displays the text originally contained in it("hi"), I don't see the text I add through loginfo function ("hello") and after exiting the GUI I get the below error.
Traceback (most recent call last):
File "1.py", line 5, in <module>
monitor.loginfo()
File "/home/shreyas/Desktop/test/main.py", line 45, in loginfo
self.text.configure(state='normal')
File "/usr/lib/python3.8/tkinter/__init__.py", line 1637, in configure
return self._configure('configure', cnf, kw)
File "/usr/lib/python3.8/tkinter/__init__.py", line 1627, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: invalid command name ".!frame.!text"
My task is to create a function that i will call any time with the text i want to insert.
The function will be called after the mainloop is running as i recieve the text to display.
These are the 2 files I created:
main.py
import tkinter
from tkinter import *
class Monitor:
def __init__(self):
self.root = Tk()
self.root.title('Monitor')
self.root.geometry("800x400")
self.root.grid_columnconfigure((0,1), weight=1)
self.root.grid_rowconfigure(0, weight=1)
"""-----------------------------------------------"""
self.console = Frame(self.root,borderwidth=1)
self.console.grid(row = 0, column = 0, sticky = W+E+N+S)
self.console.grid_columnconfigure(0, weight=1)
self.console.grid_rowconfigure(2, weight=1)
self.lbl_c = Label(self.console, text="console",bg='white')
self.lbl_c.grid(row = 1, column = 0, sticky = W+E+N+S)
self.text = tkinter.Text(self.console)
self.text.grid(row = 2, column = 0,rowspan = 3, columnspan = 1, sticky = N+S+E+W)
self.text.insert(tkinter.END,"hi")
self.text.configure(state='disabled')
"""------------------------------------------------"""
self.fm = Frame(self.root,borderwidth=1)
self.fm.grid(row = 0, column = 1, sticky = W+E+N+S)
self.fm.grid_columnconfigure(0, weight=1)
self.fm.grid_rowconfigure(2, weight=1)
self.lbl_fm = Label(self.fm, text="frequency_monitor",bg='white')
self.lbl_fm.grid(row = 1, column = 0, sticky = W+E+N+S)
self.text1 = tkinter.Text(self.fm)
self.text1.grid(row = 2, column = 0,rowspan = 1, columnspan = 1, sticky = N+S+E+W)
self.text1.insert(tkinter.END,"<---------- Frequency Monitor ---------->\n\n"+"Camera100\n"+"Frequency: 9.6 CPU Time: 3.0ms\n"+("----------------------------------------")+"Screen100\n"+"Frequency: 29.8 CPU Time: 6.0ms\n"+("----------------------------------------"))
self.text1.configure(state='disabled')
def loginfo(self):
self.text.configure(state='normal')
self.text.insert(tkinter.END,"hello")
self.text.update()
self.text.configure(state='disabled')
1.py
import main as m
monitor = m.Monitor()
monitor.root.mainloop()
monitor.loginfo()
I use python 3.1 to run my code. Can someone please tell me what's causing the error and how could I achieve the expected result.
update:
when i use mainloop() like so
import main as m
monitor = m.Monitor()
monitor.root.mainloop()
monitor.root.update()
monitor.root.update_idletasks()
monitor.loginfo()
i get the same error but when i use while
import main as m
monitor = m.Monitor()
#monitor.root.mainloop()
#monitor.loginfo()
while True:
monitor.root.update()
monitor.root.update_idletasks()
monitor.loginfo()
it updates text and keeps updating it since i called loginfo in while But it doesnot update if i call it outside the while loop.
The code after mainloop() gets called only after your applications is closed. So after the application is closed, the method is called, but the widgets used in the method is destroyed. So change your code to:
monitor = Monitor()
monitor.loginfo()
monitor.root.mainloop()
This way, the function is called before you exit the GUI. Think of mainloop() as a while loop that keeps updating till the window is closed. Technically saying mainloop() is same as:
while True: # Only exits, because update cannot be used on a destroyed application
root.update()
root.update_idletasks()
Edit:
Since you wanted a delay, you have to add a button or something that calls this method while the GUI is active, an example is to use after to show the function after some time, like:
monitor = Monitor()
monitor.root.after(1000,monitor.loginfo) # Cause a delay of 1 second or 1000 millisecond
monitor.root.mainloop()
I am constructing a simple hex editor in Python using Tkinter. I want to be able to indicate selection of a value (the "0000"s) in the GUI by changing the colors of a pressed button.
To implement this, I instantiated each button as a class inside a for loop and put each instance into a list to reference them later in the code. When I attempt to alter the Button API properties of the classes in the while loop, the IDLE prints an error when the hex editor is closed:
Traceback (most recent call last):
File "C:/Users/Administrator/Desktop/Files/Python/hex editor.py", line 64, in <module>
ml.button.configure(bg='#00FF00', fg='#000000')
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1479, in configure
return self._configure('configure', cnf, kw)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1470, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: invalid command name ".!frame.!frame.!button"
The intended behavior of the button is that when any "0000" is clicked, the selected button will become green and remain so until another button is pressed. (i.e the pressed button will turn green and the previously selected button will turn black)
Source Code:
from tkinter import *
selected_i = 0
selected_j = 0
data_w = 16
address_w = 8
class mem_location:
def __init__(self, row, column, data):
self.row = row
self.column = column
self.data = data
self.button = Button(subframe,
text=self.data,
font=('Consolas',9),
bd=0,
command=self.select)
self.button.pack(side=LEFT)
self.button.configure(bg='#000000', fg='#00FF00')
def select(self):
selected_i = self.row
selected_j = self.column
root = Tk()
root.configure(bg="black")
root.title('Hex Editor')
frame = Frame(root,
padx=30,
pady=10,
bg='black')
frame.pack()
ml_list = []
for i in range((1<<address_w)>>4):
subframe = Frame(frame,
padx=10,
bg='black')
subframe.pack()
addr_label = Label(subframe,
text=hex(i<<4)+" ",
bg='black',
fg='#00FF00',
width=5)
addr_label.pack(side = LEFT)
for j in range(16):
ml_list.append(mem_location(i, j, '0000'))
root.mainloop()
while True:
for ml in ml_list:
if selected_i == ml.row and selected_j == ml.column:
ml.button.configure(bg='#00FF00', fg='#000000')
else:
ml.button.configure(bg='#000000', fg='#00FF00')
I am currently in the process of learning about Tkinter. Why can't I modify the class's Button configuration and how can this be fixed?
The code after root.mainloop will not run until the window is not closed, so you are trying to modify the buttons after the window has been closed.
Instead, you could move the code in the submit method like this:
from tkinter import *
# You don’t need selected_i and selected_j anymore
data_w = 16
address_w = 8
class MemLocation:
def __init__(self, data):
# Same code without self.row and self.column
def select(self):
for ml in [ml_ for ml_ in ml_list if ml_ is not self]: # All elements except this one
ml.button.config(bg='#000', fg='#0F0')
self.button.config(bg='#0F0', fg='#000')
# Same code here
root.mainloop()
#END
Note that I renamed the class according to PEP 8 but there are many other improvement possible like making MemLocation inherit from Frame or Button, or adding event listeners to make the code cleaner.
So I'm currently teaching myself python, and I am developing a simple GUI restaurant menu. But I came across an error I'm hoping someone could explain, I've solved it by doing self.total_button by option, rather than putting the options in parentheses (like I did for the button above it), but the only thing I have changed is that and the program runs with no errors.
The line in the total method self.total_button["text"] = "Total £"+ str(self.cash_total) only works when I declare self.total_button the way I do, if I declare each option in parentheses it states a Nonetype' object does not support item assignment.
Does layout matter with buttons in tkinter when updating them later in a program?
#Order Up!
#A Simple GUI program, that presents a simple restaurant menu.
#It lists items, and prices.
#Let the user select different items and then show the user the total bill.
from tkinter import *
class Application(Frame):
"""Create a GUI application."""
def __init__(self, master):
"""Initialises the frame"""
super(Application, self).__init__(master)
self.grid()
self.cash_total = 0
self.total_list = []
self.create_widgets()
def create_widgets(self):
"""Creates the widgets that creates the menu ordering systems"""
#Create a instruction label
Label(self,
text = "Click the desired buttons to order something"
).grid(row = 0, column = 0, columnspan = 4, sticky = N)
#Create Burger button
Button(self,
text = "Hamburger no bun",
command = self.hamburger_no_bun
).grid(row = 1, column = 0, sticky = W)
#Creates a total button
#Super weird bug have to set it out like this so i can use the total method later.
self.total_button = Button(self)
self.total_button["text"] = "Total: £"
self.total_button["command"] = self.total
self.total_button.grid(row = 5, column = 5, sticky = W)
#Create Text Box to show current order
self.order_txt = Text (self, width = 100, height = 8, wrap = WORD)
self.order_txt.grid(row = 1, column = 5, sticky = W)
def hamburger_no_bun(self):
"""Creates a hamburger tuple to be added to the list."""
self.hamburger_no_bun = ("Hamburger no bun, £", 2.95)
self.total_list.append(self.hamburger_no_bun)
self.order_txt.insert(0.2, str(self.hamburger_no_bun))
def total(self):
#The affected method
"""Adds the total amount due taken from the total_list"""
for i in self.total_list:
self.cash_total += i[1]
print(self.cash_total)
self.total_button["text"] = "Total £"+ str(self.cash_total)
self.cash_total = 0
#main
root = Tk()
root.title("Order Up! - A Restaurant Menu GUI")
app = Application(root)
root.mainloop()
You probably wrote code like this
self.total_button = Button(self, self, text="Total: £", command=self.total).grid(row = 5, column = 5, sticky = W)
You may be suprised to learn that self.total_button now holds the value None. This is because it is holding the return value of the grid method of the Button not the button reference itself.
Later when you try to use self.total_button it will throw an exception, because the value is None and None has no attribute "Text".
To resolve the issue you must correctly capture the reference to the button and to do this split the line creating and setting up the button into two lines.
self.total_button = Button(self, text="Total: £", command=self.total)
self.total_button.grid(row = 5, column = 5, sticky = W)
Now you have a correct reference to the button, that will be usable later in the total method.
I am new to python and Tkinter and I need some help. I try to write a program which will show toplevel window with message on defined time. I introduce date, hour and text to program. Press "START" button and wait until toplevel window with message appear.
Program work when I do not use thread, but main window "freeze" until loop is done. Then new toplevel window appear with text.
What I would like to do is to get rid of "freezing" main window. My idea was to use thread for loop executing. But it does not work. When loop is finished in a thread it should call function which cause to Toplevel window appear. But it does not. Moreover program freeze.
I know that I should not use thread within tkinter mainloop but I can not figure out how in other way I can get rid of "freezing" main window.
thank you for all your answers.
Rafal
here is my program:
from Tkinter import *
import time
import calendar
import datetime
import thread
class Okienka(object):
def __init__(self, master):
self.rok = Label(master, text = "Podaj rok: ")
self.rok.grid(row = 0, sticky = E)
self.miesiac = Label(master, text = "Podaj miesiac w formacie XX: ")
self.miesiac.grid(row = 1, sticky = E)
self.dzien = Label(master, text = "Podaj dzien w formacie XX: ")
self.dzien.grid(row = 2, sticky = E)
self.godzina = Label(master, text = "Podaj godzine w formacie XX:XX: ")
self.godzina.grid(row = 3, sticky = E)
self.przyp = Label(master, text = "Tekst przypomnienia: ")
self.przyp.grid(columnspan = 2)
self.erok = Entry(master, width = 4)
self.erok.grid(row = 0 ,column = 1)
self.emiesiac = Entry(master, width = 2)
self.emiesiac.grid(row = 1 ,column = 1)
self.edzien = Entry(master, width = 2)
self.edzien.grid(row = 2 ,column = 1)
self.egodzina = Entry(master, width = 5)
self.egodzina.grid(row = 3 ,column = 1)
self.eprzyp = Text(master, width = 50, heigh = 10, font = ("Helvetica",10))
self.eprzyp.grid(columnspan = 2)
self.button1 = Button(master, text = "START", fg = "red", command = watek)
self.button1.grid(columnspan = 2)
def watek():
thread.start_new_thread(Czas,())
def Czas():
data = "{0}-{1}-{2} {3}".format(c.erok.get(), c.emiesiac.get(), c.edzien.get(), c.egodzina.get())
while True:
aktualny_czas = datetime.datetime.today()
czas_str = time.strftime(str(aktualny_czas))
czas_str = czas_str[:16]
print czas_str
if data == czas_str:
okienko()
break
def okienko():
komunikat = c.eprzyp.get("1.0","end-1c")
top = Toplevel()
top.title("Przypomnienie")
msg = Message(top, text = komunikat)
msg.pack()
root = Tk()
c = Okienka(root)
root.mainloop()
Destroying the root window in a Tkinter application actually means destroying (freezing) the whole application, not only the root window. The root window is the main
window for the application; it is the first to pop up and must be the last to
go. If I understand correctly, your application does not have an actual main
window: if a second window is opened from the initial window, closing the
initial window should not quit the application. Am I right?
If I am, the way to do that with Tkinter (or tcl/tk) is to create a fake root
window and hide it. This window will only be used to quit the application when
the last window is closed:
So, here's the deal, i have designed a menu (main) in python using the tkinter module, and what I want is that when I click on a button on the main menu, it opens up another menu (slave), but inside the same window the main menu was. (there is an "Return to main menu" button in the slave menu, so I would like to get back to the main menu if I click this button)
Now, when I click a button, it opens up a new window containing the selected menu...
this is my code so far
from Tkinter import *
def cars ():
cars = Tk ()
Label (cars, text = "Cars Menu").grid (row = 0, columnspan = 2, pady = 20)
Button (cars, text = "Car list", width = 50).grid (row = 1)
Button (cars, text = "New car", width = 50).grid (row = 2)
Button (cars, text = "Erase car", width = 50).grid (row = 3)
Button (cars, text = "Return to main menu", width = 50).grid (row = 4, pady = 20)
root = Tk ()
Label (root, text = "Main Menu").grid (row = 0, columnspan = 2, pady = 20)
Button (root, text = "Cars", width = 30, command = cars).grid (row = 1, column = 0)
Button (root, text = "Bikes", width = 30).grid (row = 1, column = 1)
root.mainloop ()
I'm not sure if I inderstand u correctly : you have main menu with options,and when you click on some option,you want to remove main menu and show another one on its place.I had a similar problem and I think this could help you:
I have the following function:
def _switch_major_panel(self, panel_to_switch_with):
self.major_panel.grid_remove()
self.major_panel = panel_to_switch_with
self.major_panel.grid(sticky='nwes')
self.update()
: here self is Frame object,so is major_panel.major_panel is an attribute to my class Application.Your's may be something like current_menu or something like that.
What I do is this: every time I want to change what my content of major_panel is I create a new Frame with the new content and simply use this function to replace the old one.Try that.
I'm far from an expert but that worked for me.
Good luck