在 QLayout PyQT 5 中的 QLabel 类型棋盘上绘制棋子

Posted

技术标签:

【中文标题】在 QLayout PyQT 5 中的 QLabel 类型棋盘上绘制棋子【英文标题】:Drawing chess pieces on a QLabel type chessboard in a QLayout PyQT 5 【发布时间】:2021-08-04 17:43:48 【问题描述】:

对于一个学校项目,我正在编写一个国际象棋游戏。我用以下代码制作了第一个 GUI:

class chessUI(QtWidgets.QMainWindow):

    def __init__(self, partie):

        """super().__init__() ===> pour QtWidgets.QWidget"""
        # Initialisation de la fenêtre
        QtWidgets.QMainWindow.__init__(self)
        self.setGeometry(0, 0, 1100, 1100)
        #self.setFixedSize(1100, 1100)
        self.setWindowTitle("IHM")

        """self.layout1 = QtWidgets.QHBoxLayout()
        self.setLayout(self.layout1)
        self.layout1.addStretch(1)

        self.layout2 = QtWidgets.QHBoxLayout()
        self.layout2.addStretch(1)
        bout1 = QtWidgets.QPushButton('Nouvelle partie', self)
        bout2 = QtWidgets.QPushButton('Custom', self)
        self.layout2.addWidget(bout1)
        self.layout2.addWidget(bout2)

        self.layout3 = QtWidgets.QHBoxLayout()
        im = QtGui.QPixmap("echiquier.png")
        label = QtWidgets.QLabel()
        label.setPixmap(im)
        self.layout3.addWidget(label)"""

        """# Définition des différents boutons de l'interface
        bout1 = QtWidgets.QPushButton('Nouvelle partie', self)
        bout2 = QtWidgets.QPushButton('Custom', self)
        bout1.setToolTip('Nouvelle partie')
        bout2.setToolTip('Custom')
        bout1.move(1350, 100)
        bout2.move(1500, 100)
        bout1.clicked.connect(self.nvl_partie)
        bout2.clicked.connect(self.custom)"""

        """self.layout3.addLayout(self.layout3)
        self.layout1.addLayout(self.layout2)"""

        self.coups = QTableWidget(1, 3, self)
        self.coups.setItem(0, 0, QTableWidgetItem(0))
        self.coups.setItem(0, 1, QTableWidgetItem(0))
        self.coups.setItem(0, 2, QTableWidgetItem(0))
        self.coups.setHorizontalHeaderItem(0, QTableWidgetItem(str("Tour")))
        self.coups.setHorizontalHeaderItem(1, QTableWidgetItem(str("Joueur blanc")))
        self.coups.setHorizontalHeaderItem(2, QTableWidgetItem(str("Joueur noir")))
        self.coups.move(1000, 100)
        self.coups.setFixedSize(500, 840)

        # Définition des paramètres pour le jeu
        self.taille = 105                                                   # taille en pixel d'une case de l'échiquier
        self.partie = partie                                                # partie d'échec associée à la fenêtre
        self.show()

    def paintEvent(self, event = None):
        """
        :return: les différentes actions selon les conditions satisfaites
        """
        qp = QtGui.QPainter()
        qp.begin(self)
        if self.partie is not None:                             # Si une partie d'échecs a été associée :
            self.drawRectangles(qp)                             # On dessine l'échiquier
            j_blanc = self.partie.joueur_1                      # On sélectionne les deux joueurs
            j_noir = self.partie.joueur_2
            if self.partie.type is None :                       # Si la partie est un match et qu'on est au début
                if self.partie.debut:                           # si on est au début, on place les pièces de chaque joueur
                                                                # sur l'échiquier
                    self.partie.debut = False
                    self.set_pieces(qp, j_blanc)
                    self.set_pieces(qp, j_noir)
                else:                                           # Sinon, on place juste les pièces et cela fait la mise
                                                                # à jour du plateau durant la partie
                    self.set_pieces(qp, j_blanc)
                    self.set_pieces(qp, j_noir)
            else :                                              # Le joueur veut étudier différentes stratégies selon
                                                                # des positions de pièces précises : on le laisse placer
                                                                # les pièces sur l'échiquier ==> non fini
                if self.partie.debut:
                    self.partie.debut = False
                    self.choix_pieces(qp)
                    #self.placement_pieces()
                else :
                    self.set_pieces(qp, j_blanc)
                    self.set_pieces(qp, j_noir)
        qp.end()

    def drawRectangles(self, qp):
        """
        :param qp: le module QtGui.QPainter()
        :return: dessine dans l'IHM l'image du jeu d'échec
        """
        taille = self.taille
        qp.drawPixmap(100, 100, taille * 8, taille * 8, QtGui.QPixmap("echiquier.png"))

    def set_pieces(self, qp, joueur):
        """
        :param qp: le module QtGui.QPainter()
        :param joueur: le joueur dont on veut placer les pièces
        :return:
        """
        taille = self.taille
        for pi in joueur.pieces_restantes:                      # Pour chaque pièce restante au joueur, on trouve l'image associée
                                                                # et on la place sur l'échiquier
            y, x = pi.coords
            var = pi.car()
            c = joueur.couleur
            qp.drawPixmap(100 + x* taille, (7-y+1) * taille, 0.9*taille,
                                                0.9*taille, QtGui.QPixmap("impieces/" + var + "_" + c + ".png"))

    def disp_cases(self, qp, cases):
        """
        :param cases: la liste des cases accessibles par la pièce
        :return: affiche les cases accessibles par la pièce
        """
        # parcourir les cases dans la liste cases
        # sur chaque case, faire un qp.drawPixmap pour y mettre le CERCLE !!!!! j'insiste sur le CERCLE :)

    def mousePressEvent(self, event):
        """
        :param event: paramètre de type QMouseEvent
        :return: permet de générer les déplacements des pièces
        """
        y, x = event.x(), event.y()      # récupération des coordonnées de la sourie
        if self.partie is not None and self.partie.type is None and 100 <= x <= 1055 and 0 < y < 945:       # si une partie a été associée et que l'on
                                                                                                            # a bien cliqué sur une case de l'échiquier
            if event.button() == QtCore.Qt.LeftButton:
                self.partie.coupencours.append((7-(x//105-1), y//105-1))
                event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, event.pos(), QtCore.Qt.LeftButton,
                                          QtCore.Qt.LeftButton, QtCore.Qt.NoModifier)
                QtWidgets.QMainWindow.mousePressEvent(self, event)                                          # appel récursif à la fonction pour avoir la case destination
                                                                                                            # la case départ est stocké dans la liste coupencours
                if len(self.partie.coupencours) == 2:
                    self.bouger_piece(self.partie.coupencours)
                    self.partie.coupencours = []
        """elif self.partie.type == 'Custom' :
            if 1100 <= x <= 1730 and 300 <= y <= 400:
                index = (x-1100)//105
                self.creer_piece()
            elif 1100 <= x <= 1730 and 400 <= y <= 500:
                index = (x - 1100) // 105
                self.creer_piece()"""

    def bouger_piece(self, coup):
        """
        :param coup: tuple regroupant la case de départ et la case d'arrivée
        :return: si le coup est valable, bouge la pièce et met à jour l'IHM
        """
        case_dep, case_arr = coup[0], coup[1]
        self.coups.setItem(self.partie.tour//2, 0, QTableWidgetItem("".format(self.partie.tour//2+1)))
        if case_dep != case_arr:
            pi = self.partie.plateau[case_dep[0], case_dep[1]]
            t = self.partie.tour
            if pi != 0:
                if t % 2 == 1 and pi.joueur == self.partie.joueur_2:  # on vérifie que c'est bien à nous de jouer
                    # et que la pièce nous appartient
                    if pi.move(case_arr):
                        row = self.partie.tour // 2
                        s = " à ".format(case_dep, case_arr)
                        self.coups.setItem(row, 2, QTableWidgetItem(s))
                        self.partie.tour += 1
                        self.coups.insertRow(self.coups.rowCount())
                elif t % 2 == 0 and pi.joueur == self.partie.joueur_1:
                    if pi.move(case_arr):
                        s = " à ".format(case_dep, case_arr)
                        row = self.partie.tour // 2
                        self.coups.setItem(row, 1, QTableWidgetItem(s))
                        self.partie.tour += 1
                self.update()                                                                   # on met à jour l'IHM
                

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = chessUI()
    sys.exit(app.exec_())

这会呈现以下 GUI::

但是,我的代码存在一个主要缺陷:它无法调整大小。我查看了文档,发现您需要使用布局才能调整大小。这让我尝试了以下代码:

import sys
import partie as pa
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtWidgets import *


class random_ui(QtWidgets.QWidget):

    def __init__(self):
        super().__init__()
        self.partie = pa.partie()
        self.setWindowTitle("QVBoxLayout Example")
        outerlayout = QHBoxLayout()
        leftlabel = QLabel()
        leftlabel.setGeometry(100, 100, 840, 840)
        leftlabel.setPixmap(QtGui.QPixmap("echiquier.png"))
        outerlayout.addWidget(leftlabel)

        self.coups = QTableWidget(1, 3, self)
        self.coups.setItem(0, 0, QTableWidgetItem(0))
        self.coups.setItem(0, 1, QTableWidgetItem(0))
        self.coups.setItem(0, 2, QTableWidgetItem(0))
        self.coups.setHorizontalHeaderItem(0, QTableWidgetItem(str("Tour")))
        self.coups.setHorizontalHeaderItem(1, QTableWidgetItem(str("Joueur blanc")))
        self.coups.setHorizontalHeaderItem(2, QTableWidgetItem(str("Joueur noir")))
        self.coups.move(1000, 100)
        self.coups.setFixedSize(500, 840)
        outerlayout.addWidget(self.coups)
        self.setLayout(outerlayout)
        self.show()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = random_ui()
    sys.exit(app.exec_())

呈现以下内容 ::

但是当我想添加我的棋子的图像时,我不知道如何将它添加到棋盘上。我继续将它们添加到布局中,在棋盘和表格小部件之间。

有人能给点建议吗?

【问题讨论】:

我建议把棋盘分成几块,每一块都是正方形。在 GridLayout 中正确添加它们,它们之间没有水平或垂直空间。然后使用 addWidget(widget, row, column, alignment) 将棋子添加到所需位置。 【参考方案1】:

处理具有固定纵横比的小部件并非易事,必须采取一些预防措施以确保“不兼容”的父尺寸不会妨碍正确显示。

在这种情况下,一种可能的解决方案是为棋盘使用一个小部件,该小部件对所有方块块使用网格布局。 请注意,QLabel 不是棋盘的好选择,因为它不允许尺寸小于 QPixmap,因此应该将 QWidget 子类化。

诀窍是覆盖resizeEvent(),忽略基本实现(默认情况下会适应布局的几何形状)并根据宽度和高度之间的最小范围手动设置几何形状。

为了确保布局在行或列为空时具有适当的等间距,必须为整个网格大小调用setRowStretch()setColumnStretch()

然后,您将片段直接添加到布局中,当您需要移动它们时,您只需创建一个辅助函数,该函数使用 addWidget() 和正确的行/列(它会自动将小部件“移动”到新职位)。

这是一个可能的实现。

from PyQt5 import QtCore, QtGui, QtWidgets

class Pawn(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.image = QtGui.QPixmap('whitepawn.png')
        self.setMinimumSize(32, 32)

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        size = min(self.width(), self.height())
        qp.drawPixmap(0, 0, self.image.scaled(
            size, size, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))


class Board(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.background = QtGui.QPixmap('chessboard.png')

        for i in range(8):
            layout.setRowStretch(i, 1)
            layout.setColumnStretch(i, 1)

        for col in range(8):
            layout.addWidget(Pawn(), 1, col)

    def minimumSizeHint(self):
        return QtCore.QSize(256, 256)

    def sizesHint(self):
        return QtCore.QSize(768, 768)

    def resizeEvent(self, event):
        size = min(self.width(), self.height())
        rect = QtCore.QRect(0, 0, size, size)
        rect.moveCenter(self.rect().center())
        self.layout().setGeometry(rect)

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        rect = self.layout().geometry()
        qp.drawPixmap(rect, self.background.scaled(rect.size(), 
            QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))


class ChessGame(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        central = QtWidgets.QWidget()
        self.setCentralWidget(central)
        layout = QtWidgets.QHBoxLayout(central)
        self.board = Board()
        layout.addWidget(self.board)
        self.table = QtWidgets.QTableWidget(1, 3)
        layout.addWidget(self.table)


import sys
app = QtWidgets.QApplication(sys.argv)
game = ChessGame()
game.show()
sys.exit(app.exec_())

请注意,您还应该考虑使用Graphics View Framework,它提供了更多的控制和功能,对于此类界面非常有用。请注意,它既强大又难以习惯,并且需要很长时间才能理解它的所有方面。

【讨论】:

你好,首先我想原谅自己这么晚才回答。我刚刚测试了你的建议,效果很好!非常感谢! @ArthurCassou 不客气!请记住,如果某个答案解决了您的问题,您应该通过单击其左侧的灰色勾号将其标记为已接受。

以上是关于在 QLayout PyQT 5 中的 QLabel 类型棋盘上绘制棋子的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5快速入门PyQt5布局管理

python pyqt5 载入gif

试图将 QLayout "" 添加到 QWidget "",它已经有一个布局

如何使用 matplotlib 的对象选取功能在 PyQt 文本字段中显示值?

PyQT5 正确的布局对齐

QLabelCV——专门给OpenCV做的PyQt QLabel控件