I'm learning about sockets in python and as practice I made a python gui app with a text box, two entry boxes and a button. It works like a chat app, I can run many modules at once, and in each, when a user enters text and click send, it shows the message entered, on the text box of the user's module and all other modules open. I've gotten most of it working but the issue is that it only updates the text box when the send button is pressed but I want to update constantly so as soon as a new message is sent, it shows it on the screen.
Here's my code so far:
#!/usr/bin/python
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.root = tk.Tk()
self.create_widgets()
def create_widgets(self):
... #code that creates widgets shortended for readability
#text box widget named self.txt
self.send_btn["command"] = self.send_msg //this handles sending messages to the server when the button is pressed
... #code that creates widgets shortended for readability
def send_msg(self):
s = socket.socket()
host = socket.gethostname()
port = 9999
address = (host, port)
msg=self.name_bar.get() + ": " +self.input_bar.get()
#name bar is another widget so I can enter an identity for each module
#input bar is the entry box where text is entered in
self.input_bar.delete(0, len(msg))
s.connect(address)
s.send(msg.encode())
#Wrote this code that updates textbox when button pushed
#self.txt.insert(tk.END, s.recv(1024))
#self.txt.insert(tk.END, "\n")
#Method I created to call constantly to update textbox
def rcv_msg(self):
s = socket.socket()
host = socket.gethostname()
port = 9999
address = (host, port)
s.connect(address)
if s.recv(1024).decode() != "":
#self.txt.insert(tk.END, s.recv(1024))
#self.txt.insert(tk.END, "\n")
Also I been doing Java lately so I apologise if my terminology is mixed up.
I've already made the method to update the text box, I just don't know how to call it, I tried a while loop but it just stops the app from running. Also, the server is really simple and just sends the message from the client above to all the all modules of the client open. I din't know if the code was necessary, I was told previously to try to keep questions short but if it's needed please tell me. Thank you!
You could have something listening for new information. A separate thread that doesn't interfere with the GUI. (Pseudo code ahead!)
import socket
from threading import Thread, Lock
import time
class ListenThread(Thread):
def __init__(self, app, lock):
Thread.__init__(self)
self._app = app
self._lock = lock
self._terminating = False
def destroy(self):
self._terminating = True
def run(self):
s = socket.socket()
host = socket.gethostname()
port = 9999
address = (host, port)
while True:
if self._terminating:
break;
if s.recv(1024).decode() != "":
self._lock.acquire()
self._app.post_new_data(s.recv(1024))
self._lock.release()
time.sleep(0.1)
class App(object):
# ...
def __init__(self):
self._lock = Lock()
self._listener = ListenThread(self, self._lock)
self._listener.start()
def post_new_data(self, data):
# Post information to the GUI
def all_data(self):
self._lock.acquire()
# ...
self._lock.release()
def destroy(self):
self._listener.destroy()
# ... Tk mainloop ...
Breakdown
class ListenThread(Thread):
This is the item that will listen for new data coming from other connections and post them to the GUI via self._app.post_new_data(...) call in the run(self) operation.
def run(self):
s = socket.socket()
When we first start the execution on a thread (via start()) we create our socket and get a connection going. This way, all incoming transmissions will route through this in order to keep our GUI free for things it likes to do (paint the interface, take on user input, etc.)
The while loop on the ListenThread.run will keep looking for new data until we've killed the thread. As soon as it receives data, it will push that information to our App. In the post_new_data function, you can add our new data onto the GUI, store it, whatever you would like.
def post_new_data(self, data):
self.txt.insert(tk.END, data)
self.txt.insert(tk.END, "\n")
Related
I am currently working on a server / client chat program which works fine on terminal but in trying to migrate it into a windowed program I've come across a few errors which I have managed to solve except for this one, for whatever reason, in the GUI class running on the main thread seems to lag considerably whenever the user focuses their attention on it or whenever they try to interact with it, I've tried to use threading but I'm not sure what the problem is.
...
class GUI():
def __init__(self):
self.txtval = []
self.nochngtxtval = []
self.root = tk.Tk()
self.root.title("Chatroom")
self.root.geometry("400x200")
self.labl = tk.Label(self.root, text="", justify=tk.LEFT, anchor='nw', width=45, relief=tk.RIDGE, height=8)
self.labl.pack()
count = 1
while events["connected"] == False:
if events["FError"] == True:
quit()
self.labl["text"] = "Loading"+"."*count
count += 1
if count > 3:
count = 1
self.labl["text"] = ""
self.inputtxt = tk.Text(self.root,height=1,width=40,undo=True)
self.inputtxt.bind("<Return>", self.sendinput)
self.sendButton = tk.Button(self.root, text="Send")
self.sendButton.bind("<Button-1>", self.sendinput)
self.sendButton.pack(padx=5,pady=5,side=tk.BOTTOM)
self.inputtxt.pack(padx=5,pady=5,side=tk.BOTTOM)
uc = Thread(target=self.updatechat,daemon=True)
uc.start()
self.root.protocol("WM_DELETE_WINDOW", cleanAndClose)
self.root.mainloop()
def updatechat(self):
global events
while True:
if events["recv"] != None:
try:
current = events["recv"]
events["recv"] = None
... # shorten to reasonable length (45 characters)
self.labl['text'] = ''
self.txtval = []
self.nochngtxtval.append(new+"\n")
for i in range(len(nochngtxtval)):
self.txtval.append(nochngtxtval[i])
self.txtval.pop(len(self.txtval)-1) # probably not necessary
self.txtval.append(new+"\n") # probably not necessary
self.txtval[len(self.txtval)-1] = self.txtval[len(self.txtval)-1][:-1]
for i in range(len(self.txtval)):
self.labl['text'] = self.labl['text']+self.txtval[i]
except Exception as e:
events["Error"] = str(e)
pass
def sendinput(self, event):
global events
inp = self.inputtxt.get("1.0", "end").strip()
events["send"] = inp
self.inputtxt.delete('1.0', "end")
... # start the logger and server threads
GUI()
I'm using my custom encryption which is linked on github https://github.com/Nidhogg-Wyrmborn/BBR
my server is named server.py in the same git repo as my encryption.
EDIT:
I added a test file which is called WindowClasses.py to my git repo which has no socket interactions and works fine, however the problem seems to be introduced the moment i use socket as displayed in the Client GUI. I hope this can help to ascertain the issue.
I can use QStatusBar to display a message by feeding it a single string, e.g.:
self.statusBar().showMessage("My message here, also show it for 1 sec", 1000)
In my event-loop, the above message will be shown on the status-bar for exactly 1000 ms. But say that instead of a string "My message here", I have a list of strings I want to display one at the time. I can do that via a loop by giving it a delay via time.sleep(1) - but that would unfortunately block the gui until the loop is over, and I don't want that. Can I separate the processes of main event loop and status bar updating, and if so, how?
The example code below has a button, which when pressed, generates three different messages stored in a list. They are then shown in the status-bar, one at a time, and the button cannot be pressed until the loop ends. The behaviour that I want is that the button is clickable (gui isn't blocked), and if it's clicked while the previous messages are showing, the showing is interrupted and new messages are shown.
---Example code below---
import sys
import time
from PyQt5 import QtWidgets
class Window(QtWidgets.QMainWindow):
"""Main Window."""
MSG_ID = 1
def __init__(self, parent=None):
"""Initializer."""
super().__init__(parent)
#stuff
self.messages = []
self.setWindowTitle('Main Window')
self.setStatusBar(QtWidgets.QStatusBar())
self.statusBar().showMessage("My message first time")
self.button = QtWidgets.QPushButton("Test",self)
self.button.clicked.connect(self.handleButton)
def handleButton(self):
self.messages = [f"message_num {msg_id}" for msg_id in range(Window.MSG_ID,Window.MSG_ID+3)]
Window.MSG_ID+=3
self.updateStatus()
print(self.messages)
def updateStatus(self):
self.statusBar().clearMessage()
for item in self.messages:
self.statusBar().showMessage(item,1000)
time.sleep(1.2)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
The above code creates a list of messages
There is no need to use timers or sleep for this, because the status-bar sends a messageChanged signal every time a temporary message changes (including when it's removed). This can be used to create a simple call-back loop that does what you want:
class Window(QtWidgets.QMainWindow):
...
def __init__(self, parent=None):
...
self.statusBar().messageChanged.connect(self.updateStatus)
def handleButton(self):
self.messages = [f"message_num {msg_id}" for msg_id in range(Window.MSG_ID,Window.MSG_ID+3)]
Window.MSG_ID+=3
self.updateStatus()
def updateStatus(self, message=None):
print('update-status:', (message, self.messages))
if not message and self.messages:
self.statusBar().showMessage(self.messages.pop(0), 1000)
This does not block the gui and allows the message sequence to be re-started at any time by clicking the button.
I am displaying a sensor device's measurement values using the Tkinter GUI application. The sensor sends new data every second. I started a new thread and put the result in a Queue and process the queue to display the values on GUI. Now I am facing another problem. The sensor has two modes of operations. For simplicity, I have used a random generator in the program. Users should be able to switch the modes using two buttons. Button-1 for Mode-1, Button-2 for Mode-2. (say the mode-1 operation is random.randrange(0,10) and mode-2 operation is random.randrange(100, 200). How do I control these two operations through Threading? if a user started a mode-1 operation, when he presses the Button-2, the mode-1 operation should stop (thread-1) and mode-2 operation (thread-2) should start. Does it mean do I need to kill thread-1? Or is there any way to control two modes in same thread? I am totally new into threading. Any suggestions, please.
import tkinter
import threading
import queue
import time
import random
class GuiGenerator:
def __init__(self, master, queue):
self.queue = queue
# Set up the GUI
master.geometry('800x480')
self.output = tkinter.StringVar()
output_label = tkinter.Label(master, textvariable= self.output)
output_label.place(x=300, y=200)
#I haven't shown command parts in following buttons. No idea how to use it to witch modes?
mode_1_Button = tkinter.Button(master, text = "Mode-1")
mode_1_Button.place(x=600, y=300)
mode_2_Button = tkinter.Button(master, text = "Mode-2")
mode_2_Button.place(x=600, y=400)
def processQueue(self):
while self.queue.qsize():
try:
sensorOutput = self.queue.get() #Q value
self.output.set(sensorOutput) #Display Q value on GUI
except queue.Empty:
pass
class ClientClass:
def __init__(self, master):
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.myGui = GuiGenerator(master, self.queue)
#How do I switch the modes of operations? do I need some flags setting through button press?
# Set up the thread to do asynchronous I/O
self.thread1_mode1 = threading.Thread(target=self.firstModeOperation)
self.thread1_mode1.start()
# Start the periodic call in the GUI to check if the queue contains
# anything new
self.periodicCall()
def periodicCall(self):
# Check every 1000 ms if there is something new in the queue.
self.myGui.processQueue()
self.master.after(1000, self.periodicCall)
def firstModeOperation(self):
while True: #??? how do i control here through mode selection
time.sleep(1.0)
msg_mode_1= random.randrange(0,10)
self.queue.put(msg_mode_1)
def secondModeOperation(self):
while True: #??? how do i control here through mode selection
time.sleep(1.0)
msg_mode_2= random.randrange(100,200)
self.queue.put(msg_mode_2)
#Operation part
root = tkinter.Tk()
client = ClientClass(root)
root.mainloop()
So as part of my project (2D Multiplayer Card Game), I've figured out how to host and run a server script online. My plan is to have two separate kivy clients connect to the server (which will just be a script with commands).
However I'm somewhat confused about the order of operations because I think the client connection is potentially in conflict with the message loop so I'm wondering if someone could basically tell me what I should be doing:
I'm going to be using this as my serverscript:
import socket
serversocket = socket.socket()
host = 'INSERTIPHERE'
port = PORTHERE
serversocket.bind(('', port))
serversocket.listen(1)
while True:
clientsocket,addr = serversocket.accept()
print("got a connection from %s" % str(addr))
msg = 'Thank you for connecting' + "\r\n"
clientsocket.send(msg.encode('ascii'))
clientsocket.close()
This is my client connection function
def Main():
host = 'INSERTIPHERE'
port = PORTHERE
mySocket = socket.socket()
mySocket.connect((host, port))
message = input(' -> ')
while message != 'q':
mySocket.send(message.encode())
data = mySocket.recv(1024).decode()
print('Received from server: ' + data)
message = input(' -> ')
mySocket.close()
Note: I understand that the server and client aren't perfectly aligned in functions but provided I can at least a connection confirmation for now, I can work from there.
I'm basically wondering how do I put this code into a simple kivy app like this:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class BoxWidget(BoxLayout):
pass
class BoxApp(App):
def build(self):
return BoxWidget()
if __name__ == '__main__':
BoxApp().run()
My best guess is that you want to:
Establish the connection before opening the client
Passing the server connection to the primary widget (in this case the Box Widget) as you're running an instance of the client (ie BoxApp(server).run()?)
Use that connection in a message loop function of the BoxWidget
I also understand that Kivy has built in solutions with Twisted but I'm having trouble with the python 2-3 differences.
Thank you for reading.
Just to clarify: All I want to do right now is open a blank window and also have a confirmation message sent to the command line (or failing that a label in the window).
You can use threading so you don't interrupt the main thread in kivy.
I rewrote your example a bit, so what you send from the server, will be the text of a label.
server.py
import socket
serversocket = socket.socket()
host = 'localhost'
port = 54545
serversocket.bind(('', port))
serversocket.listen(1)
clientsocket,addr = serversocket.accept()
print("got a connection from %s" % str(addr))
while True:
msg = input("> ") + "\r\n"
clientsocket.send(msg.encode('ascii'))
client.py
import socket
class MySocket:
def __init__(self,host="localhost",port=54545):
self.sock = socket.socket()
self.sock.connect((host, port))
def get_data(self):
return self.sock.recv(1024)
main.py
from kivy.app import App
from kivy.uix.label import Label
from client import *
from threading import Thread
class MyLabel(Label):
def __init__(self, **kwargs):
super(MyLabel,self).__init__(**kwargs)
self.sock = MySocket()
Thread(target=self.get_data).start()
def get_data(self):
while True:
self.text = self.sock.get_data()
class BoxApp(App):
def build(self):
return MyLabel()
if __name__ == '__main__':
BoxApp().run()
Now just run server.py in one terminal, then main.py from another
I got a basic version of it working with buttons. Both on local machine and online. This Solution is likely not viable for many real time apps or even a chat server since the reply has to be initiated. However for my goal of a multiplayer card game it should more than suffice with proper conditionals.
video of test on local machine
EDIT: In the video I talk about double clicking. I have just realized this is because the first click is putting the window back in focus.
EDIT 2: Using TextInput in kv file instead of input in Python file.
server script:
import socket
def Main():
host = '127.0.0.1'
port = 7000
mySocket = socket.socket()
mySocket.bind((host,port))
mySocket.listen(1)
conn, addr = mySocket.accept()
print ("Connection from: " + str(addr))
message = 'Thank you connecting'
conn.send(message.encode())
while True:
data = conn.recv(1024).decode()
strdata = str(data)
print(strdata)
reply = 'confirmed'
conn.send(reply.encode())
mySocket.close()
if __name__ == '__main__':
Main()
This is a pretty simple server. Listen for a single client, confirm connection, open a send and receive message loop.
This is the client script which isn't hugely complicated really:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
import socket
class BoxWidget(BoxLayout):
s = socket.socket()
host = '127.0.0.1'
port = 7000
display = ObjectProperty()
def connect_to_server(self):
# called by a Button press
# Connects to the server
self.s.connect((self.host, self.port))
# Receives confirmation from Server
data = self.s.recv(1024).decode()
# Converts confirmation to string
strdata = str(data)
# Prints confirmation
print(strdata)
def send_message(self):
# Is called by the function below
# Encodes and sends the message variable
self.s.send(self.message.encode())
# Waits for a reply
self.receive_message()
def message_to_send(self):
# Defines Message to send
self.message = self.display.text
# Calls function to send the message
self.send_message()
# Note
# When I used message = input directly in send_message,
# the app would crash. So I defined message input
# in its own function which then calls the
# send function
# message_to_send is the function actually
# called by a button press which then
# starts the chain of events
# Define Message, Send Message, get Reply
def receive_message(self):
# Decodes a reply
reply = self.s.recv(1024).decode()
# Converts reply to a str
strreply = str(reply)
# prints reply
print(strreply)
class ServerApp(App):
def build(self):
box = BoxWidget()
return box
if __name__ == '__main__':
ServerApp().run()
Edit: Forgot to include the kv file
<BoxWidget>:
display: display
Button:
text: 'Hello'
on_press: root.message_to_send()
Button:
text: 'Connect'
on_press: root.connect_to_server()
TextInput:
id: display
In future iterations, I'll be replacing print statements with conditionals (ie did client one draw a card? if so client 2's opponent draws a face-down card etc).
Relatively rudimentary as it is now but there is a lot you could do from here.
i'm trying to manage two MainWindow (MainWindow & AccessWindow) with PyQt for my RFID ACCESS CONTROL Project.
I want to show the first MainWindow all time (Endless Loop).
Then, i want to hide it and show the second MainWindow when the RFID Reader (who's working on "auto-reading mode") read an RFID Tag.
so in the main python program i have a pseudo "do while" loop (while True: and break with a condition) to read on serial port the data provided by the reader. Then i check a DB.. It's not important. So the trigger event is "when the reader read something).
I got some help from another forum and now i have this:
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
import sys, pyodbc, serial
import os
import time
#Variables
Code_Zone = "d"
class MainWindow(QtGui.QWidget):
def __init__(self, main):
super(MainWindow, self).__init__()
self.main = main
self.grid = QtGui.QGridLayout(self)
self.welcome = QtGui.QLabel("WELCOME", self)
self.grid.addWidget(self.welcome, 2, 2, 1, 5)
class AccessWindow(QtGui.QWidget):
def __init__(self):
super(AccessWindow, self).__init__()
self.setMinimumSize(150, 50)
self.grid = QtGui.QGridLayout(self)
self.label = QtGui.QLabel(self)
self.grid.addWidget(self.label, 1, 1, 1, 1)
class Main(object):
def __init__(self):
self.accueil = MainWindow(self)
self.accueil.show()
self.access = AccessWindow()
def wait(self):
# RFID READER ENDLESS LOOP
while 1:
global EPC_Code
ser = serial.Serial(port='COM6', baudrate=115200)
a = ser.read(19).encode('hex')
if (len(a)==38):
EPC_Code = a[14:]
print ('EPC is : ' + EPC_Code)
break
else:
continue
ser.close()
self.on_event(EPC_Code)
def on_event(self, data):
def refresh():
self.toggle_widget(False)
self.wait()
# vérification des données
EPC_Code = data
sql_command = "[Get_Access_RFID] #Code_RFID = '"+EPC_Code+"', #Code_Zone = '"+Code_Zone+"'" # STORED PROCEDURE
db_cursor.execute(sql_command)
rows = db_cursor.fetchone()
result= str(rows[0])
print ("result = " + str(result))
if result == "True":
# si OK
self.access.label.setText('ACCESS GRANTED')
else:
# si pas OK
self.access.label.setText('ACCESS DENIED')
self.toggle_widget(True)
QtCore.QTimer.singleShot(2000, refresh)
def toggle_widget(self, b):
self.accueil.setVisible(not b)
self.access.setVisible(b)
if __name__=='__main__':
cnxn = """DRIVER={SQL Server};SERVER=***;PORT=***;UID=***;PWD=***;DATABASE=***"""
db_connection = pyodbc.connect(cnxn)
db_cursor = db_connection.cursor()
print ('Connected TO DB & READY')
app = QtGui.QApplication(sys.argv)
main = Main()
main.wait()
sys.exit(app.exec_())
and now my problem is that the text of the first window doesn't appear when i run the program but the text of the second window appear when i keep my badge near the RFID Reader.
Instead of two MainWindow, create one. As content, create two classes which extend QtGui.QWidget called MainView and AccessView. Instead of replacing the window, just put the correct view into the window. That way, you can swap views without opening/closing windows.
If you use a layout, then the window will resize to fit the view.
The next problem is that you block the UI thread which means Qt can't handle events (like the "paint UI" event). To fix this, you must move the RFID handling code in a background thread. You can emit signals from this background thread to update the UI.
Note: You must not call UI code from a thread!! Just emit signals. PyQt's main loop will see them and process them.
Related:
https://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/
Updating GUI elements in MultiThreaded PyQT, especially the second example using signals. The first example is broken (calling addItem() from a thread) is not allowed)