How to force widgets in QGridLayout be not equally spaced? - c++

qGridLayout.addWidget(button1, 3,0,1,1, Qt::AlignBottom);
//qGridLayout.addWidget(button2, 3,1,1,1, Qt::AlignBottom);
// is there a way to make button1/3/4 stay at cell 1/3/4
// even if button2 is hidden
qGridLayout.addWidget(button3, 3,2,1,1, Qt::AlignBottom);
qGridLayout.addWidget(button4, 3,3,1,1, Qt::AlignBottom);
The problem I'm having is that, when I hide button2, button1/3/4 becomes automatically equally spaced.
I tried
qGridLayout.addItem(new QSpacerItem,3,1,1,1, Qt::AlignBottom);
but it doesn't work.

qGridLayout.addItem(new QSpacerItem((1,
1,
QSizePolicy::Expanding,
QSizePolicy::Preferred),
3,1,1,4,
Qt::AlignTop);
My current solution is to add a spacer item above the row of buttons to force it to have 4 equally spaced cells(2nd cell doesn't shrink to nothing even if button2 is hidden).
The reason not to put spacer directly into button2's position is that the spacer will disrupt the layout when button2 is shown and co-exist with the spacer.

Your solution looks fine for me. But, If you wish to get this done without adding a new row to your grid layout, you can use following class.
class Hider : public QObject
{
public:
Hider(QObject* pParent = 0) : QObject(pParent) {}
bool eventFilter(QObject* pObject, QEvent* pEvent)
{
return pEvent->type() == QEvent::Paint;
}
void hide(QWidget* pWidget)
{
pWidget->installEventFilter(this);
pWidget->update();
}
void unhide(QWidget* pWidget)
{
pWidget->removeEventFilter(this);
pWidget->update();
}
};
Keep a Hider object as a class variable. When you want to hide the button,
hider->hide(button2);
And when you want to show button again,
hider->unhide(button2);
Hider filters out all paint events of the widget it hides, making the widget not to be drawn but widget keeps its space in the layout.

Related

Qt how to use paintEvent function to refresh two or more widgets silmultaneously

Now I am using Qt5 to draw Kline of stocks. Two classes are defined for price bar and volume bar, then I add them to a widget by a QSplitter. However, When I move mouse forward or backward and the price-bar chart updates, the volume-bar section will not repaint simultaneously until I put mouse under volume widget. I tried some ideas, but it didn't work. Many thanks.
A part of codes like this:
pvolume = new VolumeBar(this);
pvolume->setObjectName(tr("volume_bar"));
pvolume->setFocusPolicy(Qt::StrongFocus);
pkline = new PriceBar(this);
pkline->setObjectName("price_bar");
pkline->setFocusPolicy(Qt::StrongFocus);
QSplitter *splitterMain = new QSplitter(Qt::Vertical, 0);
splitterMain->insertWidget(0, pkline);
splitterMain->insertWidget(1, pvolume);
splitterMain->setStretchFactor(0, 4);
splitterMain->setStretchFactor(1, 1);
For pricebar, the paintEvent function is overrided as follows,
void PriceBar::paintEvent(QPaintEvent *event)
{
KLineGrid::paintEvent(event); // parent of VolumeBar and PriceBar
drawLine(); // draw price bar
}
For VolumeBar, it is overrided like this,
void VolumeBar::paintEvent(QPaintEvent *event)
{
KLineGrid::paintEvent(event);
drawYtick(); // y axis
drawVolume();
}

How to adjust QTextEdit to fit it's contents

I'm developing a Qt Application and I'm trying to find a way to use QTextEdit as a label with long text without the scroll bar. In my ui I have a QScrollArea and inside of it I want to place a couple off QTextEdit widgets and I only want use scrolling inside QScrollArea. Problem is that no matter how I try to resize the QTextEdit it seems it has a maximum height and cuts of text, even if I set the size manually and QTextEdit::size returns the correct value.
I did the same thing with QLabel and it works fine, but in this case I need some methods that are only provided in QTextEdit.
I found this post:
Resizing QT's QTextEdit to Match Text Height: maximumViewportSize()
And the answer given was the following:
I have solved this issue. There were 2 things that I had to do to get
it to work:
Walk up the widget hierarchy and make sure all the size policies made
sense to ensure that if any child widget wanted to be big/small, then
the parent widget would want to be the same thing.
This is the main
source of the fix. It turns out that since the QTextEdit is inside a
QFrame that is the main widget in a QScrollArea, the QScrollArea has a
constraint that it will not resize the internal widget unless the
"widgetResizable" property is true. The documentation for that is
here: http://doc.qt.io/qt-4.8/qscrollarea.html#widgetResizable-prop.
The documentation was not clear to me until I played around with this
setting and got it to work. From the docs, it seems that this property
only deals with times where the main scroll area wants to resize a
widget (i.e. from parent to child). It actually means that if the main
widget in the scroll area wants to ever resize (i.e. child to parent),
then this setting has to be set to true. So, the moral of the story is
that the QTextEdit code was correct in overriding sizeHint, but the
QScrollArea was ignoring the value returned from the main frame's
sizeHint.
The problem is that I have no idea how to access the QTextEdit's QScrollArea to enable widgetResizable. Can anyone explain how I can achieve this or suggest a different way of resizing QTextEdit to perfectly fit it's content?
This will allow the height of the text box to change as required. You can edit the code a little to handle the width as well.
connect( m_textField, SIGNAL( textChanged() ), this, SLOT( onTextChanged() ) );
void MyClass::onTextChanged()
{
QSize size = m_textField->document()->size().toSize();
m_textField->setFixedHeight( size.height() + 3 );
}
Try this one :
QTextEdit textEdit;
textEdit.setHtml("<p>test test test test test test</p><p>|||||||||</p>");
textEdit.show();
textEdit.setFixedWidth(textEdit.document()->idealWidth() +
textEdit.contentsMargins().left() +
textEdit.contentsMargins().right());
Without a concrete example it's difficult to judge, but... it sounds as if you simply want a QTextEdit whose sizeHint depends on the current document size.
class text_edit: public QTextEdit {
using super = QTextEdit;
public:
explicit text_edit (QWidget *parent = nullptr)
: super(parent)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
virtual QSize sizeHint () const override
{
QSize s(document()->size().toSize());
/*
* Make sure width and height have `usable' values.
*/
s.rwidth() = std::max(100, s.width());
s.rheight() = std::max(100, s.height());
return(s);
}
protected:
virtual void resizeEvent (QResizeEvent *event) override
{
/*
* If the widget has been resized then the size hint will
* also have changed. Call updateGeometry to make sure
* any layouts are notified of the change.
*/
updateGeometry();
super::resizeEvent(event);
}
};
Then use as...
QScrollArea sa;
sa.setWidgetResizable(true);
text_edit te;
te.setPlainText(...);
sa.setWidget(&te);
sa.show();
It appears to work as expected in the few tests I've done.
In ui i defined QTextEdit *textEdit object. I write it as height scalable-content :
int count = 0;
QString str = "";
// set textEdit text
ui->textEdit->setText("hfdsf\ncsad\nfsc\dajkjkjkjhhkdkca\n925");
str = ui->textEdit->toPlainText();
for(int i = 0;i < str.length();i++)
if(str.at(i).cell() == '\n')
count++;
// resize textEdit (width and height)
ui->textEdit->resize(ui->textEdit->fontMetrics().width("this is the max-length line in qlabel")
, ui->textEdit->fontMetrics().height() * (count + 2));
Notice : this work if you change QTextEdit font face or size! just in height scalable (before every thing set your QTextEdit frameShape to BOX).
if you want do width scalable-content, you should do these steps :
read QTextEdit(textEdit object) text as line to line
calculate every line length
select maximum of line length
use of QTextEdit::fontMetrics().width(QString str) for investigate str size in width
I hope this can help you...

How to put QGraphicsEffect on QScrollBar inside QScrollArea?

I try to set QGraphicsDropShadowEffect on a QScrollBar. This code works:
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setColor(Qt::red);
dse->setOffset(0);
ui->verticalScrollBar->setGraphicsEffect(dse); // verticalScrollBar is `QScrollBar`.
However the following does not:
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setColor(Qt::red);
dse->setOffset(0);
ui->scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
ui->scrollArea->verticalScrollBar()->setGraphicsEffect(dse);
In the second example code I try to set effect on a slider inside QScrollArea but it does not apply to it. However, it can be applied to the whole scrollArea by ui->scrollArea->setGraphicsEffect(dse). What am I doing wrong?
The problem I had was caused by parent widget of QScrollBar. So, QScrollArea has item area and scroll bar area. Scroll bar area contains QWidgets and QScrollBars actually placed on these QWidgets. So, to make this work I actually had to set effect for the parent widget:
for(auto *child : ui->scrollArea->findChildren<QScrollBar*>()) {
if (child->orientation() == Qt::Vertical) {
auto * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setColor(Qt::red);
dse->setXOffset(-3);
dse->setYOffset(0);
child->parentWidget()->setGraphicsEffect(dse);
qDebug() << child->metaObject()->className(); // QScrollBar
qDebug() << child->parentWidget()->metaObject()->className(); // QWidget
}
}

copying QGraphicsItem from one QGraphicsScene to another, items snap to (0,0)

I am trying to create items in one panel and add to a second panel. In the second panel I want them to be movable (and have context menu). The AddItem button should add the item from the RenderArea, to the existing list of items in the CollectionView (which may already have been moved)
In the code below, the ShapeView::addItem() is supposed to create a copy of the item from the RenderArea (where it can change shape, color etc but is not movable, starts at (0,0)), place it in the CollectionView, where it is movable. The RenderArea holds one item - once added to CollectionView, the RenderArea item should reset.
What is happening... I can't seem to be able to separate the item from the two classes.
When I add item, even if the items in
CollectionView have moved, their position resets to 0,0 (or whatever the initial position was in the RenderArea; but the adding
works properly, the individual item properties are correct, like shape and color; also - CollectionView items move, RenderArea item
doesn't).
I am posting all the relevant code:
class Item : public QGraphicsItem
{
Item() { setFlag(ItemIsMovable, false); }
Item::Item(Item &copyItem) { // copy constructor
setFlag(ItemIsMovable);
setPos(copyItem.pos()); // doesn't seem to work
QRectF boundingRect() const { return QRectF(-35, -30, 35, 20); }
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) {
painter->drawRect(boundingRect()); }
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsItem::mouseMoveEvent(event);
}
};
class CollectionView : public QGraphicsView
{
Q_OBJECT
public:
CollectionView(QWidget *parent = 0);
void update();
QList<Item*> *m_items;
};
CollectionView::CollectionView(QWidget *parent)
: QGraphicsView(parent)
{
QGraphicsScene *s = new QGraphicsScene(this);
s->setSceneRect(-width()/2, -height()/2, width(), height());
setScene(s);
setViewportUpdateMode(BoundingRectViewportUpdate);
m_items = new QList<Item*>();
scene()->clear();
}
void CollectionView::update()
{
scene()->clear();
for(int i = 0; i< m_items->size(); i++)
{
Item* item = new Item(*m_items->at(i));
item->setPos(m_items->at(i)->p); // doesn't seem to work
scene()->addItem(item);
}
viewport()->update();
}
class RenderArea : public QGraphicsView
{
Q_OBJECT
public:
RenderArea(QWidget *parent = 0);
public:
Item* item;
};
RenderArea::RenderArea(QWidget *parent)
: QGraphicsView(parent)
{
QGraphicsScene *s = new QGraphicsScene(this);
s->setSceneRect(-width()/2, -height()/2, width(), height());
setScene(s);
setViewportUpdateMode(BoundingRectViewportUpdate);
item = new Item();
s->addItem(item);
}
// this is the boss/coordinator class
class ShapeView : public QGraphicsView
{
Q_OBJECT
public:
ShapeView(QWidget *parent = 0);
CollectionView *collectionView;
private slots: // more slots corresponding to more controls
void addItem();
private:
QPushButton *addButton;
RenderArea *renderArea;
};
ShapeView::ShapeView(QWidget *parent)
: QGraphicsView(parent)
{
collectionView = new CollectionView(parent);
renderArea = new RenderArea(this);
addButton = new QPushButton(tr("Add Item"));
connect(addButton, SIGNAL(clicked()), this, SLOT(addItem()));
}
void ShapeView::addItem()
{
Item* item = new Item(*renderArea->item);
collectionView->m_items->append(item);
collectionView->update();
// place a new item on renderarea
renderArea->item = new Item();
renderArea->scene()->clear();
renderArea->scene()->addItem(renderArea->item);
renderArea->viewport()->update();
}
Something is wrong in either the way I copy the item, I just don't know what. I see no reason why, when adding items, the CollectionView items all snap to the (0,0) position. Perhaps the issue is in the copy constructor but I can't figure what is wrong.
I hope someone can help give me a hint
Update: It seems that the problem is in the CollectionView::update() function... adding a 'delete' at the end of the loop removes the item from the collection... Or in the ShapeView::addItem() function... Am I creating copy of the item or just reusing it ?
But how do I create a copy of the item ?
My assumption was that there was a connection between the items I paint on QGraphicsScene and their representation (as pointers to the objects). Wrong... In fact I misunderstood what the pointer was pointing at...
I updated the position of the items in my list after mouse move and everything is fine.
void CollectionView::mouseMoveEvent(QMouseEvent *event)
{
Item *currentItem = (Item*)itemAt(event->pos().x(), event->pos().y());
if(!currentItem) return;
QGraphicsView::mouseMoveEvent(event);
m_items->at(currentItem->z)->setPos(currentItem->pos());
}
The above fixes the code shown. But it is not the best fix.
The better solution to the problem - remove my list of items completely, since QGraphicsScene already holds a copy. (there was one benefit of handling my own list of items - bringForward and sendBackward for items was easier to implement since my z values were equal to item index)

Hiding a vertical layout programmatically?

I wanted to know if its possible to hide a vertical layout. I currently have a a horizontal layout with two vertical layouts.I wanted to hide one of the vertical layouts(with all its content) on button click. Any suggestions on how I could do that.
As #jmk said, you need to use a QWidget. I'll just add that it's very easy to turn an existing horizontal or vertical layout into a widget from Qt Designer by right-clicking on it and selecting Morph Into->QWidget:
The layout is entirely preserved, but now you can show/hide the layout box because it's an ordinary widget with that layout.
Instead of inserting vertical layouts directly into your top-level horizontal layout, use container widgets to easily control visibility:
// Create your left and right widgets
QWidget* leftWidget = new QWidget();
QVBoxLayout* leftLayout = new QVBoxLayout(leftWidget);
QWidget* rightWidget = new QWidget();
QVBoxLayout* rightLayout = new QVBoxLayout(rightWidget);
// Populate your vertical layouts here ...
QHBoxLayout* horizontalLayout = new QHBoxLayout(parentWidget);
horizontalLayout->addWidget(leftWidget);
horizontalLayout->addWidget(rightWidget);
Then, you can simply hide or show leftWidget or rightWidget to effectively control the visibility of everything in the vertical layouts that you have, without having to hide/show each individual widget.
My suggestion:
// l is the layout pointer
for (int i = 0; i != l->count(); ++i) {
QWidget* w = qobject_cast<QWidget*>(l->itemAt(i));
if (w != 0) {
w->setVisible(false); // hides the widget
}
else {
// do some recursive things with the layout
}
}
(Hope it works ;))
The widget is basically invisible.