Let's say I have a class with two Gtk::Containers like so:
class Example : public Gtk::VBox
{
public:
Example();
virtual ~Example();
private:
Gtk::HBox m_c1; // First container
Gtk::Grid m_c2; // Second container
};
Constructing an Example is implemented this way:
Example::Example()
{
const int nbRows{6};
const int nbColumns{7};
for(int col{0}; col < nbColumns; ++col)
{
Gtk::Button* btn {new Gtk::Button("A")};
m_c1.pack_start(*btn);
}
for(int row{0}; row < nbRows; ++row)
{
for(int col{0}; col < nbColumns; ++col)
{
Gtk::Button* btn {new Gtk::Button("A")};
m_c2.attach(*btn, col, row, 1, 1);
}
}
// Layout setup:
pack_start(m_c1);
pack_start(m_c2);
show_all();
}
What I want to do is to make sure the child widgets in m_c2 are the same size as the child widget in m_c1, to ensure visual consistency between the two containers. Otherwise, it looks like this:
Just to make sure, I do not want m_c1 to be made a Gtk::Grid.
Here's what I have tried so far: I used the get_child_at() method available from the Gtk::HBox m_c1 to get a reference to its first child. I then called get_width() and get_height() on that child. The idea was to feed m_c2's child widgets these dimensions. The problem is that the returned reference is a nullptr.
From some posts I have read, it seems my widgets may no yet be realized, and that would explain my difficulties. How could I achieve this?
You need to set each button in the grid to expand horizontally. Then all the sizing will take care of itself.
Related
as the title says, i want to access the member variables of a class that inheritates of QWidget from a QGridLayout in a QMainWindow.
I am able to access member functions of the QWidget class, but i cant reach the members of my "Player" class.
I am aware, that "->widget()" returns just a QWidget*. Is there another way to return the real class, which sits at this coordinates?
This question shows just to access functions of the QWidget but not of classes inheritated of QWidget.
Code of the QMainWindow class:
...
for(int row = 0; row < rowsCount; row++) {
for(int col = 0; col < colsCount; col++) {
QWidget *player = this->ui->gridLayout->itemAtPosition(row, col)->widget();
player->[HERE I WANT TO ACCESS THE PUBLIC MEMBER]
}
}
...
If I well understand you just have to dynamic cast your widget to a Player and check you really have a Player by security :
QWidget *widget = this->ui->gridLayout->itemAtPosition(row, col)->widget();
Player * player = dynamic_cast<Player *>(widget);
if (player != NULL) {
...
}
I try to create a tiled base map by using Qt GUI:
I have two classes: one to set my tile images that inherits from QGraphicPixmapItem, one to load my map from a text file that is a 2D array composed of the numbers 1, 0 and 2, and add it to a scene:
But my program quits unexpectedly and I do not know where is the break:
my tile class:
class mapTile: public QGraphicsPixmapItem
{
public:
enum Tile {DOOR, GRASS, FIRE};
mapTile(): QGraphicsPixmapItem(),currentTile(GRASS){
syncBitmap();
}
Tile getTile() const {return currentTile;}
void setTile(Tile newTile){
if(currentTile!= newTile){
currentTile=newTile;
syncBitmap();
}
}
private:
void syncBitmap() {//Set my image to my tile
switch(currentTile) {
case DOOR:
image->setPixmap(QPixmap(":/mpa/castledoors.png"));
case GRASS:
image->setPixmap(QPixmap(":/map/grass3_blur.jpg"));
case FIRE:
image->setPixmap(QPixmap(":/map/feu1/png"));
}
}
Tile currentTile;
QGraphicsPixmapItem *image;
};
my class map:
Map.h:
class Map
{
public:
static const int TILE_SIZE=20; //value of the tile in pixels!!!->20X20=a tile
void paintMap(QGraphicsScene *scene);
Map();
private:
static const int WIDTH= 13;// width of the grid cell
static const int HEIGHT= 9;//height of the grid cell
//my grid cell map !!!!
int array[WIDTH][HEIGHT];
mapTile *tile; //my tile
};
And Map.cpp
/*Create a default Map*/
Map::Map()
{
QFile myfile("path to my file");
if (!myfile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::information(0, "error", myfile.errorString());
}
QTextStream in(&myfile);
QString line ;
QStringList fields;
int i=0;
int j=0;
while (!in.atEnd()) {
//Fill my array with my list--> convert Qtring into Int
for (i=0; i<HEIGHT; i++ ) {
line = in.readLine();
fields = line.split(" ");
for(j=0;j<WIDTH;j++)
{
//cout<<fields[j].toInt();
array[i][j] = fields[j].toInt();
}
}
}
}
//Paint my map with my tile
void Map::paintMap(QGraphicsScene *scene){
int i=0, j=0;
tile= new mapTile();
for (i=0; i<HEIGHT; i++){
for(j=0; j<WIDTH; j++){
switch(array[i][j]){
case 0:
tile->setTile(mapTile::GRASS);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
case 1:
tile->setTile(mapTile::FIRE);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
case 2:
tile->setTile(mapTile::DOOR);
tile->setPos(i,j);
scene->addItem(tile);
j+=TILE_SIZE;
}
i+=TILE_SIZE;//
}
}
}
and finally my mainwindow (I directly give the file.cpp):
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
myMap= new Map;
scene= new QGraphicsScene(this);
myMap->paintMap(scene);
ui->graphicsView->setScene(scene);
}
MainWindow::~MainWindow()
{
delete ui;
}
Sorry guys for the long post but I am stuck!
Any ideas on what I missed?
Well, there are at least five problems in your code:
You can't share the same myTile instance when adding them to the scene. You need to create a new one each time you're adding a tile to the scene. At the moment you're creating only one instance at the start of Map::paintMap.
Your myTile class inherits from QGraphicsPixmapItem, however it also has a QGraphicsPixmapItem *image; member which it leaves uninitialized (so it's a roaming pointer), but then it uses it in image->setPixmap(QPixmap(":/mpa/castledoors.png")); which will crash. Instead, you'll want to just call setPixmap(QPixmap(":/mpa/castledoors.png")) (calling the function of your superclass).
In the above item you may have noticed that you misspelled "map" as "mpa", though this will not be the reason of your crash.
In mapTile::syncPixmap as well as in Map::paintMap, you forgot the break; statements in the switch. Without those, all tiles would appear like fire tiles.
You're adding your tile size to the i and j iterators, as if they were the pixel coordinates, but at the same time you're using them as array indexes. You need to either use separate variables for the pixel coordinates or multiply i and j by the TILE_SIZE when calling setPos instead.
Good luck!
I have finally found solutions to my problems:
I added a scene to my function paintMap for adding each item to my scene and created a QGraphicScene* getScene() that returns the scene then I just call it my mainWindow by just writing
ui->graphicsView->setScene(myMap->getScene())
Moreover in my mapTile class I don't use anymore the function setTile(). I removed it, then changed my QGraphicPixmapItem *image by a QPixmap image and in my function syncbitmap() I did
this->setPixmap(image.scaled(TILE_SIZE,TILE_SIZE));at the end of my switch! Why? Because my tile is already a QGraphicsPixmapItem (inheritance) so I just have to set a pixmap to it! And I put it at the end of the switch because if I set the image before, the image will never change because it will be already set! And finally in the constructor I removed currentTile(GRASS), added a Tile variable in my constructor as an argument and just put currentTile= name of your variable in the constructor and then called the syncBitmap function. So in paintMap() you just have to do:
tile= new mapTile(mapTile::GRASS);
tile->setPos(j*TILE_SIZE,i*TILE_SIZE);
scene->addItem(tile);
in each case! VoilĂ !
I hope that will help a newbie like me, at least to understand the logic behind! I am not sure that is the best way to do but this way works for me! :)
I'm adding images as items to a QTableView, I'm also adding a specific text to each images, problem is the text is shown beside the image or the icon, but I want QTableView to show it below the image or the icon. My code snippet is as below:
QStandardItemModel * model = new QStandardItemModel(NumOfRow, 3, this);
Then comes this part which is in the loop
//
QStandardItem * itm = new QStandardItem;
itm->setIcon(image);
itm->setText(text);
model->setItem(row, column, itm);
//
Then this part outside the loop
ui->listOfImages->setModel(model);
ui->listOfImages->setStyleSheet(QString("icon-size: 150px 150px"));
ui->listOfImages->verticalHeader()->setVisible(false);
ui->listOfImages->horizontalHeader()->setVisible(false);
for(int i=0; i<=rowPointer; i++)
{
ui->listOfImages->setRowHeight(i,150);
}
for(int j=0; j<3; j++)
{
ui->listOfImages->setColumnWidth(j,150);
}
Could you say me if there is any way to put the name below the icon rather than in the right side of the icon?
Thanks
Well, I would try to handle the text alignment with the custom QStandardItemModel sub class. Here is the sample model, that implements it:
class ItemModel : public QStandardItemModel
{
public:
ItemModel(int rows, int columns, QObject *parent = 0)
:
QStandardItemModel(rows, columns, parent)
{}
QVariant data(const QModelIndex &index, int role) const
{
if (role == Qt::TextAlignmentRole) {
return Qt::AlignBottom; // <- Make alignment look different, i.e.
// <- text at the bottom.
} else {
return QStandardItemModel::data(index, role);
}
}
};
So, instead of using the Qt class, you will need to create an instance of this custom class:
QStandardItemModel * model = new ItemModel(NumOfRow, 3, this);
The rest will remain the same.
I am trying to implement in Qt a main window which has 2 widgets: one area where I draw some points and one list box where I write all the points with their respective coordinates. And I would like to implement the function "delete point" of a button on the main window, i.e. when I press the button then the point selected from the list box should disappear from my area where I am drawing. So I was thinking of doing this with signals/slots, but when I try to gain access to my list of points from my drawing area it just doesn't find any containing data. This is my code until now:
paintwidget.cpp (my main window):
PaintWidget::PaintWidget(QWidget parent) :
QWidget(parent),
ui(new Ui::PaintWidget)
{
area = new RenderArea(this);
ui->setupUi(this);
connect(ui->displayWidget, SIGNAL(listUpdated(QList)), ui->pointsListWidget,
SLOT(onListUpdated(QList*)));
connect(ui->deletePoints, SIGNAL(clicked()), this, SLOT(deleteItem()));
}
void PaintWidget::deleteItem()
{
area->deletePoint(ui->pointsListWidget->currentItem());
}
renderarea.cpp (my drawing area):
void RenderArea::mousePressEvent(QMouseEvent *e)
{
point = e->pos();
updateList(point);
this->update();
}
void RenderArea::updateList(const QPoint& p)
{
list.append(p);
if (list.count()>1)
lineAdded(p);
emit listUpdated(&list);
}
void RenderArea::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setPen(QPen(Qt::black,2));
for (int i = 0; i < list.size(); ++i)
painter.drawPoint(list[i]);
if (list.size()>1)
for(int j = 0; j < list.size()-1; ++j)
painter.drawLine(list[j], list[j+1]);
}
void RenderArea::deletePoint(QListWidgetItem *item)
{
bool ok1;
bool ok2;
int index = item->text().indexOf(",");
int x = item->text().left(index).toInt(&ok1, 10);
int y = item->text().mid(index + 1).toInt(&ok2, 10);
for (int i = 0; i < list.size(); ++i)
//find the point with x and y as coordinates and delete it
}
listbox.cpp:
void ListBox::onListUpdated(QList *list)
{
clear();
for (int i = 0; i < list->size(); ++i)
addItem(new QListWidgetItem(QString::number(list->at(i).x()) + ", " +
QString::number(list->at(i).y())));
}
The list from the render area is a QList of QPoints. The problem is that in the FOR-loop the size of the list is 0 so I cannot see any of the points that it should contain. I think that I am failing to initialize it somewhere but I am not sure where.
The points are drawn with QPainter so when I delete the point from the list is there any possibility to delete them from my drawing area also?
I'm suspecting you've got two RenderArea widgets hanging around for some reason.
You're connecting ui->displayWidget's signal, but acting on the area widget for the delete.
Shouldn't you be calling ui->displayWidget->deletePoint or connecting area's signal?
As for the repaint, you should call the widget's update() method to have it repaint itself.
I try to remove widgets from a specified row in a QGridLayout like this:
void delete_grid_row(QGridLayout *layout, int row)
{
if (!layout || row < 0) return;
for (int i = 0; i < layout->columnCount(); ++i) {
QLayoutItem* item = layout->itemAtPosition(row, i);
if (!item) continue;
if (item->widget()) {
layout->removeWidget(item->widget());
} else {
layout->removeItem(item);
}
delete item;
}
}
But when I call it, the app crashes with SIGSEGV on delete item in the first iteration. Any ideas?
Short answer: Use the code provided below
Removing a row or column (or even a single cell) from a QGridLayout is tricky. Use the code provided below.
Long answer: Digging into QGridLayout details
First, note that QGridLayout::rowCount() and QGridLayout::columnCount() always return the number of internally allocated rows and columns in the grid layout. As an example, if you call QGridLayout::addWidget(widget,5,7) on a freshly constructed grid layout, the row count will be 6 and the column count will be 8, and all cells of the grid layout except the cell on index (5,7) will be empty and thus invisible within the GUI.
Note that it's unfortunately impossible to remove such an internal row or column from the grid layout. In other words, the row and column count of a grid layout can always only grow, but never shrink.
What you can do is to remove the contents of a row or column, which will effectively have the same visual effect as removing the row or column itself. But this of course means that all row and column counts and indices will remain unchanged.
So how can the contents of a row or column (or cell) be cleared? This unfortunately also isn't as easy as it might seem.
First, you need to think about if you only want to remove the widgets from the layout, or if you also want them to become deleted. If you only remove the widgets from the layout, you must put them back into a different layout afterwards or manually give them a reasonable geometry. If the widgets also become deleted, they will disappear from the GUI. The provided code uses a boolean parameter to control widget deletion.
Next, you have to consider that a layout cell can not just only contain a widget, but also a nested layout, which itself can contain nested layouts, and so on. You further need to handle layout items which span over multiple rows and columns. And, finally, there are some row and column attributes like minimum widths and heights which don't depend on the actual contents but still have to be taken care of.
The code
#include <QGridLayout>
#include <QWidget>
/**
* Utility class to remove the contents of a QGridLayout row, column or
* cell. If the deleteWidgets parameter is true, then the widgets become
* not only removed from the layout, but also deleted. Note that we won't
* actually remove any row or column itself from the layout, as this isn't
* possible. So the rowCount() and columnCount() will always stay the same,
* but the contents of the row, column or cell will be removed.
*/
class GridLayoutUtil {
public:
// Removes the contents of the given layout row.
static void removeRow(QGridLayout *layout, int row, bool deleteWidgets = true) {
remove(layout, row, -1, deleteWidgets);
layout->setRowMinimumHeight(row, 0);
layout->setRowStretch(row, 0);
}
// Removes the contents of the given layout column.
static void removeColumn(QGridLayout *layout, int column, bool deleteWidgets = true) {
remove(layout, -1, column, deleteWidgets);
layout->setColumnMinimumWidth(column, 0);
layout->setColumnStretch(column, 0);
}
// Removes the contents of the given layout cell.
static void removeCell(QGridLayout *layout, int row, int column, bool deleteWidgets = true) {
remove(layout, row, column, deleteWidgets);
}
private:
// Removes all layout items which span the given row and column.
static void remove(QGridLayout *layout, int row, int column, bool deleteWidgets) {
// We avoid usage of QGridLayout::itemAtPosition() here to improve performance.
for (int i = layout->count() - 1; i >= 0; i--) {
int r, c, rs, cs;
layout->getItemPosition(i, &r, &c, &rs, &cs);
if (
(row == -1 || (r <= row && r + rs > row)) &&
(column == -1 || (c <= column && c + cs > column))) {
// This layout item is subject to deletion.
QLayoutItem *item = layout->takeAt(i);
if (deleteWidgets) {
deleteChildWidgets(item);
}
delete item;
}
}
}
// Deletes all child widgets of the given layout item.
static void deleteChildWidgets(QLayoutItem *item) {
QLayout *layout = item->layout();
if (layout) {
// Process all child items recursively.
int itemCount = layout->count();
for (int i = 0; i < itemCount; i++) {
deleteChildWidgets(layout->itemAt(i));
}
}
delete item->widget();
}
};
The QGridLayout itself is managing the QLayoutItem's. I believe the moment you call removeWidget the item will be deleted. Thus you have an invalid pointer at that point. Attempting to do anything with it, not just delete, will fail.
Thus, just don't delete it, you'll be fine.