Удаление виджетов из QGridLayout

Я пытаюсь удалить виджеты из указанной строки в QGridLayout следующим образом:

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;
    }
}

Но когда я его вызываю, приложение сбой на SIGSEGV на delete item в первой итерации. Любые идеи?

Ответ 1

Краткий ответ: используйте код, указанный ниже

Удалить строку или столбец (или даже одну ячейку) из QGridLayout сложно. Используйте код, указанный ниже.

Длинный ответ: копание в деталях QGridLayout

Во-первых, обратите внимание, что QGridLayout::rowCount() и QGridLayout::columnCount() всегда возвращают количество внутренних строк и столбцов в макете сетки. Например, если вы QGridLayout::addWidget(widget,5,7) в только что построенном макете сетки, то число строк будет равно 6, а количество столбцов будет равно 8, и все ячейки макета сетки, кроме ячейки на Индекс (5,7) будет пустым и, следовательно, невидимым в GUI.

Обратите внимание, что, к сожалению, невозможно удалить такую внутреннюю строку или столбец из компоновки сетки. Другими словами, количество строк и столбцов макета сетки всегда может только расти, но никогда не уменьшаться.

Что вы можете сделать, так это удалить содержимое строки или столбца, что будет иметь тот же визуальный эффект, что и удаление самой строки или столбца. Но это, конечно, означает, что все количество строк и столбцов и индексы останутся неизменными.

Так как же очистить содержимое строки или столбца (или ячейки)? К сожалению, это не так просто, как может показаться.

Во-первых, вам нужно подумать, хотите ли вы только удалить виджеты из макета или же вы хотите, чтобы они стали удалены. Если вы удаляете только виджеты из макета, вы должны впоследствии вернуть их в другой макет или вручную придать им разумную геометрию. Если виджеты также будут удалены, они исчезнут из графического интерфейса. Предоставленный код использует логический параметр для управления удалением виджета.

Далее необходимо учитывать, что ячейка макета может содержать не только виджет, но и вложенный макет, который сам может содержать вложенные макеты и т.д. Кроме того, вам нужно обрабатывать элементы макета, которые охватывают несколько строк и столбцов. И, наконец, есть некоторые атрибуты строк и столбцов, такие как минимальная ширина и высота, которые не зависят от фактического содержимого, но о которых нужно позаботиться.

Код

#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();
  }
};

Ответ 2

Сама QGridLayout управляет QLayoutItem. Я считаю, что в тот момент, когда вы вызываете removeWidget, элемент будет удален. Таким образом, у вас есть неверный указатель в этой точке. Попытка сделать что-либо с ней, а не только delete, не удастся.

Таким образом, просто не удаляйте его, все будет в порядке.