TkInter Frame doesn't load if another function is called - python

I'm writing a Python programme which listens for RFID input and only runs if a valid token is presented. The programme also has a GUI which I'm wanting to build using TkInter.
Both parts of the puzzle work fine in isolation, however as it stands I seem to be able to choose one or the other - but not both! I can draw my TkInter window fine, however if I call the function to start listening for the RFID input then whilst that bit runs OK and works... there's no GUI.
Code is below. You can see my debugging efforts so far with my printouts to the terminal...
#!/usr/bin/env python3
import sys
import MySQLdb
if sys.version_info[0] == 2:
from Tkinter import *
import Tkinter as ttk
else:
from tkinter import *
import tkinter as ttk
class Fullscreen_Window:
def __init__(self):
self.tk = Tk()
self.frame = Frame(self.tk)
self.frame.pack()
ttk.Button(self.tk, text="hello world").pack()
self.tk.attributes('-zoomed', True)
self.state = False
self.tk.bind("<F11>", self.toggle_fullscreen)
self.tk.bind("<Escape>", self.end_fullscreen)
print("init running")
self.listen_rfid() # Commenting this out makes the GUI appear, uncommenting means no GUI :(
def toggle_fullscreen(self, event=None):
self.state = not self.state # Just toggling the boolean
self.tk.attributes("-fullscreen", self.state)
print("Toggling")
return "break"
def end_fullscreen(self, event=None):
self.state = False
self.tk.attributes("-fullscreen", False)
return "break"
def listen_rfid(self):
print("Main loop running")
dbHost = 'localhost'
dbName = 'python'
dbUser = 'python'
dbPass = 'PASSWORD'
dbConnection = MySQLdb.connect(host=dbHost, user=dbUser, passwd=dbPass, db=dbName)
cur = dbConnection.cursor(MySQLdb.cursors.DictCursor)
with open('/dev/stdin', 'r') as tty:
while True:
RFID_input = tty.readline().rstrip()
cur.execute("SELECT * FROM access_list WHERE rfid_code = '%s'" % (RFID_input))
if cur.rowcount != 1:
print("ACCESS DENIED")
else:
user_info = cur.fetchone()
print("Welcome %s!!" % (user_info['name']))
tty.close()
listen_rfid()
if __name__ == '__main__':
w = Fullscreen_Window()
w.tk.mainloop()
I'm sure it's something really simple but as I'm a Python/TkInter n00b it's beaten me and I'm all done Googling. Any help gratefully received :)

Tkinter (and all GUIs) has an infinite loop called the mainloop that keeps the GUI active and responsive. When you make another infinite loop (while True) you block Tkinter's mainloop; and the GUI fails. You need to either put your loop in a separate thread or use Tkinter's mainloop to do your work. Since you are using a blocking readline, the thread is the best way to go. As a guess, replace your call with this:
from threading import Thread
t = Thread(target=self.listen_rfid)
t.daemon = True # this line tells the thread to quit if the GUI (master thread) quits.
t.start()
Edit: BTW, your imports are very bad. "ttk" is a subset of tkinter, not an alias, the alias "tk" is usually used for tkinter, and wildcard imports are bad and should be avoided. This is how your tkinter imports should look:
try:
# python 2
import Tkinter as tk
import ttk
except ImportError:
# python 3
import tkinter as tk
from tkinter import ttk
And then you use the appropriate prefix:
self.tk = tk.Tk()
self.frame = tk.Frame(self.tk)

You should run listen_rfid using after. The problem is that listen_rfid as you have written it will run forever meaning that mainloop never starts. If you do this:
#!/usr/bin/env python3
import sys
import select
import MySQLdb
if sys.version_info[0] == 2:
from Tkinter import *
import Tkinter as ttk
else:
from tkinter import *
import tkinter as ttk
class Fullscreen_Window:
def __init__(self):
self.tk = Tk()
self.frame = Frame(self.tk)
self.frame.pack()
ttk.Button(self.tk, text="hello world").pack()
self.tk.attributes('-zoomed', True)
self.state = False
self.tk.bind("<F11>", self.toggle_fullscreen)
self.tk.bind("<Escape>", self.end_fullscreen)
print("init running")
# Schedule self.listen_rfid to run after the mainloop starts
self.tk.after(0, self.listen_rfid)
def toggle_fullscreen(self, event=None):
self.state = not self.state # Just toggling the boolean
self.tk.attributes("-fullscreen", self.state)
print("Toggling")
return "break"
def end_fullscreen(self, event=None):
self.state = False
self.tk.attributes("-fullscreen", False)
return "break"
def listen_rfid(self):
print("Main loop running")
dbHost = 'localhost'
dbName = 'python'
dbUser = 'python'
dbPass = 'PASSWORD'
dbConnection = MySQLdb.connect(host=dbHost, user=dbUser, passwd=dbPass, db=dbName)
cur = dbConnection.cursor(MySQLdb.cursors.DictCursor)
# readline is blocking so check that there is input
# before attempting to read it.
r, w, x = select.select([sys.stdin], [], [], 0)
if r:
# There is available input, so read a line.
RFID_input = sys.stdin.readline().rstrip()
cur.execute("SELECT * FROM access_list WHERE rfid_code = '%s'" % (RFID_input))
if cur.rowcount != 1:
print("ACCESS DENIED")
else:
user_info = cur.fetchone()
print("Welcome %s!!" % (user_info['name']))
# keep running every 500 milliseconds for as long as
# the mainloop is active.
self.tk.after(500, self.listen_rfid)
if __name__ == '__main__':
w = Fullscreen_Window()
w.tk.mainloop()
it will check every half second whether there is some input on the command line and process it.

Related

python thread data manipulation

I am trying to manipulate some data in a thread from the main function. The issue I am facing about modifying some of the variables which are part of the function which is running on a thread.
So I am trying to run a Tkinter based GUI loop in a thread to ensure it is always running. And want to modify some of the label corresponding to the status in the main function execution. I am facing an issue where it is unable to locate the label variables in the main loop as it is part of the function running on the thread.
Below is a simplified psuedo code for that approach. Please suggest if this a correct way to the above task or is there is any better and efficient way.
import threading
def thread_func():
i = 0
while True:
print('i from thread: ', i)
if __name__ == '__main__':
t = threading.Thread(target=thread_func)
t.start()
while True:
i += 1
Actual scaled down simplified code
import threading
import tkinter as tk
def gui():
window = tk.Tk()
label = tk.Label(text='On')
label.pack()
window.mainloop()
if __name__ == '__main__':
t = threading.Thread(target=gui)
t.start()
while True:
label['text'] = 'Active'
Error:
Traceback (most recent call last):
File "test.py", line 17, in <module>
label['text'] = 'Active'
NameError: name 'label' is not defined
Is there a better way to keep the tkinter gui always on and perform some task in the loop?
Using class and threading:
import tkinter
class Test(tkinter.Label):
x = False
def __init__(self):
super().__init__()
self.pack()
self['text'] = 'On'
def gui(self):
if not self.x:
self['text'] = 'Active'
self.mainloop()
else:
self.mainloop()
if __name__ == '__main__':
label = Test()
while isinstance(label, Test):
t = threading.Thread(target=label.gui())
t.start()
When you write code of label there then you will get error because when program start it starts from creating thread, that thread will only end when tkinter window is close and same for previous thread_fuc code. And you wrote the label code after tkinter window is closed.
The above issue will be solved by doing this :
import threading
def thread_func():
while True:
print('i from thread: ', i)
def tt():
global i
while True:
i += 1
if __name__ == '__main__':
i=0
t = threading.Thread(target=thread_func)
t.start()
yt = threading.Thread(target=tt)
yt.start()
Making i global and running 2 function parallely. We have to global because we can't use the variable of one function to another. And additionally we are running 2 function in 2 thread.
And for your tkinter file as #TheLizzard suggest, you can use .after insted of using thread in tkinter if you want to change content constantly/want to use loop.
Here's the basic example how you can implement it:
import random
import tkinter as tk
app = tk.Tk()
app.geometry("200x220")
label = tk.Label(app, text="0")
label.pack()
def change(b=0):
if b < 30:
a = random.randrange(1, 7, 1)
label.config(text=a)
app.after(100, change, b+1)
b1 = tk.Button(app, text="Get New Number", command=change)
b1.pack()
app.mainloop()
For more explanation about it you may visit here.

I want to toggle a real pushbutton and display it on tkinter GUI

I want to toggle a pushbutton and show its changes on a label using tkinter.
If I press the button it shows "on" on the label and when I press again it shows "off" on the label
So I try these codes and If I'm trying the wrong code please help me write the correct using tkinter.
I have a problem in combining this code
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BOARD)
GPIO.setup(22,GPIO.IN,up_down=GPIO.PUD_UP)
while(1):
if GPIO.input(22)==1:
if bs == False :
x.set("on")
bs=True
sleep(0.5)
else:
x.set("off")
bs=False
sleep(0.5)
This works okay but I want to connect it to a GUI label to print on it on or off.
Here is the tkinter code
import tkinter.*
root = tk()
x = StringVar()
s=Label(root,textvariable=x)
s.grid(column=0,row=0)
root.mainloop()
When I try to combine it I make it like this
from Tkinter import *
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BOARD)
GPIO.setup(7,GPIO.IN)
b=False
def check_button1():
if GPIO.input(7)== 1:
if b == False :
labelText1.set("on")
print"on"
b=True
time.sleep(0.5)
else:
labelText1.set("off")
print"off"
b=False
time.sleep(0.5)
mamdouh.after(10,check_button1)
mamdouh = Tk()
labelText1 = StringVar()
x1 = Label(mamdouh,textvariable=labelText1)
x1.config(font=('Helvetica',25,'bold'))
x1.grid(row=0,column=0)
mamdouh.title("mamdouh")
mamdouh.geometry('1200x700')
mamdouh.after(10,check_button1)
mamdouh.mainloop()
but it didn't works it keeps blank every time I press the push button actually If it works well I will put 17 push button
I think that the problem is in placing this if statment on the right place and placing the b variable in it's right place and I think also there is a problem between this if statment and tkinter because I tried this code wich works perfect but it is not toggling the push button so I want to change this lets add this code here also :
from Tkinter import *
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(7,GPIO.IN)
def check_button1():
if(GPIO.input(7) == GPIO.LOW):
labelText1.set("on")
else:
labelText1.set("off")
mamdouh.after(10,check_button1)
mamdouh = Tk()
labelText1 = StringVar()
x1 = Label(mamdouh,textvariable=labelText1)
x1.config(font=('Helvetica',25,'bold'))
x1.grid(row=0,column=0)
mamdouh.title("mamdouh")
mamdouh.geometry('1200x700')
mamdouh.after(10,check_button1)
mamdouh.mainloop()
So how I can make this toggle push button on an Label?
Your problem is recognizing button down and button up events. Your OS mouse driver does this for your mouse buttons. If your GPIO module does not do this for you, you will have to detect these events by comparing the current state to the previous state. (I am ignoring here any possible need to 'de-bounce' the button.) You are sort of trying to do this with the time.sleep(.5) calls, but do not use time.sleep in gui code.
Your driver should be self-contained and independent of any tk widgets other than the root needed for .after. For multiple buttons, you will need your own GPIOButton class. Your code that works is a starting point. Tkinter allows you to tie a command to button-up events. Your class init should similarly take up and or down event commands (callbacks).
Here is something untested that might get you started.
class GPIOButton:
def __init__(self, master, buttons, command_down=None, command_up=None):
self.master = master
self.buttons = buttons
self.command_down = command_down
self.command_up = command_up
GPIO.setmode(GPIO.BOARD)
for button in buttons:
GPIO.setup(button, GPIO.IN)
self.state = [GPIO.HIGH] * len(buttons) # best initial value?
self.check_buttons() # or call this elsewhere
def check_buttons(self):
for i, button in enumerate(self.buttons):
oldstate = self.state[i]
newstate = GPIO.input(button)
if oldstate != newstate:
self.state[i] = newstate
command = (self.command_down if newstate==GPIO.LOW
else self.command_up)
command(button)
self.master.after(10, self.check_button)
Let me preface my answer with a disclaimer—I don't have a Raspberry Pi, so couldn't verify this works with the real thing. For testing I used a proxy class that simulates random button pressing. You may have to adjust the DELAY value depending on how fast the GPIO interface works.
However, I have put commented-out code in near the top showing what I think you would need to use based on of doing so in your code.
try:
import Tkinter as tk
import tkFont
except ImportError: # Python 3
import tkinter as tk
import tkinter.font as tkFont
#import RPi.GPIO as GPIO
#
#GPIO.setmode(GPIO.BOARD)
#
#class GPIOButton(object):
# """ Encapsulates GPIO button interface. """
# def __init__(self, pin):
# self.pin = pin
# self.status = 0
# GPIO.setup(pin, GPIO.IN)
#
# def update_status(self):
# self.status = GPIO.input(pin) == GPIO.LOW
### Proxy class since I don't have a Rasperry Pi. ###
import random
class GPIOButton(object):
def __init__(self, pin):
self.pin = pin
self.status = 0
def update_status(self):
if not random.randint(0, 99) % 20: # occassionally toggle status
self.status = not self.status
class App(tk.Frame):
STRIDE = 8
DELAY = 100 # delay in millsecs between button status updates
def __init__(self, gpio_buttons, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.gpio_buttons = gpio_buttons
self.create_widgets()
self.after(self.DELAY, self.update_buttons, self.DELAY) # start updates
def create_widgets(self):
self.btn_font = tkFont.Font(family="Helvetica", size=12, weight='bold')
self.gui_buttons = []
for i, button in enumerate(self.gpio_buttons):
is_pressed = tk.BooleanVar()
is_pressed.set(False)
radiobutton = tk.Radiobutton(self,
text=format(i+1, '02d'),
font=self.btn_font,
value=True,
variable=is_pressed,
relief=tk.RIDGE)
row, col = divmod(i, self.STRIDE)
radiobutton.grid(column=col, row=row)
self.gui_buttons.append(is_pressed)
def update_buttons(self, delay):
for i, gpio_button in enumerate(self.gpio_buttons):
previous_status = gpio_button.status
gpio_button.update_status()
if gpio_button.status != previous_status:
self.gui_buttons[i].set(gpio_button.status)
self.after(delay, self.update_buttons, delay) # rinse and repeat
gpio_buttons = [GPIOButton(pin) for pin in range(16)]
app = App(gpio_buttons)
app.master.title('Rasberry Pi Buttons')
app.mainloop()
Here's what the simulation looks like running on my Windows computer:

Display count variable from background task in main task (Tkinter GUI)

I am trying to display a count variable from a background task in the main task which is my tkinter GUI. Why? I want to display that the long time taking background task is performing and later use this count variable to visualize it with a progress bar.
My problem is, that even when using a Queue, I am not able to display the count variable. Maybe I've got problems in understanding python and its behaviour with objects and/or threads.
import threading
import time
import Queue
import Tkinter as Tk
import Tkconstants as TkConst
from ScrolledText import ScrolledText
from tkFont import Font
import loop_simulation as loop
def on_after_elapsed():
while True:
try:
v = dataQ.get(timeout=0.1)
except:
break
scrText.insert(TkConst.END, str(v)) # "value=%d\n" % v
scrText.see(TkConst.END)
scrText.update()
top.after(100, on_after_elapsed)
def thread_proc1():
x = -1
dataQ.put(x)
x = loop.loop_simulation().start_counting()
# th_proc = threading.Thread(target=x.start_counting())
# th_proc.start()
for i in range(5):
for j in range(20):
dataQ.put(x.get_i())
time.sleep(0.1)
# x += 1
time.sleep(0.5)
dataQ.put(x.get_i())
top = Tk.Tk()
dataQ = Queue.Queue(maxsize=0)
f = Font(family='Courier New', size=12)
scrText = ScrolledText(master=top, height=20, width=120, font=f)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15, expand=True)
th = threading.Thread(target=thread_proc1)
th.start()
top.after(100, on_after_elapsed)
top.mainloop()
th.join()
In thread_proc1() I want to get the value of the counter of background task. This is the background task:
import time
class loop_simulation:
def __init__(self):
self.j = 0
# self.start_counting()
def start_counting(self):
for i in range(0, 1000000):
self.j = i
time.sleep(0.5)
def get_i(self):
return str(self.j)
The reason the count variable isn't being displayed is due to the
x = loop.loop_simulation().start_counting()
statement in thread_proc1(). This creates a loop_simulation instance and calls its start_counting() method. However, other than already inserting a -1 into the dataQ, thread_proc1() doesn't do anything else until start_counting() returns, which won't be for a long time (500K seconds).
Meanwhile, the rest of your script is running and displaying only that initial -1 that was put in.
Also note that if start_counting() ever did return, its value of None is going to be assigned to x which later code attempts to use with: x.get_i().
Below is reworking of your code that fixes these issues and also follows the PEP 8 - Style Guide for Python Code more closely. To avoid the main problem of calling start_counting(), I changed your loop_simulation class into a subclass of threading.Thread and renamed it LoopSimulation, and create an instance of it in thread_proc1, so there are now two background threads in addition to the main one handling the tkinter-based GUI.
import loop_simulation as loop
from ScrolledText import ScrolledText
import threading
import Tkinter as Tk
import Tkconstants as TkConst
from tkFont import Font
import time
import Queue
def on_after_elapsed():
# removes all data currently in the queue and displays it in the text box
while True:
try:
v = dataQ.get_nowait()
scrText.insert(TkConst.END, str(v)+'\n')
scrText.see(TkConst.END)
except Queue.Empty:
top.after(100, on_after_elapsed)
break
def thread_proc1():
dataQ.put(-1)
ls = loop.LoopSimulation() # slow background task Thread
ls.start_counting()
while ls.is_alive(): # background task still running?
for i in range(5):
for j in range(20):
dataQ.put(ls.get_i())
time.sleep(0.1)
time.sleep(0.5)
dataQ.put('background task finished')
top = Tk.Tk()
dataQ = Queue.Queue(maxsize=0)
font = Font(family='Courier New', size=12)
scrText = ScrolledText(top, height=20, width=120, font=font)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15,
expand=TkConst.YES)
th = threading.Thread(target=thread_proc1)
th.daemon = True # OK for main to exit even if thread is still running
th.start()
top.after(100, on_after_elapsed)
top.mainloop()
loop_simulation.py module:
import threading
import time
class LoopSimulation(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.daemon = True # OK for main to exit even if instance still running
self.lock = threading.Lock()
self.j = 0
start_counting = threading.Thread.start # an alias for starting thread
def run(self):
for i in range(1000000):
with self.lock:
self.j = i
time.sleep(0.5)
def get_i(self):
with self.lock:
return self.j

Tkinter text entry with pyHook hangs GUI window

I have a Tkinter GUI application that I need to enter text in. I cannot assume that the application will have focus, so I implemented pyHook, keylogger-style.
When the GUI window does not have focus, text entry works just fine and the StringVar updates correctly. When the GUI window does have focus and I try to enter text, the whole thing crashes.
i.e., if I click on the console window or anything else after launching the program, text entry works. If I try entering text immediately (the GUI starts with focus), or I refocus the window at any point and enter text, it crashes.
What's going on?
Below is a minimal complete verifiable example to demonstrate what I mean:
from Tkinter import *
import threading
import time
try:
import pythoncom, pyHook
except ImportError:
print 'The pythoncom or pyHook modules are not installed.'
# main gui box
class TestingGUI:
def __init__(self, root):
self.root = root
self.root.title('TestingGUI')
self.search = StringVar()
self.searchbox = Label(root, textvariable=self.search)
self.searchbox.grid()
def ButtonPress(self, scancode, ascii):
self.search.set(ascii)
root = Tk()
TestingGUI = TestingGUI(root)
def keypressed(event):
key = chr(event.Ascii)
threading.Thread(target=TestingGUI.ButtonPress, args=(event.ScanCode,key)).start()
return True
def startlogger():
obj = pyHook.HookManager()
obj.KeyDown = keypressed
obj.HookKeyboard()
pythoncom.PumpMessages()
# need this to run at the same time
logger = threading.Thread(target=startlogger)
# quits on main program exit
logger.daemon = True
logger.start()
# main gui loop
root.mainloop()
I modified the source code given in the question (and the other one) so that the pyHook
related callback function sends keyboard event related data to a
queue. The way the GUI object is notified about the event may look
needlessly complicated. Trying to call root.event_generate in
keypressed seemed to hang. Also the set method of
threading.Event seemed to cause trouble when called in
keypressed.
The context where keypressed is called, is probably behind the
trouble.
from Tkinter import *
import threading
import pythoncom, pyHook
from multiprocessing import Pipe
import Queue
import functools
class TestingGUI:
def __init__(self, root, queue, quitfun):
self.root = root
self.root.title('TestingGUI')
self.queue = queue
self.quitfun = quitfun
self.button = Button(root, text="Withdraw", command=self.hide)
self.button.grid()
self.search = StringVar()
self.searchbox = Label(root, textvariable=self.search)
self.searchbox.grid()
self.root.bind('<<pyHookKeyDown>>', self.on_pyhook)
self.root.protocol("WM_DELETE_WINDOW", self.on_quit)
self.hiding = False
def hide(self):
if not self.hiding:
print 'hiding'
self.root.withdraw()
# instead of time.sleep + self.root.deiconify()
self.root.after(2000, self.unhide)
self.hiding = True
def unhide(self):
self.root.deiconify()
self.hiding = False
def on_quit(self):
self.quitfun()
self.root.destroy()
def on_pyhook(self, event):
if not queue.empty():
scancode, ascii = queue.get()
print scancode, ascii
if scancode == 82:
self.hide()
self.search.set(ascii)
root = Tk()
pread, pwrite = Pipe(duplex=False)
queue = Queue.Queue()
def quitfun():
pwrite.send('quit')
TestingGUI = TestingGUI(root, queue, quitfun)
def hook_loop(root, pipe):
while 1:
msg = pipe.recv()
if type(msg) is str and msg == 'quit':
print 'exiting hook_loop'
break
root.event_generate('<<pyHookKeyDown>>', when='tail')
# functools.partial puts arguments in this order
def keypressed(pipe, queue, event):
queue.put((event.ScanCode, chr(event.Ascii)))
pipe.send(1)
return True
t = threading.Thread(target=hook_loop, args=(root, pread))
t.start()
hm = pyHook.HookManager()
hm.HookKeyboard()
hm.KeyDown = functools.partial(keypressed, pwrite, queue)
try:
root.mainloop()
except KeyboardInterrupt:
quit_event.set()

Python: How to update a GUI with variables from a separate process while maintaining GUI interactivity

After quite a bit of reading here about multiple processes, pipes, etc., I haven't found an answer yet, but my apologies if it already exists.
I have a piece of peripheral hardware for which I'm trying to create a GUI. I'd like to have the GUI get updated constantly with data from the peripheral, while still maintaining interactivity for the user. For example, I have a gain parameter that I'm using to drive a bargraph, and while that is constantly being updated, I'd like the user to be able to click a button to cause some action. Here is some example code. Despite my certainty that I have some serious mistakes here, this actually almost works, but the 'quit' button remains unresponsive:
#!/usr/bin/env python`
# -*- coding: utf-8 -*-
# 2014-07-24 S. Petit
import matplotlib.pyplot as plt
from serial import Serial
import serial, socket, time, datetime, sys, struct
from datetime import datetime
import numpy as np
import shutil
import os
from random import randint
from Tkinter import *
from multiprocessing import *
dcbSerialPort = 'COM10'
def getGainLNA(pipeToParent):
try:
S_dcb = Serial(dcbSerialPort, 115200, timeout=.1)
print 'Opened DCB at', dcbSerialPort
except:
print '\r\n'
print '*************************************************'
print 'ERROR: Unable to open', dcbSerialPort, 'serial connection.'
print '*************************************************'
print '\r\n'
raw_input()
exit()
while True:
promptFound = False
PICreturn = ''
S_dcb.write('gain\r')
while not promptFound:
PICreturn += S_dcb.read(S_dcb.inWaiting())
if 'DCB>' in PICreturn:
promptFound = True
gainLNA = float(PICreturn[20:28].strip())
gainLNA_scaled = int(100*(gainLNA/33))
pipeToParent.send(gainLNA_scaled)
return()
if __name__ == '__main__':
gainUpdaterPipe, gainUpdaterPipeChild = Pipe()
lnaGainUpdater = Process(target=getGainLNA, args=(gainUpdaterPipeChild,))
lnaGainUpdater.start()
root=Tk()
root.title = 'AGC'
while True:
if gainUpdaterPipe.poll():
gainLNA = gainUpdaterPipe.recv()
print gainLNA
quitButton = Button(text='Quit', command=quit)
quitButton.grid(row=1, column=0)
areaAGC = Canvas(width=120, height=100, bg='blue')
objectAGC = areaAGC.create_polygon(20,20, gainLNA,20, gainLNA,50, 20,50, outline='green', fill='yellow')
areaAGC.grid(row=0, column=0)
root.update_idletasks()
Thanks for any help...
Steve P
EDIT: Okay, after attempting to make use of #ebarr's example, here's what I have. The label widget updates with the count but the bargraph does not:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 2014-07-24 S. Petit
import matplotlib.pyplot as plt
from serial import Serial
import serial, socket, time, datetime, sys, struct
from datetime import datetime
import numpy as np
import shutil
import os
from random import randint
import Tkinter as tk
from multiprocessing import *
dcbSerialPort = 'COM10'
# count from 0 to infinity, writing the value to a pipe
def count(pipe,stop):
ii = 0
while not stop.is_set():
ii+=1
pipe.send(ii)
time.sleep(1)
class UpdatingGUI(tk.Frame):
def __init__(self,parent):
tk.Frame.__init__(self,parent)
self.parent = parent
self.parent_pipe, self.child_pipe = Pipe()
self.stop_event = Event()
# label to show count value
self.updating_int = tk.IntVar()
self.updating_int.set(0)
self.updating_lbl = tk.Label(self,textvariable=self.updating_int)
self.updating_lbl.pack()
# bargraph to show count value
self.area_barGraph = tk.Canvas(width=120, height=100, bg='blue')
self.bargraph = self.area_barGraph.create_polygon(10,10, (10+self.updating_int.get()),10, (10+self.updating_int.get()),20, 10,20, outline='green', fill='yellow')
self.area_barGraph.pack()
# button that will stay responsive to requests while count is on going
self.quit_btn = tk.Button(self,text="Quit",command=self.quit)
self.quit_btn.pack()
# launch count as a process
self.counter = Process(target=count,args=(self.child_pipe,self.stop_event))
self.counter.start()
# call an update method to check the pipe and update the label
self.update()
def quit(self):
self.stop_event.set()
self.parent.destroy()
def update(self):
# While the pipe has data, read and update the StringVar
while self.parent_pipe.poll():
self.updating_int.set(self.parent_pipe.recv())
# set the update method to run again in 1 seconds time
self.parent.after(1000,self.update)
def main():
root = tk.Tk()
gui = UpdatingGUI(root)
gui.pack()
root.mainloop()
# print __name__
if __name__ == "__main__":
main()
You are pretty close to a working solution. As is noted in one of the comments above, using the tkinter after will solve most of your problem.
Below is a minimal example of a separate process (running a simple counter) passing a state that can be used to update your GUI:
import Tkinter as tk
from multiprocessing import Event,Process,Pipe
from time import sleep
# count from 0 to infinity, writing the value to a pipe
def count(pipe,stop):
ii = 0
while not stop.is_set():
ii+=1
pipe.send(ii)
sleep(1)
class UpdatingGUI(tk.Frame):
def __init__(self,parent):
tk.Frame.__init__(self,parent)
self.parent = parent
self.parent_pipe, self.child_pipe = Pipe()
self.stop_event = Event()
# label to show count value
self.updating_txt = tk.StringVar()
self.updating_txt.set("Waiting...")
self.updating_lbl = tk.Label(self,textvariable=self.updating_txt)
self.updating_lbl.pack()
# button that will stay responsive to requests while count is on going
self.quit_btn = tk.Button(self,text="Quit",command=self.quit)
self.quit_btn.pack()
# launch count as a process
self.counter = Process(target=count,args=(self.child_pipe,self.stop_event))
self.counter.start()
# call an update method to check the pipe and update the label
self.update()
def quit(self):
self.stop_event.set()
self.parent.destroy()
def update(self):
# While the pipe has data, read and update the StringVar
while self.parent_pipe.poll():
self.updating_txt.set(self.parent_pipe.recv())
# set the update method to run again in 1 seconds time
self.parent.after(1000,self.update)
def main():
root = tk.Tk()
gui = UpdatingGUI(root)
gui.pack()
root.mainloop()
if __name__ == "__main__":
main()
UPDATE
In response to the updated code: You are pretty much done, the only issue is that you are only calling the bargraph creator once, whereas it needs to be added to your update function like:
def update(self):
# While the pipe has data, read and update the StringVar
while self.parent_pipe.poll():
self.updating_int.set(self.parent_pipe.recv())
dx = self.updating_int.get()
self.area_barGraph.create_polygon(10,10, (10+dx),10, (10+dx),20, 10,20, outline='green', fill='yellow')
# set the update method to run again in 1 seconds time
self.parent.after(1000,self.update)
This will ensure that every time the intVar is updated the graph is also updated appropriately.

Categories