is it possible to lock on an item in QListWidget, so when I press for example: a button, the item stays selected?
I have tried to look it up but I failed
I really hope you can provide me with an example, since I am just a beginner
Yes, I can. Here we go:
testQListWidgetLockSelection.cc:
#include <set>
#include <QtWidgets>
class ListWidget: public QListWidget {
public:
using QListWidget::QListWidget;
virtual ~ListWidget() = default;
void lockSelection(bool lock);
virtual void selectionChanged(
const QItemSelection &selected,
const QItemSelection &deselected) override;
private:
bool _lockSel = false;
std::set<int> _selLocked;
bool _lockResel = false;
struct LockGuard {
bool &lock;
LockGuard(bool &lock): lock(lock) { lock = true; }
~LockGuard() { lock = false; }
};
};
void ListWidget::lockSelection(bool lock)
{
_lockSel = lock;
if (_lockSel) {
// store selected indices
for (const QModelIndex &qMI : selectedIndexes()) _selLocked.insert(qMI.row());
} else _selLocked.clear();
}
void ListWidget::selectionChanged(
const QItemSelection& selected, const QItemSelection& deselected)
{
if (_lockSel && !_lockResel) {
const LockGuard lock(_lockResel);
QItemSelection reselect;
for (int row : _selLocked) {
const QModelIndex qMI = model()->index(row, 0);
reselect.select(qMI, qMI);
}
selectionModel()->select(reselect, QItemSelectionModel::Select);
}
}
void populate(QListWidget& qLstView)
{
for (int i = 1; i <= 20; ++i) {
qLstView.addItem(QString("Item %1").arg(i));
}
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
QWidget qWinMain;
qWinMain.resize(640, 480);
qWinMain.setWindowTitle("Lock Selection Demo");
QVBoxLayout qVBox;
QCheckBox qTglLockSel("Lock Selection");
qVBox.addWidget(&qTglLockSel, false);
ListWidget qLstView;
qLstView.setSelectionMode(ListWidget::ExtendedSelection);
qVBox.addWidget(&qLstView, true);
qWinMain.setLayout(&qVBox);
qWinMain.show();
// install signal handlers
QObject::connect(&qTglLockSel, &QCheckBox::toggled,
&qLstView, &ListWidget::lockSelection);
// fill GUI with sample data
populate(qLstView);
// runtime loop
return app.exec();
}
Demo:
Note:
This sample has a weakness: It doesn't consider that items may be inserted or removed after the selection has been locked.
To enhance this, the member functions rowsInserted() and rowsAboutToBeRemoved() had to be overridden as well to correct the indices in ListWidget::_selLocked respectively.
Related
I'm trying to create a simple notepad-style Qt application. Since I think I might want a lot of control over exactly how text is rendered, I'm trying to write a custom QAbstractTextDocumentLayout class. The code below compiles and runs, but the text area is blank, no matter how much I type. The debug statements in my draw() function correctly show the entered text, so I know my input is in some form making it all the way to the draw() function, but it's not being rendered.
I just want to tweak this in a minimal way so I can at least see some text being rendered, even if the implementation is incomplete. I also haven't been able to find any documentation whatsoever on writing custom QAbstractTextDocumentLayout, so if anyone had a reference I'd also appreciate that.
main.cpp
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QTextEdit>
#include <QKeyEvent>
#include "./DocumentLayout.cpp"
class MainContent : public QWidget
{
Q_OBJECT
QTextEdit textEdit;
DocumentLayout layout;
public:
MainContent() : textEdit(this), layout(textEdit.document())
{
int innerPadding = 20;
textEdit.document()->setDocumentMargin(innerPadding);
int textColor = 0xD8DEE9;
int backgroundColor = 0x2E3440;
int selectionColor = 0x2E3440;
int selectionBackgroundColor = 0x81A1C1;
QPalette palette = textEdit.palette();
palette.setColor(QPalette::Base, QColor(backgroundColor));
palette.setColor(QPalette::Text, QColor(textColor));
palette.setColor(QPalette::Highlight, QColor(selectionBackgroundColor));
palette.setColor(QPalette::HighlightedText, QColor(selectionColor));
textEdit.setPalette(palette);
textEdit.setFrameStyle(QFrame::NoFrame);
textEdit.document()->setDocumentLayout(&layout);
};
void resizeEvent(QResizeEvent *event)
{
textEdit.setGeometry(0, 0, this->width(), this->height());
}
void keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_W && event->modifiers() == Qt::ControlModifier)
{
qApp->quit();
}
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow window;
window.setWindowTitle("Hello, world!");
window.resize(400, 500);
MainContent content;
window.setCentralWidget(&content);
window.show();
return app.exec();
}
#include "main.moc"
DocumentLayout.cpp
#include <QAbstractTextDocumentLayout>
#include <QPainter>
#include <QTextBlock>
#include <QDebug>
class DocumentLayout : public QAbstractTextDocumentLayout
{
public:
DocumentLayout(QTextDocument *document) : QAbstractTextDocumentLayout(document)
{
}
void draw(QPainter *painter, const PaintContext &context)
{
QTextDocument *document = this->document();
QTextBlock block = document->begin();
QPointF position(20, 20);
painter->setPen(context.palette.color(QPalette::Text));
QTextLayout *layout = block.layout();
layout->draw(painter, position);
position.ry() += layout->boundingRect().height();
qDebug() << "drew" << block.text() << "at" << position;
}
int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
{
return 0;
}
int pageCount() const
{
return 1;
}
QSizeF documentSize() const
{
return QSizeF(400, 800);
}
QRectF frameBoundingRect(QTextFrame *frame) const
{
return QRectF(0, 0, 400, 800);
}
QRectF blockBoundingRect(const QTextBlock &block) const
{
return QRectF(0, 0, 400, 800);
}
void documentChanged(int from, int charsRemoved, int charsAdded)
{
}
};
Im looking to implement a sudoku gui in Qt. First things first im trying to generate a grid of QToolButtons, which when pressed will increment their value by one.
Here is my header for window.h
class Window : public QWidget
{
Q_OBJECT
public:
Window(QWidget *parent = nullptr);
private slots:
void digitClicked();
private:
Button *createButton(const QString &text, const char *member);
QString *readFileChar(const QString file_name, QString digit[] );
QGridLayout *previewLayout;
void setSudokuChar(const int digit, const char *member);
enum { NumDigitButtons = 81 };
Button *digitButtons[NumDigitButtons];
QString digit[NumDigitButtons];
protected:
void paintEvent(QPaintEvent *event) override;
void mouseReleaseEvent(QMouseEvent * event) override;
};
And the corresponding source file window.cpp
Window::Window(QWidget *parent) : QWidget(parent) //constrcutor
{
//button creation
QString* current_digit = readFileChar("/home/pi/Documents/ELEC1204/P6/sudoku.txt", digit);
for(int i = 0; i < NumDigitButtons; i++){
digitButtons[i] = createButton(*(current_digit++), SLOT(digitClicked())); //initialize sudoku
}
// set form size
QGridLayout *mainLayout = new QGridLayout;
setMaximumSize(600,600);
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(tr("button game"));
// place each digit button
for(int i = 1; i < NumDigitButtons; i++){
int row = (81 - i) / 9 ; // y position for button
int col = (i - 1) % 9 ; // x position for button
mainLayout->addWidget(digitButtons[i], row, col);
}
}
void Window::digitClicked()
{
Button *clickedButton = qobject_cast<Button*>(sender()); //clicked button = object button that was pressed
int val = ++(clickedButton->text().toInt()); //incrememt digit value of the button which was pressed
setSudokuChar(val, clickedButton); //updates sudoku with new value
}
QString* Window::readFileChar(const QString file_name, QString digit[]) //reads in file to initialize buttons
{
QFile file(file_name);
int i = 0;
if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
QTextStream in(&file);
while(!in.atEnd()){
digit[i] = in.read(1); //read each character into an array of QStrings
i++ ;
}
}
else{
QTextStream(stdout) << "could not open file, program terminating" << endl;
exit(1);
}
return digit;
}
Button *Window::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
void setSudokuChar(const int digit, Button* clickedButton) //takes in a digit, and assigns its value to the char that was pass
{
}
Please note that the function to update the button's value when pressed 'setSudokuChar' is yet to be implemented, but that pretty irrelevant for now.
Here is the button class header button.h, it's only a small modification of the QToolButton class to aid in resizing.
class Button : public QToolButton
{
Q_OBJECT
public:
explicit Button(const QString &text, QWidget *parent = nullptr);
QSize sizeHint() const override;
};
And finally, here is the button.cpp source file
Button::Button(const QString &text, QWidget *parent) : QToolButton(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
setText(text);
}
QSize Button::sizeHint() const
{
QSize size = QToolButton::sizeHint();
size.rheight() += 20;
size.rwidth() = qMax(size.width(), size.height());
return size;
}
Hopefully thats not too much to digest, I'm still a beginner!
**My problem is ... ** when I run the code, a window is produced but its blank, which means the compilation is fine, but the program is not working as intended. Could anyone by chance spot what is going wrong? All that i do in the main function is create a window as follows :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
Any and all help is thoroughly appreciated.
I'm new to programming. Would like to know how to print the output data from the code below (C++) using the Qt. I need an answer to appear in the QTextEdit window.
for (int x=0; x<10; x++);
Here's how you might capture the qDebug messages in a QAbstractItemModel or QTextDocument. Both of those classes are models, their associated views are QListView (or any other view), and QTextEdit (preferably QPlainTextEdit or QTextBrowser), respectively.
// https://github.com/KubaO/stackoverflown/tree/master/questions/qdebug-window-output-52061269
#include <QtWidgets>
struct LogToModelData {
bool installed;
QtMessageHandler previous = {};
QList<QPointer<QObject>> models;
};
Q_GLOBAL_STATIC(LogToModelData, logToModelData)
void logToModelHandler(QtMsgType type, const QMessageLogContext &context,
const QString &msg) {
for (auto m : qAsConst(logToModelData->models)) {
if (auto model = qobject_cast<QAbstractItemModel *>(m)) {
auto row = model->rowCount();
model->insertRow(row);
model->setData(model->index(row, 0), msg);
} else if (auto doc = qobject_cast<QTextDocument *>(m)) {
QTextCursor cur(doc);
cur.movePosition(QTextCursor::End);
if (cur.position() != 0) cur.insertBlock();
cur.insertText(msg);
}
}
if (logToModelData->previous) logToModelData->previous(type, context, msg);
}
void logToModel(QObject *model) {
logToModelData->models.append(QPointer<QObject>(model));
if (!logToModelData->installed) {
logToModelData->previous = qInstallMessageHandler(logToModelHandler);
logToModelData->installed = true;
}
}
void rescrollToBottom(QAbstractScrollArea *view) {
static const char kViewAtBottom[] = "viewAtBottom";
auto *scrollBar = view->verticalScrollBar();
Q_ASSERT(scrollBar);
auto rescroller = [scrollBar]() mutable {
if (scrollBar->property(kViewAtBottom).isNull())
scrollBar->setProperty(kViewAtBottom, true);
auto const atBottom = scrollBar->property(kViewAtBottom).toBool();
if (atBottom) scrollBar->setValue(scrollBar->maximum());
};
QObject::connect(scrollBar, &QAbstractSlider::rangeChanged, view, rescroller,
Qt::QueuedConnection);
QObject::connect(scrollBar, &QAbstractSlider::valueChanged, view, [scrollBar] {
auto const atBottom = scrollBar->value() == scrollBar->maximum();
scrollBar->setProperty(kViewAtBottom, atBottom);
});
}
int main(int argc, char *argv[]) {
QApplication app{argc, argv};
QWidget ui;
QVBoxLayout layout{&ui};
QListView view;
QTextBrowser browser;
layout.addWidget(new QLabel(QLatin1String(view.metaObject()->className())));
layout.addWidget(&view);
layout.addWidget(new QLabel(QLatin1String(browser.metaObject()->className())));
layout.addWidget(&browser);
QStringListModel model;
view.setModel(&model);
logToModel(view.model());
logToModel(browser.document());
rescrollToBottom(&view);
rescrollToBottom(&browser);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [] {
static int n;
qDebug() << "Tick" << ++n;
});
timer.start(500);
ui.show();
return app.exec();
}
Also note that the separate declaration of the loop induction variable i is a very much obsolete C-ism - the last time you had to write code like that was two decades ago.
I have subclassed QTreeWidget (called it ToolsSelectorWidget) and enabled reordering in it by overriding QTreeWidget::dropEvent()
void ToolsSelectorWidget::dropEvent(QDropEvent *event) {
QModelIndex droppedIndex = indexAt(event->pos());
if( !droppedIndex.isValid() || droppedIndex.parent().isValid()) {
return;
}
QTreeWidget::dropEvent(event);
}
Also, I am adding QWidgets (QPushButton, QLineEdit) to top level items of QTreeWidget:
ToolsSelectorWidget::ToolsSelectorWidget(QWidget *parent) : QTreeWidget(parent) {
header()->hide();
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
viewport()->setAcceptDrops(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i<4; ++i) {
QTreeWidgetItem *part = new QTreeWidgetItem(this);
part->setFlags(part->flags() & Qt::ItemFlag((~Qt::ItemIsDropEnabled)));
setItemWidget(part, 0, new QLabel("Part" + QString::number(i) + " Preview", this));
setItemWidget(part, 1, new QLineEdit("Part" + QString::number(i) + " Name", this));
setItemWidget(part, 2, new QCheckBox("Part" + QString::number(i) + " Visible", this));
setItemWidget(part, 3, new QCheckBox("Part" + QString::number(i) + " Locked", this));
}
}
So now I have 4 top level items each containing 4 QWidgets. It's populating them fine, but when I rearrange them by drag and drop the QWidgets disappear and I end up having an empty row. What should I do to preserve them?
Before:
After Part2 has been moved and is under Part4, it's children have been preserved, but it's conents, which are QWidgets, are gone:
Why are widgets deleted?
When the drag and drop is performed, the data of the selected items is coded (roles and associated values) and saved in a QMimeData. When the drop is accepted, the source items are deleted and new items are created with the information stored in the QMimeData, inside the saved information there is no widgets information since this does not have relation with the model. And since the items are deleted, their widgets are also deleted.
To check it we can use the following example
#include <QApplication>
#include <QLabel>
#include <QTreeWidget>
#include <QDebug>
static void on_destroyed(){
qDebug()<<"destroyed";
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeWidget w;
w.setSelectionMode(QAbstractItemView::SingleSelection);
w.setDragEnabled(true);
w.viewport()->setAcceptDrops(true);
w.setDropIndicatorShown(true);
w.setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i< 5; i++){
QTreeWidgetItem *it = new QTreeWidgetItem(&w);
QLabel *lbl = new QLabel(QString::number(i));
QObject::connect(lbl, &QObject::destroyed, on_destroyed);
w.setItemWidget(it, 0, lbl);
}
w.show();
return a.exec();
}
It shows that the widgets will emit the signal they destroy when you drag and drop the items.
Possible workaround:
One possible solution is to remove the widgets before accepting the drop and set them in the new items which I have not implemented.
I have explored another solution, it is to change the QTreeWidget for a QTreeView + QStandardItemModel. In the case of the QCheckBox, the checkboxes with the Qt::ItemIsUserCheckable flag are enabled, in the case of the QLineEdit a delegate will be used and to always be shown, the openPersistentEditor() method must be used.
#include <QApplication>
#include <QStandardItemModel>
#include <QTreeView>
#include <QHeaderView>
#include <QDropEvent>
#include <QStyledItemDelegate>
#include <QLineEdit>
class ToolsSelectorDelegate: public QStyledItemDelegate{
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const{
QLineEdit *le = new QLineEdit(parent);
return le;
}
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const{
QRect r(option.rect);
r.adjust(2, 2, -2, -2);
editor->setGeometry(r);
}
};
class ToolsSelectorWidget: public QTreeView{
QStandardItemModel model;
public:
ToolsSelectorWidget(QWidget *parent=nullptr): QTreeView(parent){
setItemDelegate(new ToolsSelectorDelegate(this));
setModel(&model);
header()->hide();
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
viewport()->setAcceptDrops(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i<4; ++i) {
QList<QStandardItem *> items;
for(const QString & text: {"Preview", "Name", "Visible", "Locked"}){
QStandardItem *it = new QStandardItem(QString("Part%1 %2").arg(i).arg(text));
it->setFlags(it->flags() & ~Qt::ItemIsDropEnabled & ~Qt::ItemIsEditable);
items.append(it);
if(text == "Visible" || text == "Locked"){
it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
it->setCheckState(Qt::Unchecked);
}
else if (text == "Name") {
it->setFlags(it->flags() | Qt::ItemIsEditable);
}
}
for(const QString & children: {"The", "quick", "Brown", "fox", "jump...", "over", "the", "lazy", "dog"})
items.first()->appendRow(new QStandardItem(children));
model.invisibleRootItem()->appendRow(items);
for( int i = 0; i < model.rowCount(); ++i )
openPersistentEditor(model.index(i, 1));
}
}
protected:
void dropEvent(QDropEvent *event) {
QModelIndex droppedIndex = indexAt(event->pos());
if( !droppedIndex.isValid() || droppedIndex.parent().isValid())
return;
QTreeView::dropEvent(event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ToolsSelectorWidget w;
w.show();
return a.exec();
}
I want to make widgets increase in height with QPropertyAnimation, when widgets are arranged with QVboxLayout.
The problem is that when I open more than one widget, they start to move/shake during animation.
I have prepared minimum working example, here tar gz project
The problem appears when you press "open" button for first, second, then third widget, you can see then that they are shaking, moving slightly up and down during "open" animation.
Has someone idea what to do to avoid this ?
I can set setSizeConstraint(QLayout::SetFixedSize) on main layout and they dont shake, but then resizing and other doesn't work.
Best Regards
Marek
Some time ago I've wrote a layout which animates widget position it contains.
You should build your layout in such way that each widget which should be animated should be inside this layout (one AnimLayout per widget which should be animated):
#include <QLayout>
QT_FORWARD_DECLARE_CLASS(QPropertyAnimation)
class AnimLayout : public QLayout
{
Q_OBJECT
Q_PROPERTY(QPoint delta
READ delta
WRITE setDelta
NOTIFY deltaChanged)
Q_PROPERTY(QRect widgetRect
READ widgetRect
WRITE setWidgetRect
NOTIFY widgetRectChanged)
Q_PROPERTY(bool active
READ isDeltaActive
WRITE setDeltaActive
NOTIFY deltaActiveChanged)
public:
explicit AnimLayout(QWidget *parent = 0);
~AnimLayout();
QPoint delta() const;
void setDelta(const QPoint &value);
QSize sizeHint() const;
void setGeometry(const QRect &);
QSize minimumSize() const;
int count() const;
QSize deltaSize() const;
QRect widgetRect() const;
void setWidgetRect(const QRect &value);
bool isDeltaActive() const;
void setDeltaActive(bool active = true);
void updateItemPosition();
private:
void addItem(QLayoutItem *item);
QLayoutItem *itemAt(int index) const;
QLayoutItem *takeAt(int index);
signals:
void deltaChanged(const QPoint &value);
void widgetRectChanged(const QRect &value);
void deltaActiveChanged(bool active);
public slots:
void testIt();
private:
QLayoutItem *item;
QPropertyAnimation *animation;
QPoint mDelta;
bool mDeltaActive;
};
///////////////////////////////////////////////////////////
#include "animlayout.h"
#include <QPropertyAnimation>
AnimLayout::AnimLayout(QWidget *parent) :
QLayout(parent) ,
item(0)
{
animation = new QPropertyAnimation(this);
animation->setPropertyName("widgetRect");
animation->setDuration(400);
animation->setTargetObject(this);
mDeltaActive = false;
}
AnimLayout::~AnimLayout()
{
delete item;
}
QPoint AnimLayout::delta() const
{
return mDelta;
}
void AnimLayout::setDelta(const QPoint &value)
{
if (mDelta != value) {
mDelta = value;
emit deltaChanged(mDelta);
invalidate();
}
}
void AnimLayout::addItem(QLayoutItem *newItem)
{
Q_ASSERT(!item);
animation->stop();
item =newItem;
emit widgetRectChanged(item->geometry());
invalidate();
}
QSize AnimLayout::sizeHint() const
{
if (!item)
return QSize();
QSize result(item->sizeHint());
result += deltaSize();
int m = 2*margin();
result += QSize(m,m);
return result;
}
void AnimLayout::updateItemPosition()
{
QRect dest = contentsRect();
QPoint d = delta();
if (isDeltaActive()) {
d = -d;
}
if (d.x()!=0) {
if (d.x()>0) {
dest.setLeft(dest.left()+d.x());
} else {
dest.setRight(dest.right()+d.x());
}
}
if (d.y()) {
if (d.y()>0) {
dest.setTop(dest.top()+d.y());
} else {
dest.setBottom(dest.bottom()+d.y());
}
}
animation->setEndValue(dest);
if (widgetRect()!=dest) {
animation->start();
}
}
void AnimLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
updateItemPosition();
}
QLayoutItem *AnimLayout::itemAt(int i) const
{
return i==0?item:0;
}
QLayoutItem *AnimLayout::takeAt(int i)
{
Q_ASSERT(i==0);
QLayoutItem *r = item;
item = 0;
return r;
}
void AnimLayout::testIt()
{
setDeltaActive(!isDeltaActive());
}
QRect AnimLayout::widgetRect() const
{
if (item)
return item->geometry();
return QRect();
}
void AnimLayout::setWidgetRect(const QRect &value)
{
if (item && item->geometry()!=value) {
item->setGeometry(value);
emit widgetRectChanged(item->geometry());
}
}
bool AnimLayout::isDeltaActive() const
{
return mDeltaActive;
}
void AnimLayout::setDeltaActive(bool active)
{
if (active!=mDeltaActive) {
mDeltaActive = active;
animation->stop();
updateItemPosition();
emit deltaActiveChanged(active);
}
}
QSize AnimLayout::minimumSize() const
{
QSize result(deltaSize());
if (item) {
result += item->minimumSize();
}
int m = 2*margin();
result += QSize(m,m);
return result;
}
int AnimLayout::count() const
{
return item?1:0;
}
QSize AnimLayout::deltaSize() const
{
return QSize(qAbs(mDelta.x()), qAbs(mDelta.y()));
}
It has some extra functionality you don't need (mDelta).
Sorry it took me so long ;)
I have tested it and it works great.
However, when I was working with my previous code I have made it work without shaking. The change I made was to add QWidget into QScrollArea and then set QVBoxLayout on that widget.
Anyway many thanks for help.
Below there is example in one main.cpp and there is variable "animatedLayout" which turns on or off your AnimLayout.
#include
#include
class AnimLayout : public QLayout
{
Q_OBJECT
Q_PROPERTY(QRect widgetRect
READ widgetRect
WRITE setWidgetRect
NOTIFY widgetRectChanged)
public:
explicit AnimLayout(QWidget *parent = 0);
~AnimLayout();
QSize sizeHint() const;
void setGeometry(const QRect &);
QSize minimumSize() const;
int count() const;
QRect widgetRect() const;
void setWidgetRect(const QRect &value);
void updateItemPosition();
private:
void addItem(QLayoutItem *item);
QLayoutItem *itemAt(int index) const;
QLayoutItem *takeAt(int index);
signals:
void widgetRectChanged(const QRect &value);
public slots:
private:
QLayoutItem *item;
QPropertyAnimation *animation;
};
struct FrameDataStruct {
QFrame *mainFrame;
QFrame *upFrame;
QFrame *downFrame;
QPushButton *button;
QVBoxLayout *upFrameLayout;
QLabel *text;
QVBoxLayout *downFrameLayout;
QVBoxLayout *frameLayout;
QPropertyAnimation *animation;
int frame_id;
int basic_height;
bool expanded;
AnimLayout *animLayout;
};
class Proptest : public QMainWindow
{
Q_OBJECT
public:
explicit Proptest();
~Proptest();
private slots:
void setDataStruct();
void startAnimation(int frame_id);
void animFinished(int frame_id);
private:
QMap frameMap;
QSignalMapper *animStartMapper;
QSignalMapper *animFinishedMapper;
bool initialized;
QWidget *scrollWidget;
QVBoxLayout *main_layout;
QWidget *widget;
QScrollArea *scrollArea;
QVBoxLayout *central_layout;
bool layoutAnimated;
};
Proptest::Proptest()
: widget(new QWidget)
{
setCentralWidget(widget);
this->setGeometry(200,200,300,600);
central_layout=new QVBoxLayout(widget);
scrollArea=new QScrollArea(widget);
central_layout->addWidget(scrollArea);
animStartMapper=new QSignalMapper(this);
connect(animStartMapper,SIGNAL(mapped(int)),this,SLOT(startAnimation(int)));
animFinishedMapper=new QSignalMapper(this);
connect(animFinishedMapper,SIGNAL(mapped(int)),this,SLOT(animFinished(int)));
scrollWidget=new QWidget(widget);
scrollArea->setWidget(scrollWidget);
main_layout=new QVBoxLayout(scrollWidget);
main_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
scrollArea->setWidgetResizable(true);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
layoutAnimated=true;
this->setDataStruct();
}
void Proptest::setDataStruct() {
for(int i=0;iexpanded=false;
r->frame_id=i;
r->mainFrame=new QFrame(scrollWidget);
r->upFrame=new QFrame(r->mainFrame);
r->upFrame->setMinimumHeight(40);
r->button=new QPushButton(QString("open"),r->upFrame);
r->upFrameLayout=new QVBoxLayout(r->upFrame);
r->upFrameLayout->addWidget(r->button);
r->downFrame=new QFrame(r->mainFrame);
r->text=new QLabel(QString("some text SOME TEXT some text"),r->downFrame);
r->downFrameLayout=new QVBoxLayout(r->downFrame);
r->downFrameLayout->addWidget(r->text);
r->frameLayout=new QVBoxLayout(r->mainFrame);
r->frameLayout->addWidget(r->upFrame);
r->frameLayout->addItem(new QSpacerItem(10,10));
r->frameLayout->addWidget(r->downFrame);
r->frameLayout->setStretch(0,0);
r->frameLayout->setStretch(1,1);
r->frameLayout->setStretch(2,0);
r->downFrame->setVisible(false);
r->animation=new QPropertyAnimation(r->mainFrame,"minimumHeight");
r->animation->setDuration(500);
connect(r->button,SIGNAL(clicked(bool)),animStartMapper,SLOT(map()));
animStartMapper->setMapping(r->button,r->frame_id);
connect(r->animation,SIGNAL(finished()),animFinishedMapper,SLOT(map()));
animFinishedMapper->setMapping(r->animation,r->frame_id);
if(layoutAnimated) {
r->animLayout=new AnimLayout();
r->animLayout->addWidget(r->mainFrame);
main_layout->addItem(r->animLayout);
}
else {
main_layout->addWidget(r->mainFrame);
}
frameMap.insert(r->frame_id,r);
}
main_layout->addItem(new QSpacerItem(10,10,QSizePolicy::Minimum,QSizePolicy::Expanding));
main_layout->setStretch(main_layout->count()-1,1);
}
void Proptest::startAnimation(int frame_id) {
FrameDataStruct *r=frameMap[frame_id];
if(r->expanded) {
r->expanded=false;
if(layoutAnimated) {
r->downFrame->hide();
}
else {
r->downFrame->setVisible(false);
r->animation->setStartValue(r->mainFrame->geometry().height());
r->animation->setEndValue(r->basic_height);
}
} else {
r->expanded=true;
if(layoutAnimated) {
r->downFrame->show();
}
else {
r->basic_height=r->mainFrame->geometry().height();
r->animation->setStartValue(r->basic_height);
r->animation->setEndValue(r->basic_height*2);
r->upFrame->setMinimumHeight(r->upFrame->height());
}
}
if(!layoutAnimated)
r->animation->start();
}
void Proptest::animFinished(int frame_id) {
FrameDataStruct *r=frameMap[frame_id];
if(r->expanded)
r->downFrame->setVisible(true);
}
Proptest::~Proptest() {
}
AnimLayout::AnimLayout(QWidget *parent) :
QLayout(parent) ,
item(0)
{
animation = new QPropertyAnimation(this);
animation->setPropertyName("widgetRect");
animation->setDuration(400);
animation->setTargetObject(this);
}
AnimLayout::~AnimLayout()
{
delete item;
}
void AnimLayout::addItem(QLayoutItem *newItem)
{
Q_ASSERT(!item);
animation->stop();
item =newItem;
emit widgetRectChanged(item->geometry());
invalidate();
}
QSize AnimLayout::sizeHint() const
{
if (!item)
return QSize();
QSize result(item->sizeHint());
int m = 2*margin();
result += QSize(m,m);
return result;
}
void AnimLayout::updateItemPosition()
{
QRect dest = contentsRect();
animation->setEndValue(dest);
if (widgetRect()!=dest) {
animation->start();
}
}
void AnimLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
updateItemPosition();
}
QLayoutItem *AnimLayout::itemAt(int i) const
{
return i==0?item:0;
}
QLayoutItem *AnimLayout::takeAt(int i)
{
Q_ASSERT(i==0);
QLayoutItem *r = item;
item = 0;
return r;
}
QRect AnimLayout::widgetRect() const
{
if (item)
return item->geometry();
return QRect();
}
void AnimLayout::setWidgetRect(const QRect &value)
{
if (item && item->geometry()!=value) {
item->setGeometry(value);
emit widgetRectChanged(item->geometry());
}
}
QSize AnimLayout::minimumSize() const
{
QSize result(item->minimumSize());
int m = 2*margin();
result += QSize(m,m);
return result;
}
int AnimLayout::count() const
{
return item?1:0;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Proptest w;
w.show();
return a.exec();
}
#include "main.moc"
Best Regards
Marek
My answer again, dont know why but previosu was deleted.
I have used Your AnimLayout and it works great.
Below there is example in main.cpp with "layoutAnimated" variable to switch on and off AnimLayout.
#include <QApplication>
#include <QtWidgets>
class AnimLayout : public QLayout
{
Q_OBJECT
Q_PROPERTY(QRect widgetRect
READ widgetRect
WRITE setWidgetRect
NOTIFY widgetRectChanged)
public:
explicit AnimLayout(QWidget *parent = 0);
~AnimLayout();
QSize sizeHint() const;
void setGeometry(const QRect &);
QSize minimumSize() const;
int count() const;
QRect widgetRect() const;
void setWidgetRect(const QRect &value);
void updateItemPosition();
private:
void addItem(QLayoutItem *item);
QLayoutItem *itemAt(int index) const;
QLayoutItem *takeAt(int index);
signals:
void widgetRectChanged(const QRect &value);
public slots:
private:
QLayoutItem *item;
QPropertyAnimation *animation;
};
struct FrameDataStruct {
QFrame *mainFrame;
QFrame *upFrame;
QFrame *downFrame;
QPushButton *button;
QVBoxLayout *upFrameLayout;
QLabel *text;
QVBoxLayout *downFrameLayout;
QVBoxLayout *frameLayout;
QPropertyAnimation *animation;
int frame_id;
int basic_height;
bool expanded;
AnimLayout *animLayout;
};
class Proptest : public QMainWindow
{
Q_OBJECT
public:
explicit Proptest();
~Proptest();
private slots:
void setDataStruct();
void startAnimation(int frame_id);
void animFinished(int frame_id);
private:
QMap<int,FrameDataStruct*> frameMap;
QSignalMapper *animStartMapper;
QSignalMapper *animFinishedMapper;
bool initialized;
QWidget *scrollWidget;
QVBoxLayout *main_layout;
QWidget *widget;
QScrollArea *scrollArea;
QVBoxLayout *central_layout;
bool layoutAnimated;
};
Proptest::Proptest()
: widget(new QWidget)
{
setCentralWidget(widget);
this->setGeometry(200,200,300,600);
central_layout=new QVBoxLayout(widget);
scrollArea=new QScrollArea(widget);
central_layout->addWidget(scrollArea);
animStartMapper=new QSignalMapper(this);
connect(animStartMapper,SIGNAL(mapped(int)),this,SLOT(startAnimation(int)));
animFinishedMapper=new QSignalMapper(this);
connect(animFinishedMapper,SIGNAL(mapped(int)),this,SLOT(animFinished(int)));
scrollWidget=new QWidget(widget);
scrollArea->setWidget(scrollWidget);
main_layout=new QVBoxLayout(scrollWidget);
main_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
scrollArea->setWidgetResizable(true);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
layoutAnimated=true;
this->setDataStruct();
}
void Proptest::setDataStruct() {
for(int i=0;i<5;i++) {
FrameDataStruct *r=new FrameDataStruct;
r->expanded=false;
r->frame_id=i;
r->mainFrame=new QFrame(scrollWidget);
r->upFrame=new QFrame(r->mainFrame);
r->upFrame->setMinimumHeight(40);
r->button=new QPushButton(QString("open"),r->upFrame);
r->upFrameLayout=new QVBoxLayout(r->upFrame);
r->upFrameLayout->addWidget(r->button);
r->downFrame=new QFrame(r->mainFrame);
r->text=new QLabel(QString("some text SOME TEXT some text"),r->downFrame);
r->downFrameLayout=new QVBoxLayout(r->downFrame);
r->downFrameLayout->addWidget(r->text);
r->frameLayout=new QVBoxLayout(r->mainFrame);
r->frameLayout->addWidget(r->upFrame);
r->frameLayout->addItem(new QSpacerItem(10,10));
r->frameLayout->addWidget(r->downFrame);
r->frameLayout->setStretch(0,0);
r->frameLayout->setStretch(1,1);
r->frameLayout->setStretch(2,0);
r->downFrame->setVisible(false);
r->animation=new QPropertyAnimation(r->mainFrame,"minimumHeight");
r->animation->setDuration(500);
connect(r->button,SIGNAL(clicked(bool)),animStartMapper,SLOT(map()));
animStartMapper->setMapping(r->button,r->frame_id);
connect(r->animation,SIGNAL(finished()),animFinishedMapper,SLOT(map()));
animFinishedMapper->setMapping(r->animation,r->frame_id);
if(layoutAnimated) {
r->animLayout=new AnimLayout();
r->animLayout->addWidget(r->mainFrame);
main_layout->addItem(r->animLayout);
}
else {
main_layout->addWidget(r->mainFrame);
}
frameMap.insert(r->frame_id,r);
}
main_layout->addItem(new QSpacerItem(10,10,QSizePolicy::Minimum,QSizePolicy::Expanding));
main_layout->setStretch(main_layout->count()-1,1);
}
void Proptest::startAnimation(int frame_id) {
FrameDataStruct *r=frameMap[frame_id];
if(r->expanded) {
r->expanded=false;
if(layoutAnimated) {
r->downFrame->hide();
}
else {
r->downFrame->setVisible(false);
r->animation->setStartValue(r->mainFrame->geometry().height());
r->animation->setEndValue(r->basic_height);
}
} else {
r->expanded=true;
if(layoutAnimated) {
r->downFrame->show();
}
else {
r->basic_height=r->mainFrame->geometry().height();
r->animation->setStartValue(r->basic_height);
r->animation->setEndValue(r->basic_height*2);
r->upFrame->setMinimumHeight(r->upFrame->height());
}
}
if(!layoutAnimated)
r->animation->start();
}
void Proptest::animFinished(int frame_id) {
FrameDataStruct *r=frameMap[frame_id];
if(r->expanded)
r->downFrame->setVisible(true);
}
Proptest::~Proptest() {
}
AnimLayout::AnimLayout(QWidget *parent) :
QLayout(parent) ,
item(0)
{
animation = new QPropertyAnimation(this);
animation->setPropertyName("widgetRect");
animation->setDuration(400);
animation->setTargetObject(this);
}
AnimLayout::~AnimLayout()
{
delete item;
}
void AnimLayout::addItem(QLayoutItem *newItem)
{
Q_ASSERT(!item);
animation->stop();
item =newItem;
emit widgetRectChanged(item->geometry());
invalidate();
}
QSize AnimLayout::sizeHint() const
{
if (!item)
return QSize();
QSize result(item->sizeHint());
int m = 2*margin();
result += QSize(m,m);
return result;
}
void AnimLayout::updateItemPosition()
{
QRect dest = contentsRect();
animation->setEndValue(dest);
if (widgetRect()!=dest) {
animation->start();
}
}
void AnimLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
updateItemPosition();
}
QLayoutItem *AnimLayout::itemAt(int i) const
{
return i==0?item:0;
}
QLayoutItem *AnimLayout::takeAt(int i)
{
Q_ASSERT(i==0);
QLayoutItem *r = item;
item = 0;
return r;
}
QRect AnimLayout::widgetRect() const
{
if (item)
return item->geometry();
return QRect();
}
void AnimLayout::setWidgetRect(const QRect &value)
{
if (item && item->geometry()!=value) {
item->setGeometry(value);
emit widgetRectChanged(item->geometry());
}
}
QSize AnimLayout::minimumSize() const
{
QSize result(item->minimumSize());
int m = 2*margin();
result += QSize(m,m);
return result;
}
int AnimLayout::count() const
{
return item?1:0;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Proptest w;
w.show();
return a.exec();
}
#include "main.moc"
Best Regards
Marek