PyQt GUI程式設計——猜數字
本文通過PyQt GUI實現一個簡單的小遊戲,猜數字。
QTDesinger 和PyUIC的安裝和使用參見前文《從零開始 使用PyQt5》
一、 在QTDesigner中完成介面設計
0A0B 4個Label 顯示當前猜測結果中,有幾個數字和位置都正確(A),幾個僅數字正確(B);
0 Times 顯示目前為止本局已經猜了多少次;
中間四個 LineEdit 是玩家輸入數字區;
輸入區下面 Label 顯示對使用者的提示;
Guess按鈕 判決猜數字結果。
二、 使用PyUIC將上面的介面轉換為py檔案
# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'GuessNumberMainWindow11.ui' # # Created by: PyQt5 UI code generator 5.11.2 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(519, 343) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget) self.horizontalLayoutWidget.setGeometry(QtCore.QRect(120, 130, 291, 71)) self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget) self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setObjectName("horizontalLayout") self.lineEdit = QtWidgets.QLineEdit(self.horizontalLayoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lineEdit.sizePolicy().hasHeightForWidth()) self.lineEdit.setSizePolicy(sizePolicy) self.lineEdit.setFocusPolicy(QtCore.Qt.ClickFocus) self.lineEdit.setObjectName("lineEdit") self.horizontalLayout.addWidget(self.lineEdit) self.lineEdit_2 = QtWidgets.QLineEdit(self.horizontalLayoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lineEdit_2.sizePolicy().hasHeightForWidth()) self.lineEdit_2.setSizePolicy(sizePolicy) self.lineEdit_2.setObjectName("lineEdit_2") self.horizontalLayout.addWidget(self.lineEdit_2) self.lineEdit_3 = QtWidgets.QLineEdit(self.horizontalLayoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lineEdit_3.sizePolicy().hasHeightForWidth()) self.lineEdit_3.setSizePolicy(sizePolicy) self.lineEdit_3.setObjectName("lineEdit_3") self.horizontalLayout.addWidget(self.lineEdit_3) self.lineEdit_4 = QtWidgets.QLineEdit(self.horizontalLayoutWidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lineEdit_4.sizePolicy().hasHeightForWidth()) self.lineEdit_4.setSizePolicy(sizePolicy) self.lineEdit_4.setObjectName("lineEdit_4") self.horizontalLayout.addWidget(self.lineEdit_4) self.pushButton = QtWidgets.QPushButton(self.centralwidget) self.pushButton.setGeometry(QtCore.QRect(220, 250, 75, 23)) self.pushButton.setObjectName("pushButton") self.horizontalLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget) self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(210, 80, 121, 31)) self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2") self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2) self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.label_2 = QtWidgets.QLabel(self.horizontalLayoutWidget_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) self.label_2.setSizePolicy(sizePolicy) self.label_2.setAlignment(QtCore.Qt.AlignCenter) self.label_2.setObjectName("label_2") self.horizontalLayout_2.addWidget(self.label_2) self.label = QtWidgets.QLabel(self.horizontalLayoutWidget_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) self.label.setSizePolicy(sizePolicy) self.label.setAlignment(QtCore.Qt.AlignCenter) self.label.setObjectName("label") self.horizontalLayout_2.addWidget(self.label) self.label_3 = QtWidgets.QLabel(self.horizontalLayoutWidget_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) self.label_3.setSizePolicy(sizePolicy) self.label_3.setAlignment(QtCore.Qt.AlignCenter) self.label_3.setObjectName("label_3") self.horizontalLayout_2.addWidget(self.label_3) self.label_4 = QtWidgets.QLabel(self.horizontalLayoutWidget_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) self.label_4.setSizePolicy(sizePolicy) self.label_4.setAlignment(QtCore.Qt.AlignCenter) self.label_4.setObjectName("label_4") self.horizontalLayout_2.addWidget(self.label_4) self.label_5 = QtWidgets.QLabel(self.centralwidget) self.label_5.setGeometry(QtCore.QRect(-300, 400, 54, 12)) self.label_5.setObjectName("label_5") self.label_hints = QtWidgets.QLabel(self.centralwidget) self.label_hints.setGeometry(QtCore.QRect(160, 220, 211, 16)) self.label_hints.setAlignment(QtCore.Qt.AlignCenter) self.label_hints.setObjectName("label_hints") self.horizontalLayoutWidget_3 = QtWidgets.QWidget(self.centralwidget) self.horizontalLayoutWidget_3.setGeometry(QtCore.QRect(420, 150, 131, 31)) self.horizontalLayoutWidget_3.setObjectName("horizontalLayoutWidget_3") self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_3) self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.label_guesstimes = QtWidgets.QLabel(self.horizontalLayoutWidget_3) self.label_guesstimes.setAlignment(QtCore.Qt.AlignCenter) self.label_guesstimes.setObjectName("label_guesstimes") self.horizontalLayout_3.addWidget(self.label_guesstimes) self.label_7 = QtWidgets.QLabel(self.horizontalLayoutWidget_3) self.label_7.setObjectName("label_7") self.horizontalLayout_3.addWidget(self.label_7) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 519, 23)) self.menubar.setObjectName("menubar") self.menu = QtWidgets.QMenu(self.menubar) self.menu.setObjectName("menu") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.actionshuoming = QtWidgets.QAction(MainWindow) self.actionshuoming.setObjectName("actionshuoming") self.actionhistroy = QtWidgets.QAction(MainWindow) self.actionhistroy.setObjectName("actionhistroy") self.menu.addSeparator() self.menu.addAction(self.actionshuoming) self.menu.addSeparator() self.menu.addAction(self.actionhistroy) self.menu.addSeparator() self.menubar.addAction(self.menu.menuAction()) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "GuessNumber")) self.pushButton.setText(_translate("MainWindow", "Guess")) self.label_2.setText(_translate("MainWindow", "0")) self.label.setText(_translate("MainWindow", "A")) self.label_3.setText(_translate("MainWindow", "0")) self.label_4.setText(_translate("MainWindow", "B")) self.label_5.setText(_translate("MainWindow", "TextLabel")) self.label_hints.setText(_translate("MainWindow", "The number is waiting for you :)")) self.label_guesstimes.setText(_translate("MainWindow", "0")) self.label_7.setText(_translate("MainWindow", "Times")) self.menu.setTitle(_translate("MainWindow", "Menu")) self.actionshuoming.setText(_translate("MainWindow", "Rules")) self.actionhistroy.setText(_translate("MainWindow", "Histroy"))
三、 啟動遊戲介面
新建Guess.py如下,from GuessNumberMainWindow import Ui_MainWindow匯入剛才生成的遊戲介面。
# -*- coding: utf-8 -*- """猜數字""" from PyQt5 import QtWidgets # 匯入PyQt5部件 import sys from GuessNumberMainWindow import Ui_MainWindow app = QtWidgets.QApplication(sys.argv) # 建立application物件 first_window = Ui_MainWindow() # 建立窗體物件 first_window.show() # 顯示窗體 sys.exit(app.exec()) # 執行程式
直接執行Guess.py,提示錯誤 AttributeError: 'Ui_MainWindow' object has no attribute 'show'
這是因為 第二步生成的GuessNumberMainWindow.py只定義了介面元素,沒有定義自己為主介面,也沒有初始化自己。
解決方法,修改GuessNumberMainWindow.py 如下,
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import *
class Ui_MainWindow(QMainWindow):
def __init__(self, parent=None):
super(Ui_MainWindow, self).__init__(parent)
self.setupUi(self)
self.retranslateUi(self)
def setupUi(self, MainWindow):
……
再次執行Guess.py,成功顯示遊戲介面
Good Job!
四、約束使用者輸入
猜數字遊戲中,使用者輸入的是0-9的整數,且每個LineEdit中只能輸入一個數字。
然後我們要把輸入的數字顯示的大一點並且在LineEdit中間位置,這樣看起來舒服一些。這項工作本來應該在QTDesigner中完成,不過在程式碼中也很簡單。
1)約束使用者只能輸入0-9的整數中的一個,使用QtGui.QIntValidator
int_validator = QtGui.QIntValidator() # validator for lineEdit
int_validator.setRange(0, 9)
self.lineEdit.setValidator(int_validator)
self.lineEdit.setMaxLength(1) # 只能輸入一個數字
2)輸入的數字顯示的大一點(QtGui.QFont),並顯示在LineEdit中間位置
lineEdit_font = QtGui.QFont()
lineEdit_font.setPixelSize(24)
self.lineEdit.setFont(lineEdit_font)
self.lineEdit.setAlignment(QtCore.Qt.AlignCenter) #數字顯示在LineEdit中間位置
以上程式碼新增到GuessNumberMainWindow.py的retranslateUi方法中。
四個LineEdit全部如此處理。執行Guess.py 輸入數字,介面如下
五、 Guess按鈕被點選後,首先進一步校驗使用者輸入
猜數字遊戲中,使用者輸入的四個數字中不能出現重複,這種情況下判為無效,要求使用者重新輸入。
修改GuessNumberMainWindow.py檔案
1)定義校驗函式
def judge_guess(self):
# 數字依次加入陣列(連結串列)
print('judge_guess')
array = [0, 0, 0, 0]
array[0] = int(self.lineEdit.text())
array[1] = int(self.lineEdit_2.text())
array[2] = int(self.lineEdit_3.text())
array[3] = int(self.lineEdit_4.text())
# 去除陣列中的相同元素,根據資料長度判斷是否存在相同數字
duplicated_array = list(set(array))
# print(duplicated_array, duplicated_array.__len__())
if duplicated_array.__len__() != array.__len__():
# 重複數字的輸入不判斷猜測結果,提示使用者重新輸入
self.label_hints.setText('Do not input duplicated numbers')
self.label_hints.setStyleSheet("color:red")
2)繫結Guess按鈕的clicked訊號到judge_guess方法
def signal_slot(self, MainWindow):
self.pushButton.clicked.connect(self.judge_guess)
初始化函式中進行訊號槽繫結
def __init__(self, parent=None):
super(Ui_MainWindow, self).__init__(parent)
self.setupUi(self)
self.retranslateUi(self)
generate_aim_array() # 生成目標資料
self.signal_slot(self)
執行Guess.py 輸入相同數字並點選Guess按鈕,介面如下
六、 完成數字比較
新建Rules.py檔案實現如下功能
1)自動生成一組四個不同的數字作為目標。
2)比較使用者輸入的數字和目標數字,得出xAyB的匹配結果,有幾個數字和位置都正確(A),幾個僅數字正確(B);
# -*- coding: utf-8 -*-
import random
global aim_array, a_num, b_num
aim_array = [0, 0, 0, 0]
a_num = 0
b_num = 0
def generate_aim_array():
global aim_array, a_num, b_num
aim_array = random.sample(range(0, 9), 4) # 0-9之間隨機生成四個不相同的數
a_num = 0
b_num = 0
print(aim_array)
def compare_and_judge(array):
# print('compare_and_judge')
global a_num
global b_num
# print('range(len(aim_array):', range(len(aim_array)))
# print('range(len(array):', range(len(array)))
for aim_array_index in range(len(aim_array)):
for array_index in range(len(array)):
# print('aim_array:', aim_array_index, aim_array[aim_array_index])
# print('array:', array_index, array[array_index])
if aim_array[aim_array_index] == array[array_index]:
if aim_array_index == array_index:
a_num = a_num + 1
else:
b_num = b_num + 1
break
# print('a_num = ', a_num, 'b_num = ', b_num)
def getA():
global a_num
return a_num
def setA(a):
global a_num
a_num = a
def getB():
global b_num
return b_num
def setB(b):
global b_num
b_num = b
七、 完成遊戲
修改GuessNumberMainWindow.py檔案
1)第一次進入遊戲時自動生成一組四個不同的數字作為目標。
def __init__(self, parent=None):
……
self.retranslateUi(self)
generate_aim_array() # 生成目標數字
self.signal_slot(self)
2)點選Guess按鈕時判斷結果,當比較結果為4A0B時使用者輸入數字完全正確,本局遊戲結束,Guess按鈕上的文字變為New Game,玩家點選按鈕可以開始下一局遊戲。如果比較結果不為4A0B,玩家繼續猜數字,直到結果正確。
def judge_guess(self):
……
if duplicated_array.__len__() != array.__len__():
……
else:
# 統計本局猜測的次數
self.label_guesstimes.setText(str(int(self.label_guesstimes.text()) + 1))
self.label_hints.setStyleSheet("color:black")
# 判斷數字是否與結果相同,並給出對應提示
compare_and_judge(array)
self.label_2.setText(str(getA()))
self.label_3.setText(str(getB()))
if getA() == 4: # 所有數字及位置都正確
self.label_hints.setText('Good Job!')
self.pushButton.setText('New Game')
self.pushButton.disconnect()
self.pushButton.clicked.connect(self.new_guess)
else:
self.label_hints.setText('Come on,you can make it!')
setA(0)
setB(0)
def new_guess(self):
generate_aim_array()
self.pushButton.setText('Guess')
self.pushButton.disconnect()
self.pushButton.clicked.connect(self.judge_guess)
self.label_guesstimes.setText('0')
self.label_2.setText('0')
self.label_3.setText('0')
self.lineEdit.clear()
self.lineEdit_2.clear()
self.lineEdit_3.clear()
self.lineEdit_4.clear()
3)顯示玩家目前為止猜測的次數。程式碼包含在第二步。
到此遊戲就實現完畢了,執行Guess.py,玩一下吧。
八、一點點小優化
玩遊戲的時候發現,每次輸入數字的時候都要滑鼠去點一下對應方框才行,不大舒適。我們把它改成輸入一個數字後,游標自動跳轉到下一個EditLine,這樣玩家就可以連續輸入數字了。
修改GuessNumberMainWindow.py檔案
1)定義游標跳轉方法
def move_cursor(self):
if self.sender() == self.lineEdit:
self.lineEdit_2.setFocus()
elif self.sender() == self.lineEdit_2:
self.lineEdit_3.setFocus()
elif self.sender() == self.lineEdit_3:
self.lineEdit_4.setFocus()
2)繫結editLine輸入內容改變(訊號)與move_cursor(槽)
def signal_slot(self, MainWindow):
……
self.lineEdit.textEdited.connect(self.move_cursor)
self.lineEdit_2.textEdited.connect(self.move_cursor)
self.lineEdit_3.textEdited.connect(self.move_cursor)
-------------------------------------------------------------------------------------------------------------------------
That's all.Thank you!