Qt:使用2d数组值更新像素图网格布局 [英] Qt: update pixmap grid layout with 2d array values

查看:122
本文介绍了Qt:使用2d数组值更新像素图网格布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在Visual Studio 2010和Qt 4.7(两个窗口)中结合使用c ++进行游戏.该游戏是战舰的复制品,基于控制台输入.我已经创建了自己的gui外观,在Qt设计器的Qt一侧,我的gui由一个10x10的网格布局组成,使用标签保存游戏单元的像素图:

I am undertaking a game using a combination of c++ in visual studios 2010 and Qt 4.7 (both windows). The game is a clone of battleship and is console input based. I have created my gui how I want it to look, and on the Qt side in Qt designer, my gui consists of a grid layout 10x10, using labels to hold pixmaps of game cells:

我精心命名了每个标签,以表示其在2d数组中的位置(即,车队地图=> F_00 => F[0,0] => F[i],[j]).我可以使用属性编辑器手动选择要显示的像素图,但是我想要动态的东西.

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]). I can manually choose what pixmap I would like to display using the properties editor, but I would like something dynamic.

我使用一个更新mapboard类在玩家开火后重绘游戏板,该类一直存储在char数组上.我想使用通用的getupdatearray类型函数来更新每个像素图.当我们遍历数组时,它将更新当前与各个标签关联的像素图,以匹配它们在数组中的表亲. (用F[5][6] = 'X'表示命中,然后当循环到达数组中的该位置时,它将更新F_56处的像素图网格等于 hit.png ,替换为 empty.png .

I use an update mapboard class to redraw the game board after a player fires, which keeps storing over a char array. 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 hit.png, replacing the empty.png.

我有一个想法,如何使循环完成此任务,但不确定如何将每个标签的像素图变得更像运行时功能,而不是现在的编译时(静态)功能.我已经阅读了有关QPainter和另一个处理图像的Qt类的信息,但是仍然很难解决.

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 QPainter and another Qt class that deals with images, but still having a hard go at it.

任何人的问题,我如何基于2d数组更新这些像素图?

Question to any of you, how do I update these pixmaps based on a 2d array?

  1. 循环结构-我能弄清楚
  2. 条件陈述-我可以弄清楚
  3. qt处理标签的特定语法-新手,所以不知道atm.

下面是一些我想对map.h进行处理的伪代码:

Here's some pseudocode of the kind of thing I am trying to do with map.h:

#include <QtCore>
#include <QtGui>

// WARNING: PSEUDOCODE, DOES NOT COMPILE
// AT A LOSS ON HOW TO SELECT THE CORRECT LABEL
// MAYBE A CHILD CLASS FOR THAT?

class map {
public:
    char updateboard(char mapname, char b[][10]){
        for(int i=0;i<10;i++){
            for(int j=0;j<10;j++){
                char C = b[i][j]; 
                if (C == 'M'){//miss
                    Qlabel mapname_[i][j](<img src='images/missspace.png'/>);
                    mapname_[i][j].show();
                } 
                else if(C == 'X'){//hit
                    Qlabel mapname_[i][j](<img src='images/hitspace.png'/>);
                    mapname_[i][j].show();
                }
                else if(C == ' '){//undiscovered space
                    Qlabel mapname_[i][j](<img src='image/emptyspace.png'/>);
                    mapname_[i][j].show();
                }
            }
        }
    }
};

然后在我的mainwindow.cpp文件中,包含map.h并说:

Then in my mainwindow.cpp, I include map.h and say:

// calls class function update board
// takes updated array values and replaces old pixmap with new

map.updateboard(T,b[][10]); // target map update
map.updateboard(F,v[][10]); // fleet map update

预先感谢

更新:

我已经可以用按钮交换像素图了,但是我想创建更动态的东西.我想使用Qstring,在其中放置要更改的标签名称,方法是使用以下命令附加x y值:

I've gotten to the point where I can swap pixmaps with buttons presses, but I would like to create something more dynamic. I wanted to use a Qstring in which I place the name of the label I want to change using by appending x y values using:

TR_position.append(QString::number(xvalue));

当我尝试使用以下方式调用它时:

When I try to call it using:

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

...它显然不起作用.有没有办法键入大小写,或将字符串的内容用作Qlabel名称?

...it obviously doesnt work. Is there a way to type case it, or use the contents of the string as the Qlabel name?

推荐答案

您手动输入并命名了 200个标签小部件?不要让任何人叫你懒. :)

You manually entered and named 200 label widgets? Let no one call you lazy. :)

根据您的更新,现在您了解如何使用QLabel::setPixmap() .您认为所需要的是从名称中获取QLabel指针,这将是两件事的结合:

Per your update, you now know how to use QLabel::setPixmap(). What you think you need is getting a QLabel pointer from a name, which would be a combination of two things:

qobject_cast 从中获取QLabel*一个QWidget

如果您走这条路,那么最终会得到如下结果:

If you go down this path, what you'd wind up with is something like:

QWidget* cellWidget = ui->findChild(TR_position);
QLabel* cellLabel = qobject_cast<QLabel*>(cellWidget);
cellLabel->setPixmap(QPixmap(":/images/missspace.png"));

但是要小心!这种方法有很多问题.

But BEWARE! There are many things wrong with this approach.

  • 它很脆:如果没有碰巧有这个名字的小部件(神秘崩溃)怎么办?甚至更糟的是,如果有多个具有相同名称的 小部件,并且此代码行事地对那些可能是bug的奇怪情况一无所知,那么该怎么办?

  • It's brittle: What if there doesn't happen to be any widget with that name (mysterious crash)? Or even worse, what if there are multiple widgets with that name and this code marches along blissfully ignorant of that odd condition that is likely a bug?

糟糕的OOP :尽管在某些情况下可以使用动态转换(或向下转换"),但它通常表示设计中存在缺陷.您知道所有QLabel都是QWidget,但是并非所有QWidget都是QLabels ...,因此qobject_cast调用可能返回NULL.这只是失败的另一点.有时您无法避免这种情况,但是实际上没有理由需要以这种方式来构造您的程序.

It's poor OOP: While there are some decent cases to use dynamic casting (or "downcasting"), it usually indicates a flaw in a design. You know that all QLabels are QWidgets, but not all QWidgets are QLabels...so that qobject_cast call might return NULL. It's just one more point of failure. Sometimes you can't avoid this, but really there is no reason your program needs to be structured in such a way.

这非常慢:按名称搜索小部件本质上是幼稚的递归搜索.如果您为每个网格留出一个单独的小部件框架并仅进行搜索,则Qt将必须进行100个字符串比较才能找到最后一个元素(平均情况下为50个).想象一下用循环清除网格...现在您正在谈论100 * 50字符串比较!

It's terribly slow: Searching for a widget by its name is essentially a naive recursive search. If you've set aside a separate widget frame for each grid and only search that, Qt will have to do 100 string compares to find the last element (so 50 in the average case). Imagine clearing the grid with a loop...now you're talking about 100*50 string compares!

所有这些事情都是可以避免的.就像可以使用循环按名称设置控件上的图像一样,也可以首先使用循环来创建窗口小部件.基本上,您将在设计工具中将游戏板的区域留空,然后使用代码动态创建控件...使用代码将其附加到布局中...并将指向它们的指针保存在2D数组中. (此时,您将不会按标签名称访问它们,而是在对板进行索引时对它们进行索引.)

All these things are avoidable. 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.)

您可以创建一个自己的派生自QLabel的类(例如GameCell类),其中包含有关板单元的信息以及与之相关的方法.这样,您将不需要与代表您的电路板的阵列并行的标签小部件阵列.您只需拥有一个对象数组即可处理实现的两个方面.

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.

更新:由于您在注释中要求提供详细信息,因此这里是GameCell类:

UPDATE: Since you asked in the comments for specifics, here's a GameCell class:

class GameCell : public QLabel
{
    Q_OBJECT    

public:
    enum State { Undiscovered, Hit, Miss };

    GameCell (QWidget *parent = 0) : QLabel (parent),
        currentState (Undiscovered)
    {
        syncBitmap();
    }

    State getState() const { return currentState; }

    void setState(State newState) {
        if (currentState != newState) {
            currentState = newState;
            syncBitmap();
        }
    }

private:
    void syncBitmap() { // you'd use setPixmap instead of setText
        switch (currentState) {
        case Undiscovered: setText("U"); break;
        case Hit: setText("H"); break;
        case Miss: setText("M"); break;
        }
    }

    State currentState;
};

这通过充当QWidget并保持内部状态来完成双重任务.然后,GameMap小部件可以使用以下GameCell的QGridLayout:

This does double duty by behaving like a QWidget as well as maintaining a piece of internal state. Then a GameMap widget can use a QGridLayout of these GameCells:

class GameMap : public QWidget {
    Q_OBJECT

public:
    static const int Rows = 10;
    static const int Columns = 10;

    GameMap (QWidget* parent = 0) :
        QWidget (parent)
    {
        layout = new QGridLayout (this);
        for (int column = 0; column < Columns; column++) {
            for (int row = 0; row < Rows; row++) {
                GameCell* cell = new GameCell (this);
                cells[column][row] = cell;
                layout->addWidget(cell, row, column);
            }
        }
    }

private:
    GameCell* cells[Columns][Rows];
    QGridLayout* layout;
};

如果愿意,您可以在要使用GameMap小部件填充的设计器中的布局中保留空格.或者,您可以以编程方式推动并完成整个过程.为简单起见,我将两个板彼此相邻放置,并在对话框的表面上使用垂直分隔符:

If you wanted to, you could just leave spaces in your layout in the designer you wanted to fill in with the GameMap widget. Or you can push on and do the whole thing programmatically. For the sake of simplicity I'll just put two boards next to each other with a vertical separator on the surface of a dialog:

class Game : public QDialog
{
    Q_OBJECT

public:
    Game (QWidget *parent = 0)
        : QDialog(parent)
    {
        targetMap = new GameMap (this);
        fleetMap = new GameMap (this);

        verticalSeparator = new QFrame (this);
        verticalSeparator->setFrameShape(QFrame::VLine);
        verticalSeparator->setFrameShadow(QFrame::Sunken);

        layout = new QHBoxLayout (this);
        layout->addWidget(targetMap);
        layout->addWidget(verticalSeparator);
        layout->addWidget(fleetMap);
        setLayout(layout);

        setWindowTitle(tr("Battleship"));
    }

private:
    GameMap* targetMap;
    QFrame* verticalSeparator;
    GameMap* fleetMap;
    QHBoxLayout* layout;
};

我不会在这里写整个游戏,也不会让它看起来花哨.这只是要点,展示了如何以编程方式获取200个标签:

I'm not going to write a whole game here or make it look fancy. That's just the gist, showing how to get 200 labels up in a programmatic fashion:

使用我的代码,从(x,y)坐标获取GameCell不需要平均50个字符串比较.由于2D数组具有形式化和可预测的性质,因此对cells[x][y]的索引仅需要单个乘法运算和单个加法运算.没有向下转换,您可以简单地编写:

With my code, getting a GameCell from an (x,y) coordinate doesn't require an average of 50 string compares. Due to the formalized and predictable nature of 2D arrays, indexing into cells[x][y] only requires a single multiply operation and a single addition operation. There's no downcasting, and you can simply write:

cells[x][y].setState(GameCell::Miss);


附录:首先,不必为每个网格单元创建QWidget.有些人可能会认为重量级".如果您的游戏是在巨大的虚拟瓷砖空间上进行的,那就太慢了.您可能会发现研究 QGraphicsGridLayout 很有用.从长远来看,更合适的方法:


ADDENDUM: Creating a QWidget for each grid cell isn't necessarily the way to go in the first place. Some might consider that "heavyweight". If your game were being played out on a large virtual space of tiles then it could be much too slow. You might find it useful to look into QGraphicsGridLayout, which could be a more appropriate approach in the long run:

http://doc.qt.io/qt -5/qtwidgets-graphicsview-basicgraphicslayouts-example.html

不过,使用10x10网格对于QWidgets来说并不是什么大问题,因此,如果您只想坚持使用它,则可以.如果您打算这样做,那么至少您不应该手工放置它们!

Using QWidgets won't be much of an issue with a 10x10 grid, however, so if you want to just stick with that then you can. If you're going to do it that way, then at least you shouldn't be placing them all by hand!

这篇关于Qt:使用2d数组值更新像素图网格布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆