Best way to make grid with custom amount of square items in QT - qt

I want to create a custom size grid of squares where user can choose how many columns and rows are in the gird. The sqaures in the grid must be clickable so that they can change color when user clicks them. The grid is ment for pathfinding visalization.
I thought about using QRect that are stored in some kind of map with coordinates or QGraphicsItem in QGraphicsScene. What is the best QT class for this considering performance because i'm not sure if getting the postion of click in mouse event and then looping through huge grid is the best way?
Thank you in advance.

If I were you, I would just put QPushButtons into a QGridLayout. They are clickable and then you can change the colour accordingly.
QGridLayout *buttonLayout = new QGridLayout(this);
size_t rows = 5, columns = 5; // You can make this configurable.
for (size_t row = 0; row < rows; ++row) {
for(size column = 0; column < column; ++column) {
QPushButton *button = new QPushButton;
connect(button, &QPushButton::clicked, this, [this](){/* change colours */});
buttonLayout->addWidget(button, row, column);
}
}
centralWidget->setLayout(buttonLayout);

Related

Finding QGridLayout elements in Qt

I created labels and added them to the layout. How to get the elements from the layout? I tried to use method children(), but it gives empty list... Is there a way to get them? Some sample code below.
QGridLayout* layout = new QGridLayout();
QLabel* test = new QLabel();
test->setPixmap(m_staticStorage->getFirstImg());
test->setScaledContents(true);
QLabel* test2 = new QLabel();
test2->setMaximumSize(50,50);
test2->setPixmap(m_staticStorage->getSecondImg());
tes2->setScaledContents(true);
layout->addWidget(worker, 0, 0);
layout->addWidget(farmer, 0, 1);
ui->verticalLayout->addLayout(layout);
//layout->children() ->>>> empty
This will iterate over any QLayout subclass to find the items which have been added so far:
for (int i=0; i < layout->count(); ++i) {
QLayoutItem *item = layout->itemAt(i);
if (!item || !item->widget())
continue;
QLabel *label = qobject_cast<QLabel*>(item->widget());
if (label) {
// .... do stuff with label
}
}
One can also iterate in a similar fashion over each row or column of a QGridLayout using QGridLayout::columnCount() or QGridLayout::rowCount() and then QGridLayout::itemAtPosition() to get the actual QLayoutItem.
If you need to uniquely identify the QLabel after finding it, you could for example give each label a unique objectName or do a setProperty() on them with a unique ID when creating them.
QLabel *test1 = new QLabel(this);
test1->setObjectName(QStringLiteral("test1"));
....
if (label) {
if (!label->objectName().compare(QLatin1String("test1")))
// this is "test1" label
}
QLabel *test1 = new QLabel(this);
test1->setProperty("id", 1);
....
if (label) {
if (label->property("id").toInt() == 1)
// this is "test1" label
}
Better to use the function QObject::findChild
Qt is returning all children of a given Type and Objectname. You can decide to get only direct children or also all recursively.
this->findChild<QLabel*>(QString(), Qt::FindDirectChildrenOnly);
This will return all direct children of this (where this is your parent widget, not your layout) with any name and of type QLabel*
Your approach do not work because the layout do not take the ownership of the labels:
From Layout Management:
Tips for Using Layouts
When you use a layout, you do not need to pass a parent when
constructing the child widgets. The layout will automatically
reparent the widgets (using QWidget::setParent()) so that they are
children of the widget on which the layout is installed.
Note: Widgets in a layout are children of the widget on which the
layout is installed, not of the layout itself. Widgets can only have
other widgets as parent, not layouts.
You can nest layouts using addLayout() on a layout; the inner layout
then becomes a child of the layout it is inserted into.
BTW: Don't forget to set a parent for your layout
QGridLayout* layout = new QGridLayout(this);
and for your labels too
QLabel* test2 = new QLabel(this);

equal row heights for QFormLayout

I am using QFormLayout with QLabels in the left column and various widgets in the right column. On the right, there are either labels, check boxes, combos or line edits. Unfortunately each of there controls has different natural height. But I would like to have each row in the form layout to have equal heights determined by the biggest one (I know in which row it is). Is there any simple way to achieve this? I cannot find anything like QFormLayout::setRowHeight().
One solution, just assign equal size to all widgets at runtime using the following function:
void setEqualRowHeight(QFormLayout *formLayout, int height)
{
QWidget *w;
for(int i = 0; i < formLayout->rowCount(); i++) {
QLayoutItem *item = formLayout->itemAt(i, QFormLayout::FieldRole);
if (item && (w = item->widget())) {
w->setFixedHeight(height);
}
}
}

(Qt) Rendering scene, different items in the same relative positions

I have a QSqlTableModel model that contains my data.
I have made a QGraphicsScene scene and a QGraphicsView view so the user can move around same myQGraphicsTextItem text items until the desired position.
Something like this:
myQWidget::myQWidget()
{
//these are member of my class
chequeScene = new QGraphicsScene();
chequeView = new QGraphicsView();
model = new QSQLTableModel();
//populate model, inialize things here...
//add predefined items to the scene
setScene();
}
there's a button to show the view and move the textitems of scene. It works well.
there's a button that calls the slot print that belongs to the class. It configures a QPrinter and then calls the following paint method myQWidget::paint(), after that scene->render() is called.
The porpoise of the method below is to print data on a paper that is configured to have the same size than the scene while printing the data in the same relative position the textItem had on the scene. Can't do it with QList it doesn't order the items in the same way I added them to the scene.
Here is my code below, it prints with overlapping of some fields doe to QList order items as they appear on the scene.
void myQWidget::paint()
{
qreal dx = 0;
qreal dy = 0;
QList<QGraphicsItem*> L = chequeScene->items();
for (int j=0; j<model->columnCount(); j++) {
if(!L.isEmpty())
{
//Saves the position on dx, dy
dx = L.first()->scenePos().x();
dy = L.first()->scenePos().y();
chequeScene->removeItem( L.first() );
delete L.first();
L.removeFirst();
}
QString txt("");
//selecting printing formar for each column
switch(j)
{
case COLUMNADEFECHA:
txt = QDate::fromString(model->data(model->index(chequenum,j)).toString(), "yyyy/MM/dd").toString("dd/MM/yyyy");
break;
case COLUMNADECHEQUES:
break;
default:
txt = model->data(model->index(chequenum,j)).toString();
break;
}
//filtering not important columns
if(j!=COLUMNADECHEQUES)
{
//Supposubly item with the desired information is added to the scene
//on the same position it had before. Not working.
GraphicsTextItem *item=new GraphicsTextItem();
item->setPlainText(txt);
item->setPos(dx,dy);
chequeScene->addItem(item);
}
}
}
Any idea on how to get this working?
I think as you are getting the scenePos in dx and dy but are setting it using setPos function.
Also as you are using your GraphicsTextItem and not QGraphicsTextItem maybe a look at your paint method will help in understanding the problem.
Try using item->mapFromScene(dx, dy) and then use those coordinates to set the item position by item->setPos(..).
Hope This Helps..

Can you set a QWidget to take up an entire column of a QTreeWidget?

I have a custom QTreeWidget subclass that I'm using to display track names/etc. in my MIDI editor project (https://github.com/waddlesplash/ragingmidi). I'd like to add another column to this tree widget, but with one widget taking up the whole column and not per-item widgets.
Is this possible or will I have to figure out some other solution?
EDIT: I'm trying to accomplish something like this: http://www.anvilstudio.com/compose.jpg - see the last "column" in the header view (3rd after "L/R Balance") showing all the lines/notes (which is entirely custom, and written in VB.NET and closed-source anyway).
EDIT 2: You can't see it, but the last column scrolls without the other columns scrolling in the above picture. In their method, you have to scroll using the mouse. I want a scrollbar.
Looking at the Qt documentation, there seems to be a few options to accomplish this, however there are a few important factors to address before you can decide what approach best suits your needs.
Is the content being displayed in this custom tree column static or dynamic?
Is there a one to one mapping of rows from your QTreeWidget to your custom tree column?
If your custom tree column content IS static and there IS a one to one mapping of rows , use of the QTreeWidget::setItemWidget ( QTreeWidgetItem * item, int column, QWidget * widget ) function should suffice.
However, if the content of your custom tree column is dynamic OR there is not a one to one mapping of rows, this will require a more complex approach.
As described in the documentation for QTreeWidget; "If you want to display custom dynamic content or implement a custom editor widget, use QTreeView and subclass QItemDelegate. "
QItemDelegate, and its sub classes, perform all drawing facilities for items inserted into Qt item views (like QTreeView, QListView, QTableView, etc..). This essentially allows you to control ALL drawing operations for any item inserted into a QTreeView class, letting you draw dynamic content in addition to being able to extend content across multiple rows.
Having implemented a similar approach for a QListWidget, I recommend using QStyledItemDelegate in lieu of QItemDelegate as it allows you to more easily integrate this widget with your application's style layout. As you did not detail the exact use of this custom QWidget, you also might need the additional facilities provided by QItemEditorCreator, QItemEditorCreatorBase and QItemEditorFactory. I would post the similar widget I developed here if I could, but sadly it is part of a proprietary software suite.
This is not completely pretty, because it got it's problems when the custom widget is in the right-most column and the column is made narrow, but it's a start:
#include <QtGui>
class TreeWidget : public QTreeWidget
{
Q_OBJECT
public:
TreeWidget();
QRect columnRect(int column) const;
private slots:
void repositionColumnWidget();
private:
QPushButton * mColumnWidget;
};
TreeWidget::TreeWidget()
: mColumnWidget(new QPushButton("Custom Column Button", viewport()))
{
const int COLUMN_COUNT = 6;
setColumnCount(COLUMN_COUNT);
for (int row = 0; row < 400; ++row)
{
QStringList columns;
for (int column = 0; column < COLUMN_COUNT; ++column)
{
columns << QString("row %1, column %2").arg(row + 1).arg(column + 1);
}
addTopLevelItem(new QTreeWidgetItem(columns));
}
for (int column = 0; column < COLUMN_COUNT; ++column)
{
resizeColumnToContents(column);
}
repositionColumnWidget();
mColumnWidget->show();
connect(header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(repositionColumnWidget()));
connect(header(), SIGNAL(sectionMoved(int,int,int)), this, SLOT(repositionColumnWidget()));
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(repositionColumnWidget()));
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(repositionColumnWidget()));
}
QRect TreeWidget::columnRect(int column) const
{
int itemCount = topLevelItemCount();
if (!itemCount)
{
return QRect();
}
int columnX = header()->sectionViewportPosition(column);
int columnY = visualItemRect(topLevelItem(0)).top();
int columnWidth = header()->sectionSize(column);
int columnHeight = visualItemRect(topLevelItem(itemCount-1)).bottom() - columnY + 1;
return QRect(columnX, columnY, columnWidth, columnHeight);
}
void TreeWidget::repositionColumnWidget()
{
mColumnWidget->setGeometry(columnRect(3));
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setQuitOnLastWindowClosed(true);
TreeWidget treeWidget;
treeWidget.resize(800, 600);
treeWidget.show();
return a.exec();
}
#include "main.moc"
The though that has come to my mind after a week is to hijack the H-scrollbar for the QTreeWidget, and then make that scrollbar scroll only the final column. Because right now, all the columns fit when the window is 620x670px, and who has a screen that small anymore?
Unless anyone has a better solution or objections as to why this is a bad idea, this is the way I'm going to do it.

Don't repaint instantly after setVisble in Qt

I'm using QPushButton in my mineSweeping game.
After changing from easy mode to hard mode, the number of QPushButton is supposed to change from 9x9 to 30x16.
So, I add QPushButton with the largest number(which is of hard mode) to GridLayout in constructor of MainWindow.
btnArr = new QPushButton[HARD_WIDTH * HARD_HEIGHT]; // member element
int index = 0;
for (int i = 0; i < HARD_HEIGHT; ++i) {
for (int j = 0; j < HARD_WIDTH; ++j) {
ui->mainGrid->addWidget(&btnArr[index], i, j, 1, 1,
Qt::AlignCenter);
++index;
}
}
Then if the user change mode(e.g.: easy mode to hard mode), resetBtn(HARD_WIDTH, HARD_HEIGHT); will be called.
void MainWindow::resetBtn(const int width, const int height)
{
int index = 0;
for (int i = 0; i < HARD_HEIGHT; ++i) {
for (int j = 0; j < HARD_WIDTH; ++j) {
if (j < width && i < height) {
btnArr[index].setVisible(true);
} else {
btnArr[index].setVisible(false);
}
++index;
}
}
}
The problem is that it seems the widget repaints each time setVisible is called. So in the hard mode case, it will be called 30x16 times, which caused strange effect like this:
So how can I set the widget not repaint during this loop?
Thanks in advance.
I think that you are trying to solve the wrong problem. You shouldn't be updating the widgets like this. If you necessarily want to, then hiding the parent widget of the layout before the change and showing it again afterwards should work.
A better approach is to use QStackedWidget and have all the boards prepared initially. Switching to a different board is then simply a matter of switching the active widget.
The total number of QPushButton is really big: 30x16 = 480!!! I don't use to make people change their programming logic, but in this case I think that using QPushButtons is not the better approach. The layout must have a really bad time trying to move the objects as they are added, and perhaps you are reaching some internal limit in refresh time for the layout to be repainted.
What I would have done is a custom widget with a custom paintEvent method. There you can divide its width and height in the number of columns and rows that you wish and paint the cells with pixmaps as the game is played.
For the mouse interaction, the best would have been to override the mousePressEvent with a custom logic that calculates the mouse position in the grid and calls the corresponding methods or emits signals indicating the position of the event. Not very hard to code. You can also use the event->buttons() method to know which mouse button was pressed and emit different signals if you wish.
I don't use to answer telling that it is better to change your whole program, but in this case I think you are going "the hard way". I know this is not the kind of answer you are looking for, but consider this possibility.
You could try calling setUpdatesEnabled(false) on the parent widget before doing those "massive" changes, and re-enable it once all is done.
Maybe I'm wrong but as far as I know Qt doesn't render the widget right after setVisible() is called. Rendering happens as a result of a 'render' event, except if you call render() manually.
From the official Qt doc (http://qt-project.org/doc/qt-4.8/qwidget.html#paintEvent):
Qt also tries to speed up painting by merging multiple paint events
into one. When update() is called several times or the window system
sends several paint events, Qt merges these events into one event with
a larger region (see QRegion::united()). The repaint() function does
not permit this optimization, so we suggest using update() whenever
possible.
My instincts tell me that it's not a painting problem rather a layouting (not enough space to present every button in 'hard mode').
Also I think you shouldn't use Qt::AlignCenter when you add your buttons to the layout, it will try to centerize every button in the layout. You should rather centerize the parent widget of the layout (if you don't have one create one and centerize it) and set size-policies correctly (QWidget setSizePolicy).
But as #Mat suggested if this really is a painting problem you can use setUpdatesEnabled(false/true) (if setUpdatesEnabled solves your problem please accept #Mat 's solution)
Try to enabling/disabling instead of visible/invisible:
void MainWindow::resetBtn(const int width, const int height)
{
int index = 0;
for (int i = 0; i < HARD_HEIGHT; i++)
for (int j = 0; j < HARD_WIDTH; j++)
btnArr[index++].setEnabled(j < width && i < height);
}

Resources