Qt:用2d数组值更新pixmap网格布局

时间:2011-12-01 00:23:03

标签: qt qpixmap grid-layout

我正在使用视觉工作室2010和Qt 4.7(两个窗口)中的c ++组合进行游戏。游戏是战舰的克隆,基于控制台输入。我已经创建了我想要它的gui,在Qt设计器的Qt端,我的gui包含一个10x10的网格布局,使用标签来保存游戏单元的像素图:

current look of the game

我精心命名每个标签以表示其在二维阵列中的位置(即。舰队地图=> F_00 => F[0,0] => F[i],[j])。我可以使用属性编辑器手动选择我想要显示的像素图,但我想要一些动态的东西。

我使用更新mapboard类在玩家触发后重绘游戏板,这将继续存储在char数组中。我想使用通用的getupdatearray类型函数更新每个pixmaps。当我们遍历数组时,它将更新当前与各个标签相关联的像素图,以匹配来自阵列的兄弟。 (比如命中F[5][6] = 'X',然后当循环到达数组中的那个位置时,它会将F_56处的pixmaps网格更新为等于 hit.png ,将替换为空。 PNG

我知道如何制作可实现此目的的循环,但不确定如何让每个标签的像素图更符合运行时功能与现在编译时(静态)功能的关系。我已经阅读了有关QPainter和另一个处理图像的Qt课程,但仍然很努力。

问你们,如何根据二维数组更新这些pixmaps?

  1. 循环结构 - 我可以搞清楚
  2. 条件陈述 - 我可以弄清楚
  3. qt处理标签的特定语法 - 新手所以不知道atm。
  4. 这是我尝试用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并说:

    // 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
    

    先谢谢

    更新

    我已经达到了可以用按钮按下交换pixmaps的程度,但我想创建更具动感的东西。我想使用一个Qstring,我在其中使用附加x y值来放置我想要更改的标签的名称:

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

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

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

    ......它显然不起作用。有没有办法输入case,或者使用字符串的内容作为Qlabel名称?

1 个答案:

答案 0 :(得分:3)

您手动输入并命名 200个标签小部件?没有人叫你懒。 :)

根据您的更新,您现在know how to use QLabel::setPixmap()。您需要的是认为从名称中获取QLabel指针,这可能是两件事的组合:

如果你沿着这条路走下去,你最终会得到的结论是:

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

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

  • 它很脆弱:如果没有任何具有该名称的小部件(神秘的崩溃)怎么办?或者甚至更糟糕的是,如果有多个带有该名称的小部件,并且这段代码会幸福地忽略那些可能是错误的奇怪情况?

  • 这是糟糕的OOP :虽然有一些不错的案例可以使用动态投射(或“向下转换”),但它通常表明设计存在缺陷。您知道所有QLabel都是QWidgets,但并非所有QWidgets都是QLabel ...因此qobject_cast调用可能会返回NULL。这只是另一个失败点。有时你无法避免这种情况,但实际上没有理由要求你的程序以这种方式构建。

  • 速度非常慢:按名称搜索小部件本质上是一种天真的递归搜索。如果你为每个网格留出一个单独的小部件框架并且只搜索它,Qt将不得不进行100次字符串比较以找到最后一个元素(在平均情况下为50)。想象一下用循环清除网格......现在你谈的是100 * 50字符串比较!

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

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


更新:由于您在评论中询问了具体细节,因此这是一个GameCell类:

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小部件可以使用这些GameCells的QGridLayout:

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小部件填充的空间。或者你可以继续推进并以编程方式完成整个事情。为了简单起见,我只是将两块板彼此相邻,并在对话框的表面上放置一个垂直分隔符:

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个标签:

simple battleship

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

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

ADDENDUM :为每个网格单元创建一个QWidget不一定是首先要做的事情。有些人可能会认为“重量级”。如果你的游戏是在一个巨大的虚拟空间上玩,那么它可能会太慢。您可能会发现查看QGraphicsGridLayout很有用,从长远来看,这可能是一种更合适的方法:

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

使用QWidgets对于10x10网格来说不是什么大问题,所以如果你想坚持下去就可以了。如果你打算这样做,那么至少你不应该把它们全部放在手边!