PDA

View Full Version : update grid of pixmap labels based on 2d char arrray value



wuts.the.martyr
1st December 2011, 01:26
Hello all, I am undertaking a game using a combination of c++ in visual studios 2010 and Qt 4.7 (both windows). I have created my gui how i want it to look, but now i would like to integrate it into the game files. The game is a clone of battleship and is console input based. I will keep with this, but I used an update mapboard class to redraw the game board after a player fires, which keeps storing over the char array.

On the Qt side in Qt designer, my gui consists of a grid layout 10x10, using labels to hold pixmaps of game cells. I can manually choose what pixmap I would like to display using the properties editor, but I would something dynamic. I have painstakingly named each label to represent its position in the 2d array (ie. fleet map => F_00 => F[0,0] => F[i],[j])

using this, I would like to update my pixmaps for each, using a generic getupdatearray type function. As we traverse the array it will update the pixmap currently associated with individual labels to match their cousins from the array. say F[5][6] = 'X' for hit, then when the loops got to that position in the array it would update the grid of pixmaps at F_56 to equal a hit.png, replacing the empty.png.

I have an idea how to make the loop that would accomplish this, but unsure how i would go about getting the pixmap for each label to be more along the lines of a runtime feature versus the now compile time (static) feature.

I have read about Qtpainter and another Qt class that deals with images, but still having a hard go at it.

Question to any of you, how do I update these pixmaps based on a 2d array?
1.) loop structure - i can figure out
2.) condition statements - i can figure out
3.) qt specific syntax dealing with labels- newbie so no idea atm.

Thanks in Advance

ChrisW67
1st December 2011, 03:32
One approach would to use the grid layout that contains the (anonymous) widgets to allow access by row and column. This makes mapping from underlying 2D array to widget trivial. For example:


#include <QtGui>
#include <QDebug>

class MainWindow: public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *p = 0):
QMainWindow(p) ,
m_blankPixmap("blank.png"),
m_otherPixmap("other.png")
{
QWidget *central = new QWidget(this);

m_layout = new QGridLayout(central);
for(int r = 0; r < 10; ++r) {
for(int c = 0; c < 10; ++c) {
QLabel *label = new QLabel(central);
label->setPixmap(m_blankPixmap);
m_layout->addWidget(label, r, c, Qt::AlignCenter);
}
}

central->setLayout(m_layout);
setCentralWidget(central);

setCellPixmap(3, 3, m_otherPixmap);
}

void setCellPixmap(int row, int col, const QPixmap &pixmap) {
QLabel *label = qobject_cast<QLabel*>( m_layout->itemAtPosition(row, col)->widget() );
if (label) {
label->setPixmap(pixmap);
}
}
private:
QGridLayout *m_layout;
const QPixmap m_blankPixmap;
const QPixmap m_otherPixmap;
};

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

MainWindow m;
m.show();
return app.exec();
}
#include "main.moc"

wuts.the.martyr
1st December 2011, 04:08
If i understand this correctly you are doing something like this?


Note that just as it's possible to use loops to set the images on the controls by name, it's possible to use loops to create the widgets in the first place. You basically would leave the area for the game board blank in the design tool, and then dynamically create the controls with code...attach them to the layout with code...and save pointers to them in 2D array. (You wouldn't access them by label name at that point, you'd index them just as you are indexing your board.)

You could create your own class derived from QLabel (such as a GameCell class) which contained the information for your board cell and methods related to it. Then you wouldn't need an array of label widgets in parallel to an array representing your board. You'd simply have one array of objects that took care of both aspects of the implementation.

this is from a user on stackoverflow (http://stackoverflow.com/questions/8335025/qt-update-pixmap-grid-layout-with-2d-array-values), I like the idea, but I am totally lost with what you have given me. Would it be possible to add comments to some of the lines of code so i can follow it?

This is what the gameloop window looks like now:

http://i629.photobucket.com/albums/uu18/ezeil907/th_currentlook.png (http://s629.photobucket.com/albums/uu18/ezeil907/?action=view&current=currentlook.png)

I had created some code using a class that would update the pixmaps. I would like to make something like you suggested or what the other user mentioned. If i did use this method, would I need to use a Qframe as a container to hold all the widgets in a grid arrangment? Also if I change widget focus ( I have two main windows: game view, splash menu view), will I lose the Qframe under another widget?

Thanks A bunch

Wuts

ChrisW67
1st December 2011, 05:33
What I am doing in the example is cruder than the StackOverflow suggestions and closer to what you are already doing. I am creating 100 widgets, and the StackOverflow suggestion of using the Qt graphics view classes and QGraphicsGridLayout is another way to go about it.

Lines 10 through 24 creates a 10x10 grid of QLabels using nested for() loops. Each label is initially set to a blank pixmap. This is a quick way of doing what your Designer UI has done with 100 individually created labels (have a look in your ui_*.h file). The layout is put into a QMainWindow for example purposes only, you could put it in a QFrame or QWidget. You will create two objects of this class to place in your main UI.

setCellPixmap() shows how to obtain the address of the QLabel in a given cell in order to change its pixmap. Once you have this address you can do anything to that label. Rather than keep a pointer to each label in my own data structure I just fetch it from the layout when required.

If you have an array of data then you could set the labels with a quick loop or two. Try this version of main() in the code above:

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

MainWindow m;
m.show();

QPixmap empty("empty.png");
QPixmap hit("hit.png");
char data[10][10] = {
{'X', ' ', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'},
{'X', 'X', 'X', 'X', 'X', 'X', 'X', ' ', 'X', 'X'},
{'X', 'X', ' ', ' ', 'X', 'X', 'X', 'X', 'X', 'X'},
{'X', 'X', 'X', 'X', 'X', 'X', ' ', ' ', 'X', 'X'},
{'X', 'X', 'X', 'X', 'X', 'X', ' ', 'X', 'X', 'X'},
{'X', 'X', 'X', ' ', 'X', 'X', ' ', 'X', 'X', 'X'},
{'X', 'X', 'X', 'X', 'X', 'X', ' ', 'X', ' ', 'X'},
{'X', 'X', 'X', ' ', 'X', 'X', 'X', 'X', 'X', 'X'},
{'X', ' ', ' ', ' ', 'X', 'X', 'X', 'X', 'X', 'X'},
{'X', 'X', 'X', ' ', 'X', 'X', 'X', 'X', 'X', 'X'},
};
for (int row = 0; row < 10; ++row) {
for (int col = 0; col < 10; ++col) {
if (data[row][col] == ' ')
m.setCellPixmap(row, col, empty);
else
m.setCellPixmap(row, col, hit);
}
}

return app.exec();
}




Also if I change widget focus ( I have two main windows: game view, splash menu view), will I lose the Qframe under another widget?

Sorry, I have no idea what the question means.

wuts.the.martyr
1st December 2011, 09:04
Wow, this is exactly what I was looking for earlier. I really am starting to like Qt, seems pretty straight forward after you play with it for awhile. Anyways like you mentioned I would like to place both arrays of pixmaps into container widgets, either Qframe or Qwidget. So could I create the container in the designer, and just place the arrays inside by calling the code you show with a function call from the main program loop, or would it be easier to just do both at the same time by code alone? If so How would I get the code to go inside the container? Lastly, with my ui as of now, I have the pixmaps scaled to fit inside 20x20 labels that are arranged in a grid layout 250x250 (H/V centered 5x5 spacing 2x2 margins) am I still going to be able to have it like it appears in the image i attached? Thank you so much, you have shown me what I have been googling for who knows how long, I kept finding hints but no definitive ways of doing this. If I had known about the graphicsclass /grid layout method I would have been doing that instead.

I tried out your code, cleaned up a few items to make it work. It will compile/run but gives me a window that is blank, I have tried adding a background image, widget, frame. nothing shows up on a brand new Qt gui application template. I basically disabled the premade mainwindow.cpp since all the implementation goes on in the header using your codes prototypes/functions. I will keep playing with it.

oh and on the part you didn't understand from my last post...
I am not sure how I should be doing this, but I have two main widgets as containers. one holds my pixmap grids that you see in the picture, the other one has the splash screen / start menu where you can select type of networked game you want. I thought you could use focus to bring each widget to the top most layer like you can in many other software programs. I may be wrong about this, since this stuff is still pretty confusing to me. Is this the correct way to do things, or should I be using another mainwindow ui and just call the game stuff after the user is done with the networked game type selection. I keep hearing "scenes", maybe this is what i need to be using instead, dunno. I will keep looking around in regards to this one for sure, since I want my game to look clean and run smoothly.

again many thanks for all your help

Wuts


mainwindow.h code:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtCore>
#include <QtGui>
#include <QDebug>
#include "ui_mainwindow.h"

class MainWindow: public QMainWindow, private Ui::MainWindow {
Q_OBJECT
public:
MainWindow(QWidget *p = 0):QMainWindow(p) ,m_missPixmap("images/missspace.png"),m_hitPixmap("images/hitspace.png"), m_emptyPixmap("images/emptyspace.png"){
QWidget *central = new QWidget(this);

m_layout = new QGridLayout(central);//gridlayout centered in mainwindow

for(int r = 0; r < 10; ++r) {//creates a 2d array of emptyspaces
for(int c = 0; c < 10; ++c) {
QLabel *label = new QLabel(central);
label->setPixmap(m_emptyPixmap);
m_layout->addWidget(label, r, c, Qt::AlignCenter);
}
}

central->setLayout(m_layout);//updates layout on central widget with new gridlayout in the center
setCentralWidget(central);//ditto
}

void setCellPixmap(int row, int col, const QPixmap &pixmap) {//in main.cpp fuction to label controls of widgets with matching [i][j] info
QLabel *label = qobject_cast<QLabel*>( m_layout->itemAtPosition(row, col)->widget() );
if (label) {
label->setPixmap(pixmap);
}
}

private:
QGridLayout *m_layout;
const QPixmap m_emptyPixmap;
const QPixmap m_hitPixmap;
const QPixmap m_missPixmap;
};

#endif // MAINWINDOW_H

main.cpp code:

#include <QtGui/QApplication>
#include <QLabel>
#include <QtCore>
#include <QSound>
#include <QGraphicsWidget>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

MainWindow m;
m.show();

QPixmap empty("images/emptyspace.png");
QPixmap hit("images/hitspace.png");
QPixmap miss("images/missspace.png");
char data[10][10] = {
{'X', ' ', 'X', 'X', 'S', 'X', 'X', 'X', 'X', 'X'},
{'X', 'X', 'X', 'X', 'X', 'X', 'X', ' ', 'X', 'X'},
{'X', 'X', ' ', ' ', 'X', 'X', 'S', 'X', 'X', 'X'},
{'S', 'X', 'X', 'X', 'X', 'X', ' ', ' ', 'S', 'X'},
{'S', 'X', 'X', 'X', 'S', 'X', ' ', 'X', 'S', 'X'},
{'S', 'S', 'X', ' ', 'S', 'X', ' ', 'X', 'S', 'X'},
{'S', 'X', 'X', 'X', 'S', 'X', ' ', 'X', ' ', 'X'},
{'S', 'X', 'X', ' ', 'X', 'X', 'X', 'X', 'X', 'X'},
{'S', ' ', ' ', ' ', 'X', 'X', 'X', 'X', 'X', 'X'},
{'X', 'X', 'X', ' ', 'X', 'X', 'X', 'X', 'X', 'X'},
};
for (int row = 0; row < 10; ++row) {
for (int col = 0; col < 10; ++col) {
if (data[row][col] == ' '){
m.setCellPixmap(row, col, empty);
}
else if (data[row][col]== 'X'){
m.setCellPixmap(row, col, hit);
}
else m.setCellPixmap(row,col, miss);
}
}

return app.exec();
}






/*QApplication a(argc, argv);
MainWindow w;
w.show();

QImage myImage;
myImage.load("images/hitspace.png");

QLabel myLabel;
myLabel.setPixmap(QPixmap::fromImage(myImage));

myLabel.show();*/

ChrisW67
1st December 2011, 10:36
The example is dependent on a few PNG files "blank.png" etc. and will fail silently if they are not present (it is an example not a finished product). My example is a single file for convenience. In a real application you would separate the class into a header and implementation.

You should look at the "Promotion" feature in Designer: place a Qwidget in your main UI layout and promote it to a MyGameGrid class that you hand write along the lines of the example.
You should look at QStackedWidget to hold the start widget and the game widget and allow switching from one to another.

wuts.the.martyr
5th December 2011, 16:20
okay, so i now have a qstackedwidget running with 3 pages, that are selectable on button/menu presses. I have created a function to automatically set the page 0 to start every time. a few qmessage/dialog boxes have been implemented as well. I have created some hardcoded arrays to test your script with and two buttons to swap them back and forth to make sure they update fine. I have tried to incorporate what you have shown, but failing horribly lol. All i get is a white screen, and if i change layers to another widget i crash. so my new question is, given my hierarchy of mainwindow.cpp, mainwindow.h, main.cpp, and mainwindow.ui... where should i be creating this loop to generate the array, i thought it should be on the main.cpp. Also what will i need to do to create these pixmap arrays on the correct page/with offset, so that when i switch views they dont still show up-anchoring maybe? they need to lie on my second pages widget called Game_Loop. Thanks again for all the help. below is what i have done to get me a white window of death.

I placed the:

void setCellPixmap(int row, int col, const QPixmap &pixmap) //whole thing is there i just trimmed for post
QGridLayout *m_layout;
const QPixmap m_emptyPixmap;
const QPixmap m_hitPixmap;
const QPixmap m_missPixmap;
inside my mainwindow.h

then i placed:

QWidget *central = new QWidget(this);
m_layout = new QGridLayout(central);
for(int r = 0; r < 10; ++r) {
for(int c = 0; c < 10; ++c) {
QLabel *label = new QLabel(central);
label->setPixmap(m_emptyPixmap);
m_layout->addWidget(label, r, c, Qt::AlignCenter);
}
}
central->setLayout(m_layout);
setCentralWidget(central);
setCellPixmap(3, 3, m_hitPixmap);
inside the mainwindow.cpp with the proper urinary scope variables initialized

then on the main.cpp i had this:

...
QPixmap empty("images/emptyspace.png");
QPixmap hit("images/hitspace.png");
QPixmap miss("images/missspace.png");
char data[10][10] = {
{'X',' ',' ','X',' ',' ',' ','X',' ',' '},
{' ','X',' ',' ','X',' ','X','S','X',' '},
{' ','X','S','X',' ',' ','X',' ','X',' '},
{'X','X','X',' ',' ',' ','X',' ',' ','X'},
{' ',' ','X','X',' ',' ','X',' ',' ','X'},
{' ',' ','X','S','X','X','X','X','X',' '},
{' ','S','X','X','X','X','X','S',' ',' '},
{' ','X','X',' ','X','X','X','X',' ','X'},
{' ',' ','S',' ',' ',' ',' ','X',' ',' '},
{' ',' ',' ',' ',' ',' ',' ','X',' ',' '}
};
for (int row = 0; row < 10; ++row) {
for (int col = 0; col < 10; ++col) {
if (data[row][col] == ' '){
w.setCellPixmap(row, col, empty);
}
else if(data[row][col] == 'X'){
w.setCellPixmap(row,col,hit);
}
else
w.setCellPixmap(row, col, hit);
}
}
w.setstackwidget();
return a.exec();
}

ChrisW67
5th December 2011, 23:35
If all you get is a blank grid then you should check your pixmaps: QPixmap::isNull(). Then ask yourself, why would the constructor give me a null pixmap? Where is the program looking for the the image files and are they present?

wuts.the.martyr
6th December 2011, 00:00
QPixmap::QPixmap ( const QString & fileName, const char * format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor )

Constructs a pixmap from the file with the given fileName. If the file does not exist or is of an unknown format, the pixmap becomes a null pixmap.

The loader attempts to read the pixmap using the specified format. If the format is not specified (which is the default), the loader probes the file for a header to guess the file format.

The file name can either refer to an actual file on disk or to one of the application's embedded resources. See the Resource System overview for details on how to embed images and other resource files in the application's executable.

If the image needs to be modified to fit in a lower-resolution result (e.g. converting from 32-bit to 8-bit), use the flags to control the conversion.

The fileName, format and flags parameters are passed on to load(). This means that the data in fileName is not compiled into the binary. If fileName contains a relative path (e.g. the filename only) the relevant file must be found relative to the runtime working directory.

I evidently didn't place it in the runtime folder? I was attempting to use the directory i use for my image resource file, is it possible to use that? or do I have to copy the folder over at runtime to test? Also is it possible to replace pixmaps placed using QtCreator by label name, such as my fire button switching from place during different modes of the game? could i use a boolean check slot /signal connection like this (after the boolean condition check) ui->F_43->setCellPixmap(4,3,fire);

ChrisW67
6th December 2011, 00:53
You can use any method you like to find your external resources; having the files on the file system relative to the application executable is one method (see QCoreApplication::applicationDirPath()). For small icons and the like it is often desirable to build them into your program so they cannot get lost: Qt Resource System.

Once your program is running you can change any UI widget property you like, you are not stuck with what you have set in the Qt Designer .ui file.

wuts.the.martyr
6th December 2011, 01:32
well i got that part working now by using:

ui->label->setPixmap(QPixmap(":/images/missspace.png"));


Now I am trying to make a for loop situation where i make a QString equal the name of a label I would like to change, but am having a problem trying to typecaste it to be that label I want. Maybe you can figure out an alternative from this:


xvalue = ui->X_coord->value();//value of x taken from a spin box
yvalue = ui->Y_coord->value();//value of y taken from a spin box
TR_position.clear();//initialize the QString TR_position
TR_position = ("TR_");//place the first part of the label name on there
TR_position.append(QString::number(xvalue));//append the x value to the end of the string
TR_position.append(QString::number(yvalue));//append the y value to the end of the string
ui->(QLabel)TR_position->setPixmap(QPixmap(":/images/missspace.png"));//fails, but trying to call the label that is represented by that string

was thinking that maybe I can just put the string characters themselves there, but i doubt that it would run.

thank you for all your tips again

ChrisW67
6th December 2011, 02:24
Now I am trying to make a for loop situation where i make a QString equal the name of a label I would like to change, but am having a problem trying to typecaste it to be that label I want.
You cannot rewrite C++ code at run time, which seems to be what you are expecting to happen. You need to abandon this line of thinking. You don't have 100 labels with individual names any more and you already have a way to change the pixmap on the anonymous label at a given row and column in the layout (setCellPixmap() in my example). What is the new problem you are trying to solve?

wuts.the.martyr
6th December 2011, 02:42
so i can't use the contents of the QString to reference a QLabel by name? that's all I am trying to do. I understand you can't rewrite. I would just like to bend the rules a bit, and use the string that i conjure up each run through to grab the appropriate label and swap the image. I will attempt to go back to what you had, now that I know how to get the png files I require, if that's what it takes, I just wanted to do something simple albeit time consuming to code since I am running out of time. I am not trying to be hard headed here either, I just am having a hard time getting what I ultimately want, that I currently have minus the updating of game tiles. With what you have given me, I will need to figure out how to place it in my existing widget container. That is why I havent gone that route. I know you have already done so much, but i can only do fairly simple things using the Designer and slots/signals. Would you still be willing to at the very least help me get the game board we made placed inside my page widget. as it lies right now it breaks my game.

wuts.the.martyr
6th December 2011, 21:02
Sorry again, its just hard to give up on something I spent a lot of time on, I've gotten to work finally even, but honestly its a terrible method and I have to write a bunch of code to differentiate between the QWidget class and QMainwindow back and forth since the mainwindow takes on the characteristics of the QWidget class. Whilst I have managed to get your code to resize a bit, I would still rather have it take on the properties of my current QGridLayout I created using Designer, or even just place the pixmaps in there, but i see the later being a problem possibly? any ideas?

ChrisW67
7th December 2011, 04:57
You can build the game panel with Designer and still use the general method for finding the label at [r,c] in a grid layout. See the attached complete Designer built example (3x3 but you get the idea). I have put a timed, random event in the main window so you can see stuff changing on the two game panels. It doesn't matter what you call the labels, and you don't have to try to find them by name.