I can't pass data from thread to GUI JLabel - python

if name == 'main':
demo = DesktopFrame()
demo.setLocation(30, 30)
demo.show()
comm = "COM4"
global comPort
comPort = MyCommPort()
comPort.set_port(comm)
poll = Polling(comPort)
poll.start()
poll.join()
This code runs (as def), My GUI comes up and allows me to change the frame content from the menu while the thread POLL sends and receives data from my serial port (fixed test loop of 20 times right now)
**GOAL:
My goal is to have the thread update a JLabel (make it change node address) as it goes through the polling sequence.**
I've been following the jthon-swingutils2.1.1 documentation
https://pythonhosted.org/jython-swingutils/threads.html
-- Running GUI code from background threads.
from swingutils.threads.swing import callSwing
def fillInExchangeRate():
rate = fetchExchangeRate('USD', 'EUR')
callSwing(rateField.setValue, rate)
PROBLEM:
Method pollUpdate(self, address)
I can't discover the correct way for callSwing() to find my GUI JLabel [pollText] I get the following error message.
Exception in thread Thread-1:Traceback (most recent call last):
File "C:\jython2.7.0\Lib\threading.py", line 222, in _Thread__bootstrap self.run()
File "C:\Users\jwkel\Documents\NetBeansProjects\JythonProject\src\mycommport.py", line 95, in run self.xmit_data(self.toOutput)
File "C:\Users\jwkel\Documents\NetBeansProjects\JythonProject\src\mycommport.py", line 71, in xmit_data self.pollUpdate("Polling A")
File "C:\Users\jwkel\Documents\NetBeansProjects\JythonProject\src\mycommport.py", line 66, in pollUpdate callSwing(pollText.setValue, address)
NameError: global name 'pollText' is not defined
now for some general information about my GUI
demo = JFrame
self.menubar = JMenuBar() + 5 JMenu() + 15 JMenuItem() // all working correctly
self.add(self.mainPanel, BorderLayout.EAST)
contains:
self.mainPanel = JPanel()
self.mainText = JTextArea()
self.mainScroll = JScrollPane(self.mainText) +
self.mainScroll.getViewport().setView((self.mainText))
self.mainPanel = JPanel() self.mainPanel.add(self.mainScroll)
self.add(self.modePanel, BorderLayout.NORTH)
contains:
self.modePanel = JPanel()
self.modeText = JLabel("Current Mode Setting : Stand Alone")
self.pollText = JLabel("Polling Reader - ?") ******* MY TARGET ********
self.add(self.panel, BorderLayout.WEST)
contains:
self.panel = JPanel()
self.panel.add(JLabel(ImageIcon(self.showImage)))
problem code
from swingutils.threads.swing import callSwing
class Polling(Thread):
def __init__(self, pollPort):
Thread.__init__(self)
self._pollPort= pollPort
def pollUpdate(self, address):
callSwing(pollText.setValue, address)
def xmit_data(self,sendThis):
'''commands and data sent to readers'''
print 'send this ',sendThis
self.pollUpdate("Polling A")
self._pollPort.outStream.write(sendThis)
time.sleep(0.3)
self._pollPort.outStream.flush()
def recv_data(self):
'''relies and requests from readers'''
print 'recev data'
self.pollUpdate("Polling #")
self.s =''
self.text = ''
for num in range(100):
self.s = self._pollPort.inStream.read()
if self.s == 10:
break
else:
self.text = self.text + chr(self.s)
print 'text = ', self.text
return self.text
def run(self):
for i in range(20):
time.sleep(.100)
self.toOutput="*ATest\n"
self.xmit_data(self.toOutput)
time.sleep(.300)
self.myText = self.recv_data()
print self.myText

I found a better example to follow and rewrote the code to use a java Class SwingWorker. All is well!

Related

Is there a way to get "text" or "fg_color" value from custom Tkinter button widget?

I have a customtkinter (CTk) button widget that, when pressed, sends an encoded message to a client depending on the button's "text" value; in this case, if the button's text is "Off", it sends the message "On" and vice versa to the client.
import tkinter as tk
import traceback
import customtkinter as cust
import socket
from threading import Thread
from tkinter import messagebox
class GUI2(cust.CTk): #second window; not the root
def __init__(self):
super().__init__()
self.PORT = 5000
self.SERVER = socket.gethostbyname(socket.gethostname())
self.ADDRESS = (self.SERVER, self.PORT)
self.FORMAT = "utf-8"
self.clients = [] #list to store all client connections
self.server = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
self.server.bind(self.ADDRESS)
self.master2 = cust.CTkToplevel()
self.ecdpower = cust.CTkButton(self.master2, text = "Off", fg_color = "Black", text_color = "White", hover_color = "Silver", command = lambda: Thread(target = self.startstream).start())
self.ecdpower.grid(row = 0, column = 0) #button to send message to client connections
self.thread = Thread(target = self.startChat)
self.thread.start() #starts function to accept client connections
def startChat(self): #function to accept client connections
self.server.listen(30)
try:
while True:
self.conn, self.addr = self.server.accept()
self.clients.append(self.conn) #stores all client connections
except:
pass
def startstream(self):
try:
if not self.clients: #checks if list is empty
messagebox.showerror("No Connections!", "No clients connected to host!")
else:
for x in self.clients:
if self.ecdpower["text"] == "Off": #ecdpower button widget acts like a "power button"
self.ecdpower.configure(text = "On", fg_color = "Green")
x.send(("On").encode(self.FORMAT))
else:
self.ecdpower.configure(text = "Off", fg_color = "Red")
x.send(("Off").encode(self.FORMAT))
except:
print (traceback.format_exc())
Error is as follows:
Traceback (most recent call last):
File "f:\project\mainmenu.py", line 390, in startstream
File "F:\Program Files (x86)\Python\lib\tkinter\__init__.py", line 1681, in cget
return self.tk.call(self._w, 'cget', '-' + key)
_tkinter.TclError: unknown option "-text"
I have also tried if self.ecdpower.cget("text") == "Off: and tried other values like fg_color; both result in the same error. When I removed the if condition, the message sending works correctly so the only problem is how to verify the button "text" value.
Any help to fix this or possible other alternatives is greatly appreciated.
Per #jasonharper's comment above, CTkButton is
not actually a Tkinter Button at all (it's made from a Frame containing a Canvas and a Label), so the normal Tkinter attribute-getting functions don't apply.
Instead of using self.ecdpower["text"] or self.ecdpower.cget("text"): I should use
self.ecdpower.text
to get the CTKbutton's text value.
The same can be applied with fg_color; use self.ecdpower.fg_color to get the value.

Open printer selection window in Tkinter

I am having a problem possibly due to my lack of knowledge. I want to open the windows dialog to choose printer and send to print (to a printer) using Tkinter.
Currently, I use a code to relate Tkinter to Wxpython and make it asynchronous creating a separate process.
This is the code mentioned above:
from tkinter import *
from threading import Thread
import wx
def f_imprimir(ventana, entry):
class TextDocPrintout(wx.Printout):
def __init__(self):
wx.Printout.__init__(self)
def OnPrintPage(self, page):
dc = self.GetDC()
ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
ppiScreenX, ppiScreenY = self.GetPPIScreen()
logScale = float(ppiPrinterX)/float(ppiScreenX)
pw, ph = self.GetPageSizePixels()
dw, dh = dc.GetSize()
scale = logScale * float(dw)/float(pw)
dc.SetUserScale(scale, scale)
logUnitsMM = float(ppiPrinterX)/(logScale*25.4)
### Print code ###
return True
class PrintFrameworkSample(wx.Frame):
def OnPrint(self):
pdata = wx.PrintData()
pdata.SetPaperId(wx.PAPER_A4)
pdata.SetOrientation(wx.LANDSCAPE)
data = wx.PrintDialogData(pdata)
printer = wx.Printer(data)
printout = TextDocPrintout()
useSetupDialog = True
if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() ==
wx.PRINTER_ERROR:
wx.MessageBox(
"There was a problem printing.\n"
"Perhaps your current printer is not set correctly?",
"Printing Error", wx.OK)
else:
data = printer.GetPrintDialogData()
pdata = wx.PrintData(data.GetPrintData()) # force a copy
printout.Destroy()
self.Destroy()
app=wx.App(False)
PrintFrameworkSample().OnPrint()
entry.config(state="normal")
def process(ventana, entry):
entry.config(state="disable")
t = Thread(target=f_imprimir, args=(ventana,entry))
t.start()
v = Tk()
entry = Entry(v)
entry.pack()
v.bind("a", lambda a:process(v,entry))
when wx.app finishes, which can happen when the Printer Selector closes, I plan to change the status of the entry to "normal".
But it throws an error when changing the state of the entry to "normal", which I suppose is because the window and the order I send are in separate processes. The error would be:
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python38-32\lib\threading.py", line 932, in _bootstrap_inner
self.run()
File "C:\Python38-32\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "C:\Users\DANTE\Google Drive\JNAAB\DESARROLLO\pruebas\pedasito.py", line 65, in f_imprimir
entry.config(state="normal")
File "C:\Python38-32\lib\tkinter\__init__.py", line 1637, in configure
return self._configure('configure', cnf, kw)
File "C:\Python38-32\lib\tkinter\__init__.py", line 1627, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
RuntimeError: main thread is not in main loop
Does anyone have a solution for this problem or an alternative way to create the print window without blocking the TCL window and being able to block the entry? If there is a way to do it or send to print using Tkinter and avoid this mess it would be even better. Thank you.
I dont use wxPython however your error is due to a threading issue related to tkinter. Tkinter likes to be in the main thread and trying to pass widgets to a seperate thread can cause problems. However your entry field is in the global namespace already so you do not need to pass it.
Simply updated from your thread once it needs to be.
I would do this in your if/else condition so it only happens at the correct time.
Something like this would work:
Note you will need to actually do something with the value passed. As it is now none of your code actually prints anything other than a blank page.
import tkinter as tk
from threading import Thread
import wx
def f_imprimir(value):
# here you can see the value of entry was passed as a string so we can avoid any issues with the widget
print(value)
class TextDocPrintout(wx.Printout):
def __init__(self):
wx.Printout.__init__(self)
def OnPrintPage(self, page):
dc = self.GetDC()
ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
ppiScreenX, ppiScreenY = self.GetPPIScreen()
logScale = float(ppiPrinterX)/float(ppiScreenX)
pw, ph = self.GetPageSizePixels()
dw, dh = dc.GetSize()
scale = logScale * float(dw)/float(pw)
dc.SetUserScale(scale, scale)
logUnitsMM = float(ppiPrinterX)/(logScale*25.4)
return True
class PrintFrameworkSample(wx.Frame):
def OnPrint(self):
pdata = wx.PrintData()
pdata.SetPaperId(wx.PAPER_A4)
pdata.SetOrientation(wx.LANDSCAPE)
data = wx.PrintDialogData(pdata)
printer = wx.Printer(data)
printout = TextDocPrintout()
useSetupDialog = True
if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() == wx.PRINTER_ERROR:
wx.MessageBox("There was a problem printing.\n\n"
"Perhaps your current printer is not set correctly?\n\n"
"Printing Error", wx.OK)
entry.config(state="normal")
else:
data = printer.GetPrintDialogData()
pdata = wx.PrintData(data.GetPrintData()) # force a copy
entry.config(state="normal")
printout.Destroy()
self.Destroy()
app = wx.App(False)
PrintFrameworkSample().OnPrint()
def process(_=None):
entry.config(state="disable")
t = Thread(target=f_imprimir, args=(entry.get(),))
t.start()
v = tk.Tk()
entry = tk.Entry(v)
entry.pack()
v.bind("<Return>", process)
v.mainloop()

Attribute Error. Can not access attributes in Class

I'm trying to call a class Pager in my main function in Python. When I run the program it gives me an error:
Traceback (most recent call last): File "lab4.py", line 113, in <module>
main() File "lab4.py", line 34, in main
demandPager.processRef(p, clock, rand) File "/Users/kiranbhimani/Desktop/OSLab4/Pager.py", line 16, in processRef
desiredPage = Pager.frameTable.findPageById(testId, process.getProcessId())
**AttributeError: class Pager has no attribute 'frameTable'**
How can I access frameTable? If I insert "self" as a parameter, I can't call the class. It says processRef requires 4 arguments but only 3 are given.
I'm not sure what is going on here. Thank you in advance!
class Pager:
def __init__(self, machineSize, pageSize):
self.machineSize = machineSize
self.pageSize = pageSize
self.frameTable = FrameTable(int(machineSize/pageSize))
#staticmethod
def processRef(process, clock, randreader):
ref = int(process.currentReference)
testId = int(ref / Page.size)
#print Pager.machineSize
desiredPage = Pager.frameTable.findPageById(testId, process.getProcessId())
if (isHit(desiredPage, process)):
desiredPage.addRefdWord(ref)
else:
if (not frameTable.isFull()):
frameTable.addPage(process, testId, ref)
else:
pageToEvict = findPageToReplace(replacementAlgo, randreader)
frameTable.evictPage(pageToEvict)
frameTable.addPage(process, testId, ref)
desiredPage = frameTable.lastPageAdded
desiredPage = frameTable.lastPageAdded
desiredPage.setIfLoaded(true)
process.incrNumPageFaults()
desiredPage.timeLastUsed = clock
frameTable.updateTimes()
This is the main function:
from Process import Process
from Page import Page as Page
from Pager import Pager
from FrameTable import FrameTable
import sys
runningProcesses = []
finishedProcesses = []
def main():
#read input
machineSize = int(sys.argv[1])
pageSize = int(sys.argv[2])
processSize = int(sys.argv[3])
jobMix = int(sys.argv[4])
numOfRefPerProcess = int(sys.argv[5])
replacementAlgo = (sys.argv[6])
demandPager = Pager(machineSize, pageSize)
Page.size = pageSize
Process.size = processSize
setProc(jobMix)
demandPager.replacementAlgo = replacementAlgo
index = 0
clock = 0
while(len(runningProcesses) > 0):
p = runningProcesses[index]
for i in range(3):
demandPager.processRef(p, clock, rand)
p.setCurrentReference(p.calcNextReference(rand))
p.incrRefsMade()
clock+=1
if (p.getRefsMade() == numRefPerProcess):
finishedProcesses.add(p)
runningProcesses.remove(p)
index-=1
break
if (index == numProcesses-1):
index = 0
else:
index+=1
print "printing....."
printOutput(args)
You tried to access a class property frameTable, but this class has no added properties at all. Objects of the class have properties of machineSize, FrameSize, and pageTable -- that's one of each for every object you instantiate.
For instance, there will be a demandPager.frameTable once you hit the creation command, but you haven't given any properties (other than the built-ins) to Pager as a class.
Perhaps you want to use self
desiredPage = self.frameTable.findPageById(testId, process.getProcessId())

Tkinter window not playing well with threads

I've got a program that will eventually receive data from an external source over serial, but I'm trying to develop the display-side first.
I've got this "main" module that has the simulated data send and receive. It updates a global that is used by a Matplotlib stripchart. All of this works.
#-------------------------------------------------------------------------------
# Name: BBQData
# Purpose: Gets the data from the Arduino, and runs the threads.
#-------------------------------------------------------------------------------
import time
import math
import random
from threading import Thread
import my_globals as bbq
import sys
import BBQStripChart as sc
import serial
import BBQControl as control
ser = serial.serial_for_url('loop://', timeout=10)
def simData():
newTime = time.time()
if not hasattr(simData, "lastUpdate"):
simData.lastUpdate = newTime # it doesn't exist yet, so initialize it
simData.firstTime = newTime # it doesn't exist yet, so initialize it
if newTime > simData.lastUpdate:
simData.lastUpdate = newTime
return (140 + 0.05*(simData.lastUpdate - simData.firstTime), \
145 + 0.022*(simData.lastUpdate - simData.firstTime), \
210 + random.randrange(-10, 10))
else:
return None
def serialDataPump():
testCtr = 0;
while not bbq.closing and testCtr<100:
newData = simData()
if newData != None:
reportStr = "D " + "".join(['{:3.0f} ' for x in newData]) + '\n'
reportStr = reportStr.format(*newData)
ser.write(bytes(reportStr, 'ascii'))
testCtr+=1
time.sleep(1)
bbq.closing = True
def serialDataRcv():
while not bbq.closing:
line = ser.readline()
rcvdTime = time.time()
temps = str(line, 'ascii').split(" ")
temps = temps[1:-1]
for j, x in enumerate(temps):
bbq.temps[j].append(float(x))
bbq.plotTimes.append(rcvdTime)
def main():
sendThread = Thread(target = serialDataPump)
receiveThread = Thread(target = serialDataRcv)
sendThread.start()
receiveThread.start()
# sc.runUI()
control.runControl() #blocks until user closes window
bbq.closing = True
time.sleep(2)
exit()
if __name__ == '__main__':
main()
## testSerMain()
However, I'd like to add a SEPARATE tkinter window that just has the most recent data on it, a close button, etc. I can get that window to come up, and show data initially, but none of the other threads run. (and nothing works when I try to run the window and the plot at the same time.)
#-------------------------------------------------------------------------------
# Name: BBQ Display/Control
# Purpose: displays current temp data, and control options
#-------------------------------------------------------------------------------
import tkinter as tk
import tkinter.font
import my_globals as bbq
import threading
fontSize = 78
class BBQControl(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.labelFont = tkinter.font.Font(family='Helvetica', size=int(fontSize*0.8))
self.dataFont = tkinter.font.Font(family='Helvetica', size=fontSize, weight = 'bold')
self.makeWindow()
def makeWindow(self):
self.grid()
btnClose = tk.Button(self,text=u"Close")
btnClose.grid(column=1,row=5)
lblFood = tk.Label(self,anchor=tk.CENTER, text="Food Temps", \
font = self.labelFont)
lblFood.grid(column=0,row=0)
lblPit = tk.Label(self,anchor=tk.CENTER, text="Pit Temps", \
font = self.labelFont)
lblPit.grid(column=1,row=0)
self.food1Temp = tk.StringVar()
lblFoodTemp1 = tk.Label(self,anchor=tk.E, \
textvariable=self.food1Temp, font = self.dataFont)
lblFoodTemp1.grid(column=0,row=1)
#spawn thread to update temps
updateThread = threading.Thread(target = self.updateLoop)
updateThread.start()
def updateLoop(self):
self.food1Temp.set(str(bbq.temps[1][-1]))
def runControl():
app = BBQControl(None)
app.title('BBQ Display')
app.after(0, app.updateLoop)
app.mainloop()
bbq.closing = True
if __name__ == '__main__':
runControl()
Your title sums up the problem nicely: Tkinter doesn't play well with threads. That's not a question, that's the answer.
You can only access tkinter widgets from the same thread that created the widgets. If you want to use threads, you'll need your non-gui threads to put data on a queue and have the gui thread poll the queue periodically.
One way of getting tkinter to play well with threads is to modify the library so all method calls run on a single thread. Two other questions deal with this same problem: Updating a TKinter GUI from a multiprocessing calculation and Python GUI is not responding while thread is executing. In turn, the given answers point to several modules that help to solve the problem you are facing. Whenever I work with tkinter, I always use the safetkinter module in case threads appear to be helpful in the program.

Python: Can't pop from an empty list

I am creating a python program to detect and enable usb to usb data transfer between usb storage drives. However I am having an issue with updating the dev_label (device name of the drive) and passing it to Exchange. Here is the code :
serial_list=[]
context = Context()
monitor = Monitor.from_netlink(context)
monitor.filter_by(subsystem='block',device_type='partition')
observer = GUDevMonitorObserver(monitor)
def device_connected(observer, device):
Welcome.device_count+=1
flag =False
for iden in serial_list :
if iden == device.__getitem__('ID_SERIAL_SHORT'):
flag=True
if flag ==False:
serial_list.append(device.__getitem__('ID_SERIAL_SHORT'))
Welcome.dev_label.append(str(device.__getitem__('ID_FS_LABEL')))
size = len(Welcome.dev_label)
label = gtk.Label('Device connected :: {0!r}'.format(Welcome.dev_label[size-1]))
Welcome.vbox.pack_start(label)
Welcome.window.show_all()
if Welcome.device_count<2:
label = gtk.Label('Connect the second device')
Welcome.vbox.pack_start(label)
Welcome.window.show_all()
else :
Exchange()
observer.connect("device-added",device_connected)
monitor.start()
class Welcome:
device_count = 0
window = gtk.Window()
vbox= gtk.VBox(False, 5)
dev_label = []
def __init__(self):
self.window.set_default_size(300, 300)
self.window.set_title("Welcome")
label = gtk.Label("Connect the desired device")
self.vbox.pack_start(label)
self.window.add(self.vbox)
self.window.connect("destroy", lambda q: gtk.main_quit())
self.window.show_all()
class Exchange:
window1 = gtk.Window(Welcome.dev_label.pop())
window2 = gtk.Window(Welcome.dev_label.pop())
def __init__(self):
width = gtk.gdk.screen_get_default().get_width()
height = gtk.gdk.screen_get_default().get_height()
self.window1.resize(width/2,height)
self.window2.resize(width/2,height)
self.window2.move(self.window1.get_position()[0]+width/2, self.window1.get_position()[1])
label = gtk.Label("Hello")
self.window1.add(label)
self.window1.connect("destroy" , lambda q : gtk.main_quit())
self.window1.show_all()
label = gtk.Label("World")
self.window2.add(label)
self.window2.connect("destroy",lambda q : gtk.main_quit())
self.window2.show_all()
Welcome()
gtk.main()
The error shown in the trace back is :
Traceback (most recent call last):
File "project.py", line 70, in <module>
class Exchange:
File "project.py", line 71, in Exchange
window1 = gtk.Window(Welcome.dev_label.pop())
IndexError: pop from empty list
I can't figure out how to synchronize all these event so that the compiler doesn't throw an error. Values are being popped from Welcome.dev_label only after they've been updated in device_connected so why does the compiler have a problem? I am a python newbie so please be gentle.
This is not the compiler givin errors but the program.
You can change your class to this:
import time
class Exchange:
while not Welcome.dev_label: time.sleep(0.001)
window1 = gtk.Window(Welcome.dev_label.pop()) # line 4
while not Welcome.dev_label: time.sleep(0.001)
window2 = gtk.Window(Welcome.dev_label.pop())
This would be kind of a synchronization primitive given that only line 4 and 6 remove content.
In general you would use a queue for this.
import queue # Python3 # import Queue Python 2
Welcome.dev_label # = queue.Queue()
Welcome.dev_label.put(...) # instead of append
Welcome.dev_label.get(...) # instead of pop
But I do not know wether your code uses threads and runs in parallel. If the time.sleep example works then you can switch to a queue.

Categories