Python GUI buttons wont execute - python

I'm writing a basic war-driving program. I have gotten it to loop the command to pull all the wireless access points near by. The problem is my stop button doesn't work and I am unable to update the label(I'm not even sure if I can update the label).
import sys, os, subprocess, re
from Tkinter import *
missionGO = 0
count = 0
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.start = Button(frame, text="Start", fg="green",
command=self.startButtonClick)
self.start.grid(row=3)
self.stop = Button(frame, text="Stop", fg="red",
command=self.stopButtonClick)
self.stop.grid(row=3, column=1)
self.totalSSIDLabel = Label(frame, text="Current Access Points: ")
self.totalSSIDLabel.grid(row=0)
self.totalSSID = Label(frame, text=count)
self.totalSSID.grid(row=0, column=1)
def startButtonClick(self):
missionGO = 1
while (missionGO == 1):
wlan = getAccessPoints()
x = numberOfAccessPoints(wlan)
print x
return
def stopButtonClick(self):
missionGO = 0
return
def stop(event):
missionGO = 0
# Finds all wireless AP
def getAccessPoints():
X = subprocess.check_output("netsh wlan show network mode=Bssid",
shell=True)
return X
def numberOfAccessPoints(file):
count = 0
words = file.split()
for line in words:
if re.match('SSID', line):
count = count + 1
return count
#Main
root = Tk()
app = App(root)
root.mainloop()

Tkinter is single threaded. That means that while you are in the while loop inside startButtonClick, no other events are processed. The stop button won't call its command until the startButtonClick function finishes
You need to remember that your program is already running a global infinite loop: the event loop. There's no reason to put another infinite loop inside it. When you want something to run forever, the trick is to put one iteration on the event loop, then when it runs it puts another iteration on the event loop.
The other key to this is to make sure that one iteration of the loop is fast -- it needs to be well under a second (more like under 100ms) or the UI will become laggy.
The logic looks something like this:
def startButtonClick(self):
self.missionGO = 1
self._do_one_iteration()
def _do_one_iteration(self):
if self.missionGO == 1:
wlan = getAccessPoints()
x = numberOfAccessPoints(wlan)
print x
# this adds another iteration to the event loop
self.after(10, self._do_one_iteration)
def stopButtonClick(self):
self.missionGO = 0

I think the main thread is hanging in the while loop of the start button click. Since it's busy it won't even notice the stop button has been pressed.

I can't tell you exactly why your stop button doesn't work, but I think I got the idea of your programm. My suggestion is to establish two threads. The first thread for the UI, and the second for constantly checking wireless networks with given interval (your current code checks ASAP - bad practice, you should pause within the loop.
Since I have not dealt with multithreading in Tkinter, I can only provide you with entry points:
threading Module
time.sleep for updating the nearby networks every second or similar
Is there a way to request a function to be called on Tkinter mainloop from a thread which is not the mainloop?
Tkinter: invoke event in main loop
Good luck!

Related

Tkinter ignores root.after delay

Tkinter ignores root.after
The countdown animation plays but it ignores the delay and does everything that's after root.after before the countdown is over
All tests suggest that the countdown is happening and the variable is changing, it just skips the delay
def StartTheSpam():
global TimeLable, ErrorLabel
#destoryes error labels and the countdown timer
try:
for child in root.winfo_children():
if child == TimeLable or child == ErrorLabel:
child.destroy()
except NameError:
pass
#needed for fail-safe 1
mouse = MouseController()
countdown_value = 5
counter = tk.StringVar()
counter.set(countdown_value)
TimeLable = tk.Label(frame, textvariable=counter, padx=10, pady=5, bg=Modes[mode.get()][3]['background'], fg=Modes[mode.get()][2]['text'])
TimeLable.pack()
#coundown
for countdown in range(1, countdown_value):
root.after(1000 * countdown, counter.set, countdown_value - countdown)
x = 100
y = 100
try:
with open(PreferencesStorage['FilePath'], 'r') as SpamText:
while PreferencesStorage['loop']:
for word in SpamText:
#fail safe 1
if x < 21 and y < 21:
break
TempX, TempY = mouse.position
x = int(TempX)
y = int(TempY)
#fail-safe 2
if keyboard.is_pressed('ctrl+d'):
break
keyboard.write(word)
print(word)
#return to begining when at end, thats why its outside of the loop
SpamText.seek(0)
for word in SpamText:
keyboard.write(word)
except FileNotFoundError:
NoFile = tk.Label(frame, text = 'Please Select A File', padx=10, pady=5, fg=Modes[mode.get()][2]['text'], bg=Modes[mode.get()][3]['background'])
NoFile.pack()
root.after does not cause a delay. What that says is "please have the main loop call this function later". Your code is going to queue up 5 timer callback requests very quickly, and then continue on. The callbacks will happen later.
When you're coding for a GUI, you have to start thinking about event-driven programming. When you create widgets or call "pack", NOTHING HAPPENS. All that does is queue up a message to be handled later. At some future point, when the mainloop is able to execute, your messages will be popped off and processed. Only then will your visible screen change. That requires a change in the way you program things. You set up your screen, start the mainloop, and then wait for events. Your event callbacks change the state and return, and Tk in the background will update the screen.

Python Set Button Text While busy

I'm new to python and I am trying to create a program but I can't even get the basics right. I have a button app that looks like this:
#simple GUI
from tkinter import *
import time
#create the window
root = Tk()
#modify root window
root.title("Button Example")
root.geometry("200x50")
button1state = 0
def start():
count = 0
button1["text"] ="Busy!"
while (count < 5):
root.after(1000)
count = count + 1
def button1clicked():
global button1state
if button1state == 0:
start()
button1["text"] ="On!"
button1state = 1
else:
button1["text"] ="Off!"
button1state = 0
app = Frame(root)
app.pack()
button1 = Button(app, text ="Off!", command = button1clicked)
button1.pack()
#kick off the event loop
root.mainloop()
Now everything works except it doesn't change the button text to busy while
**start()** is called. How can I fix this? Once I've got it working I want to use images to show the user that its OFF ON and BUSY. Please help me
You need to force the GUI to update before starting the task:
def start():
count = 0
button1.configure(text="Busy!")
root.update() # <-- update window
while (count < 5):
root.after(1000)
count = count + 1
But if you don't want your GUI to be frozen while the task is executed, you will need to use a thread as Dedi suggested.
You have to make a thread in order to make you function as a "background event" while your interface is working. Consider using that :
from threading import Thread
and then :
my_thread=Thread(target=start())
my_thread.start()
Where the first "start()" is the name of your function and the second one a call for the thread to begin.

Tkinter pack_forget() and pack() issue on key-release event

My Requirement
I am developing a program in which I handle space and KeyPress-space Events to handle time based event.
What I am doing is that on the start of program a window gets open to display a time interval, say "5" which depicts 5 Second. Once the time lapses(5Sec) the text "5" gets disappear.
After that the user is required to press space key, when user press space key the same number will display until user don't release space key.
I have given 2 second gap to show next interval(once user releases the space) and in that interval the number should not appear which was appearing in previous set time interval space Event.
Problem Statement:
The issue that I am facing is that I have managed to display number(text) on key press but when I key-release, time interval is not disappearing for 2 second.
Here is my Code:
from tkinter import Label, Tk
import time
times = [3, 4, 5]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
print('called', loop)
self.lbl.pack()
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
Here you go..Please let me know if you have any further query -
from tkinter import Label, Tk
import time
times = [3, 4, 5]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
print('called', loop)
self.lbl.pack()
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
self.parent.update()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
You appear to have found a "crevice" in the way Python interacts with the Tk main loop. At least on my system (Python 2.7, Fedora 22, x86_64) none of the operations in showLabel() have any effect on the screen until the function (including time.sleep(2)) completes.
If you modify the code as follows for troubleshooting, I think you can see what I mean.
def hideLabel(self, event):
global loop, times
# self.lbl.config(text=times[loop])
self.lbl.config(bg = "red")
self.lbl.pack()
def showLabel(self, event):
global loop
# self.lbl.pack_forget()
self.lbl.config(fg = "blue")
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
# self.lbl.config(text= '')
self.lbl.config(bg = "green")
The second label now shows up below the first. The first label gets a blue foreground, but not on the keypress, as we would expect. It apparently only gets it when the function returns, after time.sleep().
I don't pretend to completely understand this - someone who has a deep understanding of Python and Tk might explain. One workaround for this particular program is to add update_idletasks(). This (warning: sloppy terminology ahead) forces the Tk main loop to immediately process pending events.
def showLabel(self, event):
global loop
# self.lbl.pack_forget()
self.lbl.config(fg = "blue")
master.update_idletasks()
time.sleep(2)
loop += 1
Demo(self.parent)
On my system, that made the first label turn blue before time.sleep(). I didn't experiment beyond that.
p.s showLabel() hides the label and hideLabel() shows it?
EDIT: The following slight mod of the original posted code works on my system almost as I would expect. I'm not 100% sure what it is intended to do. But the current label does disappear when I tap the space bar, and the next one shows up 2 seconds later, only to be blanked a variable time after that.
The only puzzle I ran into was that my system basically went crazy when I tried to hold down the space bar for more than 2 seconds. It turned out that the key auto-repeat was kicking in on my system, giving me dozens of keystrokes and causing dozens of subscript out-of-range errors. I gave up at that point so I still don't know how the program behaves if given a single space-down followed 3 seconds later by a single space-up.
from Tkinter import Label, Tk
import time
times = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
master.bind("<h>", self.showLabel)
print('called', loop)
self.lbl.pack()
for child in master.children: print child
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
master.update_idletasks()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
self.lbl.config(bg = "green")
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
If the pack_forget has no effect on your system even with the update_idletasks(), I'd have to guess that the Tk main loop is more platform dependent than is widely known.

Python tkinter GUI freezing/crashing

from Tkinter import *
import tkFileDialog
import tkMessageBox
import os
import ttk
import serial
import timeit
import time
######################################################################################
class MyApp:
def __init__(self, parent):
########################################################
#Setup Frames
self.MiddleFrame = Frame(parent) #Middle Frame
self.MiddleFrame.pack()
#GLOBAL VARIABLES
self.chip_number = 0 #number of chip testing
###########################################
#Middle Frame setup
Label(self.MiddleFrame, text='Done').grid(row=8, column=1, sticky = E)
self.Done = Canvas(self.MiddleFrame, bg="yellow", width=10, height=10)
self.Done.grid(row=8, column=2)
Label(self.MiddleFrame, text='Chip Number:').grid(row=9, column=1, sticky = E)
#start button
self.button1 = Button(self.MiddleFrame,state=NORMAL, command= self.start_pre)
self.button1["text"]= "START"
self.button1.grid(row=1, column=2, sticky = E)
###########################################
#Action of Start Button
def start_pre(self):
x = 0
while x<10000:
self.start_button()
x=x+1
#Talking to Board
def start_button(self):
#increase chip count number and update
self.chip_number += 1
Label(self.MiddleFrame, text=str(self.chip_number)).grid(row=9, column=2, sticky = E)
#reset-yellow
self.reset_color()
print "Still Working", self.chip_number
self.Done.configure(background="green")
self.Done.update_idletasks()
###############################################################
#Color Boxes
#Reset
def reset_color(self):
self.Done.configure(background="yellow")
self.Done.update_idletasks()
###############################################################################################################
#Start Programs
root = Tk() #makes window
root.title("Interface")
myapp = MyApp(root) #this really runs program
root.mainloop() #keep window open
With my program, i first push the start button.
I will print "still working" and the GUi will update chip number and blink done light over and over. The start button go to function that will execute 10000 times. However after 3000 iterations, the gui freeze, but the program is still print "still working". How do I keep the gui from crashing?
There are many problems with your code. For one, this is fundamentally flawed:
while self.stop == True:
self.start_button()
time.sleep(0.5)
You simply can't expect a GUI to behave properly with code like that. As a general rule of thumb you should never have the main thread of a GUI call sleep. Causing sleep prevents the event loop from processing any events, including low level events such as requests to refresh the screen.
The use of sleep has been asked and answered many times on stackoverflow. You might find some of those questions useful. For example,
windows thinks tkinter is not responding
Python Tkinter coords function not moving canvas objects inside loop
How do widgets update in Tkinter?
Tkinter multiple operations
Python Tkinter Stopwatch Error
You have another problem that falls into the category of a memory leak. From that while loop, you call self.start_button() indefinitely. This happens about once a second, due to sleep being called for half a second in the loop, and another half a second in start_button.
Each time you call start_button, you create another label widget that you stack on top of all previous widgets in row 9, column 2. Eventually this will cause your program to crash. I'm surprised that it causes your program to fail so quickly, but that's beside the point.
My recommendation is to start over with a simple example that does nothing but update a label every second. Get that working so that you understand the basic mechanism. Then, once it's working, you can add in your code that reads from the serial port.
May I suggest that you start over with the following code? You can port in back to Python 2 if needed, but your program has been rewritten to use Python 3 and has been designed to use tkinter's ability to schedule future events with the after methods. Hopefully, you will find the code easier to follow.
import collections
import timeit
import tkinter
def main():
root = Application()
root.setup()
root.mainloop()
class Application(tkinter.Tk):
def setup(self):
mf = self.__middle_frame = tkinter.Frame(self)
self.__middle_frame.grid()
bf = self.__bot_frame = tkinter.Frame(self)
self.__bot_frame.grid()
self.__port_set = False
self.__chip_number = 0
self.__chip_pass_num = 0
self.__chip_fail_num = 0
self.__chip_yield_num = 0
self.__stop = True
self.__widgets = collections.OrderedDict((
('COT', 'Continuity Test'), ('CHE', 'Chip Erase'),
('ERT', 'Erase Test'), ('WRT', 'Write Test'),
('WIRT', 'Wire Reading Test'), ('WIT', 'Wire Reading Test'),
('WRAT', 'Write All Test'), ('DO', 'Done')))
for row, (key, value) in enumerate(self.__widgets.items()):
label = tkinter.Label(mf, text=value+':')
label.grid(row=row, column=0, sticky=tkinter.E)
canvas = tkinter.Canvas(mf, bg='yellow', width=10, height=10)
canvas.grid(row=row, column=1)
self.__widgets[key] = label, canvas
self.__cn = tkinter.Label(mf, text='Chip Number:')
self.__cn.grid(row=8, column=0, sticky=tkinter.E)
self.__display = tkinter.Label(mf)
self.__display.grid(row=8, column=1, sticky=tkinter.E)
self.__button = tkinter.Button(bf, text='START',
command=self.__start_pre)
self.__button.grid(sticky=tkinter.E)
def __start_pre(self):
self.__button['state'] = tkinter.DISABLED
self.__start_button(0)
def __start_button(self, count):
if count < 100:
self.__chip_number += 1
self.__display['text'] = str(self.__chip_number)
self.__widgets['DO'][1]['bg'] = 'yellow'
start_time = timeit.default_timer()
print('Still Working:', self.__chip_number)
self.after(500, self.__end_button, count)
else:
self.__button['state'] = tkinter.NORMAL
def __end_button(self, count):
self.__widgets['DO'][1]['bg'] = 'green'
self.after(500, self.__start_button, count + 1)
if __name__ == '__main__':
main()

Making a canvas object loop and redraw each loop in tkinter [duplicate]

I would like the code to run in the background and to update my GUI periodically. How can I accomplish this?
For example, suppose I want to execute something like this in the background of the GUI code you can see below:
x = 0
while True:
print(x)
x = x + 1
time.sleep(1)
This is the GUI code:
class GUIFramework(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
self.master.title("Volume Monitor")
self.grid(padx=10, pady=10,sticky=N+S+E+W)
self.CreateWidgets()
def CreateWidgets(self):
textOne = Entry(self, width=2)
textOne.grid(row=1, column=0)
listbox = Listbox(self,relief=SUNKEN)
listbox.grid(row=5,rowspan=2,column=0,columnspan=4,sticky=N+W+S+E,pady=5)
listbox.insert(END,"This is an alert message.")
if __name__ == "__main__":
guiFrame = GUIFramework()
guiFrame.mainloop()
It is a little unclear what your code at the top is supposed to do, however, if you just want to call a function every second (or every the amount of seconds you want), you can use the after method.
So, if you just want to do something with textOne, you'd probably do something like:
...
textOne = Entry(self, width=2)
textOne.x = 0
def increment_textOne():
textOne.x += 1
# register "increment_textOne" to be called every 1 sec
self.after(1000, increment_textOne)
You could make this function a method of your class (in this case I called it callback), and your code would look like this:
class Foo(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.x = 0
self.id = self.after(1000, self.callback)
def callback(self):
self.x += 1
print(self.x)
#You can cancel the call by doing "self.after_cancel(self.id)"
self.id = self.after(1000, self.callback)
gui = Foo()
gui.mainloop()
If you truly want to run a distinct infinite loop you have no choice but to use a separate thread, and communicate via a thread safe queue. However, except under fairly unusual circumstances you should never need to run an infinite loop. Afte all, you already have an infinite loop running: the event loop. So, when you say you want an infinite loop you are really asking how to do an infinite loop inside an infinite loop.
#mgilson has given a good example on how to do that using after, which you should consider trying before trying to use threads. Threading makes what you want possible, but it also makes your code considerably more complex.

Categories

Resources