[PyQt] Python界面编程学习总结

Python编程在大部分情况下可以借助丰富的第三方库,快速实现所需功能,满足使用要求,但是从其他用户使用的角度而言,最好是在GUI环境下进行操作,这里针对近期的项目需要,总结PyQt在python编程中界面化显示的基础知识。

|- UDP解析上位机

|- 整体思路

上位机拟采用如下思路:

  • 通讯部分 : 采用socket库实现UDP通讯
  • 解析部分: 采用.xml文件保存解析UDP包所需的数据,通过xml库进行xml文件的读写与配置
  • 界面部分: 采用PyQt5进行界面的编写

|- UDP通讯主要业务逻辑

UDP通讯采用socket库,设置连接超时时间为1s,程序如下:

# -*- coding: utf-8 -*-

__author__ = 'Sprinkle_WPD'

import socket


#TODO: 开启UDP
def udp_server_start(address):
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 设置连接超时时间
    udp_socket.settimeout(1)

    try:
        udp_socket.bind(address)
        print('{} 已经连接成功'.format(address))
    except Exception as ret:
        print('{} 连接失败'.format(address))
    else:
        udp_server_concurrency(udp_socket)


# TODO: 持续监听UDP
def udp_server_concurrency(udp_socket):
    while True:
        try:
            recv_msg, source_addr = udp_socket.recvfrom(2048)
            print(recv_msg)
        except:
            '如果超时'
            print('{} 未收到消息'.format(address))


if __name__ == '__main__':
    address = ('192.168.1.13', 8888)
    udp_server_start(address)

|- 封装业务逻辑类

将上面的业务逻辑封装成一个类,从而可以实现实例化业务逻辑,程序修改如下:

# -*- coding: utf-8 -*-

__author__ = 'Sprinkle_WPD'

import socket

class UdpLogic(object):
    def __init__(self, address):

        self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 设置连接超时时间
        self.udp_socket.settimeout(1)
        self.address = address

    #TODO: 开启UDP
    def udp_server_start(self):
        try:
            self.udp_socket.bind(self.address)
            print('{} 已经连接成功'.format(self.address))
        except Exception as ret:
            print('{} 连接失败'.format(self.address))
            return False
        else:
            self.udp_server_concurrency()
            return True

    #TODO: 监听UDP
    def udp_server_concurrency(self):
        while True:
            try:
                recv_msg, source_addr = self.udp_socket.recvfrom(2048)
                print(recv_msg)
            except:
                '如果超时'
                print('{} 未收到消息'.format(self.address))

if __name__ == '__main__':
    address = ('192.168.0.40', 9090)
    udp_example = UdpLogic(address)
    udp_example.udp_server_start()

|- 完善UdpLogic类

增加更改udp address的methods,修改后的程序如下:

# -*- coding: utf-8 -*-

__author__ = 'Sprinkle_WPD'

import socket

class UdpLogic(object):
    def __init__(self, address):

        self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 设置连接超时时间
        self.udp_socket.settimeout(1)
        self.address = address
        # 添加change的flag
        self.change = False

    #TODO: 开启UDP
    def udp_server_start(self):
        try:
            self.udp_socket.bind(self.address)
            print('{} 已经连接成功'.format(self.address))
        except Exception as ret:
            print('{} 连接失败'.format(self.address))
            return False
        else:
            self.udp_server_concurrency()
            return True

    #TODO: 监听UDP
    def udp_server_concurrency(self):
        while True:
            if not self.change:
                try:
                    recv_msg, source_addr = self.udp_socket.recvfrom(2048)
                    print(recv_msg)
                except:
                    '如果超时'
                    print('{} 未收到消息'.format(self.address))
            else:
                self.change_udp_address()
                self.change = False

    #TODO: 切换address
    def change_udp_address(self):
        try:
            self.udp_socket.close()
            print('{} 已经关闭'.format(self.udp_socket))
            del self.udp_socket
            self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # 设置连接超时时间
            self.udp_socket.settimeout(1)
            print('{} 切换完成'.format(self.address))
            self.udp_server_start()

        except:
            print('{} 切换失败'.format(self.address))

if __name__ == '__main__':
    address = ('192.168.0.40', 9090)
    udp_example = UdpLogic(address)
    udp_example.address = ('192.168.0.40', 8190)
    udp_example.change_udp_address()

|- 通讯业务线程

通讯业务构建完成,接下来两个思路:

  • 将该通讯业务继承对象改成QThread
  • 将该通讯业务用threading改写
    因为最终的目的是为了在界面上显示,所以这里采用了QThread

|- QThread

  • class UdpLogic(object):修改为class UdpLogic(QThread):
  • 重写def run(self)函数,指定对应的入口函数,这里是self.udp_server_start()
# -*- coding: utf-8 -*-

__author__ = 'Sprinkle_WPD'

import socket
from PyQt5.Qt import QThread

class UdpLogic(QThread):
    def __init__(self, address):
        super(UdpLogic, self).__init__()

        self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 设置连接超时时间
        self.udp_socket.settimeout(1)
        self.address = address
        # 添加change的flag
        self.change = False

    def run(self):
        self.udp_server_start()

    #TODO: 开启UDP
    def udp_server_start(self):
        try:
            self.udp_socket.bind(self.address)
            print('{} 已经连接成功'.format(self.address))
        except Exception as ret:
            print('{} 连接失败'.format(self.address))
            return False
        else:
            self.udp_server_concurrency()
            return True

    #TODO: 监听UDP
    def udp_server_concurrency(self):
        while True:
            if not self.change:
                try:
                    recv_msg, source_addr = self.udp_socket.recvfrom(2048)
                    print(recv_msg)
                except:
                    '如果超时'
                    print('{} 未收到消息'.format(self.address))
            else:
                self.change_udp_address()
                self.change = False

    #TODO: 切换address
    def change_udp_address(self):
        try:
            self.udp_socket.close()
            print('{} 已经关闭'.format(self.udp_socket))
            del self.udp_socket
            self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # 设置连接超时时间
            self.udp_socket.settimeout(1)
            print('{} 切换完成'.format(self.address))
            self.udp_socket.bind(self.address)
        except:
            print('{} 切换失败'.format(self.address))


if __name__ == '__main__':
    from PyQt5.Qt import QApplication
    import sys
    import time

    address = ('192.168.0.40', 9090)
    app = QApplication(sys.argv)
    thread = UdpLogic(address)
    thread.start()
    time.sleep(2)
    thread.address = ('192.168.0.40', 9092)
    thread.change = True
    sys.exit(thread.exec_())

上面的程序绑定 address = (‘192.168.0.40’, 9090) 持续2s之后,更换地址thread.address = (‘192.168.0.40’, 9092),可以利用UDP网络助手观察结果。

现在完成了基本的QThread的业务逻辑,接下来添加pyqtsignal进行与UI的交互,数据的传递

|- QThread + pyqtSignal

这里添加了一个pyqtSignal,为了说明参数传递的过程,这里建立了一个Main界面,具体程序如下:

# -*- coding: utf-8 -*-

__author__ = 'Sprinkle_WPD'

import socket
from PyQt5.Qt import QThread,QWidget,QPushButton,QHBoxLayout,QLabel
from PyQt5.QtCore import pyqtSignal

class UdpLogic(QThread):
    signal_data_pass = pyqtSignal(str)
    def __init__(self, address):
        super(UdpLogic, self).__init__()

        self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 设置连接超时时间
        self.udp_socket.settimeout(1)
        self.address = address
        # 添加change的flag
        self.change = False

    def run(self):
        self.udp_server_start()

    #TODO: 开启UDP
    def udp_server_start(self):
        try:
            self.udp_socket.bind(self.address)
            msg = '{} 已经连接成功'.format(self.address)
            self.signal_data_pass.emit(msg)
        except Exception as ret:
            msg = '{} 连接失败'.format(self.address)
            self.signal_data_pass.emit(msg)
            return False
        else:
            self.udp_server_concurrency()
            return True

    #TODO: 监听UDP
    def udp_server_concurrency(self):
        while True:
            if not self.change:
                try:
                    recv_msg, source_addr = self.udp_socket.recvfrom(2048)
                    print(recv_msg)
                    self.signal_data_pass.emit(recv_msg.decode('utf-8'))
                except:
                    '如果超时'
                    msg = '{} 未收到消息'.format(self.address)
                    self.signal_data_pass.emit(msg)
            else:
                self.change_udp_address()
                self.change = False

    #TODO: 切换address
    def change_udp_address(self):
        try:
            self.udp_socket.close()
            print('{} 已经关闭'.format(self.udp_socket))
            del self.udp_socket
            self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # 设置连接超时时间
            self.udp_socket.settimeout(1)
            print('{} 切换完成'.format(self.address))
            self.udp_socket.bind(self.address)
        except:
            print('{} 切换失败'.format(self.address))


class Main(QWidget):
    def __init__(self):
        super(Main, self).__init__()

        self.pbn = QPushButton('change')
        self.pbn_start = QPushButton('start')
        self.lbl = QLabel()
        self.pbn_start.clicked.connect(self.udp_start)
        self.pbn.clicked.connect(self.change)
        address = ('192.168.0.40', 9090)
        self.udp = UdpLogic(address)

        self.layout = QHBoxLayout()
        self.layout.addWidget(self.pbn_start)
        self.layout.addWidget(self.pbn)
        self.layout.addWidget(self.lbl)
        self.setLayout(self.layout)

        self.udp.signal_data_pass.connect(self.freshui)

    def udp_start(self):
        self.udp.start()

    def change(self):
        self.udp.address = ('192.168.0.40', 9190)
        self.udp.change = True

    def freshui(self, msg):
        self.lbl.setText(msg)





if __name__ == '__main__':
    from PyQt5.Qt import QApplication
    import sys
    import time

    app = QApplication(sys.argv)
    main = Main()
    main.show()
    sys.exit(app.exec_())

|- PyQt界面编程

通讯业务搞好之后,接下来是让界面显示UDP的数据,这里利用PyQt5进行GUI的编写

|- 界面的编写

这里采用按模块分界面编写,整体拼接的方法进行界面gui的设计,这里给出UDP address的设置界面。

# -*- coding: utf-8 -*-

__author__ = 'Sprinkle_WPD'


from PyQt5.Qt import *
from PyQt5.QtCore import pyqtSignal

class View_Udp_Setting(QWidget):
    def __init__(self):
        super(View_Udp_Setting, self).__init__()

        self.setWindowTitle("UDP SERVER DEMO")
        self.setFixedHeight(170)
        self.group = QGroupBox('UDP Setting')
        self.group.setFixedWidth(200)
        self.edit_SourceIp = QLineEdit()
        self.edit_SourcePort = QLineEdit()
        self.edit_DestinIp = QLineEdit()
        self.edit_DestinPort = QLineEdit()

        group_layout = QFormLayout()
        group_layout.addRow('Source IP: ', self.edit_SourceIp)
        group_layout.addRow('Source Port: ', self.edit_SourcePort)
        group_layout.addRow('Destin IP: ', self.edit_DestinIp)
        group_layout.addRow('Destin Port: ', self.edit_DestinPort)
        self.group.setLayout(group_layout)
        self.pbn_connect = QPushButton('Connet')
        self.pbn_disconnect = QPushButton('Change')

        self.msg_display = QPlainTextEdit()

        layout_left = QGridLayout()
        layout_left.addWidget(self.group, 0, 0, 1, 2)
        layout_left.addWidget(self.pbn_connect, 1, 0, 1, 1)
        layout_left.addWidget(self.pbn_disconnect, 1, 1, 1, 1)

        layout = QHBoxLayout()
        layout.addLayout(layout_left)
        layout.addSpacing(15)
        layout.addWidget(self.msg_display)
        self.setLayout(layout)



if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    main = View_Udp_Setting()
    main.show()
    sys.exit(app.exec_())

在这里插入图片描述

|- QSS的使用

QSS称为Qt Style Sheets也就是Qt样式表,它是Qt提供的一种用来自定义控件外观的机制。
对上面的UI外观进行一定的改造,使用到的QSS文件如下:

QGroupBox{
	background:qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 #e9e9e9, stop:1 #E1E1E1);
    border-radius:10px;
    font-family:Arial;
    font:12px;
	font:bold;
    margin-top: 6ex;}

QGroupBox::title{
    color: #2E579A;
    font-family:Arial;
    font:12px;
    subcontrol-origin: margin;
    subcontrol-position: top center;}
	
QLabel{
	font-family: Arial;
}

QLineEdit{
	font-family: Arial;
}

QPlainTextEdit{
	font-family: Arial;
}

QPushButton{
	font-family: Arial;
}

通过下面的命令,导入对应的QSS样式

        with open('demo_style.qss','r',encoding='utf-8') as f:
            self.setStyleSheet(f.read())

最终的效果如下:
在这里插入图片描述

|- 打包resource

这部分之前笔记有记录,见下面的链接
https://blog.youkuaiyun.com/qq_26915769/article/details/84535106

MCV基础

UDP通讯业务得到的数据要在界面上显示,而不同的子界面可能需要显示相同的数据,而当其中一个子界面做了数据更新(例如修改了UDP的ip地址),其他子界面又需要显示这个信息时,利用item-based这种方法就出现了短板,需要使用大量的signal和slot,放弃。
关于MCV的设计方法,这里推荐youtube的视频:
https://www.youtube.com/watch?v=mCHVI8OXDxw&list=PL8B63F2091D787896

TreeNode & TreeModel

UDP解析后的数据拟采用树结构存放起来,因此,按照上面的链接制作一个树结构的类TreeNode和对应的抽象的TreeModel。

Controller编写

将View和Model相结合,这里仅是展示,因此,没有用到MCV的知识,所用到的文件是前面的View_Udp_Setting.py和
【未完】

总结

写博客真累
【百度网盘链接】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值