PyQt實現一個簡單的License系統(二)
本文接著上一篇繼續講解“PyQt實現一個簡單的License系統”,主要包括:
3)如何用python建立一個GUI。
4)python如何調C DLL庫。
5)ctypes中型別處理。
上一篇文章只是簡單的將ui檔案轉換為py檔案,並執行,生成了一個原始的GUI。本文將在這個基礎上,運用python編碼豐富這個GUI。
一、介面修飾
我們希望在介面生成的時候,自動獲取系統時間,並將它轉換為合適的格式,填充到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”的封裝。