欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

PyQt实现一个简单的License系统(二)

程序员文章站 2022-06-17 14:30:07
1)PyQt、WinPython的安装、配置。2)如何用PyQt编译QDesigner生成的*.ui文件。3)如何用python创建一个GUI。4)python如何调C DLL库。5)ctypes中类型处理。6)如何用VS调试被python调用的DLL库。7)如何设置VS为python的IDE。8)如何在C++中嵌入python。9)如何构建和优化高效python代码。...

    本文接着上一篇继续讲解“PyQt实现一个简单的License系统”,主要包括:

3)如何用python创建一个GUI。

4)python如何调C DLL库。

5)ctypes中类型处理。


    上一篇文章只是简单的将ui文件转换为py文件,并执行,生成了一个原始的GUI。本文将在这个基础上,运用python编码丰富这个GUI。

PyQt实现一个简单的License系统(二)


一、界面修饰

    我们希望在界面生成的时候,自动获取系统时间,并将它转换为合适的格式,填充到GUI的控件:StartDate、DueDate和CurrentDate栏。实现方法如下:

1)导入时间库

在mainwindow.py的顶部相关位置添加一行代码

from datetime import date
from datetime import timedelta

2)获取系统时间,并显示到控件

在mainwindow.py文件的“setupUi”函数中添加如下代码:

	#datetime today
        currDate = date.today().strftime('%Y/%m/%d')
        dueDate = (date.today() + timedelta(days=60)).strftime('%Y/%m/%d')
        self.lineEdit_6.setText(currDate)
        self.lineEdit_7.setText(dueDate)
        self.lineEdit_8.setText(currDate)


注意:python的库非常丰富,它分为标准库和外部库。date就是一个标准库,可以通过python的在线文档查看。

https://docs.python.org/3/library/functions.html#bytearray


二、添加控件响应

    我们需要分别添加“Preview”、“Encrypt”和“Decrypt”三个按钮的响应函数。

1)在mainwindow.py文件的“setupUi”函数中(尾部)添加如下代码:

        self.retranslateUi(MainWindow)
        self.btnPreview.clicked.connect(self.showplaintext)
        self.btnEncrypt.clicked.connect(self.showencryptresult)
        self.btnDecrypt.clicked.connect(self.showerecovertext)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    如上所示:第一行和最后一行是PyQt生成的代码,我们实际添加的是中间那三行。

另外需要注意的是:connect函数也可以在mainwindow.py文件外指定。


2)定义对应的函数

    上一步绑定了三个控件响应函数(等同与Qt中的connect),这一步就是要具体定义这个三个响应函数。

在mainwindow.py文件的class Ui_MainWindow(object)中添加如下代码块:

    def showplaintext(self):
        print("preview")

		
    def showencryptresult(self):
        print("encrypt")
  
  
    def showerecovertext(self):
        print("decrypt")

这个就是python中类的成员函数的典型定义方式,需要带“self”参数,相当于C++中的this。

关于python的类和函数,也可以查看python文档库:

https://docs.python.org/3.6/tutorial/classes.html


三、GUI交互

    上面只是建立了基本的程序框架,还没有具体细节。我们先来实现“showplainttext”函数:

    def showplaintext(self):
        print("preview")
        listLabel = [self.label_1, self.label_2, self.label_3, self.label_4,\
        self.label_5, self.label_6, self.label_7, self.label_8]
        listLineEdit = [self.lineEdit_1,self.lineEdit_2,self.lineEdit_3,self.lineEdit_4,\
        self.lineEdit_5,self.lineEdit_6,self.lineEdit_7,self.lineEdit_8]
        
        head = "f0f0,"
        strPlain = head
        
        for n in range(8):
        	strPlain = strPlain + listLabel[n].text();
        	strPlain = strPlain + listLineEdit[n].text();
        	strPlain = strPlain + ',';
        
        strPlain = strPlain + "0f0f";
        self.plaintext.setPlainText(strPlain)

这段代码演示了python的列表(list)、for循环、字符串。


四、python调DLL

    由于“LicenseSystem”要用到加密算法,而这个加密算法是一个外部的C++实现的DLL库,事实上,我对它进行了封装,导出两个函数:一个加密,一个解密。也因此,我们要用到“python调DLL”的技术。

    “python调DLL”的几种方式中,我推荐用“ctypes”库。参考文档:

    https://docs.python.org/3/library/ctypes.html

此外,也可以参考博客:https://zhuanlan.zhihu.com/p/20152309

                                        http://blog.csdn.net/magictong/article/details/3075478

                                        http://blog.csdn.net/magictong/article/details/3075478

    有了“ctypes”,在python中加载DLL库还是比较简单的,难点在于“参数和返回值”的类型转换。在此,我折腾了好几个小时,才把我需要的类型完全正确转换完。目前还没有很深的理解,故不展开说了,请大家仔细研究我上面给出的“ctypes”文档和google。我在此仅贴出代码,以供参考:

    def showencryptresult(self):
        print("encrypt")
        #lib = ctypes.WinDLL("EncryptorDll.dll")
        lib = CDLL("EncryptorDll.dll")
        plainText = bytes(self.plaintext.toPlainText(), encoding = "utf-8")
        cPlainText = c_char_p(plainText)
        fileName = self.lineEdit_3.text() + ".license"
        cFileName = c_wchar_p(fileName)
        bufKey = c_char_p(b"\x0A\x0B\x0C\x0D\x0E\x0F\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10")
        bufIV = c_char_p(b"\x0F\x0E\x0D\x0C\x0B\x0A\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01")
        #ret = lib.EncryptString2File(byref(cPlainText), byref(cFileName), byref(bufKey), byref(bufIV), 16)
        ret = lib.EncryptString2File(cPlainText, cFileName, bufKey, bufIV, 16)
        if 1 == ret:
            self.ciphertext.setPlainText("Encrypt succeed!")
        else:
            self.ciphertext.setPlainText("Encrypt failed!")
        
          
    def showerecovertext(self):
        print("decrypt")
        lib = CDLL("EncryptorDll.dll")
        fileName = self.lineEdit_3.text() + ".license"
        filesize = os.path.getsize(fileName)
        print(filesize)
        recover = create_string_buffer(b'\0'*filesize)
        print(recover)
        cRecover = c_char_p(recover.value)
        cFileName = c_wchar_p(fileName)
        bufKey = c_char_p(b"\x0A\x0B\x0C\x0D\x0E\x0F\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10")
        bufIV = c_char_p(b"\x0F\x0E\x0D\x0C\x0B\x0A\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01")
        ret = lib.DecryptFile2String(cFileName, cRecover, bufKey, bufIV, 16)
        if 1 == ret:
            self.recovertext.setPlainText(str(cRecover.value, encoding = "utf-8"))
        else:
            self.recovertext.setPlainText("Encrypt failed!")

注:CDLL对应“cdecl”,而WinDLL对应“stdcall”

被调的C函数原型为:

        bool(*pfnEncrypt)(const char *, const wchar_t *, unsigned char *, unsigned char *, int) = 
            (bool(*)(const char *, const wchar_t *, unsigned char *, unsigned char *, int))m_library.resolve("EncryptString2File");

        bool(*pfnDecrypt)(const wchar_t *, char *, unsigned char *, unsigned char *, int) =
            (bool(*)(const wchar_t *, char *, unsigned char *, unsigned char *, int))m_library.resolve("DecryptFile2String");

    上面的代码共涉及到如下几种类型转换:

1)QString或string转char数组(bytes),然后转char指针

        plainText = bytes(self.plaintext.toPlainText(), encoding = "utf-8")
        cPlainText = c_char_p(plainText)
2)QString或string转wchar_t指针

        fileName = self.lineEdit_3.text() + ".license"
        cFileName = c_wchar_p(fileName)

比较上面两条可知:python默认的str是Unicode编码,即宽字符编码(wchar_t)

3)以0结尾的字符串常量

        bufKey = c_char_p(b"\x0A\x0B\x0C\x0D\x0E\x0F\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10")

注意,括号里的“b”是将字符串指定为char字符(字节字符),而默认是Unicode字符。


    也有一种说法:

    这里的“b”指的是“binary”(二进制),也就是说,python中的bytes object是以二进制的形式保存的。而str是以“文本”形式保存的,即“Unicode”。

4)返回值bool

        if 1 == ret:
            self.ciphertext.setPlainText("Encrypt succeed!")
        else:
            self.ciphertext.setPlainText("Encrypt failed!")


五、运行

    添加完上述代码,再将加密库“EncryptorDll.dll”文件与“mainwindow.py”文件放在同一个目录下,就已经是一个完整的“LicenseSystem”软件了。

    同前一篇文章,在cmd中,用python执行“mainwindow.py”,即可启动该软件。


六、软件架构

    尽管这个已经是一个完整的软件了,但是从架构上来说,它并不是很好。我们是直接在“mainwindow.py”文件中实现这个功能的,如果后续需要修改界面,即修改“mainwindow.ui”文件,我们又需要重新编译生成新的“mainwindow.py”文件,然后再合并“mainwindow.py”文件。这样显得很麻烦!

    更好的办法是:保持“mainwindow.py”文件不变,我们将功能在另外一个文件中实现。

    这里我新建了一个“start.py”文件

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from mainwindow import Ui_MainWindow
 
class LicenseGui(Ui_MainWindow):
	def __init__(self, mainwindow):
		Ui_MainWindow.__init__(self)
		self.setupUi(mainwindow)
 
		# Connect "add" button with a custom function (addInputTextToListbox)
		#self.addBtn.clicked.connect(self.addInputTextToListbox)
	'''
	def addInputTextToListbox(self):
		txt = self.myTextInput.text()
		self.listWidget.addItem(txt)
	'''
if __name__ == '__main__':
	app = QtWidgets.QApplication(sys.argv)
	mainwindow = QtWidgets.QMainWindow()
 
	prog = LicenseGui(mainwindow)
 
	mainwindow.show()
	sys.exit(app.exec_())

    我们可以将其他功能在这个文件中实现,而保存“mainwindow.py”的原生态。然后,在程序执行的时候,从这个文件启动。

    从上面的程序可以看出:类LicenseGui实际上是对“mainwindow.py”中原生态的类“Ui_MainWindow”的封装。


本文地址:https://blog.csdn.net/Sagittarius_Warrior/article/details/72831039