Python 3.7.1 //
Tk version 8.6 //
pywinauto-0.6.8 //
PyCharm Community Edition 2020.1 x64
The objective is to have the button send keys to the window in the background.
Issue appears due to the presence of pywinauto.
If inactive:
from tkinter import *
# from pywinauto.keyboard import send_keys
def left_click(event):
# send_keys('%{TAB}')
# send_keys('{{}ENTER{}}')
print("hello")
root = Tk()
label1 = Label(root, text="Other")
label1.grid(row=0, sticky=E)
bt1_label1 = Button(root, text="Button_1", bg="red")
bt1_label1.grid(row=0, column=1)
bt1_label1.bind("<Button-1>", left_click)
root.mainloop()
Return when manually closing the tkinter window is:
hello
Process finished with exit code 0
If pywinauto is active (uncommented):
from tkinter import *
from pywinauto.keyboard import send_keys
def left_click(event):
# send_keys('%{TAB}')
# send_keys('{{}ENTER{}}')
print("hello")
root = Tk()
label1 = Label(root, text="Other")
label1.grid(row=0, sticky=E)
bt1_label1 = Button(root, text="Button_1", bg="red")
bt1_label1.grid(row=0, column=1)
bt1_label1.bind("<Button-1>", left_click)
root.mainloop()
Return when manually closing the tkinter window is:
hello
Process finished with exit code -1073740771 (0xC000041D)
Any ideas why this happens?
Related
This is a simple gui.
I am taking url from the user in the Entry. When the button is pressed, url is saved in a file and another function is called to start another process through call (within subprocess).
while the process runs, I want to show the indeterminate progress bar (until the button is hit the bar needs to be hidden) and when the process is completed a showinfo message displays to destroy the gui.
Problem: The bar doesn't show up until the process is finished. After the showinfo dialog is displayed, only then it starts progressing. Means, the bar starts progressing the moment it should actually get destroyed.
What is wrong with my code?
import scrapy
import tkinter as tk
from tkinter import messagebox as tkms
from tkinter import ttk
import shlex
from subprocess import call
def get_url():
# get value from entry and write to a file
def scrape():
progress_bar = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress_bar.grid(row=3, column=2)
progress_bar.start(10)
command_line = shlex.split('scrapy runspider /media/mayank/Local/Coding/Lab/Scraping/Practices/img.py')
call(command_line)
mes = tkms.showinfo(title='progress', message='Scraping Done')
if mes == 'ok':
root.destroy()
root = tk.Tk()
root.title("Title")
entry1 = tk.Entry(root, width=90, textvariable=url)
entry1.grid(row=0, column=0, columnspan=3)
my_button = tk.Button(root, text="Process", command=lambda: [get_url(), scrape()])
my_button.grid(row=2, column=2)
root.mainloop()
----Updated Code ---
import scrapy
import tkinter as tk
from tkinter import messagebox as tkms
from tkinter import ttk
import shlex
from subprocess import call
def get_url():
# get value from entry and write to a file
scrapy = None
def watch():
global scrapy
if scrapy:
if scrapy.poll() != None:
# Update your progressbar to finished.
progress_bar.stop()
progress_bar.destroy()
# Maybe report scrapy.returncode?
print(f'scrapy return code =--######==== {scrapy.returncode}')
scrapy = None
else:
# indicate that process is running.
progress_bar.start(10)
print(f'scrapy return code =--######==== {scrapy.returncode}')
# Re-schedule `watch` to be called again after 0.1 s.
root.after(100, watch)
def scrape():
global scrapy
command_line = shlex.split('scrapy runspider ./img.py')
scrapy = Popen(command_line)
watch()
mes = tkms.showinfo(title='progress', message='Scraping Done')
if mes == 'ok':
root.destroy()
root = tk.Tk()
root.title("Title")
url = tk.StringVar(root)
entry1 = tk.Entry(root, width=90, textvariable=url)
entry1.grid(row=0, column=0, columnspan=3)
my_button = tk.Button(root, text="Process", command=lambda: [get_url(), scrape()])
my_button.grid(row=2, column=2)
progress_bar = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress_bar.grid(row=3, column=2)
root.mainloop()
Using subprocess.call interrupts the current process until the called process is finised.
So the GUI won't update until the call is finished.
Important takeaway: Never call subprocess.run, subprocess.call or one of the other convenience functions from the main thread of a tkinter program. Doing so will freeze the GUI. You should only create subprocess.Popen objects from the main thread.
What you should do instead is create a Popen object, while at the same time disabling the start button.
To track the progress, define a function that is periodically called with root.after(), say every 0.1 s.
In this function you could call the poll() method to check if the subprocess has finished.
Alternatively, you could set stdout=subprocess.PIPE and read the data from the subprocess from the stdout attribute of the Popen object.
The code below is a working (for me) example based on your updated question.
Note that I have replaced scrapy (which I don't have) with a relative long-running command on my UNIX machine.
Since you are running scrapy as a subprocess, you should not need import scrapy.
import tkinter as tk
from tkinter import messagebox as tkms
from tkinter import ttk
from subprocess import Popen
proc = None
def watch():
global proc
if proc:
if proc.poll() is not None:
# Update your progressbar to finished.
progress_bar.stop()
progress_bar.destroy()
# Maybe report proc.returncode?
print(f'proc return code =--######==== {proc.returncode}')
proc = None
mes = tkms.showinfo(title='progress', message='Scraping Done')
if mes == 'ok':
root.destroy()
else:
# indicate that process is running.
progress_bar.start(10)
# print(f'proc return code =--######==== {proc.returncode}')
# Re-schedule `watch` to be called again after 0.1 s.
root.after(100, watch)
def scrape():
global proc
command_line = ['netstat']
proc = Popen(command_line)
watch()
root = tk.Tk()
root.title("Title")
url = tk.StringVar(root)
entry1 = tk.Entry(root, width=90, textvariable=url)
entry1.grid(row=0, column=0, columnspan=3)
my_button = tk.Button(root, text="Process", command=lambda: [get_url(), scrape()])
my_button.grid(row=2, column=2)
progress_bar = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress_bar.grid(row=3, column=2)
root.mainloop()
I am doing a project that uses many tkinter buttons on different windows and I want to be able to close a window and run a function at the same time using lambda but the window doesn't close. If I stop using lambda it works again. I tried in repl and in idle but got the same result. This works:
tk = Tk()
tk.geometry('500x300')
def function():
print('hi')
btn = Button(tk, text='hi', command = tk.destroy)
btn.pack()
tk.mainloop()
but this doesn't:
from tkinter import*
tk = Tk()
tk.geometry('500x300')
def function():
print('hi')
btn = Button(tk, text='hi', command = lambda:[ function(),tk.destroy])
btn.pack()
tk.mainloop()
why?
Instead of using:
btn = Button(tk, text='hi', command = lambda:[ hello.hi(),tk.destroy])
use:
btn = Button(tk, text='hi', command = lambda:[hello.hi(),tk.destroy()])
and it should work.
I have a simple program with start and exit buttons. The start button makes a notification using win10toast, but the button remains visibly pressed down and the window becomes unresponsive. The exit button works fine before the start button is pressed. Here's my code:
from tkinter import *
from win10toast import ToastNotifier
root = Tk()
def exit_p():
exit()
def new():
hr.show_toast("New", "Alert")
return
#creates a label widget
myLabel1 = Label(root, text="Full Moon Notification!")
myLabel2 = Label(root, text="Here you can start and exit the program")
button1 = Button(root, text="Start",padx=50,command=new).grid(row=3,column=0)
button2 = Button(root, text="Exit", padx=50,command=exit_p).grid(row=4,column=0)
#puts the widget on the screen
myLabel1.grid(row=0,column=0)
myLabel2.grid(row=1,column=0)
#loop to keep program running
root.mainloop()
The issue is likely because hr.show_toast("New", "Alert") blocks.
The win10toast library conveniently provides an option threaded=True, so just change that code to
hr.show_toast("New", "Alert", threaded=True)
should make it work.
import tkinter as tk
from tkinter import filedialog, Text
from subprocess import call
import os
root = tk.Tk()
def buttonClick():
print('Button is clicked')
def openAgenda():
call("cd '/media/emilia/Linux/Programming/PycharmProjects/SmartschoolSelenium' && python3 SeleniumMain.py",
shell=True)
return
canvas = tk.Canvas(root, height=700, width=700, bg='#263D42')
canvas.pack()
frame = tk.Frame(root, bg='white')
frame.place(relwidth=0.8, relheight=0.8, relx=0.1, rely=0.1)
openFile = tk.Button(root, text='Open file', padx=10,
pady=5, fg="white", bg='#263D42', command=openAgenda)
openFile.pack()
root.mainloop()
the script it calls opens a new browser window, after finishing entering text in that window, it opens a new browser windows and loops.
meanwhile the tkinter button stays clicked, visually.
the reason your Tk GUI freezes is because you have everything running on 1 thread. The mainloop is haulted by the submit function call which must be taking a "long time", so you probably see "Not Responding" appear in your Tk window when you click the button. To fix this, you need spawn a separate thread for submit to run in, so that the mainloop can keep doing it's thing and keep your Tk window from freezing.
this is done using threading. Instead of your button directly calling submit, have the button call a function that starts a new thread which then starts submit. Then create another functions which checks on the status of the submit thread. You can add a status bar too
import tkinter as tk
from tkinter import filedialog, Text
from subprocess import call
import os
import threading
root = tk.Tk()
def buttonClick():
print('Button is clicked')
def openAgenda():
call("cd ' /media/emilia/Linux/Programming/PycharmProjects/SmartschoolSelenium' && python3 SeleniumMain.py",
shell=True)
canvas.update()
return
def start_Agenda_thread(event):
global Agenda_thread
Agenda_thread = threading.Thread(target=openAgenda)
Agenda_thread.daemon = True
Agenda_thread.start()
canvas = tk.Canvas(root, height=700, width=700, bg='#263D42')
canvas.pack()
frame = tk.Frame(root, bg='white')
frame.place(relwidth=0.8, relheight=0.8, relx=0.1, rely=0.1)
openFile = tk.Button(root, text='Open file', padx=10,
pady=5, fg="white", bg='#263D42', command=lambda:start_Agenda_thread(None))
openFile.pack()
root.mainloop()
Tkinter is single-threaded: it can only do one thing at a time. While the script is running, the GUI will be frozen. You'll need to do threading, multiprocessing, or find some other way to incorporate that other script in your GUI.
I've made 3 buttons on my window. I choosed that the main window should have a specific background image and a full screen.
Now there is a problem. I would like to move to a new window (page) (with an other background and other things) by clicking on button 3.
Things i tryd:
from Main.Info.travelhistry import *
I've added this to the main window to open a new python file with the code of the second screen that has to open when clicking on button 3. But I found out that if I do this both windows will open when running main window.
I added root1 = Tk() at the beginning, root1.mainloop() at the end and between them the code for the other window. But this won't work also, its opening 2 windows like above.
Those were all my attempts and i cant figure out a better way. I can but the background would stay the same. But I have to change the background for the new window to a background image i made...
Any idea what im doing wrong?
from tkinter import *
from tkinter.messagebox import showinfo
from Main.Info.travelhistry import *
def clicked1():
bericht = 'Deze functie is uitgeschakeld.'
showinfo(title='popup', message=bericht)
root = Tk()
a = root.wm_attributes('-fullscreen', 1)
#Hoofdmenu achtergrond
C = Canvas(root, bg="blue", height=250, width=300)
filename = PhotoImage(file = "test1.png")
background_label = Label(root, image=filename)
background_label.place(x=0, y=0, relwidth=1, relheight=1)
C.pack()
# Geen OV-chipkaart button
b=Button(master=root, command=clicked1)
photo=PhotoImage(file="button1.png")
b.config(image=photo,width="136",height="53", background='black')
b.place(x=310, y=340)
#Buitenland button
b2=Button(master=root, command=clicked1)
photo1=PhotoImage(file="button2.png")
b2.config(image=photo1,width="136",height="53", background='black')
b2.place(x=490, y=340)
#Reis informatie
b3=Button(master=root)
photo2=PhotoImage(file="button3.png")
b3.config(image=photo2,width="136",height="53", background='black')
b3.place(x=680, y=340)
root.mainloop()
root2.mainloop()
You shouldn't call more than one Tk() window.
Instead, tkinter has another widget called Toplevel which can be used to generate a new window.
See below for an example:
from tkinter import *
root = Tk()
def command():
Toplevel(root)
button = Button(root, text="New Window", command=command)
button.pack()
root.mainloop()
This one opens new window that you can edit.
from tkinter import *
Window = Tk()
def Open():
New_Window = Tk()
#You can edit here.
New_Window.mainloop()
Btn1 = Button(text="Open", command=Open)
Bt1n.pack()
Window.mainloop()