Im trying to create an tic tac toe game, where buttons are used for the positions of where the knaughts and crosses are to be used. When I move the buttons into their respective places, the slot is not triggered and nothing happens.
`
#include "tic_tac_toe.h"
#include <iostream>
tic_tac_toe::tic_tac_toe(QWidget *parent)
: QMainWindow(parent)
{
setFixedSize(900,900);
initBoard();
//showBoard();
connect(button,SIGNAL(clicked()),this,SLOT(buttonpressed()));
}
tic_tac_toe::~tic_tac_toe()
{
}
void tic_tac_toe::initBoard()
{
int x = 0;
int y = 0;
for(int i = 0; i < 10; i++)
{
button = new QPushButton(this);
board.append(button);
button->show();
button->setFixedSize(300,300);
//button->setStyleSheet("border: 5px solid black");
button->setText("??");
// button->move(x,y);
// x = x + 300;
// if(x == 900)
// {
// y = y + 300;
// x = 0;
// }
}
}
void tic_tac_toe::showBoard()
{
}
void tic_tac_toe::buttonpressed()
{
button->setText("X");
}
I tried doing it with only one QPushbutton and it works, however when I move and create more buttons, the Slot function does not work on the buttons.
You only ever connect to the last QPushButton instance created. Move the connect call from the constructor into tic_tac_toe::initBoard and use a lambda to capture the value of button...
void tic_tac_toe::initBoard()
{
for (int i = 0; i < 10; i++) {
button = new QPushButton(this);
board.append(button);
button->show();
button->setFixedSize(300,300);
button->setText("??");
connect(button, &QPushButton::clicked, this,
[this, button]
{
buttonpressed(button);
});
}
}
Likewise, update the signature of tic_tac_toe::buttonpressed...
void tic_tac_toe::buttonpressed (QPushButton *button)
{
button->setText("X");
}
Related
In my window there is a circle. I have implemented the void MouseClicked() method to take effect on the mouse click event. That means only clicking inside the circle should change the color of the circle and do the corresponding operation.
But the problem is wherever I click (even outside the circle), it changes the color of the circle. So I understand that the mouseClicked() method isn't stable. How do I fix this?
My code in processing:
int colorValue = 0;
void setup() {
size(450, 255);
background(204);
}
void draw() {
fill(colorValue);
ellipse(56, 46, 55, 55);
}
void mouseClicked() {
if (colorValue == 0) {
colorValue = 255;
} else {
colorValue = 0;
}
}
You aren't doing any check for whether the mouse is in the circle. You could use the dist() function to help with that:
int colorValue = 0;
float circleX = 56;
float circleY = 46;
float circleR = 55;
void setup() {
size(450, 255);
background(204);
ellipseMode(RADIUS);
}
void draw() {
fill(colorValue);
ellipse(circleX, circleY, circleR, circleR);
}
void mouseClicked() {
if(dist(mouseX, mouseY, circleX, circleY) < circleR){
if (colorValue == 0) {
colorValue = 255;
} else {
colorValue = 0;
}
}
}
I would like to create coordinate points on a QGraphicsView. When the mouse hovers over the point, the coordinate will be shown.
I draw the coordinates by QGraphicsEllipseItem. In order to enable the hover event, I need to re-implement the QGraphicsEllipseItem. However, because the size of the QGraphicsEllipseItem is fixed when it was constructed, the hover text is not shown properly. How can I deal with this?
Here is my code:
The MainWindow:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
view = new QGraphicsView(this);
view->setRenderHint(QPainter::Antialiasing);
scene = new QGraphicsScene(this);
view->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
view->setScene(scene);
setCentralWidget(view);
for (int y = 0; y < 900; y += 100)
for(int x = 0; x < 1400; x += 100)
drawPoint(x, y);
}
void MainWindow::drawPoint(int x, int y)
{
CoordinatePoint* point = new CoordinatePoint();
point->setRect(QRect(x, y, 3, 3));
point->setPen(QPen(Qt::red, 3, Qt::SolidLine));
point->setBrush(Qt::red);
scene->addItem(point);
}
The re-implement QGraphicsEllipseItem:
CoordinatePoint::CoordinatePoint(QGraphicsItem* parent)
:QGraphicsEllipseItem(parent)
{
setAcceptHoverEvents(true);
}
void CoordinatePoint::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
hover = true;
mx = event->pos().x();
my = event->pos().y();
update();
}
void CoordinatePoint::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
{
hover = false;
update();
}
void CoordinatePoint::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
QGraphicsEllipseItem::paint(painter, option, widget);
if (hover)
{
painter->save();
painter->setPen(Qt::black);
painter->setBrush(Qt::black);
painter->drawText(mx + 2, my + 2,
"(" + QString::number(mx) + "," +
QString::number(my) + ")");
painter->restore();
}
}
I think that using a separate child item for the text will make your life a lot easier:
#include <QtWidgets>
class CoordinatePoint : public QGraphicsEllipseItem
{
public:
CoordinatePoint(QGraphicsItem* parent = Q_NULLPTR) :
QGraphicsEllipseItem(parent),
coordinateText(Q_NULLPTR)
{
setAcceptHoverEvents(true);
}
void hoverEnterEvent(QGraphicsSceneHoverEvent*)
{
if (!coordinateText) {
coordinateText = new QGraphicsTextItem(this);
coordinateText->setPlainText("(" + QString::number(x())
+ "," + QString::number(y()) + ")");
coordinateText->setX(2);
coordinateText->setY(2);
}
coordinateText->setVisible(true);
}
void hoverLeaveEvent(QGraphicsSceneHoverEvent*)
{
if (coordinateText) {
coordinateText->setVisible(false);
}
}
private:
QGraphicsTextItem *coordinateText;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow window;
QGraphicsView *view = new QGraphicsView(&window);
view->setRenderHint(QPainter::Antialiasing);
QGraphicsScene *scene = new QGraphicsScene(&window);
view->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
view->setScene(scene);
window.setCentralWidget(view);
for (int y = 0; y < 900; y += 100) {
for(int x = 0; x < 1400; x += 100) {
CoordinatePoint* point = new CoordinatePoint();
point->setRect(QRect(0, 0, 3, 3));
point->setX(x);
point->setY(y);
point->setPen(QPen(Qt::red, 3, Qt::SolidLine));
point->setBrush(Qt::red);
scene->addItem(point);
}
}
window.show();
return a.exec();
}
If having an extra QGraphicsTextItem for each coordinate worries you, you could construct one of them and just share it amongst them all, reparenting it as each one is hovered. This should work fine, as there can only ever be one coordinate hovered at a time.
If you try to draw the text as part of the ellipse item, you'd have to:
Increase the size of the item so that it's large enough to contain the text, which means overriding boundingRect().
Only draw the ellipse within a certain part of that area.
In hoverEnterEvent(), check that the mouse cursor is within the ellipse's area.
A bunch of other stuff, probably.
In the function - loadData(), First, show the dialog with QProgressBar,and then, call setValue() for the progressbar base on the business. when the value of progressbar increase to 100%, hide the dialog and set the value to 0.
My question is:
When I enter the loadData() function again, after exec the dlgo->show(), the value of progressbar isn't start from 0, but jump from 100 to 0, and then go on.
What can I do to make the value of progressbar start from 0 when I tried show the dialog again?
Thank you!
void loadData() {
mProcessBarDlg->show();
{
mProcessBarDlg->ui.progressBar->setValue(XX);
}
mProcessBarDlg->hide();
mProcessBarDlg->ui.progressBar->setValue(0);
}
Set it to zero before you show it.
EDIT:
The following code, derived from the poster's question, works:
#include <qapplication.h>
#include <qdialog.h>
#include <qprogressbar.h>
QDialog *mProcessBarDlg;
QProgressBar *progressBar;
void loadData() {
mProcessBarDlg->setValue(0);
mProcessBarDlg->show();
for (int i = 0; i < 100000000; ++i){
if (i % 100 == 0){
qApp->processEvents();
}
}
{
progressBar->setValue(50);
for (int i = 0; i < 100000000; ++i){
if (i % 100 == 0){
qApp->processEvents();
}
}
}
mProcessBarDlg->hide();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
mProcessBarDlg = new QDialog;
progressBar = new QProgressBar(mProcessBarDlg);
loadData();
for (int i = 0; i < 100000000; ++i){
if (i % 100 == 0){
a.processEvents();
}
}
loadData();
QMetaObject::invokeMethod(&a, "quit", Qt::QueuedConnection);
return a.exec();
}
You should set the progressBar value to 0 before showing it.
mProcessBarDlg->ui.progressBar->setValue(0);
mProcessBarDlg->show();
In this program I have built a QPropertyanimation and add to it my item and pos() property.
I override KeyPressEvent. And with using of keys consist of j, f, z item go forward ,go back and jump.
According gravity when item jump should fall. For this purpose I call down function. But item just once jump don't fall. I also have another problem: when the first press j and f (forward and back) item animate desirably but for next times item go forward and go back all of scene.
I mean It should animated for example 40 pixel but It animated 800 pixel.
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
QPoint start;
QPoint end;
~MainWindow();
private:
QGraphicsView* view;
QGraphicsScene* scene;
void keyPressEvent(QKeyEvent* k);
MyQgraphicsObject* m;
QPropertyAnimation* pr;
QElapsedTimer* timer;
int f;
int u;
int b;
void forward();
void up();
void back();
void down();
};
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
view=new QGraphicsView;
scene=new QGraphicsScene;
m=new MyQgraphicsObject;
pr=new QPropertyAnimation(m,"pos");
view->setScene(scene);
view->resize(800,800);
view->setFixedSize(800,800);
setCentralWidget(view);
scene->addItem(m);
start= QPoint(0,0);
f=30;
u=-30;
b=-30;
}
void MainWindow::keyPressEvent(QKeyEvent *k)
{
switch (k->key()) {
case Qt::Key_J: {
forward();
break;
}
case Qt::Key_Z: {
up();
down();
break;
}
case Qt::Key_F: {
back();
break;
}
default:
break;
}
}
void MainWindow::forward()
{
end.setX(f);
pr->setEndValue(end);
pr->setDuration(1000);
pr->setEasingCurve(QEasingCurve::Linear);
pr->start();
f+=40;
}
void MainWindow::up()
{
end.setY(u);
pr->setEndValue(end);
pr->setDuration(1000);
pr->setEasingCurve(QEasingCurve::Linear);
pr->start();
u-=30;
pr->pause();
}
void MainWindow::back()
{
end.setX(b);
pr->setEndValue(end);
pr->setDuration(1000);
pr->setEasingCurve(QEasingCurve::Linear);
pr->start();
b-=40;
}
void MainWindow::down()
{
u+=30;
end.setY(u);
pr->setEndValue(end);
pr->setDuration(1000);
pr->setEasingCurve(QEasingCurve::Linear);
pr->start();
}
You should not use resize and setFixedSize on view because you use it in setCentralWidget and its size will be managed by the layout. You should use setFixedSize on main window instead.
Animations are asynchonous. For example, when you call up(); down(), these functions will be executed without 1-second pause. Also, starting animation when it's already started has no effect.
Usually animations are used in such way when you know exactly where you need object to move in the next second. It's complicated to receive directives from user and change object's trajectory when the animation is already performing.
Here is an example showing correct use of animations for this task. Object can receive one directive (forward, back or jump) per second, and according animation will be performed in the next second.
Header:
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QGraphicsView* view;
QGraphicsScene* scene;
void keyPressEvent(QKeyEvent* k);
QGraphicsObject* object;
QPropertyAnimation* animation;
QPointF pos;
double speed;
enum Command {
command_none, command_jump, command_forward, command_back
};
Command next_command;
private slots:
void timeout();
};
Source:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
view = new QGraphicsView;
scene = new QGraphicsScene;
object = scene->addWidget(new QPushButton("test"));
object->setPos(0, -object->boundingRect().height());
animation = new QPropertyAnimation(object,"pos");
animation->setDuration(1000);
view->setScene(scene);
setFixedSize(800,800);
scene->addRect(-500, -200, 1000, 200);
setCentralWidget(view);
scene->addItem(object);
next_command = command_none;
speed = 100;
QTimer* timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(timeout()));
timer->start(1000);
}
MainWindow::~MainWindow() {}
void MainWindow::keyPressEvent(QKeyEvent *k)
{
switch (k->key()) {
case Qt::Key_J: {
next_command = command_forward;
break;
}
case Qt::Key_Z: {
next_command = command_jump;
break;
}
case Qt::Key_F: {
next_command = command_back;
break;
}
default:
break;
}
}
void MainWindow::timeout() {
//fall
if (pos.y() < 0) {
pos.setY(pos.y() + speed);
if (pos.y() >= 0) {
pos.setY(0);
}
}
//action
switch(next_command) {
case command_forward:
pos.setX(pos.x() + speed);
break;
case command_back:
pos.setX(pos.x() - speed);
break;
case command_jump:
if (pos.y() == 0) {
pos.setY(pos.y() - speed);
}
break;
default:
break;
}
next_command = command_none;
animation->stop();
animation->setEndValue(pos - QPointF(0, object->boundingRect().height()));
animation->start();
}
I need some help on the usage of Qtimer.
I work with Qt 5.0.2 and here my problem :
I am trying to develop a Timer, and the interface is simple :
There is just 2 button : the button "Start", to launch the timer, and the "Pause" Button, and a QtimeEdit to display the time.
This screenshot shows how it looks like : http://img834.imageshack.us/img834/1046/5ks6.png
The problem is that the pause function doesn't work. I have read all the documentation about Qtimer here : http://harmattan-dev.nokia.com/docs/library/html/qt4/qtimer.html and here : qt.developpez.com/doc/5.0-snapshot/qtimer/ , but no result.
This is the source code I have : (I put only what is needed)
// Creation of the Buttons and the time area
void MainWindow::createBottom()
{
bottom = new QWidget();
play = new QPushButton("Launch",this);
pause = new QPushButton("Pause",this);
play->setDisabled(false);
pause->setDisabled(true);
timeEdit = new QTimeEdit(this);
timeEdit->setDisplayFormat("mm:ss");
layout->addWidget(play);
layout->addWidget(pause);
layout->addWidget(timeEdit );
bottom->setLayout(layout);
connect(play, SIGNAL(clicked()), this, SLOT(startSimulation()));
connect(pause, SIGNAL(clicked()), this, SLOT(pauseSimulation()));
}
// to resume the timer where is was stopped
void MainWindow::resumeSimulation()
{
timer->blockSignals( false );
pause->setText("Pause");
pause->disconnect(SIGNAL(clicked()));
connect(pause, SIGNAL(clicked()), this, SLOT(pauseSimulation()));
paused = false;
timer->start();
int timeOfPause = time->restart();
int timeTotal = timeOfPause + timeElapsed;
time->addMSecs(-timeTotal);
}
// to Start the timer
void MainWindow::pauseSimulation()
{
timer->blockSignals(true);
pause->setText("Resume");
timer->stop();
play->setDisabled(false);
//pause->setDisabled(true);
pause->disconnect(SIGNAL(clicked()));
connect(pause, SIGNAL(clicked()), this, SLOT(resumeSimulation()));
paused = true;
}
// to Start the timer from zero.
void MainWindow::startSimulation()
{
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this , SLOT(updateTime()));
timer->start(500);
play->setDisabled(true);
pause->setDisabled(false);
}
void MainWindow::updateTime()
{
if(time == NULL)
{
time = new QTime(0,0,0,0);
time->start();
}
//timeEdit->setTime(QTime::fromS(time->elapsed()));
//time = &(time->addMSecs(1000));
if(hasRestart)
{
time->restart();
time->addMSecs(-timeElapsed);
hasRestart = false;
}
else
{
timeElapsed =+ time->elapsed();
}
int seconds = 0;
int minutes = 0;
int hours = 0;
if(!paused)
{
seconds = (timeElapsed/1000)%60;
minutes = (timeElapsed/60000)%60;
hours = (timeElapsed/3600000)%24;
std::cout << "Test : " << hours << ":" << minutes << ":" << seconds << std::endl;
timeEdit->setTime(QTime(0,minutes,seconds,0));
timeEdit->update();
}
}
When I push the Start button, the timer starts well, but when I push "Pause" it only pause it on the graphic interface, but when I resume, it shows the present time as if it hadn't paused.
For instance :
I start.
I pause at 00:05. It blocks apparently the timer.
I wait for 10 seconds. I resume the timer, it shows 00:15 instead of 00:06
How could I fix that ?
Thank you !
EDIT : Thanks Kuba Ober, but could you explain me the code you posted please ?
How does the pause work ?
Below is a SSCCE, tested under both Qt 4.8 and 5.1.
//main.cpp
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <QElapsedTimer>
#include <QTime>
class Window : public QWidget {
Q_OBJECT
int m_timerId;
qint64 m_accumulator;
QLabel *m_label;
QElapsedTimer m_timer;
Q_SLOT void on_restart_clicked() {
m_accumulator = 0;
m_timer.restart();
if (m_timerId == -1) m_timerId = startTimer(50);
}
Q_SLOT void on_pause_clicked() {
if (m_timer.isValid()) {
m_accumulator += m_timer.elapsed();
m_timer.invalidate();
} else {
m_timer.restart();
m_timerId = startTimer(50);
}
}
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timerId) {
QWidget::timerEvent(ev);
return;
}
QTime t(0,0);
t = t.addMSecs(m_accumulator);
if (m_timer.isValid()) {
t = t.addMSecs(m_timer.elapsed());
} else {
killTimer(m_timerId);
m_timerId = -1;
}
m_label->setText(t.toString("h:m:ss.zzz"));
}
public:
explicit Window(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), m_timerId(-1) {
QVBoxLayout * l = new QVBoxLayout(this);
QPushButton * restart = new QPushButton("Start");
QPushButton * pause = new QPushButton("Pause/Resume");
restart->setObjectName("restart");
pause->setObjectName("pause");
m_label = new QLabel("--");
l->addWidget(restart);
l->addWidget(pause);
l->addWidget(m_label);
QMetaObject::connectSlotsByName(this);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"
QTime totalTime, sinceStart;
void MainWindow::createBottom()
{
bottom = new QWidget();
play = new QPushButton("Launch",this);
pause = new QPushButton("Pause",this);
play->setDisabled(false);
pause->setDisabled(true);
timeEdit = new QTimeEdit(this);
timeEdit->setDisplayFormat("mm:ss");
layout->addWidget(play);
layout->addWidget(pause);
layout->addWidget(timeEdit);
bottom->setLayout(layout);
connect(play, SIGNAL(clicked()), this, SLOT(startSimulation()));
connect(pause, SIGNAL(clicked()), this, SLOT(pauseSimulation()));
connect(this, SIGNAL(timeChanged(QTime)), timeEdit, SLOT(setTime(QTime)));
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this , SLOT(updateTime()));
}
void MainWindow::updateTime() {
emit timeChanged(totalTime.addMSecs(sinceStart.elpased()));
}
void MainWindow::resumeSimulation() {
sinceStart.restart();
timer->start();
}
void MainWindow::pauseSimulation() {
timer->stop();
totalTime = totalTime.addMSecs(sinceStart.restart());
emit timeChanged(totalTime);
}
I made a Timer class for the start, stop, pause and resume
class MyTimer
{
public:
MyTimer();
QTime m_qtime;
int m_accumulator;
void start();
int stop();
void pause();
void resume();
};
MyTimer::MyTimer()
:m_accumulator(0), m_qtime(QTime())
{
}
void MyTimer::start()
{
m_qtime.start();
m_accumulator = 0;
}
int MyTimer::stop()
{
if(!m_qtime.isNull())
{
int l_elapsedTime = m_qtime.elapsed();
m_accumulator += l_elapsedTime;
}
m_qtime = QTime();
return m_accumulator;
}
void MyTimer::pause()
{
if(!m_qtime.isNull())
{
int l_elapsedTime = m_qtime.elapsed();
m_accumulator += l_elapsedTime;
}
}
void MyTimer::resume()
{
if(!m_qtime.isNull())
{
m_qtime.restart();
}
}