39、国际化与网络编程:PyQt 应用开发指南

国际化与网络编程:PyQt 应用开发指南

1. 国际化设置

1.1 翻译工具使用

可以通过以下两种方式开启翻译功能:
- 运行 mkpyqt.py 时添加 -t (translate)选项。
- 运行 Make PyQt 并勾选 Translate 复选框。

开启翻译后,这两个工具会依次运行 pylupdate4 lrelease

1.2 翻译工作流程

将 Qt Linguist 应用程序和 .ts 文件交给翻译人员,让他们对字符串进行翻译。Qt Linguist 易于使用,能通过建议之前翻译过的类似短语来减少重复劳动,还会按上下文(通常是窗口类名)对翻译字符串进行分组。

使用 Qt Linguist 的具体步骤如下:
1. 运行 Qt Linguist,点击 File -> Open ,打开 .ts 文件。
2. 点击左侧 Context 停靠窗口中的 + 符号,显示上下文中的字符串,然后点击其中一个字符串。该字符串会出现在右上角面板的 Source text 标题下。
3. 点击 Translation 标题下的区域,输入翻译内容。
4. 点击 Context 停靠窗口中相关字符串旁边的问号图标,确认该字符串翻译完成。点击图标会在问号和对勾之间切换,打勾的翻译表示已完成, lrelease 会将其放入 .qm 文件中。

1.3 代码中的国际化处理

  • 字符串处理 :确保每个用户可见的字符串使用 QObject.tr() QApplication.translate() 。对于有可替换参数的字符串,应使用 QString.arg() 及其编号 %n 参数,而不是 Python 的 % 运算符。
  • 数字处理 :对于数字,可能需要使用 %Ln 以获得正确的千位和小数点分隔符。
  • 货币符号处理 :可以使用以下代码处理货币符号:
currency = QApplication.translate("Currency", "$")

"$" 翻译为合适的符号,如 "€" "£" "¥" 等。
- 日期处理 :对于日期,可以使用 QDate.toString(Qt.SystemLocaleDate) QDate.toString(Qt.ISODate)
- 单位处理 :对于计量单位,最好提供一个合理的默认值,让用户可以通过配置对话框进行更改,或者在首次运行时让用户选择单位、默认纸张大小等。

1.4 总结

  • 创建基于 HTML 的在线帮助系统相对简单,可以使用 QTextBrowser QDesktopServices.openUrl() 。而创建使用 Qt Assistant 的系统则较难设置。
  • 设置应用程序进行翻译较为直接。通常使用 .pro 文件列出 .ts 文件以及包含用户可见字符串的 .ui .py .pyw 文件,并使用 pylupdate4 lrelease 来更新 .ts 文件并生成 .qm 文件。也可以通过生成初始 .ts 文件,然后使用 mkpyqt.py Make PyQt 来避免使用 .pro 文件。

2. 网络编程基础

2.1 Python 标准库的网络支持

Python 标准库有许多提供网络功能的模块,例如:
- urllib2.urlopen() 可用于提供 Internet 文件的“文件句柄”,然后使用 for line in fh: 逐行读取文件。
- 可以使用 urllib.urlretrieve() 从 Internet 下载整个文件:

source = "http://www.amk.ca/files/python/crypto/" + \
         "pycrypto-2.0.1.tar.gz"
target = source[source.rfind("/") + 1:]
name, message = urllib.urlretrieve(source, target)

urllib urllib2 模块功能强大,支持 FTP 和 HTTP 协议,后者可使用 GET POST 请求,还能使用 HTTP 代理。 urllib2 支持基本身份验证并可设置 HTTP 头。如果 Python 安装了 SSL 支持, urllib2 还能使用 HTTPS 协议。标准库还支持许多其他网络协议,如 IMAP4、POP3、SMTP 用于电子邮件,NNTP 用于网络新闻,以及处理 cookie、XML - RPC、CGI 和创建服务器的库。大多数 Python 的网络支持基于 socket 模块,可直接用于底层网络编程。

2.2 PyQt4 的网络类

除了 Python 标准库,PyQt4 提供了自己的网络类,包括用于客户端 FTP 支持的 QFtp 和用于 HTTP 支持的 QHttp 。底层网络编程可以使用 QAbstractSocket 的子类,如 QTcpSocket QTcpServer QUdpSocket ,从 Qt 4.3 开始还支持 QSslSocket

2.3 UDP 和 TCP 协议

PyQt 提供两种不同类型的套接字:
- UDP(User Datagram Protocol) :由 QUdpSocket 类支持。UDP 轻量级但不可靠,不保证数据能被接收,是无连接的,数据以离散项的形式发送或接收。常用于监控连续读数的仪器,偶尔丢失读数影响不大。
- TCP(Transmission Control Protocol) :由 QTcpSocket 类支持。TCP 是可靠的面向连接和流的协议,可发送和接收任意数量的数据,套接字负责将数据拆分成足够小的块进行发送,并在另一端重新组装数据。客户端/服务器应用程序通常使用 TCP,因为它们需要可靠性。

2.4 示例应用:Building Services

我们将使用 Building Services 应用作为示例,该应用的服务器存储建筑物中房间的详细信息和预订日期,客户端用于预订和取消特定房间的特定日期。服务器和客户端运行在同一台机器上,使用 localhost 作为 IP 地址,端口号为 9407(端口号应大于 1023,通常在 5001 到 32767 之间,最大为 65535)。服务器可以接受两种请求: "BOOK" "UNBOOK" ,并可以做出三种响应: "BOOK" "UNBOOK" "ERROR" 。所有请求和响应都以二进制数据形式发送和接收。

以下是相关变量的设置:

from PyQt4.QtNetwork import *
PORT = 9407
SIZEOF_UINT16 = 2

2.5 客户端实现

2.5.1 客户端类初始化
class BuildingServicesClient(QWidget):
    def __init__(self, parent=None):
        super(BuildingServicesClient, self).__init__(parent)
        self.socket = QTcpSocket()
        self.nextBlockSize = 0
        self.request = None

这里我们创建了一个 QTcpSocket 对象用于与服务器通信, nextBlockSize 用于确定是否接收到足够的响应数据以处理响应, request 是一个包含请求数据的 QByteArray 对象,若没有数据要发送则为 None

2.5.2 信号与槽连接
self.connect(self.socket, SIGNAL("connected()"),
             self.sendRequest)
self.connect(self.socket, SIGNAL("readyRead()"),
             self.readResponse)
self.connect(self.socket, SIGNAL("disconnected()"),
             self.serverHasStopped)
self.connect(self.socket,
             SIGNAL("error(QAbstractSocket::SocketError)"),
             self.serverHasError)

self.connect(self.roomEdit, SIGNAL("textEdited(QString)"),
             self.updateUi)
self.connect(self.dateEdit, SIGNAL("dateChanged(QDate)"),
             self.updateUi)
self.connect(self.bookButton, SIGNAL("clicked()"),
             self.book)
self.connect(self.unBookButton, SIGNAL("clicked()"),
             self.unBook)
self.connect(quitButton, SIGNAL("clicked()"), self.close)

前四个信号与套接字相关,用于处理连接建立、数据读取、连接断开和错误情况。其他连接与用户界面相关,用于验证输入、启用/禁用按钮、预订和取消预订房间以及关闭应用程序。

2.5.3 更新用户界面
def updateUi(self):
    enabled = False
    if not self.roomEdit.text().isEmpty() and \
            self.dateEdit.date() > QDate.currentDate():
        enabled = True
    if self.request is not None:
        enabled = False
    self.bookButton.setEnabled(enabled)
    self.unBookButton.setEnabled(enabled)

如果房间编辑框有房间号且日期编辑框的日期晚于今天,则启用预订和取消预订按钮;如果有未处理的请求,则禁用这些按钮。

2.5.4 关闭事件处理
def closeEvent(self, event):
    self.socket.close()
    event.accept()

当应用程序关闭时,关闭套接字并接受关闭事件。

2.5.5 预订和取消预订方法
def book(self):
    self.issueRequest(QString("BOOK"), self.roomEdit.text(),
                      self.dateEdit.date())

def unBook(self):
    self.issueRequest(QString("UNBOOK"), self.roomEdit.text(),
                      self.dateEdit.date())

点击 Book Unbook 按钮时,调用 issueRequest() 方法发送相应请求。

2.5.6 准备请求并连接服务器
def issueRequest(self, action, room, date):
    self.request = QByteArray()
    stream = QDataStream(self.request, QIODevice.WriteOnly)
    stream.setVersion(QDataStream.Qt_4_2)
    stream.writeUInt16(0)
    stream << action << room << date
    stream.device().seek(0)
    stream.writeUInt16(self.request.size() - SIZEOF_UINT16)
    self.updateUi()
    if self.socket.isOpen():
        self.socket.close()
    self.responseLabel.setText("Connecting to server...")
    self.socket.connectToHost("localhost", PORT)

该方法准备请求字节数组,更新用户界面,关闭可能已打开的套接字,设置响应标签以告知用户正在尝试建立连接,然后调用 connectToHost() 连接到服务器。

2.5.7 发送请求
def sendRequest(self):
    self.responseLabel.setText("Sending request...")
    self.nextBlockSize = 0
    self.socket.write(self.request)
    self.request = None

连接建立后,该方法更新响应标签,设置下一个块大小为 0,将请求字节数组写入套接字,并将请求对象设置为 None ,以便准备新的请求。

2.5.8 读取响应
def readResponse(self):
    stream = QDataStream(self.socket)
    stream.setVersion(QDataStream.Qt_4_2)
    while True:
        if self.nextBlockSize == 0:
            if self.socket.bytesAvailable() < SIZEOF_UINT16:
                break
            self.nextBlockSize = stream.readUInt16()
        if self.socket.bytesAvailable() < self.nextBlockSize:
            break
        action = QString()
        room = QString()
        date = QDate()
        stream >> action >> room
        if action != "ERROR":
            stream >> date
        if action == "ERROR":
            msg = QString("Error: %1").arg(room)
        elif action == "BOOK":
            msg = QString("Booked room %1 for %2").arg(room) \
               .arg(date.toString(Qt.ISODate))
        elif action == "UNBOOK":
            msg = QString("Unbooked room %1 for %2").arg(room) \
               .arg(date.toString(Qt.ISODate))
        self.responseLabel.setText(msg)
        self.updateUi()
        self.nextBlockSize = 0

由于服务器的响应可能会分段返回,因此使用无限循环来确保读取完整的响应。根据响应的类型,显示相应的消息,并更新用户界面。

2.5.9 处理服务器异常
def serverHasStopped(self):
    self.responseLabel.setText(
        "Error: Connection closed by server")
    self.socket.close()

def serverHasError(self, error):
    self.responseLabel.setText(QString("Error: %1") \
       .arg(self.socket.errorString()))
    self.socket.close()

当服务器终止或出现网络错误时,显示错误消息并关闭套接字。

2.6 客户端工作流程总结

以下是客户端的工作流程 mermaid 流程图:

graph TD;
    A[启动客户端] --> B[初始化套接字和变量];
    B --> C[等待用户输入];
    C --> D{用户点击 Book 或 Unbook};
    D -- 是 --> E[准备请求并连接服务器];
    E --> F{连接成功};
    F -- 是 --> G[发送请求];
    G --> H{收到响应};
    H -- 是 --> I[读取响应并显示结果];
    I --> C;
    F -- 否 --> J[显示错误信息];
    J --> C;
    H -- 否 --> K{连接断开或出错};
    K -- 是 --> J;
    K -- 否 --> H;
    D -- 否 --> C;

客户端的用户可以输入预订和取消预订请求,点击 Book Unbook 按钮将请求发送到服务器,并在响应标签中查看请求结果。请求通过将 QByteArray 写入合适设置的套接字发送,响应通过 QDataStream 从套接字读取,这使我们能够直接将 QStrings QDates 和其他支持数据流的类型读取到本地变量中。接下来我们将关注服务器的实现。

3. 服务器实现

3.1 服务器类初始化

class BuildingServicesServer(QTcpServer):
    def __init__(self, parent=None):
        super(BuildingServicesServer, self).__init__(parent)
        self.rooms = {}

这里创建了一个 QTcpServer 的子类, self.rooms 字典用于存储房间的预订信息,键为房间号,值为已预订的日期集合。

3.2 启动服务器

if __name__ == "__main__":
    app = QApplication(sys.argv)
    server = BuildingServicesServer()
    if not server.listen(QHostAddress("localhost"), PORT):
        print("Error: Could not start server")
        sys.exit(1)
    print("Server started on port %d" % PORT)
    sys.exit(app.exec_())

main 函数中,创建 QApplication 实例,启动服务器并监听指定的 IP 地址和端口号。如果启动失败,打印错误信息并退出程序;成功则打印启动信息。

3.3 处理新连接

def incomingConnection(self, socketDescriptor):
    socket = QTcpSocket()
    if not socket.setSocketDescriptor(socketDescriptor):
        return
    self.connect(socket, SIGNAL("readyRead()"),
                 lambda: self.readRequest(socket))
    self.connect(socket, SIGNAL("disconnected()"),
                 lambda: self.socketDisconnected(socket))

当有新的客户端连接时, incomingConnection 方法会被调用。创建一个 QTcpSocket 对象并设置其描述符,然后连接 readyRead disconnected 信号到相应的槽函数。

3.4 读取请求

def readRequest(self, socket):
    stream = QDataStream(socket)
    stream.setVersion(QDataStream.Qt_4_2)
    while True:
        if socket.bytesAvailable() < SIZEOF_UINT16:
            break
        nextBlockSize = stream.readUInt16()
        if socket.bytesAvailable() < nextBlockSize:
            break
        action = QString()
        room = QString()
        date = QDate()
        stream >> action >> room >> date
        if action == "BOOK":
            self.handleBookRequest(socket, room, date)
        elif action == "UNBOOK":
            self.handleUnbookRequest(socket, room, date)

使用 QDataStream 从套接字读取请求数据,根据请求的动作( BOOK UNBOOK )调用相应的处理函数。

3.5 处理预订请求

def handleBookRequest(self, socket, room, date):
    if room not in self.rooms:
        self.rooms[room] = set()
    if date in self.rooms[room]:
        self.sendErrorResponse(socket, "Room already booked on this date")
    else:
        self.rooms[room].add(date)
        self.sendSuccessResponse(socket, "BOOK", room, date)

如果房间不在 self.rooms 字典中,创建一个新的集合来存储该房间的预订日期。如果该日期已被预订,发送错误响应;否则,将日期添加到集合中并发送成功响应。

3.6 处理取消预订请求

def handleUnbookRequest(self, socket, room, date):
    if room in self.rooms and date in self.rooms[room]:
        self.rooms[room].remove(date)
        self.sendSuccessResponse(socket, "UNBOOK", room, date)
    else:
        self.sendErrorResponse(socket, "Room not booked on this date")

如果房间存在且该日期已被预订,从集合中移除该日期并发送成功响应;否则,发送错误响应。

3.7 发送成功响应

def sendSuccessResponse(self, socket, action, room, date):
    response = QByteArray()
    stream = QDataStream(response, QIODevice.WriteOnly)
    stream.setVersion(QDataStream.Qt_4_2)
    stream.writeUInt16(0)
    stream << action << room << date
    stream.device().seek(0)
    stream.writeUInt16(response.size() - SIZEOF_UINT16)
    socket.write(response)

准备成功响应的字节数组,写入响应数据,设置响应大小,然后将响应发送到客户端。

3.8 发送错误响应

def sendErrorResponse(self, socket, errorMessage):
    response = QByteArray()
    stream = QDataStream(response, QIODevice.WriteOnly)
    stream.setVersion(QDataStream.Qt_4_2)
    stream.writeUInt16(0)
    stream << QString("ERROR") << QString(errorMessage)
    stream.device().seek(0)
    stream.writeUInt16(response.size() - SIZEOF_UINT16)
    socket.write(response)

准备错误响应的字节数组,写入错误信息,设置响应大小,然后将响应发送到客户端。

3.9 处理客户端断开连接

def socketDisconnected(self, socket):
    socket.deleteLater()

当客户端断开连接时,调用 deleteLater 方法删除套接字对象。

3.10 服务器工作流程总结

以下是服务器的工作流程 mermaid 流程图:

graph TD;
    A[启动服务器] --> B[监听指定端口];
    B --> C{有新连接};
    C -- 是 --> D[处理新连接];
    D --> E{有数据可读};
    E -- 是 --> F[读取请求];
    F --> G{请求类型};
    G -- BOOK --> H[处理预订请求];
    G -- UNBOOK --> I[处理取消预订请求];
    H --> J{预订成功};
    J -- 是 --> K[发送成功响应];
    J -- 否 --> L[发送错误响应];
    I --> M{取消预订成功};
    M -- 是 --> K;
    M -- 否 --> L;
    K --> E;
    L --> E;
    E -- 否 --> N{连接断开};
    N -- 是 --> O[删除套接字];
    N -- 否 --> E;
    C -- 否 --> B;

服务器等待客户端的连接和请求,根据请求类型进行相应的处理,并将处理结果以二进制数据的形式发送回客户端。

4. 总结与建议

4.1 国际化总结

  • 国际化设置可以使应用程序支持多种语言,提高应用的通用性和用户体验。通过使用 pylupdate4 lrelease 工具以及 Qt Linguist 应用程序,可以方便地进行翻译工作。
  • 在代码中,要注意使用合适的方法处理字符串、数字、货币符号、日期和单位,确保在不同语言和地区的环境下都能正确显示。

4.2 网络编程总结

  • Python 标准库和 PyQt4 都提供了丰富的网络编程功能。在客户端/服务器应用程序中,TCP 协议因其可靠性而被广泛使用。
  • 通过 QTcpSocket QTcpServer 类,可以方便地实现简单的客户端/服务器应用程序。在实现过程中,要注意处理连接建立、数据读取、错误处理等情况。

4.3 建议

  • 国际化方面 :在开发应用程序时,尽早考虑国际化需求,将用户可见的字符串统一处理,方便后续的翻译工作。
  • 网络编程方面 :对于并发处理多个客户端请求的需求,可以考虑使用多线程或异步编程的方式,提高服务器的性能和响应能力。例如,在后续的开发中,可以将服务器改造成多线程版本,避免多个客户端请求冲突时的阻塞问题。

以下是一个总结表格:
| 类别 | 要点 |
| ---- | ---- |
| 国际化 | 使用 pylupdate4 lrelease 工具,Qt Linguist 进行翻译;代码中正确处理字符串、数字等 |
| 网络编程 | Python 标准库和 PyQt4 提供网络功能;TCP 协议用于客户端/服务器应用;使用 QTcpSocket QTcpServer 实现应用 |
| 建议 | 尽早考虑国际化;使用多线程或异步编程提高服务器性能 |

通过以上的国际化设置和网络编程实现,我们可以开发出功能丰富、用户友好且具有良好扩展性的 PyQt 应用程序。

需求响应动态冰蓄冷系统需求响应策略的优化研究(Matlab代码实现)内容概要:本文围绕需求响应动态冰蓄冷系统及其优化策略展开研究,结合Matlab代码实现,探讨了在电力需求侧管理背景下,冰蓄冷系统如何通过优化运行策略参需求响应,以实现削峰填谷、降低用电成本和提升能源利用效率的目标。研究内容包括系统建模、负荷预测、优化算法设计(如智能优化算法)以及多场景仿真验证,重点分析不同需求响应机制下系统的经济性和运行特性,并通过Matlab编程实现模型求解结果可视化,为实际工程应用提供理论支持和技术路径。; 适合人群:具备一定电力系统、能源工程或自动化背景的研究生、科研人员及从事综合能源系统优化工作的工程师;熟悉Matlab编程且对需求响应、储能优化等领域感兴趣的技术人员。; 使用场景及目标:①用于高校科研中关于冰蓄冷系统需求响应协同优化的课题研究;②支撑企业开展楼宇能源管理系统、智慧园区调度平台的设计仿真;③为政策制定者评估需求响应措施的有效性提供量化分析工具。; 阅读建议:建议读者结合文中Matlab代码逐段理解模型构建算法实现过程,重点关注目标函数设定、约束条件处理及优化结果分析部分,同时可拓展应用其他智能算法进行对比实验,加深对系统优化机制的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值