Logging progress/error on a tkinter widget - python

I have GUI built with tkInter like below.
My actual window is a bit more complex but I think this suffices for the question I have.
The window has Entry, 2 Button's & a scrollText widget.
With the Browse button, user selects the file that she wants to process and clicking the process button call the processFile function to process the file.
What I want to do now is to print the 'progress' of in the scrollText widget. Nothing but the values of the print statements should appear in the scrollText widget one after the other. It need not be print statements, it be some other methods also (I do not know what other methods are possible).
Also, if there is a error. If you click the process button without selecting the file for example, the program encounters an error and stops. That error also needs to be shown in the scrollText widget.
How do I accomplish this?
import tkinter as tk
import tkinter.filedialog
from tkinter import scrolledtext
import pandas as pd
root=tk.Tk() # create a window within which all buttons and fileds are located
root.geometry('700x300') #defien the sixe of the window
def quit_me():
"""Function to end the process when 'x' is clicked"""
root.quit()
root.destroy()
def browsefunc(entry):
"""Function to browse the file & set the browsed file path to the
entry box in the GUI"""
browsedFile =tkinter.filedialog.askopenfilename(filetypes=(("text files","*.txt"),("All files","*.*")))
if not browsedFile: #if cancel for pressed instead of selectign a file
pass
else: #if the file was seelcted
entry.delete(0,tk.END)
entry.insert(0, browsedFile)
def processFile(fileToProcess):
df = pd.read_csv(fileToProcess, header=None, sep=';')
print('df created...')
print('df processes...')
print('df processessing complete...')
return df
fileNameEntry=tk.Entry(root)
fileNameEntry.place(x = 30, y = 30,width = 400, height = 20)
fileNameEntryButton =tk.Button(master=root,
text="Browse",
command=lambda: browsefunc(fileNameEntry))
fileNameEntryButton.place(x = 460, y = 30, width = 45, height = 20)
executeButton =tk.Button(master=root,
text="Process",
fg="#45ad61",
command= lambda: processFile(fileNameEntry.get()))
executeButton.place(x = 400, y = 60, width = 150, height = 25)
progressWindow = scrolledtext.ScrolledText(root,
bg='white',
relief="groove")
progressWindow.place(x = 30, y = 100 ,height=100,width=600,)
root.protocol("WM_DELETE_WINDOW", quit_me)
root.mainloop()

Related

How do i pause my code before displaying the label

In my code, I am trying to make a loading screen for a frogger game but for some reason I am encountering a problem where I display a picture and then do the .sleep function before displaying a label over the top of it however it displays both of them at the same time it just runs the code 1 second after it should, can anyone help?
Here is my code below:
from tkinter import *
import tkinter as tk
import time
window = Tk()
window.geometry("1300x899")
LoadingScreen = PhotoImage(file = "FroggerLoad.gif")
Loading = Label(master = window, image = LoadingScreen)
Loading.pack()
Loading.place(x = 65, y = 0)
time.sleep(1)
FroggerDisplay = Label(master = window, font ("ComicSans",100,"bold"),text = "Frogger")
FroggerDisplay.pack()
FroggerDisplay.place(x = 500, y = 300)
window.mainloop()
When you use time.sleep(1) before starting the window.mainloop(), the window is created only after 1 second, and the FroggerDisplay label will be created at the same time with it. So, you can't use time.sleep(seconds) now.However, you can use window.after(ms, func) method, and place into the function all the code between time.sleep(1) and window.mainloop(). Note, that unlike the time.sleep(seconds) you must give the time to window.after (the first argument) as milliseconds.Here is the edited code:
from tkinter import *
def create_fd_label():
frogger_display = Label(root, font=("ComicSans", 100, "bold"), text="Frogger") # create a label to display
frogger_display.place(x=500, y=300) # place the label for frogger display
root = Tk() # create the root window
root.geometry("1300x899") # set the root window's size
loading_screen = PhotoImage(file="FroggerLoad.gif") # create the "Loading" image
loading = Label(root, image=loading_screen) # create the label with the "Loading" image
loading.place(x=65, y=0) # place the label for loading screen
root.after(1000, create_fd_label) # root.after(ms, func)
root.mainloop() # start the root window's mainloop
PS: 1) Why do you use .pack(...) and then .place(...) methods at the same time - the first one (.pack(...) here) will be ignored by Tkinter.
2) It's better to use a Canvas widget for creating a game - unlike labels it supports transparency and simpler to use. For example:
from tkinter import *
root = Tk() # create the root window
root.geometry("1300x899") # set the root window's size
canv = Canvas(root) # create the Canvas widget
canv.pack(fill=BOTH, expand=YES) # and pack it on the screen
loading_screen = PhotoImage(file="FroggerLoad.gif") # open the "Loading" image
canv.create_image((65, 0), image=loading_screen) # create it on the Canvas
root.after(1000, lambda: canv.create_text((500, 300),
font=("ComicSans", 100, "bold"),
text="Frogger")) # root.after(ms, func)
root.mainloop() # start the root window's mainloop
Note: you might need to change coords with Canvas.

Tkinter gives following error in python 3.6: TclError: NULL main Window

I am writing a python program which executes the following sequence:
1. Dialog box to open/select a directory
2. perform certain operations
3. rename the file using a tkinter dialog box
4. Perform rest of the operations
I have written the following code:
def open_directory():
directory_name = filedialog.askdirectory(title='Ordner Auswählen',parent=root)
print(directory_name)
root.destroy()
def input_name():
def callback():
print(e.get())
root.quit()
e = ttk.Entry(root)
NORM_FONT = ("Helvetica", 10)
label = ttk.Label(root,text='Enter the name of the ROI', font=NORM_FONT)
label.pack(side="top", fill="x", pady=10)
e.pack(side = 'top',padx = 10, pady = 10)
e.focus_set()
b = ttk.Button(root, text = "OK", width = 10, command = callback)
b.pack()
def close_window():
root.destory()
root = tk.Tk()
root.withdraw()
open_directory() #dialogue box to select directory
<perform certain operations>
input_name() #dialgue box for user input file name
root.mainloop()
close_window() #exit the mainloop of tkinter
<perform rest of the functions>
but I get the following error
Tclerror: NULL main window
I think it is realted to declaring root as the main window, but I dont seem to find where I have made the mistake.
Is there some other method, which is better, for what I am trying to do here?
As #CommonSense has mentioned, when you use withdraw to hide the main window, then you need to use the method deiconify to use the root again. Hence, change the function change_directory as follows:
def open_directory():
directory_name = filedialog.askdirectory(title='Ordner Auswählen',parent=root)
print(directory_name)
root.deiconify()
If you do not deiconify the window, you could not call the function input_name, which makes use of the root window.
I have tested this code and it works.
PS: You also have a typo in the function close_window (when destroying the window).
Your use of .destroy() and .quit() as #CommonSense truly said do not really seem well planned.
Not only that, you need to use triggers or events to control your function calls, else they just run straight the one preventing the other from running as is the case in your code.
You should also control when close_window() is called with an event:
from tkinter import filedialog
import tkinter as tk
def open_directory():
directory_name = filedialog.askdirectory(title='Ordner Auswählen',parent=root)
print(directory_name)
#root.destroy()
input_name()
def input_name():
def callback():
print(e.get())
#root.quit()
es_variable=tk.StringVar()
e = tk.Entry(root, textvariable=es_variable)
NORM_FONT = ("Helvetica", 10)
label = tk.Label(root,text='Enter the name of the ROI', font=NORM_FONT)
label.pack(side="top", fill="x", pady=10)
e.pack(side = 'top',padx = 10, pady = 10)
e.focus_set()
b = tk.Button(root, text = "OK", width = 10, command = callback)
b.pack()
def close_window():
root.destory()
root = tk.Tk()
#root.withdraw()
open_dir_button = tk.Button(root, text = "Open Dialog", width = 10, command =open_directory)
open_dir_button.pack()
#dialogue box to select directory
#<perform certain operations>
#dialgue box for user input file name
root.mainloop()
#close_window() #exit the mainloop of tkinter
#<perform rest of the functions>

Issue with Combobox

excuse the greeness. Trying to build GUI where option selected from Combobox populates text box. Nothing happening. First time programming so appreciate i have made a lot of errors here.
import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
# function to display course selected
def courseDisplay():
box = course.get()
print(box)
# Create instance
win = tk.Tk()
win.resizable(130,130)
win.title("RaceCourse GUI")
# create combobox
course = tk.StringVar()
courseChosen = ttk.Combobox(win,width=60,textvariable=course,state='readonly')
courseChosen['values'] = ("Choose a course","Ascot", "Bath", "Chester")
courseChosen.grid(column=5, row=1,rowspan = 3, columnspan = 3,padx = 300, pady = 40)
courseChosen.current(0)
courseChosen.bind("<<ComboboxSelected>>", courseDisplay)
# create scrolled Text control
scrolW = 46
scrolH = 10
box = scrolledtext.ScrolledText(win, width=scrolW, height=scrolH, wrap=tk.WORD)
box.grid(column=5, row=8, columnspan=3,padx = 300,pady = 10)
# Start GUI
win.mainloop()
Since function courseDisplay is called when some event occurs on combobox (namely, when some option is selected), it should accept one variable (usually called event). So, your function should look like this:
def courseDisplay(event=None):
box = course.get()
print(box)
Of course, You should add another logic for showing test in textbox instead of print.

Multiple windows open at once in python

I've searched and found a few things on parent windows in python but that is not what I was looking for. I am trying make a simple program that opens a window and another window after that when the previous one is closed. I was also trying to implement some kind of loop or sleep time to destroy the window by default if the user does not. This is what I have (I'm new please don't laugh)
from tkinter import *
import time
root = Tk()
i = 0
if i < 1:
root.title("title")
logo = PhotoImage(file="burger.gif")
w1 = Label(root, image=logo).pack()
time.sleep(3)
root.destroy()
i = i + 1
if i == 1:
root.title("title")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(root, image=photoTwo).pack()
time.sleep(3)
root.destroy()
i = i + 1
mainloop.()
Perhaps you're looking for something like this:
from tkinter import *
import time
def openNewWindow():
firstWindow.destroy()
secondWindow = Tk()
secondWindow.title("Second Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(secondWindow, image=photoTwo).pack()
secondWindow.mainloop()
firstWindow = Tk()
firstWindow.title("First Window")
logo = PhotoImage(file="burger.gif")
w1 = Label(firstWindow, image=logo).pack()
closeBttn = Button(firstWindow, text="Close!", command=openNewWindow)
closeBttn.pack()
firstWindow.mainloop()
This creates a button in the first window, which the user clicks. This then calls the openNewWindow function, which destroys that window, and opens the second window. I'm not sure there's a way to do this using the window exit button.
To get create a more sustainable window creation, use this:
from tkinter import *
import time
def openThirdWindow(previouswindow):
previouswindow.destroy()
thirdWindow = Tk()
thirdWindow.title("Third Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(thirdWindow, image=photoTwo).pack()
thirdWindow.mainloop()
def openSecondWindow(previouswindow):
previouswindow.destroy()
secondWindow = Tk()
secondWindow.title("Second Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(secondWindow, image=photoTwo).pack()
closeBttn = Button(secondWindow, text="Close!", command= lambda: openThirdWindow(secondWindow))
closeBttn.pack()
secondWindow.mainloop()
def openFirstWindow():
firstWindow = Tk()
firstWindow.title("First Window")
logo = PhotoImage(file="burger.gif")
w1 = Label(firstWindow, image=logo).pack()
closeBttn = Button(firstWindow, text="Close!", command= lambda: openSecondWindow(firstWindow))
closeBttn.pack()
firstWindow.mainloop()
openFirstWindow()
This places the opening of each window in a seperate function, and passes the name of the window through the button presses into the next function. Another method would be setting the window names as global, but this is messy.
The function "lambda:" calls the function, in tkinter you must type this if you want to pass something through a command.
We initiate the whole process first first called "openFirstWindow()"

bring window to top level

I want to present several questions, one after another. The first question is shown as I like, with the cursor set in the entry field. Then I destroy the window and call the function again to create a new window. This time the window is not shown in the front and therefore I first have to click on the screen in order to have the cursor set to the entry field. Also the escape key does not work until I click on the screen to bring the window to the top. I'd be very happy for your help!
Thank you in advance!
Here's my code:
from Tkinter import *
def text_input_restricted(fn,question, nr_letters, limit, len_min, len_max,keys, justify):
class MyApp():
def validate(root, S):
return all(c in keys for c in S)
def __init__(self, q= None):
#save response after "next"-button has been clicked
def okClicked():
lines = e.get()
if len_min < len(lines) < len_max:
lines = unicode(lines).encode('utf-8')
datFile = open(fn, "a")
datFile.write(" '%s'"%(lines))
datFile.close()
self.root.destroy()
self.root = Tk()
vcmd = (self.root.register(self.validate), '%S')
#quit if escape-key has been pressed
self.root.bind('<Escape>', lambda q: quit())
#colors
color = '#%02x%02x%02x' % (200, 200, 200)
self.root.configure(bg=color)
#set window size to screen size
RWidth=MAXX
RHeight=MAXY
self.root.geometry(("%dx%d")%(RWidth,RHeight))
#remove buttons (cross, minimize, maximize)
self.root.overrideredirect(1)
#remove title
self.root.title("")
#item
labelWidget = Label(self.root,text=question, font=("Arial", int(0.02*MAXX)), bd=5, bg=color, justify="center")
labelWidget.place(x=0, y=RHeight/40,width=RWidth)
#"next"-button
ok_width = RWidth/15
ok_height = RWidth/15
okWidget = Button(self.root, text= "next", command = okClicked, font=("Arial",int(0.015*MAXX)), bd=5, justify="center")
okWidget.place(x=RWidth/2-ok_width/2,y=13*RHeight/40, width=ok_width,height=ok_height)
def callback(sv):
c = sv.get()[0:limit]
sv.set(c)
sv = StringVar()
width=nr_letters * int(0.02*MAXX)*1.3
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(self.root, textvariable=sv,font=("Arial", int(0.02*MAXX)),justify=justify,validate="key", validatecommand=vcmd)
e.place(x=RWidth/2-width/2, y=9*RHeight/40, width=width)
#show cursor
e.focus_set()
self.root.mainloop()
MyApp()
MAXX=1366
MAXY=768
fn = "D:/test.dat"
text_input_restricted(fn = fn, question=u"f for female, m for male", nr_letters=1, limit =1, len_min =0, len_max=2, keys = 'fm', justify="center")
text_input_restricted(fn = fn, question="How old are you?", nr_letters=2,limit=2, len_min = 1, len_max = 3, keys = '1234567890',justify="center")
In Tk you use the raise command to bring a window to the front of the Z-order. However, raise is a keyword in Python so this has been renamed to lift. Provided your application is still the foreground application you can call the lift() method on a toplevel widget. If the application is not the foreground application then this will raise the window but only above other windows from the same application. On Windows this causes the taskbar icon for your application to start flashing.
You might do better to destroy the contents of the toplevel and replace them. Or even better - create a number of frames holding each 'page' of your application and toggle the visibility of each frame by packing and pack_forgetting (or grid and grid forget). This will avoid loosing the focus completely - you can just set the focus onto the first widget of each frame as you make it visible.

Categories