QT: Обновление сетки PixMap с 2D -значениями массива

StackOverflow https://stackoverflow.com/questions/8335025

  •  26-10-2019
  •  | 
  •  

Вопрос

Я провожу игру, используя комбинацию C ++ в Visual Studios 2010 и QT 4.7 (оба Windows). Игра является клоном линкора и основана на консоли. Я создал свой графический интерфейс, как я хочу, чтобы он выглядел, и на стороне QT в QT -дизайнере мой графический интерфейс состоит из сетки 10x10, используя этикетки для удержания пиксов игровых ячеек:

current look of the game

Я кропотливо назвал каждую метку, чтобы представлять его положение в 2D -массиве (т.е. флот map => F_00 => F[0,0] => F[i],[j]) Я могу вручную выбрать, какой PixMap я хотел бы отобразить, используя редактор свойств, но мне бы хотелось что -то динамичное.

Я использую класс Mapboard для обновлений, чтобы перерисовать игровой доску после того, как игрок запускается, что продолжает хранить в массиве. Я хотел бы обновить свои Pixmaps для каждого, используя общую функцию типа GetupDateArray. По мере того, как мы пересекаем массив, он обновит PixMap, в настоящее время связанный с отдельными этикетками, чтобы соответствовать их двоюродным братьям из массива. (сказать F[5][6] = 'X' Для удара, затем, когда петли добрались до этой позиции в массиве, она обновила бы сетку Pixmaps в F_56, чтобы равняться Hit.png, замена пусто.

У меня есть идея, как сделать цикл, который это достигнет, но не уверен, как я буду получить Pixmap для каждой метки, чтобы быть в большей степени в соответствии с функцией времени выполнения по сравнению с функцией «Время компиляции» (статическое). Я читал о Qpainter и еще одном классе QT, который занимается изображениями, но все же испытывает трудности.

Вопрос любому из вас, как мне обновить эти Pixmaps на основе 2D -массива?

  1. Структура петли - я могу понять
  2. Условия условий - я могу выяснить
  3. QT-специфический синтаксис, касающийся лейблов- новичок, так что понятия не имею.

Вот какой -то псевдокод того, что я пытаюсь сделать с 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();
                }
            }
        }
    }
};

Затем в MeaNwindow.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, в котором я размещаю имя метки, которую я хочу изменить, используя значения XY, используя:

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

Когда я пытаюсь назвать это, используя:

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

... это, очевидно, не работает. Есть ли способ набрать его или использовать содержимое строки в качестве имени Qlabel?

Это было полезно?

Решение

Вы вручную ввели и назвали 200 виджетов метки? Пусть никто не назовет тебя ленивым. :)

По вашему обновлению, вы сейчас знать, как использовать QLabel::setPixmap(). Анкет Что ты считать Вам нужно получить указатель Qlabel от имени, который будет комбинацией двух вещей:

Если вы пойдете по этому пути, то, с чем вы получите что -то вроде:

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

Но будьте осторожны! С этим подходом есть много вещей.

  • Это хрупко: Что, если с этим именем не будет виджета (таинственное сбое)? Или что еще хуже, если есть несколько Виджеты с этим именем и этот код маршируется вдоль блаженно не зная о том странном состоянии, которое, вероятно, является ошибкой?

  • Это плохо: Хотя есть несколько приличных случаев для использования динамического литья (или «спускания»), это обычно указывает на недостаток в дизайне. Вы знаете, что все Qlabels - это Qwidgets, но не все Qwidgets - это Qlabels ... так что это 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 может использовать qgridlayout из этих 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. Или вы можете продвинуться и делать все программно. Ради простоты я просто поставлю две доски рядом друг с другом с вертикальным сепаратором на поверхности диалога:

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

С моим кодом получение Gamecell из координаты (x, y) не требует в среднем 50 строк. Из -за формализованного и предсказуемого характера 2D массивов, индексация в cells[x][y] Требуется только одна операция умножения и одну операцию добавления. Там нет спускания, и вы можете просто написать:

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

Приложение: Создание Qwidget для каждой ячейки сетки не обязательно не подходит. Некоторые могут считать это «тяжеловесом». Если бы ваша игра была разыгрывается в большом виртуальном пространстве плиток, то это может быть слишком медленным. Вы можете найти полезно изучать Qgraphicsgridlayout, что может быть более подходящим подходом в долгосрочной перспективе:

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

Однако использование Qwidgets не будет большой проблемой с сеткой 10x10, поэтому, если вы хотите просто придерживаться этого, вы можете. Если вы собираетесь сделать это таким образом, то, по крайней мере, вы не должны размещать их всех вручную!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top