• 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.

0 głosów
65 wizyt
pytanie zadane 13 czerwca w C i C++ przez Jakub 0 Stary wyjadacz (13,170 p.)
zamknięte 14 czerwca 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 przez j23 VIP (105,000 p.)
wybrane 14 czerwca 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 przez Jakub 0 Stary wyjadacz (13,170 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 przez j23 VIP (105,000 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 przez Jakub 0 Stary wyjadacz (13,170 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 przez j23 VIP (105,000 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 przez Jakub 0 Stary wyjadacz (13,170 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 przez j23 VIP (105,000 p.)

Spróbuj wywołać buttonsResizeEvent dla false.

komentarz 16 czerwca przez Jakub 0 Stary wyjadacz (13,170 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...

komentarz 16 czerwca przez Jakub 0 Stary wyjadacz (13,170 p.)

To niestety też nie pomogło, ostatecznie rozwiązałem problem tak:

void MainWindow::updateNameLabels() {
    ui->gameBoard->buttonsResizeEvent();
    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);
}

Jest to chyba dość brzydkie rozwiązanie... ale działa. Za każdym razem kiedy zmienimy tekst przycisku to zostanie on automatycznie przeskalowany. Ogólnie mam w tym projekcie dość sporo ostrzeżeń i podkreśleń. Nie mam pojęcia dlaczego nauka Qt idzie mi tak opornie :/

Podobne pytania

0 głosów
1 odpowiedź 44 wizyt
pytanie zadane 19 kwietnia w C i C++ przez niezalogowany
0 głosów
2 odpowiedzi 175 wizyt
pytanie zadane 29 sierpnia 2018 w C i C++ przez 12david12 Dyskutant (8,370 p.)
0 głosów
1 odpowiedź 405 wizyt
pytanie zadane 5 maja 2015 w C i C++ przez marcelo89 Nowicjusz (160 p.)
Porady nie od parady
Możesz ukryć, zamknąć lub zmodyfikować swoje pytanie, za pomocą przycisków znajdujących się pod nim. Nie krępuj się poprawić pochopnie opublikowanego pytania czy zamknąć go po uzyskaniu satysfakcjonującej odpowiedzi. Umożliwi to zachowanie porządku na forum.Przyciski pytania

64,867 zapytań

111,330 odpowiedzi

234,215 komentarzy

46,731 pasjonatów

Przeglądających: 183
Pasjonatów: 7 Gości: 176

Motyw:

Akcja Pajacyk

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

Oto dwie polecane książki warte uwagi. Pełną listę znajdziesz tutaj.

...