I'm working with Qt (v 5.3) again after sometime away. As a learning exercise I am prototyping a module using QGraphicView in order to understand better how to use it.
The functionality is simple: I have several QGraphicObjects which function like buttons with states and behaviors:
OFF - image panel hidden; default button art
ON - image panel
displayed; highlight button art
At startup all buttons are in an OFF state
A clicked button will toggle it's state (non-Exclusive)
If a different button is already in an ON state, it must be turned off (Exclusive)
Everything is data driven and created dynamically at runtime.
So that is my little learning exercise. What I am trying to sort out is an efficient messaging mechanism to handle the "radio button sometimes" exclusive behavior and sending a message to a group of objects without strongly coupling them.
I've looked at signals and slots but that gets tedious if there are many connections to make.
QSignalMapper which seems somewhat better
QEvent is probably the most solid approach but I've had trouble finding good learning examples.
I am making this more complicated than need be but as I say, it's a learning exercise to get used to Qt again.
So my question (finally): is there an approach I am overlooking or one of these mentioned which would be better (e.g most flexible, maintainable, scalable). Not looking for code per se, but an understanding of how to go about coding something like this using the Qt framework.
Here's an image of the thing:
Signal and slots is the method I would use here. However, rather than connect each button to every other button, you can create an intermediate, Controller class.
The Controller class can either aggregate, or be a parent to all of the buttons.
When a button is created, it connects signals to the controller to inform it of being pressed. The receiving slot in the Controller class is then responsible for turning off any other buttons.
Skeleton code: -
class ButtonController : public QGraphicsObject
{
public slots:
void ButtonPressed();
private:
QList<MyButton*> m_buttonList;
};
void ButtonController::ButtonPressed()
{
foreach(MyButton* pButton, m_buttonList)
{
if(pButton != sender())
{
pButton->Off();
}
}
}
In the constructor of MyButton, assuming the controller is the parent: -
MyButton::MyButton(ButtonController* parent)
: QGraphicsObject(parent);
{
connect(this, &MyButton::pressed(), parent, &ButtonController::ButtonPressed());
...
}
Alternatively, the controller may just hold a list of the buttons, in which case you can do this: -
MyButton::MyButton(QObject* parent, ButtonController* pButtonController)
: QGraphicsObject(parent);
{
connect(this, &MyButton::pressed(), pButtonController, &ButtonController::ButtonPressed());
...
}
In the case of not being able to change the constructor of the buttons, the controller may simply have an AddButton function, in which it creates the connection when the button is added under its control.
Subclass QGraphicsScene
Reimplement mousePressEvent and check is left mouse button pressed.
If so, then get position of mouse by pos() method and get current graphics object under arrow by itemAt() method.
Try to cast it into your needed object, if it was successfully, then change background and other.
Store your graphics objects in vector, in this case you can check every object and decide which object should be turned off.
Fully working example:
header:
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QPoint>
#include <QMouseEvent>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
signals:
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
public slots:
private:
QVector<QGraphicsEllipseItem * > vec;
};
#endif // GRAPHICSSCENE_H
cpp:
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsItem>
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
//set circles in different positions.
vec.push_back(addEllipse(0,0,50,50,QPen(Qt::red),QBrush(Qt::blue)));
vec.push_back(addEllipse(0+100,0+100,50,50,QPen(Qt::red),QBrush(Qt::blue)));
vec.push_back(addEllipse(0+150,0+150,50,50,QPen(Qt::red),QBrush(Qt::blue)));
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
//qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QGraphicsItem *item = itemAt(mouseEvent->scenePos(), QTransform());
QGraphicsEllipseItem *ell = qgraphicsitem_cast<QGraphicsEllipseItem *>(item);
if(ell)
{
for(int i = 0; i < vec.size(); i++)
{
if(vec.at(i) != ell)
{
vec.at(i)->setBrush(QBrush(Qt::blue));//disable all other bettons
}
}
ell->setBrush(QBrush(Qt::black));//to chosen circle
qDebug() << "good";
}
else
qDebug() << "not ell" << mouseEvent->scenePos();
}
}
Usage:
GraphicsScene *scene = new GraphicsScene(this);
ui->graphicsView->setScene(scene);
With this case, your circles will be similar to radioButtons. If you want save state of all buttons you can use:
for(int i = 0; i < vec.size(); i++)
vec.at(i)->setData(0,false);
And get state with
if(!ell->data(0).toBool())
//do
I need to post an Event from an thread to an QtreeView in my Mainwindow. Now to post an event we need the pointer to the Qobject (i.e. our qtreeview inside main window).
For this i have to take the pointer to my mainwindow in constructor MainWindow::MainWindow().
sharedobj.h file contains pointer to mainwindow :----
#include "ui_mainwindow.h"
/*!
Shared object among threads.
*/
class cntrlObj
{
public:
cntrlObj();
~ctrlObj();
/// Thread run control
bool m_bQuit;
/*!
Pointer to mainwindow window
*/
Ui::MainWindow *ui;
}
Mainwindow class contains following object :---
cntrlObj cntrlObj_obj;
Mainwindow constructor :-----
/*!
Take Pointer to Mainwindow session window
*/
cntrlObj_obj->ui_ptr->setupUi(this);
Please suggest is it an right way to take an pointer to Mainwindow ?
Also can i include file "ui_mainwindow.h" inside another sharedobj.h file to access namespace UI of mainwindow to declare an pointer as i have done in "sharedobj.h" file ?
You need to localize the knowledge within the Mainwindow, and post the event to it.
So:
Post the event to the Mainwindow instance.
Reimplement MainWindow::customEvent(...) as follows (if it's a custom QEvent, otherwise you'd reimplement event(...):
void MainWindow::customEvent(QEvent * ev) {
if (ev->type() == MyEventType) {
QCoreApplication::sendEvent(ui->treeView, ev);
}
}
Alas, why on Earth would you need to send an event to a view?
I have a QMainWindow object parent to a QDialog Object. When I call the QDialog with exec() it stays open, but I can't use the QMainWindow at the same time. If I use show() instead, the QDialog opens and hides immediately after.
I know this relates to the modality flag of the QDialog, but it does not have a Qt::Modeless flag, so I'm a bit lost.
Question: How can I display a QDialog and still be able to interact with its parent QMainWindow?
My code for the QDialog object:
class catalog : public QDialog
{
Q_OBJECT
public:
explicit catalog(QWidget *parent = 0);
~catalog();
private:
Ui::catalog *ui;
};
How I'm calling it:
void DiagramWindow::showCatalog()
{
catalog catalog(this);
catalog.exec();
}
It closes, because QDialog::show() method is asynchronous and your catalog object is destroyed right after your code leaves DiagramWindow::showCatalog() method. You should rewrite it like this:
void DiagramWindow::showCatalog()
{
catalog *c = new catalog(this);
c->setAttribute(Qt::WA_DeleteOnClose);
c->show();
}
I have a QMainWindow with this flag :
this->setWindowFlags(Qt::SubWindow);
How do to forbid the window moving, and this, keeping this window style ?
I don't think there is a cross-os Qt way to achieve this when using the standard window controls.
You can try stuff like:
class Widget : public QWidget {
Q_OBJECT
public:
Widget()
: fixed_pos_(QPoint(100, 100)) {
setWindowFlags(Qt::SubWindow);
}
void SetFixedPos(const QPoint& pos) {
fixed_pos_ = pos;
}
protected:
void moveEvent(QMoveEvent* ev) {
if (ev->pos() != fixed_pos_)
move(fixed_pos_);
}
private:
QPoint fixed_pos_;
};
These have a few issues like flicker, does not update until Mouse-release and so on that's also different per OS.
Most efficient way is to just make your Window a Qt::FramelessWindowHint and render a titlebar yourself. That way you can pretty much do what you want when it comes to handling events on that titlebar.
I'm trying to create custom widget inheriting QFrame. All works fine, but I'm unable to draw the focus rectangle around my widget. Below is the sample code I use for drawing:
frame.h
class Frame : public QFrame {
Q_OBJECT
public:
Frame(QWidget *parent = 0);
~Frame();
protected:
void paintEvent(QPaintEvent *event);
private:
Ui::Frame *ui;
};
frame.cpp
Frame::Frame(QWidget *parent) :
QFrame(parent),
ui(new Ui::Frame)
{
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
}
Frame::~Frame()
{
delete ui;
}
void Frame::paintEvent(QPaintEvent *event)
{
QFrame::paintEvent(event);
if (hasFocus()) {
QStylePainter painter(this);
QStyleOptionFocusRect option;
option.initFrom(this);
option.backgroundColor = palette().dark().color();
painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
}
}
What I mean by 'unable to draw focus frame' is that when you click a standard widget that accepts focus (let's say QLineEdit), it has a blue rectangle drawn around it. When I click my widget there is no such rectangle drawn. Are there any more things I should do besides setting focusPolicy on my widget?
It might have something to do with the style your app is using. When I try your code with the "gtk" and "cleanlooks" style, no focus rectangle is drawn. With "plastique" and "windows" it is. Since I'm on Linux, I cannot test "windowsxp" and "macintosh". Try running with the -style option and see what happens.
try also
setFocusPolicy(Qt::StrongFocus);
setAttribute( Qt::WA_MacShowFocusRect);