使用pyqt的QUdpSocket传文本信息、用QTcpServer传文件的样例
本文章实现以下功能:
1.用QUdpSocket收发文本消息。
2.用QTcpServer接收文件,用QTcpSocket发送文件;支持大容量文件传输。测试过7G的文件,传输前后md5值一致。
3.文本消息采用气泡文本class BubbleLabel(QLabel)的方式展示,根据文本长度自动计算气泡宽度。
4.文件收发成功后,在聊天窗口展示固定格式的class FileInfoWidget(QWidget),并实现了简单的右键菜单。
5.重复接收同名文件时,自动重命名,避免覆盖传文件。
如下图所示:
一共3个py文件:netServer.py、netClient.py、netUtil.py
1.其中netServer.py和netClient.py都是QMainWindow,是聊天窗口的ui,代码几乎完全相同,实际上可以只保留netClient.py(更名为ChatWidget更合适)。我只是懒得搞虚拟机,才强行分为server和client两个文件,把upd、tcp占用的端口写死在代码里,方便在同一台开发电脑上测试而已。
2.聊天记录的显示既不用QTextBrowser,也不用QTextEdit,而是QListWidget,因为只有QListWidget才能通过QListWidgetItem插入自定义的QWidget:QListWidget.setItemWidget(QListWidgetItem, QWidget)
3.核心代码在netUtil.py里,需要先理解pyqt的信号、槽机制。实际上,任何一个(类似netClient.py的)QWidget,from netUtil import NetUtil之后,都可以使用udp、tcp进行数据传输。
废话不多说,直接上代码,自己看注释:
netServer.py(可收发udp文本消息,仅接收文件)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import time
import shutil
from math import sqrt
from datetime import datetime
from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtWidgets import *
from netUtil import NetUtil
class BubbleLabel(QLabel):
"""气泡文字"""
border = 2
trigon = 15 # 指向左、右的三角箭头的大小
def __init__(self, listWidgetItem, listWidget, text, maxWidth, myself=True):
super(BubbleLabel, self).__init__(text)
self.listWidgetItem = listWidgetItem
self.listWidget = listWidget
self.text = text
self.myself = myself # 标志绘制左还是右
self.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse) # 文本可被选中
self.setWordWrap(True) # 文本可换行
self.setFont(QtGui.QFont("Times New Roman", 12, QtGui.QFont.Weight.Normal))
self.autoAdjustSize(maxWidth)
self.setState(False) # 设置鼠标不进入状态,方便绘图区域的颜色更新
if self.myself:
'''为了让实现显示的图片不会在super(BubbleImage, self).paintEvent(e)时和绘制的背景气泡冲突,
设置控件的setContentsMargins绘图范围保证图像的绘图区域。'''
self.setContentsMargins(int(self.trigon * sqrt(3) / 2) + 15, self.border + 10, self.border + 15, self.border + 10)
else:
self.setContentsMargins(self.border + 15, self.border + 10, int(self.trigon * sqrt(3) / 2) + 15, self.border + 10)
def autoAdjustSize(self, maxWidth):
'''根据QLabel的字体、字号计算QLabel文本的像素长度,控制一行长度不超过maxWidth'''
fm = QtGui.QFontMetrics(self.font())
pixels = fm.horizontalAdvance(self.text)
self.setMinimumWidth(min(maxWidth, pixels + (self.trigon * 4))) # 不是setMaximumWidth
def paintEvent(self, e):
size = self.size()
qp = QtGui.QPainter()
qp.begin(self)
if self.myself:
self.leftBubble(qp, size.width(), size.height())
else:
self.rightBubble(qp, size.width(), size.height())
qp.end()
super(BubbleLabel, self).paintEvent(e)
def leftBubble(self, qp, w, h):
qp.setPen(self.colorLeftE) # 设置画笔颜色,绘制的矩形边缘颜色
qp.setBrush(self.colorLeftM) # 设置红色的笔刷
middle = int(h * (1 - 0.618))
shifty = int(self.trigon / 2)
shiftx = int(self.trigon * sqrt(3) / 2)
rL = QtCore.QRectF(shiftx, 1, w - shiftx - self.border, h - 3)
pL = QtGui.QPolygonF() # 更改为圆角矩形
pL.append(QtCore.QPointF(0, middle)) # 起始点
pL.append(QtCore.QPointF(shiftx, middle + shifty)) # 第二点
pL.append(QtCore.QPointF(shiftx, middle - shifty)) # 第三点
"""
pL.append(QtCore.QPointF(w - self.border, h - self.border)) #第四点
pL.append(QtCore.QPointF(w - self.border, self.border)) #第五点
pL.append(QtCore.QPointF(shiftx, self.border)) #第六点
pL.append(QtCore.QPointF(shiftx, middle - shifty)) #第七点
"""
qp.drawPolygon(pL)
qp.drawRoundedRect(rL, 10, 10)
qp.setPen(self.colorLeftM)
line = QtCore.QLine(shiftx, middle + shifty, shiftx, middle - shifty)
qp.drawLine(line)
def rightBubble(self, qp, w, h):
qp.setPen(self.colorRightE) # 设置画笔颜色,绘制的矩形边缘颜色
qp.setBrush(self.colorRightM) # 设置红色的笔刷
middle = int(h * (1 - 0.618))
shifty = int(self.trigon / 2)
shiftx = int(self.trigon * sqrt(3) / 2)
rL = QtCore.QRectF(self.border, 1, w - shiftx - self.border, h - 3)
pL = QtGui.QPolygonF() # 更改为圆角矩形
pL.append(QtCore.QPointF(w, middle)) # 起始点
pL.append(QtCore.QPointF(w - shiftx, middle + shifty)) # 第二点
pL.append(QtCore.QPointF(w - shiftx, middle - shifty)) # 第三点
"""
pL.append(QtCore.QPointF(w - self.border, h - self.border)) #第四点
pL.append(QtCore.QPointF(w - self.border, self.border)) #第五点
pL.append(QtCore.QPointF(shiftx, self.border)) #第六点
pL.append(QtCore.QPointF(shiftx, middle - shifty)) #第七点
"""
qp.drawPolygon(pL)
qp.drawRoundedRect(rL, 10, 10)
qp.setPen(self.colorRightM)
line = QtCore.QLine(w - shiftx, middle + shifty, w - shiftx, middle - shifty)
qp.drawLine(line)
def setState(self, mouse):
'''鼠标进入和鼠标出时需要显示不一样的效果,主要就是更新颜色变量,然后调用update更新重绘'''
if mouse: # 鼠标进入
self.colorLeftM = QtGui.QColor("#eaeaea")
self.colorLeftE = QtGui.QColor("#D6D6D6")
self.colorRightM = QtGui.QColor("#8FD648")
self.colorRightE = QtGui.QColor("#85AF65")
else:
self.colorLeftM = QtGui.QColor("#fafafa")
self.colorLeftE = QtGui.QColor("#D6D6D6")
self.colorRightM = QtGui.QColor("#9FE658")
self.colorRightE = QtGui.QColor("#85AF65")
self.update() # 更新界面,不用执行也可以更新,但是不实时
def enterEvent(self, e):
self.setState(True)
def leaveEvent(self, e):
self.setState(False)
def contextMenuEvent(self, event):
''' 右键菜单实现文本的复制和控件的删除'''
at_copy = QtGui.QAction('复制', self, triggered=self.copyItemText)
at_copyAll = QtGui.QAction('复制全部', self, triggered=self.copyItemTextAll)
at_del = QtGui.QAction('删除', self, triggered=self.delListWidgetItem)
at_clear = QtGui.QAction('清空', self, triggered=self.clearListWidget)
menu = QMenu()
menu