• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

question-closed Qt, responsywne skalowanie rozmiaru tekstu przycisku.

VPS Starter Arubacloud
0 głosów
317 wizyt
pytanie zadane 13 czerwca 2019 w C i C++ przez Jakub 0 Pasjonat (23,120 p.)
zamknięte 14 czerwca 2019 przez Jakub 0

Witam, korzystam z książki "Game Programming Using QT". Rozwijam tam podaną w przykładzie grę TicTacToe. Problem jest taki że przy zmianie rozmiaru okna programu ( tym samym planszy i pól w postaci przycisków ) czcionka dla przycisków pozostaje taka sama. Próbowałem rozwiązać to tak:

///------------------------------------------------------------------------- 

void TicTacToeWidget::buttonsResizeEvent(){
    for(int i=0; i<board.size(); ++i){
        QPushButton* button = board.at(i);
        int size = button->geometry().height();
        QFont font = button->font();
        font.setPointSize(size/2);
        button->setFont(font);
    }
}

///-------------------------------------------------------------------------

TicTacToeWidget::TicTacToeWidget(QWidget *parent)
    : QWidget(parent) {
    gridLayout = new QGridLayout(this);
    connect(this, SIGNAL(QResizeEvent), this, SLOT(buttonsResizeEvent()));
}

Problem jest jednak taki że sygnał nie jest w ogóle rozpoznawany:

QObject::connect: No such signal TicTacToeWidget::QResizeEvent()

Szukałem informacji, początkowo chciałem to zrobić w CSS dla przycisków podając procentowe wymiary czcionki. Nic jednak mi nie działało. Będę wdzięczny za pomoc.

Dla pewności daje cały kod programu:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "tictactoewidget.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
public slots:
    void startNewGame();
private slots:
    void updateNameLabels();
    void handleGameOver(TicTacToeWidget::Player winner);

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "configurationdialog.h"
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(ui->actionNewGame, SIGNAL(triggered()),
            this, SLOT(startNewGame()));
    connect(ui->actionQuit, SIGNAL(triggered()),
            qApp, SLOT(quit()));
    connect(ui->gameBoard, SIGNAL(currentPlayerChanged(Player)),
            this, SLOT(updateNameLabels()));
    connect(ui->gameBoard, SIGNAL(gameOver(TicTacToeWidget::Player)),
            this, SLOT(handleGameOver(TicTacToeWidget::Player)));

      ui->gameBoard->setEnabled(false);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::startNewGame() {
    ConfigurationDialog dlg(this);
    if(dlg.exec()==QDialog::Rejected) {
        return;
    }
    ui->player1->setText(dlg.player1Name());
    ui->player2->setText(dlg.player2Name());
    ui->gameBoard->setupBoard(dlg.boardSizeSlider());
    ui->gameBoard->initNewGame();
    ui->gameBoard->setEnabled(true);
    updateNameLabels();

    updateNameLabels();
    QFont f = ui->player2->font();
    f.setBold(true);
    ui->player2->setFont(f);
}

void MainWindow::updateNameLabels() {
    QFont f = ui->player1->font();
    f.setBold(ui->gameBoard->currentPlayer() ==
              TicTacToeWidget::Player1);
    ui->player1->setFont(f);
    f = ui->player2->font();
    f.setBold(ui->gameBoard->currentPlayer() ==
              TicTacToeWidget::Player2);
    ui->player2->setFont(f);
}

void MainWindow::handleGameOver(TicTacToeWidget::Player winner) {
    ui->gameBoard->setEnabled(false);
    QString message;
    if(winner == TicTacToeWidget::Draw) {
        message = "Game ended with a draw.";
    } else {
        message = QString("%1 wins").arg(winner == TicTacToeWidget::Player1
                    ? ui->player1->text() : ui->player2->text());
    };
    QMessageBox::information(this, "Info", message);
}

tictactoewidget.h

#ifndef TICTACTOEWIDGET_H
#define TICTACTOEWIDGET_H

#include <QWidget>
#include <QPushButton>
#include <QGridLayout>
class QPushButton;

class TicTacToeWidget : public QWidget {
    Q_OBJECT
public:
    enum Player { // nie może być w sekcji prywatnej!!!
        Invalid, Player1, Player2, Draw
    };
private:
    Q_ENUMS(Player)
    Q_PROPERTY(Player currentPlayer READ currentPlayer WRITE setCurrentPlayer
               NOTIFY currentPlayerChanged)


signals:
    void currentPlayerChanged(Player);
    void gameOver(TicTacToeWidget::Player);
public slots:
    void handleButtonClick(int index);
private:
    Player checkWinConditionForLine(const QList<int>& line);
    Player checkWinCondition();

    void buttonsResizeEvent();
public:
    TicTacToeWidget(QWidget *parent = nullptr);
    ~TicTacToeWidget();
private:
    int m_boardSize;
    QGridLayout *gridLayout;
    QList<QPushButton*> board;
    Player m_currentPlayer;
public:
    void setupBoard(int size=3);
    void initNewGame();

    Player currentPlayer() const;
    void setCurrentPlayer(Player p);
};

#endif // TICTACTOEWIDGET_H

tictactoewidget.cpp ( źródło podanych fragmentów )

#include "tictactoewidget.h"
#include "ui_tictactoewidget.h"
#include <QPushButton>
#include <QGridLayout>
#include <QSignalMapper>
#include <QLayoutItem>
#include <QResizeEvent>

///-------------------------------------------------------------------------

void TicTacToeWidget::handleButtonClick(int index) {
    if(index<0 || index>=board.size()) return;
    QPushButton *button = board.at(index);
    if(button->text() != " ") return;
    button->setText(currentPlayer() == Player1 ? "X" : "O");
    Player winner = checkWinCondition();
    if(winner==Invalid) {
        setCurrentPlayer(currentPlayer()==Player1 ? Player2 : Player1);
        return;
    } else {
        emit gameOver(winner);
    }

}

TicTacToeWidget::Player TicTacToeWidget::checkWinConditionForLine(
        const QList<int>& line) {
    QString text1 = board[line[0]]->text();
    if(text1==" ")
        return Invalid;
    bool correct = true;
    for(int i=1; i<line.size(); ++i){
        if(text1 != board[line[i]]->text()){
            correct = false;
            break;
        }
    }
    if(correct)
        return text1=="X" ? Player1 : Player2;
    return Invalid;
}

///-------------------------------------------------------------------------

TicTacToeWidget::Player TicTacToeWidget::checkWinCondition() {
    Player result = Invalid;

    QList<int> line;
    for(int row=0; row<m_boardSize; ++row) {
        for(int column=0; column<m_boardSize; ++column) {
            line.append(m_boardSize*row+column);
        }
        result = checkWinConditionForLine(line);
        line.clear();

        if(result!=Invalid)
            return result;
    }

    line.clear();
    for(int column=0; column<m_boardSize; ++column) {
        for(int row=0; row<m_boardSize; ++row){
             line.append(m_boardSize*row+column);
        }
        result = checkWinConditionForLine(line);
        line.clear();

        if(result!=Invalid)
            return result;
    }

    line.clear();
    int actualIndex = 0;

    for(int i=0; i<m_boardSize; ++i){
        line.append(actualIndex);
        actualIndex += (m_boardSize+1);
    }
    result = checkWinConditionForLine(line);
    if (result != Player::Invalid)
            return result;


    line.clear();
    actualIndex = m_boardSize-1;

    for(int i=0; i<m_boardSize; ++i){
        line.append(actualIndex);
        actualIndex += (m_boardSize-1);
    }

    result = checkWinConditionForLine(line);
    if (result != Player::Invalid)
            return result;

    for(QPushButton *button : board)
        if(button->text()==" ")
            return Invalid;

    return Draw;
}


///------------------------------------------------------------------------- jhhj

void TicTacToeWidget::buttonsResizeEvent(){
    for(int i=0; i<board.size(); ++i){
        QPushButton* button = board.at(i);
        int size = button->geometry().height();
        QFont font = button->font();
        font.setPointSize(size/2);
        button->setFont(font);
    }
}

///-------------------------------------------------------------------------

TicTacToeWidget::TicTacToeWidget(QWidget *parent)
    : QWidget(parent) {
    gridLayout = new QGridLayout(this);
    connect(this, SIGNAL(QResizeEvent()), this, SLOT(buttonsResizeEvent()));
}

///-------------------------------------------------------------------------

TicTacToeWidget::~TicTacToeWidget() {}

void remove (QLayout* layout ) {
    QLayoutItem* child;
    while ( layout->count() != 0 ) {
        child = layout->takeAt ( 0 );
        if ( child->layout() != 0 ) {
            remove ( child->layout() );
        } else if ( child->widget() != 0 ) {
            delete child->widget();
        }

        delete child;
    }
}

void TicTacToeWidget::setupBoard(int size) {

    board.clear();
    remove(gridLayout);

    m_boardSize = size;
    QSignalMapper* mapper = new QSignalMapper(this);
    for(int row = 0; row < m_boardSize; ++row) {
        for(int column = 0; column < m_boardSize; ++column) {
            QPushButton *button = new QPushButton;
            button->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
            button->setText(" ");
            button->setStyleSheet("font-size: 90em");
            gridLayout->addWidget(button, row, column);
            board.append(button);
            mapper->setMapping(button, board.count()-1);
            connect(button, SIGNAL(clicked()), mapper, SLOT(map()));  
        }
    }
    connect(mapper, SIGNAL(mapped(int)), this, SLOT(handleButtonClick(int)));
    setLayout(gridLayout);
}

///-------------------------------------------------------------------------

void TicTacToeWidget::initNewGame() {
    for(int i=0; i<board.size(); ++i) board.at(i)->setText(" ");
}

///-------------------------------------------------------------------------

TicTacToeWidget::Player TicTacToeWidget::currentPlayer() const {
    return m_currentPlayer;
}

///-------------------------------------------------------------------------

void TicTacToeWidget::setCurrentPlayer(TicTacToeWidget::Player p) {
    if(m_currentPlayer == p) return;
    m_currentPlayer = p;
    emit currentPlayerChanged(p);
}

Nie dałem kodu dla okienka konfiguracyjnego bo chyba nie ma sensu. Z góry dziękuje i pozdrawiam :)

komentarz zamknięcia: problem rozwiązany.

1 odpowiedź

+2 głosów
odpowiedź 13 czerwca 2019 przez j23 Mędrzec (195,220 p.)
wybrane 14 czerwca 2019 przez Jakub 0
 
Najlepsza

Problem jest jednak taki że sygnał nie jest w ogóle rozpoznawany:

QResizeEvent to klasa zawierająca parametry związane ze zmianą rozmiaru. To nie jest sygnał. To, co Ciebie powinno interesować, to metoda QWidget::resizeEvent, która służy do przechwytywania zmiany rozmiary okna. Zrób własną implementację w TicTacToeWidget.

komentarz 14 czerwca 2019 przez Jakub 0 Pasjonat (23,120 p.)

Napisałem teraz to tak:

///------------------------------------------------------------------------- 

void TicTacToeWidget::buttonsResizeEvent(){
    for(int i=0; i<board.size(); ++i){
        QPushButton* button = board.at(i);
        int size = button->geometry().height();
        QFont font = button->font();
        font.setPointSize(size/2);
        button->setFont(font);
    }
}

///-------------------------------------------------------------------------

TicTacToeWidget::TicTacToeWidget(QWidget *parent)
    : QWidget(parent) {
    gridLayout = new QGridLayout(this);
    connect(this, SIGNAL(resizeEvent(QResizeEvent)), this, SLOT(buttonsResizeEvent()));
}

///-------------------------------------------------------------------------

Chyba dalej tego tematu nie rozumiem ponieważ taki sygnał też nie jest rozpoznawalny. Według mnie nie ma sensu przypisywać tego sygnału do pojedynczego przycisku bo przy zmianie rozmiaru widgetu wszystkie przyciski ulegną "przeskalowaniu". Parametr typu  QResizeEvent slot ignoruje ponieważ nie jest on mu do niczego potrzebny.

komentarz 14 czerwca 2019 przez j23 Mędrzec (195,220 p.)

Chyba dalej tego tematu nie rozumiem ponieważ taki sygnał też nie jest rozpoznawalny.

A kto powiedział, że to sygnał? Pisałem wcześniej, że to (wirtualna) metoda, której implementację powinieneś zrobić w klasie TicTacToeWidget:

class TicTacToeWidget : QWidget
{
	...
	
protected:
	void resizeEvent(QResizeEvent *event) override
	{
		QWidget::resizeEvent(event);

		/* tu robisz to, co masz do zrobienia  */
	}

	...
};

 

PS. czytaj dokumentację Qt.

komentarz 15 czerwca 2019 przez Jakub 0 Pasjonat (23,120 p.)

Witam, mam tylko jeszcze jedno drobne pytanie. Teraz wszystko działa mi popranie, kiedy zmieniam wielkość okna to tekst na przyciskach zmienia swoją wielkość. Chcę jednak też by wielkość tekstu została dopasowana już początkowo podczas stworzenia widgetu, robię więc tak:

void TicTacToeWidget::setupBoard(int size) {

    board.clear();
    remove(gridLayout);

    m_boardSize = size;
    QSignalMapper* mapper = new QSignalMapper(this);
    for(int row = 0; row < m_boardSize; ++row) {
        for(int column = 0; column < m_boardSize; ++column) {
            QPushButton *button = new QPushButton;
            button->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum);
            gridLayout->addWidget(button, row, column);
            board.append(button);
            mapper->setMapping(button, board.count()-1);
            connect(button, SIGNAL(clicked()), mapper, SLOT(map()));  
        }
    }
    connect(mapper, SIGNAL(mapped(int)), this, SLOT(handleButtonClick(int)));
    setLayout(gridLayout);
    buttonsResizeEvent();
}

I funkcja:

void TicTacToeWidget::buttonsResizeEvent(){
    for(int i=0; i<board.size(); ++i){
        QPushButton* button = board.at(i);
        int hsize = button->geometry().height() / 2;
        int wsize = button->geometry().width() / 2;
        QFont font = button->font();
        font.setPointSize(hsize < wsize ? hsize : wsize);
        button->setFont(font);
    }
}

Problem jest taki że przed zmianą rozmiaru okna czcionka w ogóle się "nie mieści":

Funkcja działa poprawnie dopiero wywołana w ten sposób:

void TicTacToeWidget::resizeEvent(QResizeEvent *event) {
     QWidget::resizeEvent(event);
     buttonsResizeEvent();
}

Wiem że powinienem czytać dokumentacje, ale ogólnie ciężko znaleźć co tu konkretnie robię źle ;)

Z góry dziękuje.

komentarz 15 czerwca 2019 przez j23 Mędrzec (195,220 p.)

To remove z linii 4 to funkcja z C?

Dokumentacja odradza używanie QSignalMapper. Klasa ta jest tylko po to, by zachować zgodność ze starym kodem.

Kiedy wywołujesz setupBoard? Niewykluczone, że przy pierwszym wywołaniu buttonsResizeEvent okno ma inny rozmiar niż przy pojawieniu się. Spróbuj to pierwsze ustawianie czcionek dać w metodzie showEvent (dla QShowEvent::spontaneous() == true).

komentarz 15 czerwca 2019 przez Jakub 0 Pasjonat (23,120 p.)

To remove z linii 4 to funkcja z C?

void remove (QLayout* layout ) {
    QLayoutItem* child;
    while ( layout->count() != 0 ) {
        child = layout->takeAt ( 0 );
        if ( child->layout() != nullptr ) {
            remove ( child->layout() );
        } else if ( child->widget() != nullptr ) {
            delete child->widget();
        }

        delete child;
    }
}

Ta funkcja ma za zadanie usunąć starą planszę ( jeżeli w ogóle istniała )

Dokumentacja odradza używanie QSignalMapper. Klasa ta jest tylko po to, by zachować zgodność ze starym kodem.

Nwm. tak było pokazane w książce. Jak sobie ze wszystkim poradzę to może poczytam nad lepszym rozwiązaniem tego. 

Spróbuj to pierwsze ustawianie czcionek dać w metodzie showEvent (dla QShowEvent::spontaneous() == true).

Zrobiłem to tak:
 

void TicTacToeWidget::showEvent(QShowEvent *event) {
    QWidget::showEvent(event);
    if(event->spontaneous()) {
        buttonsResizeEvent();
        qDebug()<<"v1\n";
    }else  qDebug()<<"v2\n";
}

warunek daje false...

komentarz 15 czerwca 2019 przez j23 Mędrzec (195,220 p.)

Spróbuj wywołać buttonsResizeEvent dla false.

komentarz 16 czerwca 2019 przez Jakub 0 Pasjonat (23,120 p.)

Teraz niby warunek się spełnił ale wywołanie metody buttonsResizeEvent nie dało żadnych rezultatów. Do zmiany rozmiaru okna wielkość tekstu jest domyślna. Ale mam pomysł w czym może być problem, skaluje tekst na podstawie zmiany rozmiary widgetu gameBoard. Gdyby tak zrobić to odgórnie dla klasy MainWindow? Może to coś da...

Podobne pytania

0 głosów
1 odpowiedź 848 wizyt
pytanie zadane 19 kwietnia 2019 w C i C++ przez niezalogowany
0 głosów
2 odpowiedzi 1,033 wizyt
pytanie zadane 29 sierpnia 2018 w C i C++ przez niezalogowany
0 głosów
1 odpowiedź 678 wizyt
pytanie zadane 5 maja 2015 w C i C++ przez marcelo89 Nowicjusz (160 p.)

92,843 zapytań

141,782 odpowiedzi

320,858 komentarzy

62,174 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Wprowadzenie do ITsec, tom 2

Można już zamawiać tom 2 książki "Wprowadzenie do bezpieczeństwa IT" - będzie to około 650 stron wiedzy o ITsec (17 rozdziałów, 14 autorów, kolorowy druk).

Planowana premiera: 30.09.2024, zaś planowana wysyłka nastąpi w drugim tygodniu października 2024.

Warto preorderować, tym bardziej, iż mamy dla Was kod: pasja (użyjcie go w koszyku), dzięki któremu uzyskamy dodatkowe 15% zniżki! Dziękujemy zaprzyjaźnionej ekipie Sekuraka za kod dla naszej Społeczności!

...