I'm working on a tree view for a user-defined class called Family_tree with nodes having a pointer to their father, mother, spouse and a vector of children. I created a TreeViewNode class derived from QGraphicsItem with a node* m_node member and overrides for the boundingRect and paint methods like so:
QRectF TreeViewNode::boundingRect() const {
return QRectF(-50, -50, 100, 100);
}
void TreeViewNode::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
qDebug() << "Paint function called";
painter->drawRect(boundingRect());
painter->drawText(boundingRect(), Qt::AlignCenter, QString::fromStdString(m_node->getPatient().get_Name()));
for (auto child : m_node->getChildren()) {
TreeViewNode* childNode = new TreeViewNode(child, this);
childNode->setPos(0, 100);
QLineF line(0, 50, 0, 150);
painter->drawLine(line);
}
}
I've set up a Family_tree with just one node for simplicity (the root) and I'm trying to get it to show in the view in MainWindow but I don't see paint getting called at all despite me adding the root to the scene (scene.item().count() returns 1..) and the view is simply blank. Am I doing something blatantly wrong here? Thank you for your time!
//MainWindow.cpp
TreeViewNode* root = new TreeViewNode(family->get_root());
view = new QGraphicsView(); QGraphicsScene scene; view->setScene(&scene);
scene.addItem(root); root->setPos(100, 100); view->update();
I want to create grid layout with text items and I want this layout to be updated after added text to text item, but this not work.
Here is fragment of my source code:
MyItem::MyItem(const QString &text, QGraphicsLayout* layout, QGraphicsItem *parent):
QGraphicsLayoutItem(),
QGraphicsTextItem(parent),
mLayout(layout)
{
setGraphicsItem(this);
setHtml(text);
setFlag(QGraphicsItem::ItemIsSelectable, true);
}
void MyItem::setGeometry(const QRectF &geom)
{
prepareGeometryChange();
QGraphicsLayoutItem::setGeometry(geom);
setPos(geom.topLeft());
}
QSizeF MyItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
return boundingRect().size();
}
void MyItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::LeftButton && textInteractionFlags() == Qt::NoTextInteraction) {
setTextInteractionFlags(Qt::TextEditorInteraction);
}
QGraphicsTextItem::mousePressEvent(event);
}
void MyItem::focusOutEvent(QFocusEvent *event)
{
setTextInteractionFlags(Qt::NoTextInteraction);
auto cursor = textCursor();
cursor.clearSelection();
setTextCursor(cursor);
QGraphicsTextItem::focusOutEvent(event);
}
void MyItem::keyPressEvent(QKeyEvent *event)
{
QGraphicsTextItem::keyPressEvent(event);
qDebug() << boundingRect();
updateGeometry();
mLayout->activate();
}
Result is that when i add text to text item and his width is growing the next cell is not moving to make a place for first cell:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto scene = new QGraphicsScene;
auto layout = new QGraphicsGridLayout;
auto t1 = new MyItem("cell1", layout);
layout->addItem(t1, 0, 0);
auto t2 = new MyItem("cell2", layout);
layout->addItem(t2, 0, 1);
auto container = new QGraphicsWidget;
container->setLayout(layout);
scene->addItem(container);
auto view = new QGraphicsView(scene);
setCentralWidget(view);
resize(800, 600);
}
Adding mLayout->updateGeometry(); in void MyItem::keyPressEvent(QKeyEvent *event) method solved my problem.
I have subclassed both a Qgraphicsscene and a Qgraphicsitem, it seems it works but trying to remove the items by subclass recognition don't works.
This delete the items:
void debugSceneItemscuatrobis()
{
QList<QGraphicsItem *> allitems = items();
foreach(auto item, allitems) {
removeItem(item);
}
}
But this doesn't, it recognizes there are items but doesn't remove them, tryed different posseibilities but couldn't make it works.
void debugSceneItemscuatrotris()
{
QList<QGraphicsItem *> allitems = items();
foreach(auto item, allitems) {
if(item->type() == chord::Type) {
removeItem(item);
delete item;
}
}
}
This is how the items were added by the qgraphicsitem subclass:
void chord::addchord(QPointF sp)
{
scene()->addLine(sp.x(), sp.y(), sp.x()+10, sp.y()+10);
QList<int> midics = {10, 30, 40};
for(int i = 0; i < midics.length(); i++)
{
QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem("n");
item->setFont(QFont("omheads", 20));
item->setPos(sp.x(), sp.y()+midics[i]);
scene()->addItem(item);
coso.append(item);
}
}
Sorry, I'm very newbie and no programmer, those are my first subclasses. Someone knows how it could be approached? Thanks. :-)
Without seeing more of your code I'm only guessing. But that guess would be that when you remove an item of type chord you are still able to see the various QGraphicsItems that were added to the scene in chord::addchord. If so it's probably due to the lack of any parent/child relationship between the chord and those items: from the documentation for QGraphicsScene::removeItem(item)...
Removes the item item and all its children from the scene.
Try creating the parent/child relationship explicitly by changing your chord:addchord implementation to...
void chord::addchord (QPointF sp)
{
auto *line = scene()->addLine(sp.x(), sp.y(), sp.x() + 10, sp.y() + 10);
line->setParentItem(this);
QList<int> midics = { 10, 30, 40 };
for (int i = 0; i < midics.length(); i++)
{
QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem("n", this);
item->setFont(QFont("omheads", 20));
item->setPos(sp.x(), sp.y() + midics[i]);
scene()->addItem(item);
coso.append(item);
}
}
It might not solve all of the issues but should (hopefully) head you in the right direction.
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();
}
For my GUI i would like to have two pairs of buttons that scroll up and down a scrollarea. The first set of buttons should work on say scrollarea1 and the second set of buttons should work on a scrollarea2. The widgets that I put in the scrollarea are called viewport1 and viewport2.
Since both both set of buttons should do the same (scrolling up and down) I thought I would make two slots called scrollUp and scrollDown that would handle the scrolling for both sets of buttons. Unfortunately I cannot make this work and need some help. I have tried the following:
QPushButton up;
QPushButton down;
QPushButton up2;
QPushButton down2;
connect(&up,SIGNAL(clicked()),&up,SLOT(scrollUp()));
connect(&up2,SIGNAL(clicked()),&up,SLOT(scrollUp()));
connect(&down,SIGNAL(clicked()),&down,SLOT(scrollDown()));
connect(&down2,SIGNAL(clicked()),&down,SLOT(scrollDown()));
void MainWindow::scrollDown()
{
QScrollArea area;
QWidget view;
if((QPushButton) &sender = down)
{
area=scrollArea;
view=viewport;
}
if((QPushButton) &sender = down2)
{
area=scrollArea;
view=viewport;
}
int curpos = area.verticalScrollBar()->value();
area.verticalScrollBar()->setValue(curpos+15);
int newpos = area.verticalScrollBar()->value();
QPoint topLeft = area.viewport()->rect().topLeft();
view.move(topLeft.x(),topLeft.y()-(newpos));
}
void MainWindow::scrollUp()
{
QScrollArea area;
QWidget view;
if((QPushButton) &sender = up)
{
area=scrollArea;
view=viewport;
}
if((QPushButton) &sender = up2)
{
area=scrollArea2;
view=viewport2;
}
int curpos = area.verticalScrollBar()->value();
area.verticalScrollBar()->setValue(curpos-15);
int newpos = area.verticalScrollBar()->value();
QPoint topLeft = area.viewport()->rect().topLeft();
view.move(topLeft.x(),topLeft.y()-(newpos));
}
But this doesn´t work for several reasons. I also tried giving the slot some arguments, something like:
connect(&up,SIGNAL(clicked()),&up,SLOT(scrollUp(scrollarea1,viewport1)));
connect(&up2,SIGNAL(clicked()),&up,SLOT(scrollUp(scrollarea2,viewport2)));
But again, no succes. Can anybody help me?
First of all, "It doesn't work" does not mean anything, and it is hard to help you if you do not say what errors you get. Then, there are few problems.
All QObject's derived classes are not copiable, it means you can not do
QWidget a;
QWidget b;
b = a; // Wrong
You should use pointers (or perhaps references).
QWidget a;
QWidget * b = new QWidget(...);
QWidget * c;
c = & a; // Ok
c = b; // Ok
Then your connect calls are wrong:
connect(&up, SIGNAL(clicked()), &up, SLOT(scrollUp()));
The third argument is the object who has the slot. up is a QPushButton, it does not have a scrollUp() slot, it is your MainWindow who does:
connect(&up, SIGNAL(clicked()), this, SLOT(scrollUp()));
(since connect is called in MainWindow's constructor this points to the current MainWindow object).
Also in C++ the single = sign means assignment, for equality comparison use =='. Andsender` is a function.
Your approach should work if implemented in the right way:
class MainWindow: public QWidget
{
QScrollArea * scroll1;
QScrollArea * scroll2;
QWidget * view1;
QWidget * view2;
QPushButton * up1;
QPushButton * up2;
QPushButton * down1;
QPushButton * down2;
public:
MainWindow()
{
// Here initialize member variables.
...
connect(up1, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(up2, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(down1, SIGNAL(clicked()), this, SLOT(scrollDown()));
connect(down2, SIGNAL(clicked()), this, SLOT(scrollDown()));
}
public slots:
void scrollDown()
{
QScrollArea * area;
QWidget * view;
if(qobject_cast<QPushButton>(sender()) == down1) {
area = & scroll1;
view = & view1;
} else if(qobject_cast<QPushButton>(sender()) == down2) {
area = & scroll2;
view = & view2;
} else {
// Error.
}
// Now `area` and `view` point to the right widgets.
...
}
void scrollUp()
{
// The same as before.
}
};
Another approach would be to extract the actual scrolling instructions to a separate function:
class MainWindow: public QWidget
{
// Same variables as before
...
public:
MainWindow()
{
// Here initialize member variables.
...
connect(up1, SIGNAL(clicked()), this, SLOT(scrollUp1()));
connect(up2, SIGNAL(clicked()), this, SLOT(scrollUp2()));
connect(down1, SIGNAL(clicked()), this, SLOT(scrollDown1()));
connect(down2, SIGNAL(clicked()), this, SLOT(scrollDown2()));
}
public slots:
void scrollDown(QScrollArea * area, QWidget * view)
{
// Here you scroll over `area` and `view`.
}
void scrollDown1()
{
scrollDown(scroll1, area1);
}
void scrollDown2()
{
scrollDown(scroll2, area2);
}
// Again, the same for `scrollUp`.
};
There are several mistakes in your code :
About the sender of the signal : There is not a QObject called "sender" but a method QObject * QObject::sender() const; which returns a pointer on the sender of the signal.
In the if conditions : you are casting a QPushButton** into a QPushButton ((QPushButton) &sender) and you dont compare that thing with your buttons up(2) and down(2).
In your connections between slots and signals : the scrollUp and scrollDown slots do not belong to the QPushButton class but to your MainWindow class.
Finally, you should write something like this :
connect(&up, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(&up2, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(&down, SIGNAL(clicked()), this, SLOT(scrollDown()));
connect(&down2, SIGNAL(clicked()), this, SLOT(scrollDown()));
void MainVindow::scrollDown() {
// [...]
QPushButton * senderButton = qobject_cast<QPushButton *>(this->sender());
// QPushButton * senderButton = (QPushButton *) this->sender(); works too
if (senderButton == &down) {
// [...]
}
if (senderButton == &down2) {
// [...]
}
// [...]
}
void MainVindow::scrollUp() {
// [...]
QPushButton * senderButton = qobject_cast<QPushButton *>(this->sender());
// QPushButton * senderButton = (QPushButton *) this->sender(); works too
if (senderButton == &up) {
// [...]
}
if (senderButton == &up2) {
// [...]
}
// [...]
}
First of all the slot can have no other arguments than the signal hands to it. Clicked has no arguments and there fore the slot can have no arguments.
I would think that the easiest way to check whether scrollArea 1 or 2 has focus and decide from that which one should move.
I also think that there is an error in your code. Shouldn't this:
if((QPushButton) &sender = down2)
{
area=scrollArea;
view=viewport;
}
Be this:
if((QPushButton) &sender = down2)
{
area=scrollArea2;
view=viewport2;
}
First of all, this is pseudo code. It won't compile, but it should contain the necessary information.
I believe this problem can be most elegantly solved using the QSignalMapper class. It allows parameterless signals from multiple senders to connect to one slot.
In the header, write something like this:
class QSignalMapper;
class MainWindow : public QMainWindow
{
public:
void init();
public slots:
void handleScrollButtons(int id);
private:
enum { ScrollUp1, ScrollDown1, ScrollUp2, ScrollDown2 } // just makes it more convenient to use
QSignalMapper *m_scrollbuttonhandler;
}
In the source file, write something like this
#include <QSignalMapper>
void MainWindow::init()
{
m_scrollbuttonhandler = new QSignalMapper(this);
m_scrollbuttonhandler->setMapping(scrollup1button, ScrollUp1);
m_scrollbuttonhandler->setMapping(scrolldown1button, ScrollDown1);
m_scrollbuttonhandler->setMapping(scrollup2button, ScrollUp2);
m_scrollbuttonhandler->setMapping(scrolldown2button, ScrollDown2);
connect(scrollup1button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrolldown1button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrollup2button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrolldown2button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(m_scrollbuttonhandler, SIGNAL(mapped(int)), this, SLOT(handleScrollButtons(int)));
}
void MainWindow::handleScrollButtons(int id)
{
switch (id)
{
case ScrollUp1:
// stuff to do for scrollup1button
case ScrollDown1:
// stuff to do for scrolldown1button
case ScrollUp2:
// stuff to do for scrollup2button
case ScrollDown2:
// stuff to do for scrolldown2button
}
}