pyQT6实现画图小游戏,逻辑清晰,界面美观,非常NICE!
Inspired by PyQt5 Creating Paint Application In 40 Minutes灵感来自PyQt5在40分钟内创建油漆应用程序
https://www.youtube.com/watch?v=qEgyGyVA1ZQ
NB If the menus do not work then click on another application and then click back注意:如果菜单不工作,请单击另一个应用程序,然后单击返回
and they will work https://python-forum.io/Thread-Tkinter-macOS-Catalina-and-Python-menu-issue他们将工作
PyQt documentation links are prefixed with the word ‘documentation’ in the code below and can be accessed automatically
#在下面的代码中,PyQt文档链接的前缀是’documentation’,可以自动访问
in PyCharm using the following technique https://www.jetbrains.com/help/pycharm/inline-documentation.html
在PyCharm中使用以下技术
from PyQt6.QtWidgets import QApplication, QWidget, QMainWindow, QFileDialog, QDockWidget, QPushButton, QVBoxLayout,
QLabel, QMessageBox, QGroupBox, QRadioButton, QToolBar
from PyQt6.QtGui import QIcon, QPainter, QPen, QAction, QPixmap
import sys
import csv, random
from PyQt6.QtCore import Qt, QPoint
import tkinter as tk # 导入tkinter模块。为了方便后续讲解,命名为 tk。
import tkinter.messagebox # 引入弹窗库,防止解释器弹出报错。
from pyqt6_plugins.examplebutton import QtWidgets
class PictionaryGame(QMainWindow): # documentation https://doc.qt.io/qt-6/qwidget.html
‘’’
Painting Application class绘画应用类
‘’’
def __init__(self):
super().__init__()
# set window title设置窗口标题 图画猜字游戏- A2模板
self.setWindowTitle("Pictionary Game - A2 Template")
# set the windows dimensions设置窗口尺寸
top = 400
left = 400
width = 800
height = 600
self.setGeometry(top, left, width, height)
# set the icon#设置图标# # Windows版本
# windows version
self.setWindowIcon(
QIcon("./icons/paint-brush.png")) # documentation: https://doc.qt.io/qt-6/qwidget.html#windowIcon-prop
# mac version - not yet workingMAC版本-还不能工作
# self.setWindowIcon(QIcon(QPixmap("./icons/paint-brush.png")))
# image settings (default)图像设置(默认)
self.image = QPixmap("./icons/canvas.png") # documentation: https://doc.qt.io/qt-6/qpixmap.html
self.image.fill(Qt.GlobalColor.white) # documentation: https://doc.qt.io/qt-6/qpixmap.html#fill
mainWidget = QWidget()
mainWidget.setMaximumWidth(300)
# draw settings (default)绘制设置(默认)
self.drawing = False
self.brushSize = 3
self.brushColor = Qt.GlobalColor.black # documentation: https://doc.qt.io/qt-6/qt.html#GlobalColor-enum
# reference to last point recorded by mouse引用鼠标记录的最后一点
self.lastPoint = QPoint() # documentation: https://doc.qt.io/qt-6/qpoint.html
# set up menus设置菜单
mainMenu = self.menuBar() # create a menu bar创建一个菜单栏
mainMenu.setNativeMenuBar(False) #将文件菜单添加到菜单栏中,因为Mac中“文件”是保留的,所以需要这个空间
fileMenu = mainMenu.addMenu(" File") # add the file menu to the menu bar, the space is required as "File" is reserved in Mac
brushSizeMenu = mainMenu.addMenu(" Brush Size") # add the "Brush Size" menu to the menu bar添加“笔刷大小”菜单到菜单栏
brushColorMenu = mainMenu.addMenu(" Brush Colour") # add the "Brush Colour" menu to the menu bar添加“笔刷颜色”菜单到菜单栏
helpMenu = mainMenu.addMenu("Help")
# save menu item 保存菜单项 #创建一个以PNG作为图标的保存操作
saveAction = QAction(QIcon("./icons/save.png"), "Save", self) # create a save action with a png as an icon, documentation: https://doc.qt.io/qt-6/qaction.html
saveAction.setShortcut("Ctrl+S") # connect this save action to a keyboard shortcut, documentation: https://doc.qt.io/qt-6/qaction.html#shortcut-prop
#将此保存操作连接到键盘快捷方式
# open
openAction = QAction(QIcon("./icons/open.png"), "Open", self)
openAction.setShortcut("Ctrl+O")
fileMenu.addAction(openAction)
openAction.triggered.connect(self.open)
#将保存操作添加到文件菜单中
fileMenu.addAction(saveAction) # add the save action to the file menu, documentation: https://doc.qt.io/qt-6/qwidget.html#addAction
saveAction.triggered.connect(self.save) # when the menu option is selected or the shortcut is used the save slot is triggered, documentation: https://doc.qt.io/qt-6/qaction.html#triggered
#当选择菜单选项或使用快捷方式时,会触发保存槽
# clear
clearAction = QAction(QIcon("./icons/clear.png"), "Clear", self) # create a clear action with a png as an icon用PNG作为图标创建一个清晰的动作
clearAction.setShortcut("Ctrl+C") # connect this clear action to a keyboard shortcut将此清除操作连接到键盘快捷键
fileMenu.addAction(clearAction) # add this action to the file menu将此操作添加到文件菜单中
clearAction.triggered.connect(self.clear) # when the menu option is selected or the shortcut is used the clear slot is triggered
#当选择菜单选项或使用快捷方式时,将触发清除槽
# exit
exitAction = QAction(QIcon("./icons/exit.png"), "Exit", self)
exitAction.setShortcut("Ctrl+Q")
fileMenu.addAction(exitAction)
exitAction.triggered.connect(self.close)
# about
aboutAction = QAction(QIcon("./icons/about.png"), "about", self)
aboutAction.setShortcut("Ctrl+A")
helpMenu.addAction(aboutAction)
aboutAction.triggered.connect(self.show_message_about)
# help
helpAction = QAction(QIcon("./icons/help.png"), "help", self)
helpAction.setShortcut("Ctrl+H")
helpMenu.addAction(helpAction)
helpAction.triggered.connect(self.show_message_help)
# brush thickness刷厚度
threepxAction = QAction(QIcon("./icons/threepx.png"), "3px", self)
threepxAction.setShortcut("Ctrl+3")
brushSizeMenu.addAction(threepxAction) # connect the action to the function below将动作连接到下面的函数
threepxAction.triggered.connect(self.threepx)
fivepxAction = QAction(QIcon("./icons/fivepx.png"), "5px", self)
fivepxAction.setShortcut("Ctrl+5")
brushSizeMenu.addAction(fivepxAction)
fivepxAction.triggered.connect(self.fivepx)
sevenpxAction = QAction(QIcon("./icons/sevenpx.png"), "7px", self)
sevenpxAction.setShortcut("Ctrl+7")
brushSizeMenu.addAction(sevenpxAction)
sevenpxAction.triggered.connect(self.sevenpx)
ninepxAction = QAction(QIcon("./icons/ninepx.png"), "9px", self)
ninepxAction.setShortcut("Ctrl+9")
brushSizeMenu.addAction(ninepxAction)
ninepxAction.triggered.connect(self.ninepx)
# brush colors
blackAction = QAction(QIcon("./icons/black.png"), "Black", self)
blackAction.setShortcut("Ctrl+B")
brushColorMenu.addAction(blackAction);
blackAction.triggered.connect(self.black)
redAction = QAction(QIcon("./icons/red.png"), "Red", self)
redAction.setShortcut("Ctrl+R")
brushColorMenu.addAction(redAction);
redAction.triggered.connect(self.red)
greenAction = QAction(QIcon("./icons/green.png"), "Green", self)
greenAction.setShortcut("Ctrl+G")
brushColorMenu.addAction(greenAction);
greenAction.triggered.connect(self.green)
yellowAction = QAction(QIcon("./icons/yellow.png"), "Yellow", self)
yellowAction.setShortcut("Ctrl+Y")
brushColorMenu.addAction(yellowAction);
yellowAction.triggered.connect(self.yellow)
# Side Dock
self.dockInfo = QDockWidget()
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.dockInfo)
#widget inside the Dock码头内的小部件1
playerInfo = QWidget()
self.vbdock = QVBoxLayout()
playerInfo.setLayout(self.vbdock)
playerInfo.setMaximumSize(100, self.height())
self.player1 = 0
self.player2 = 0
self.getList("easy")
self.currentWord = self.getWord()
self.currentPlayer = 1
#add controls to custom widget向自定义小部件1添加控件
self.lab=QLabel("CurrentTurn:"+str(self.currentPlayer))
self.vbdock.addWidget(self.lab)
self.vbdock.addSpacing(20)
self.vbdock.addWidget(QLabel("Scores:"))
self.lab1=QLabel("Player 1: "+str(self.player1))
self.vbdock.addWidget(self.lab1)
self.lab2=QLabel("Player 2: "+str(self.player2))
self.vbdock.addWidget(self.lab2)
#self.vbdock.addWidget(QLabel(self.currentWord))
self.vbdock.addStretch(1)
self. correct_guess = QPushButton("Correct Guess")#得一分基础上再得一分
self.vbdock.addWidget(self.correct_guess)
self.correct_guess.clicked.connect(self.show_correct_guess)
self.start=QPushButton("Start")
self.vbdock.addWidget(self.start)#得一分
self.start.clicked.connect(self.show_message_word)
self.skip=QPushButton("Skip Turn")#不得分
self.vbdock.addWidget(self.skip)
self.skip.clicked.connect(self.show_message_skipword)
self.button1 = QRadioButton('yellow', self)
self.button1.setStyleSheet("QRadioButton::indicator {width:13px;height:13px;border-radius:6px}"
"QRadioButton::indicator:checked {background-color:yellow;}"
"QRadioButton::indicator:unchecked {background-color:white;}")
self.button1.clicked.connect(self.yellow)
self.button2 = QRadioButton('black', self)
self.button2.setStyleSheet("QRadioButton::indicator {width:13px;height:13px;border-radius:6px}"
"QRadioButton::indicator:checked {background-color:black;}"
"QRadioButton::indicator:unchecked {background-color:white;}")
self.button2.clicked.connect(self.black)
self.button3 = QRadioButton('green', self)
self.button3.setStyleSheet("QRadioButton::indicator {width:13px;height:13px;border-radius:6px}"
"QRadioButton::indicator:checked {background-color:green;}"
"QRadioButton::indicator:unchecked {background-color:white;}")
self.button3.clicked.connect(self.green)
self.button4 = QRadioButton('red', self)
self.button4.setStyleSheet("QRadioButton::indicator {width:13px;height:13px;border-radius:6px}"
"QRadioButton::indicator:checked {background-color:red;}"
"QRadioButton::indicator:unchecked {background-color:white;}")
self.button4.clicked.connect(self.red)
self.button5 = QRadioButton('3px', self)
self.button5.setStyleSheet("QRadioButton::indicator {width:13px;height:13px;border-radius:6px}"
"QRadioButton::indicator:checked {background-color:yellow;}"
"QRadioButton::indicator:unchecked {background-color:white;}")
self.button5.clicked.connect(self.threepx)
self.button6 = QRadioButton('5px', self)
self.button6.setStyleSheet("QRadioButton::indicator {width:13px;height:13px;border-radius:6px}"
"QRadioButton::indicator:checked {background-color:yellow;}"
"QRadioButton::indicator:unchecked {background-color:white;}")
self.button6.clicked.connect(self.fivepx)
self.button7 = QRadioButton('7px', self)
self.button7.setStyleSheet("QRadioButton::indicator {width:13px;height:13px;border-radius:6px}"
"QRadioButton::indicator:checked {background-color:yellow;}"
"QRadioButton::indicator:unchecked {background-color:white;}")
self.button7.clicked.connect(self.sevenpx)
self.button8 = QRadioButton('9px', self)
self.button8.setStyleSheet("QRadioButton::indicator {width:13px;height:13px;border-radius:6px}"
"QRadioButton::indicator:checked {background-color:yellow;}"
"QRadioButton::indicator:unchecked {background-color:white;}")
self.button8.clicked.connect(self.ninepx)
#Setting colour of dock to gray设置码头的颜色为灰色
playerInfo.setAutoFillBackground(True)
p = playerInfo.palette()
p.setColor(playerInfo.backgroundRole(), Qt.GlobalColor.gray)
playerInfo.setPalette(p)
#set widget for dock为码头设置小部件
self.dockInfo.setWidget(playerInfo)
# QToolBar
toolbar = QToolBar(self)
layout = QVBoxLayout()
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, toolbar)
toolbar.setWindowTitle("Color")
toolbar.setStyleSheet("background-color:rgb(166,166,166);")
action_radio_btn1 = toolbar.addWidget(self.button1)
action_radio_btn2 = toolbar.addWidget(self.button2)
action_radio_btn2.setCheckable(True)
action_radio_btn3 = toolbar.addWidget(self.button3)
action_radio_btn4 = toolbar.addWidget(self.button4)
toolbar.addSeparator()
# QToolBar
toolbar1 = QToolBar(self)
layout1 = QVBoxLayout()
self.addToolBar(Qt.ToolBarArea.RightToolBarArea, toolbar1)
toolbar1.setWindowTitle("Color")
toolbar1.setStyleSheet("background-color:rgb(166,166,166);")
action_radio_btn5 = toolbar1.addWidget(self.button5)
action_radio_btn6 = toolbar1.addWidget(self.button6)
action_radio_btn6.setCheckable(True)
action_radio_btn7 = toolbar1.addWidget(self.button7)
action_radio_btn8 = toolbar1.addWidget(self.button8)
toolbar.addSeparator()
# event handlers事件处理程序 #当按下鼠标时
def mousePressEvent(self, event): # when the mouse is pressed, documentation: https://doc.qt.io/qt-6/qwidget.html#mousePressEvent
if event.button() == Qt.MouseButton.LeftButton: # if the pressed button is the left button如果按下的按钮是左键
self.drawing = True # enter drawing mode进入绘图模式
self.lastPoint = event.pos() # save the location of the mouse press as the lastPoint保存鼠标按下的位置为lastPoint
print(self.lastPoint) # print the lastPoint for debugging purposes打印lastPoint以便调试
#当鼠标移动时
def mouseMoveEvent(self, event): # when the mouse is moved, documenation: documentation: https://doc.qt.io/qt-6/qwidget.html#mouseMoveEvent
if self.drawing: #对象,它允许在图像上进行绘图
painter = QPainter(self.image) # object which allows drawing to take place on an image
# allows the selection of brush colour, brish size, line type, cap type, join type. Images available here http://doc.qt.io/qt-6/qpen.html
#允许选择笔刷颜色,毛边大小,线型,帽型,连接型。图像可用
painter.setPen(QPen(self.brushColor, self.brushSize, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin))
painter.drawLine(self.lastPoint, event.pos()) # draw a line from the point of the orginal press to the point to where the mouse was dragged to
self.lastPoint = event.pos() # set the last point to refer to the point we have just moved to, this helps when drawing the next line segment
#从最初按下的点到鼠标被拖动的点绘制一条线,以设置最后一个点指向我们刚刚移动到的点,这在绘制下一个线段时很有帮助
self.update() # call the update method of the widget which calls the paintEvent of this class
#调用调用这个类的paintEvent的小部件的update方法
#当鼠标被释放时
def mouseReleaseEvent(self, event): # when the mouse is released, documentation: https://doc.qt.io/qt-6/qwidget.html#mouseReleaseEvent
if event.button() == Qt.MouseButton.LeftButton: # if the released button is the left button, documentation: https://doc.qt.io/qt-6/qt.html#MouseButton-enum ,
self.drawing = False # exit drawing mode如果释放按钮是左键,退出绘图模式
# paint events油漆事件
def paintEvent(self, event):
# you should only create and use the QPainter object in this method, it should be a local variable
#你应该只在这个方法中创建和使用QPainter对象,它应该是一个局部变量
#创建一个新的QPainter对象
canvasPainter = QPainter(self) # create a new QPainter object, documentation: https://doc.qt.io/qt-6/qpainter.html
canvasPainter.drawPixmap(QPoint(), self.image) # draw the image , documentation: https://doc.qt.io/qt-6/qpainter.html#drawImage-1
#绘制图像
# resize event - this function is called调整大小事件——调用这个函数
def resizeEvent(self, event):
self.image = self.image.scaled(self.width(), self.height())
# slots
def save(self):
filePath, _ = QFileDialog.getSaveFileName(self, "Save Image", "",
"PNG(*.png);;JPG(*.jpg *.jpeg);;All Files (*.*)")
if filePath == "": # if the file path is empty如果文件路径为空
return # do nothing and return什么都不做,然后回来
self.image.save(filePath) # save file image to the file path将文件图像保存到文件路径
def clear(self):
self.image.fill( #用白色填充图像
Qt.GlobalColor.white) # fill the image with white, documentation: https://doc.qt.io/qt-6/qimage.html#fill-2
self.update() # call the update method of the widget which calls the paintEvent of this class
#调用调用这个类的paintEvent的小部件的update方法
def show_message_about(self):
QMessageBox.about(self, "About", "This is Pictionary Game")
def show_message_help(self):
QMessageBox.about(self, "Help", "If you have any questions about this Pictionary Game, please contact 123@.com")
def show_message_word(self):
self.lab.setText("CurrentTurn:" + str(self.currentPlayer))
QMessageBox.about(self, "Player"+str(self.currentPlayer)+" See Your Word", "Don't Let Others See! Press Details!" + "\n" + "The WORD IS " + self.currentWord)
if self.currentPlayer == 1:
self.player1=self.player1+1
self.currentPlayer=2
print("self.player1 "+str(self.player1))
else:
self.player2=self.player2+1
self.currentPlayer=1
print("self.player2 "+str(self.player2))
self.lab1.setText("Player 1: " + str(self.player1))
self.lab2.setText("Player 2: " + str(self.player2))
def show_correct_guess(self):
self.lab.setText("CurrentTurn:" + str(self.currentPlayer))
if self.currentPlayer == 2:
self.player1=self.player1+1
else:
self.player2=self.player2+1
self.lab1.setText("Player 1: " + str(self.player1))
self.lab2.setText("Player 2: " + str(self.player2))
def show_message_skipword(self):
self.lab.setText("CurrentTurn:" + str(self.currentPlayer))
self.currentWord=self.getWord()
if self.currentPlayer == 1:
self.currentPlayer = 2
else:
self.currentPlayer = 1
QMessageBox.about(self, "Player See Your Word","Don't Let Others See! Press Details!"+"\n"+"The WORD IS "+self.currentWord)
self.lab1.setText("Player 1: " + str(self.player1))
self.lab2.setText("Player 2: " + str(self.player2))
def threepx(self): # the brush size is set to 3笔刷大小设置为3
self.brushSize = 3
def fivepx(self):
self.brushSize = 5
def sevenpx(self):
self.brushSize = 7
def ninepx(self):
self.brushSize = 9
def black(self): # the brush color is set to black 笔刷的颜色设置为黑色
self.brushColor = Qt.GlobalColor.black
def black(self):
self.brushColor = Qt.GlobalColor.black
def red(self):
self.brushColor = Qt.GlobalColor.red
def green(self):
self.brushColor = Qt.GlobalColor.green
def yellow(self):
self.brushColor = Qt.GlobalColor.yellow
#Get a random word from the list read from file
#从文件中读取的列表中获取一个随机单词
def getWord(self):
randomWord = random.choice(self.wordList)
print(randomWord)
return randomWord
#read word list from file
#从文件中读取单词列表
def getList(self, mode):
with open(mode + 'mode.txt') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
for row in csv_reader:
#print(row)
self.wordList = row
line_count += 1
#print(f'Processed {line_count} lines.')
# open a file
#打开文件
def open(self):
'''
This is an additional function which is not part of the tutorial. It will allow you to:
- open a file dialog box,
- filter the list of files according to file extension
- set the QImage of your application (self.image) to a scaled version of the file)
- update the widget
'''
'''
这是一个附加函数,不是本教程的一部分。它将允许您:
-打开文件对话框,
-根据文件扩展名过滤文件列表
-设置你的应用程序的QImage (self.image)为文件的缩放版本)
-更新小部件
'''
filePath, _ = QFileDialog.getOpenFileName(self, "Open Image", "",
"PNG(*.png);;JPG(*.jpg *.jpeg);;All Files (*.*)")
if filePath == "": # if not file is selected exit如果不选择文件,则退出
return
with open(filePath, 'rb') as f: # open the file in binary mode for reading以二进制模式打开文件进行读取
content = f.read() # read the file读取文件
self.image.loadFromData(content) # load the data into the file将数据加载到文件中
width = self.width() # get the width of the current QImage in your application获取应用程序中当前QImage的宽度
height = self.height() # get the height of the current QImage in your application获取应用程序中当前QImage的高度
self.image = self.image.scaled(width, height) # scale the image from file and put it in your QImage缩放图像从文件,并把它放在您的QImage
self.update() # call the update method of the widget which calls the paintEvent of this class调用调用这个类的paintEvent的小部件的update方法
this code will be executed if it is the main module but not if the module is imported如果它是主模块,则执行此代码,但如果模块被导入则不执行
https://stackoverflow.com/questions/419163/what-does-if-name-main-do
if name == “main”:
app = QApplication(sys.argv)
window = PictionaryGame()
window.show()
app.exec() # start the event loop running启动事件循环运行