I want my display from the console to be displayed in a GUI (Tkinter). It should display exactly when it outputs to the Python console and not after the project is finished. Can you do this with Tkinter or are there other alternatives?
These are my current output. These should be shown on a gui in real Time.
Start program
iterations: [159]
Iteration 1 = complete
Iteration 2 = complete
.....
Iteration 159 = complete
lr 1.0
rc 1.0
rf 0.9966666666666667
gb 1.0
Training time: 8.76517425537s
Training finished
Process finished with exit code 0
You can do the following:
import sys
from tkinter import Tk, Button, Frame
from tkinter.scrolledtext import ScrolledText
class PrintLogger(object): # create file like object
def __init__(self, textbox): # pass reference to text widget
self.textbox = textbox # keep ref
def write(self, text):
self.textbox.configure(state="normal") # make field editable
self.textbox.insert("end", text) # write text to textbox
self.textbox.see("end") # scroll to end
self.textbox.configure(state="disabled") # make field readonly
def flush(self): # needed for file like object
pass
class MainGUI(Tk):
def __init__(self):
Tk.__init__(self)
self.root = Frame(self)
self.root.pack()
self.redirect_button = Button(self.root, text="Redirect console to widget", command=self.redirect_logging)
self.redirect_button.pack()
self.redirect_button = Button(self.root, text="Redirect console reset", command=self.reset_logging)
self.redirect_button.pack()
self.test_button = Button(self.root, text="Test Print", command=self.test_print)
self.test_button.pack()
self.log_widget = ScrolledText(self.root, height=4, width=120, font=("consolas", "8", "normal"))
self.log_widget.pack()
def reset_logging(self):
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
def test_print(self):
print("Am i working?")
def redirect_logging(self):
logger = PrintLogger(self.log_widget)
sys.stdout = logger
sys.stderr = logger
if __name__ == "__main__":
app = MainGUI()
app.mainloop()
Related
I am writing a python application using Tkinter.
The purpose of the application is to allow the user to pick a date from a calendar.
The user have to click on a button to spawn a new window,
The user then select the date and updates the value in the mainframe.
I have coded it:
import tkinter as tk
class App(tk.Tk):
""" Allows user to pick a date """
def __init__(self):
super().__init__()
self.label_a01 = tk.Label(text = 'Selected date is:')
self.var_selected_date = tk.StringVar(value = "Noting selected")
self.label_selected_date = tk.Label(textvariable = self.var_selected_date)
self.button_a01 = tk.Button(self,
text="Select the first day", command = self.select_a_date)
self.button_a02 = tk.Button(self,
text="Select the last day", command = self.select_a_date)
self.label_a01.pack()
self.label_selected_date.pack()
self.button_a01.pack(pady = 5)
self.button_a02.pack(pady = 5)
def select_a_date(self):
self.window01 = tk.Toplevel(self)
self.label_b01 = tk.Label(self.window01, text = 'Please select a date from the calendar')
self.button_b01 = tk.Button(self.window01, text="01 Jan 2022", command = lambda: self.update_date_to_mainframe(self.button_a01, "01 Jan 2022"))
self.button_b02 = tk.Button(self.window01, text="31 Dec 2022", command = lambda: self.update_date_to_mainframe(self.button_a02, "31 Dec 2022"))
self.label_b01.pack(pady = 5)
self.button_b01.pack(pady = 5)
self.button_b02.pack(pady = 5)
def update_date_to_mainframe(self, widget, text_to_update):
widget.config(text = text_to_update)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()
However, if the user click the button multiple times, it will open many new windows.
Is there a way to limit only 1 new Toplevel window that can be opened from the button press?
Thanks to acw1668 for suggesting modal dialogs.
Found an answer somewhere on reddit:
(credits to socal_nerdtastic)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try: #python3 imports
import tkinter as tk
except ImportError: #python3 failed, try python2 imports
import Tkinter as tk
class Main(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
lbl = tk.Label(self, text="this is the main frame")
lbl.pack()
btn = tk.Button(self, text='click me', command=self.open_popup)
btn.pack()
def open_popup(self):
print("runs before the popup")
Popup(self)
print("runs after the popup closes")
class Popup(tk.Toplevel):
"""modal window requires a master"""
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
lbl = tk.Label(self, text="this is the popup")
lbl.pack()
btn = tk.Button(self, text="OK", command=self.destroy)
btn.pack()
# The following commands keep the popup on top.
# Remove these if you want a program with 2 responding windows.
# These commands must be at the end of __init__
self.transient(master) # set to be on top of the main window
self.grab_set() # hijack all commands from the master (clicks on the main window are ignored)
master.wait_window(self) # pause anything on the main window until this one closes
def main():
root = tk.Tk()
window = Main(root)
window.pack()
root.mainloop()
if __name__ == '__main__':
main()
My base program imports it's GUI interface from a script GUI.py
old_stdout = sys.stdout
root = Tk.Tk()
root.title('Coursera-dl')
root.geometry("345x230")
app = GUI.Interface(root)
app.mainloop()
if app.button_press() == True and app.return_data():
data = app.return_data()
main(data[0],data[1],data[2],data[3],data[4],data[5],data[6],data[7],data[8])
sys.stdout = old_stdout
In my GUI.py :
class Interface(ttk.Frame):
def __init__(self,parent=None):
ttk.Frame.__init__(self,parent)
self.parent = parent
self.New_Window()
def New_Window(self):
self.newWindow = Tk.Toplevel(self.parent)
self.app = CoreGUI(self.newWindow)
class StdoutRedirector(object):
def __init__(self,text_widget):
self.text_space = text_widget
def write(self,string):
self.text_space.insert('end', string)
self.text_space.see('end')
class CoreGUI(object):
def __init__(self,parent):
self.parent = parent
self.InitUI()
def InitUI(self):
self.text_box = Tk.Text(self.parent, wrap='word', height = 11, width=50)
self.text_box.grid(column=0, row=0, columnspan = 2, sticky='NSWE', padx=5, pady=5)
sys.stdout = StdoutRedirector(self.text_box)
But what It does is it opens two windows and the first window (the toplevel one) works as expected and the second is idle , This is what is expected until I click a certain button which after pressing prints data continuously and the data printed should appear in the second window's text widget however this doesn't happen and there is no response from the program and when I close the Toplevel window an error message appears
"TclError: invalid command name "".33328904.33329104"""
So How can I print the data in Text Widget rather than in the console?
EDIT:
Inorder to help ya'll if you struggling with this, I've made a script to redirect stdout to a Tkinter Text widget, see it in action here :-)
The problem is that when you call app.mainloop(), the thread is busy executing the Tkinter mainloop, so the statements before it are not executed until you exit the loop. But once you exit the mainloop, you try to use the Text widget but it is already destroyed.
I recommend you to move the call to main to the callback of a Tkinter widget (I suppose you are already trying to do that with app.button_press()), so the Text object can be used to display the text.
class CoreGUI(object):
def __init__(self,parent):
self.parent = parent
self.InitUI()
button = Button(self.parent, text="Start", command=self.main)
button.grid(column=0, row=1, columnspan=2)
def main(self):
print('whatever')
def InitUI(self):
self.text_box = Text(self.parent, wrap='word', height = 11, width=50)
self.text_box.grid(column=0, row=0, columnspan = 2, sticky='NSWE', padx=5, pady=5)
sys.stdout = StdoutRedirector(self.text_box)
root = Tk()
gui = CoreGUI(root)
root.mainloop()
I am creating 2 window in my program and i am using two class, since the code is complex, i separate it in 2 different python file. After i imported the second window file, how can i make sure it open without having this error which show in this picture
The original result should look like this after the new window button clicked:
Coding for Main Window:
from tkinter import *
import classGUIProgram
class Window(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.geometry("600x400+30+30")
self.wButton = Button(self, text='newWindow', command = self.OnButtonClick)
self.wButton.pack()
def OnButtonClick(classGUIProgram):
classGUIProgram.top = Toplevel()
master = Tk()
b = classGUIProgram.HappyButton(master)
master.mainloop()
if __name__ == "__main__":
window = Window(None)
window.title("title")
window.mainloop()
Coding for Second Window:
from tkinter import *
class HappyButton:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.printButton = Button(frame, text="Print message", command=self.printMessage)
self.printButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Quit", command= quit)
self.quitButton.pack(side=LEFT)
self.downloadHistoryCB=Checkbutton(frame, text="Download History")
self.downloadHistoryCB.pack(side=LEFT)
def printMessage(self):
print("Wow this actually worked!")
master = Tk()
b = HappyButton(master)
master.mainloop()
You're creating extra Tk windows. Here is an example of using Toplevel widgets and another file.
mainWindow.py
import tkinter as tk
import secondWindow as sW
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title("Main Window")
self.geometry("600x400+30+30")
tk.Button(self, text = "New Window", command = self.new_window).pack()
tk.Button(self, text = "Close Window", command = self.close).pack()
self._second_window = None
def new_window(self):
# This prevents multiple clicks opening multiple windows
if self._second_window is not None:
return
self._second_window = sW.SubWindow(self)
def close(self):
# Destory the 2nd window and reset the value to None
if self._second_window is not None:
self._second_window.destroy()
self._second_window = None
if __name__ == '__main__':
window = MainWindow()
window.mainloop()
secondWindow.py
import tkinter as tk
class SubWindow(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.title("Sub Window")
self.geometry("400x300+30+30")
# Change what happens when you click the X button
# This is done so changes also reflect in the main window class
self.protocol('WM_DELETE_WINDOW', master.close)
tk.Button(self, text = "Print", command = self.printMessage).pack()
def printMessage(self):
print("Wow this actually worked!")
When using another file be sure to not have any global code you don't want running. Your classes don't have to inherit from Tk and Toplevel, this is just an example. But you need to ensure you only ever have one instance of Tk otherwise you get the behaviour you encountered
Here is the code that i run in the python CMD:
import mailbox
import pprint
f = open("results.txt","w")
mbox = mailbox.mbox('c:\documents and settings\student\desktop\mail\mailall.mbox')
count = 0
for msg in mbox:
pprint.pprint(msg._headers, stream = f)
if msg['Delivered-To'] == 'example#example.co.uk':
count += 1
f.close()
print(count)
However when i run it in the GUI, it makes the GUI crash (Flashes black and doesn't open). Here is the GUI:
import datetime
from tkinter import filedialog
import tkinter
class App:
def __init__(self, master):
self.master = master
# call start to initialize to create the UI elemets
self.start()
def start(self):
self.master.title("Extract Email Headers")
self.now = datetime.datetime.now()
# CREATE A TEXT/LABEL
# create a variable with text
label01 = "Please select the .mbox file you would like to analyse"
# put "label01" in "self.master" which is the window/frame
# then, put in the first row (row=0) and in the 2nd column (column=1),
# align it to "West"/"W"
tkinter.Label(
self.master, text=label01).grid(row=0, column=0, sticky=tkinter.W)
# CREATE A TEXTBOX
self.filelocation = tkinter.Entry(self.master)
self.filelocation["width"] = 60
self.filelocation.focus_set()
self.filelocation.grid(row=1, column=0)
# CREATE A BUTTON WITH "ASK TO OPEN A FILE"
# see: def browse_file(self)
self.open_file = tkinter.Button(
self.master, text="Browse...", command=self.browse_file)
# put it beside the filelocation textbox
self.open_file.grid(row=1, column=1)
# now for a button
self.submit = tkinter.Button(
self.master, text="Execute!", command=self.start_processing,
fg="red")
self.submit.grid(row=3, column=0)
def start_processing(self):
import mailbox
import pprint
f = open("results.txt","w")
mbox = mailbox.mbox('c:\documents and settings\student\desktop\mail\mailall.mbox')
count = 0
for msg in mbox:
pprint.pprint(msg._headers, stream = f)
if msg['Delivered-To'] == 'example#example.co.uk':
count += 1
f.close()
print(count)
def browse_file(self):
# put the result in self.filename
self.filename = filedialog.askopenfilename(title="Open a file...")
# this will set the text of the self.filelocation
self.filelocation.insert(0, self.filename)
root = tkinter.Tk()
app = App(root)
root.mainloop()
If i take the process code out and put "pass", the GUI works so i know its the code that is throwing it off.? Thanks
I want to show a progress bar while downloading a file from the web using the urllib.urlretrive method.
How do I use the ttk.Progressbar to do this task?
Here is what I have done so far:
from tkinter import ttk
from tkinter import *
root = Tk()
pb = ttk.Progressbar(root, orient="horizontal", length=200, mode="determinate")
pb.pack()
pb.start()
root.mainloop()
But it just keeps looping.
For determinate mode you do not want to call start. Instead, simply configure the value of the widget or call the step method.
If you know in advance how many bytes you are going to download (and I assume you do since you're using determinate mode), the simplest thing to do is set the maxvalue option to the number you are going to read. Then, each time you read a chunk you configure the value to be the total number of bytes read. The progress bar will then figure out the percentage.
Here's a simulation to give you a rough idea:
import tkinter as tk
from tkinter import ttk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.button = ttk.Button(text="start", command=self.start)
self.button.pack()
self.progress = ttk.Progressbar(self, orient="horizontal",
length=200, mode="determinate")
self.progress.pack()
self.bytes = 0
self.maxbytes = 0
def start(self):
self.progress["value"] = 0
self.maxbytes = 50000
self.progress["maximum"] = 50000
self.read_bytes()
def read_bytes(self):
'''simulate reading 500 bytes; update progress bar'''
self.bytes += 500
self.progress["value"] = self.bytes
if self.bytes < self.maxbytes:
# read more bytes after 100 ms
self.after(100, self.read_bytes)
app = SampleApp()
app.mainloop()
For this to work you're going to need to make sure you don't block the GUI thread. That means either you read in chunks (like in the example) or do the reading in a separate thread. If you use threads you will not be able to directly call the progressbar methods because tkinter is single threaded.
You might find the progressbar example on tkdocs.com to be useful.
I simplified the code for you.
import sys
import ttk
from Tkinter import *
mGui = Tk()
mGui.geometry('450x450')
mGui.title('Hanix Downloader')
mpb = ttk.Progressbar(mGui,orient ="horizontal",length = 200, mode ="determinate")
mpb.pack()
mpb["maximum"] = 100
mpb["value"] = 50
mGui.mainloop()
Replace 50 with the percentage of the download.
If you just want a progress bar to show that the program is busy/working just change the mode from determinate to indeterminate
pb = ttk.Progressbar(root,orient ="horizontal",length = 200, mode ="indeterminate")
Here's another simple example that also shows a progress bar moving. (I have simplified the examples given at https://gist.github.com/kochie/9f0b60384ccc1ab434eb)
import Tkinter
import ttk
root = Tkinter.Tk()
pb = ttk.Progressbar(root, orient='horizontal', mode='determinate')
pb.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb.start(50)
root.mainloop()
Modal dialog window with Progressbar for the bigger project
This example is a bit long, but tested on Python 3.6 and can be used in the bigger project.
# -*- coding: utf-8 -*-
# Modal dialog window with Progressbar for the bigger project
import time
import tkinter as tk
from tkinter import ttk
from tkinter import simpledialog
class MainGUI(ttk.Frame):
''' Main GUI window '''
def __init__(self, master):
''' Init main window '''
ttk.Frame.__init__(self, master=master)
self.master.title('Main GUI')
self.master.geometry('300x200')
self.lst = [
'Bushes01.png', 'Bushes02.png', 'Bushes03.png', 'Bushes04.png', 'Bushes05.png',
'Forest01.png', 'Forest02.png', 'Forest03.png', 'Forest04.png', 'Road01.png',
'Road02.png', 'Road03.png', 'Lake01.png', 'Lake02.png', 'Field01.png']
b = ttk.Button(self.master, text='Start', command=self.start_progress)
b.pack()
b.focus_set()
def start_progress(self):
''' Open modal window '''
s = ProgressWindow(self, 'MyTest', self.lst) # create progress window
self.master.wait_window(s) # display the window and wait for it to close
class ProgressWindow(simpledialog.Dialog):
def __init__(self, parent, name, lst):
''' Init progress window '''
tk.Toplevel.__init__(self, master=parent)
self.name = name
self.lst = lst
self.length = 400
#
self.create_window()
self.create_widgets()
def create_window(self):
''' Create progress window '''
self.focus_set() # set focus on the ProgressWindow
self.grab_set() # make a modal window, so all events go to the ProgressWindow
self.transient(self.master) # show only one window in the task bar
#
self.title(u'Calculate something for {}'.format(self.name))
self.resizable(False, False) # window is not resizable
# self.close gets fired when the window is destroyed
self.protocol(u'WM_DELETE_WINDOW', self.close)
# Set proper position over the parent window
dx = (self.master.master.winfo_width() >> 1) - (self.length >> 1)
dy = (self.master.master.winfo_height() >> 1) - 50
self.geometry(u'+{x}+{y}'.format(x = self.master.winfo_rootx() + dx,
y = self.master.winfo_rooty() + dy))
self.bind(u'<Escape>', self.close) # cancel progress when <Escape> key is pressed
def create_widgets(self):
''' Widgets for progress window are created here '''
self.var1 = tk.StringVar()
self.var2 = tk.StringVar()
self.num = tk.IntVar()
self.maximum = len(self.lst)
self.tmp_str = ' / ' + str(self.maximum)
#
# pady=(0,5) means margin 5 pixels to bottom and 0 to top
ttk.Label(self, textvariable=self.var1).pack(anchor='w', padx=2)
self.progress = ttk.Progressbar(self, maximum=self.maximum, orient='horizontal',
length=self.length, variable=self.num, mode='determinate')
self.progress.pack(padx=2, pady=2)
ttk.Label(self, textvariable=self.var2).pack(side='left', padx=2)
ttk.Button(self, text='Cancel', command=self.close).pack(anchor='e', padx=1, pady=(0, 1))
#
self.next()
def next(self):
''' Take next file from the list and do something with it '''
n = self.num.get()
self.do_something_with_file(n+1, self.lst[n]) # some useful operation
self.var1.set('File name: ' + self.lst[n])
n += 1
self.var2.set(str(n) + self.tmp_str)
self.num.set(n)
if n < self.maximum:
self.after(500, self.next) # call itself after some time
else:
self.close() # close window
def do_something_with_file(self, number, name):
print(number, name)
def close(self, event=None):
''' Close progress window '''
if self.progress['value'] == self.maximum:
print('Ok: process finished successfully')
else:
print('Cancel: process is cancelled')
self.master.focus_set() # put focus back to the parent window
self.destroy() # destroy progress window
root = tk.Tk()
feedback = MainGUI(root)
root.mainloop()