На этом шаге рассмотрим реализацию пунктов меню Инструменты и Настройки в приложении Электронная таблица.
Вид меню Tools и Options (Инструменты и Настройки), приведен на рисунке 1.
Рис.1. Меню Tools и Options приложения Электронная таблица
Слот recalculate() соответствует пункту меню Tools | Recalculate (Инструменты | Пересчитать). Он также вызывается в Электронной таблице автоматически по мере необходимости.
void Spreadsheet::recalculate() { /*выполняем цикл по всем ячейкам и вызываем функцию setDirty(), которая помечает каждую из них для перерасчета значения. Следующий раз, когда QTableWidget для получения отображаемого в электронной таблице значения вызовет text() для некоторой ячейки Cell, значение этой ячейки будет пересчитано*/ for (int row = 0; row < RowCount; ++row) { for (int column = 0; column < ColumnCount; ++column) { if (cell(row, column)) cell(row, column)->setDirty(); } } /*вызываем для области отображения функцию update() перерисовки всей электронной таблицы. При этом используемый в QTableWidget программный код по перерисовке вызывает функцию text() для каждой видимой ячейки для получения отображаемого значения. Поскольку функция setDirty() вызывалась нами для каждой ячейки, в вызовах text() будет использовано новое рассчитанное значение. В этом случае может потребоваться расчет невидимых ячеек, который будет проводиться до тех пор, пока не будут рассчитаны все ячейки, влияющие на правильное отображение текста в перерассчитанной области отображения. Этот расчет выполняется в классе Cell*/ viewport()->update(); }
Слот setAutoRecalculate() соответствует пункту меню Options | Auto-Recalculate (Настройки | Автоподсчет). Если эта опция включена, мы сразу же пересчитаем всю электронную таблицу и будем уверены, что она показывает обновленные значения; впоследствии функция recalculate() будет автоматически вызываться из somethingChanged().
void Spreadsheet::setAutoRecalculate(bool recalc) { autoRecalc = recalc; if (autoRecalc) recalculate(); }
Нам не нужно реализовывать специальную функцию для пункта меню Options | Show Grid (Настройки | Показать сетку), поскольку в QTableWidget уже содержится слот setShowGrid(), который наследуется от QTableView. Остается только реализовать функцию Spreadsheet::sort(), которая вызывается из MainWindow::sort():
void Spreadsheet::sort(const SpreadsheetCompare &compare) { /*сортировка работает на текущей выделенной области и переупорядочивает строки в соответствии со значениями ключей порядка сортировки и хранящимися в объекте compare. Представляем каждую строку данных в QStringList, а выделенную область храним в виде списка строк*/ QList<QStringList> rows; QTableWidgetSelectionRange range = selectedRange(); int i; for (i = 0; i < range.rowCount(); ++i) { QStringList row; for (int j = 0; j < range.columnCount(); ++j) row.append(formula(range.topRow() + i, range.leftColumn() + j)); rows.append(row); } /*используем алгоритм Qt qStableSort() и для простоты сортируем по выражениям формул, а не по их значениям. В качестве аргументов функции qStableSort() используется итератор начала, итератор конца и функция сравнения. Функция сравнения имеет два аргумента (оба имеют тип QStringLists), и она возвращает true, когда первый аргумент «больше, чем» второй аргумент, и false в противном случае. Передаваемый как функция сравнения объект compare фактически не является функцией, но может использоваться и в таком качестве*/ qStableSort(rows.begin(), rows.end(), compare); //помещаем данные обратно в таблицу for (i = 0; i < range.rowCount(); ++i) { for (int j = 0; j < range.columnCount(); ++j) setFormula(range.topRow() + i, range.leftColumn() + j, rows[i][j]); } //сбрасываем выделение области clearSelection(); //и вызываем функцию somethingChanged() somethingChanged(); }
Класс SpreadsheetCompare в spreadsheet.h определен следующим образом:
class SpreadsheetCompare { public: bool operator()(const QStringList &row1, const QStringList &row2) const; enum { KeyCount = 3 }; int keys[KeyCount]; bool ascending[KeyCount]; };
Класс SpreadsheetCompare является специальным классом, реализующим оператор (). Это позволяет нам применять этот класс в качестве функции. Такие классы называются объектами функций или функторами (functors).
Для лучшего понимания работы функторов мы сначала разберем простой пример.
class Square { public: int operator()(int x) const { return x * x; } };
Класс Square содержит одну функцию, operator()(int), которая возвращает квадрат переданного ей значения параметра. Обозначая функцию в виде operator()(int), а не в виде, например, compute(int), мы получаем возможность применения объекта типа Square как функции:
Square square;
int у = square(5); //у равно 25
Теперь рассмотрим пример с применением объекта SpreadsheetCompare.
QStringList rowl, row2;
QSpreadsheetCompare compare;
if (compare(row1, row2)) {
// строка rowl меньше, чем row2
}
Объект compare можно использовать так же, как если бы он был обычной функцией comparе(). Кроме того, он может быть реализован таким образом, что будет осуществлять доступ ко всем ключам сортировки и всем признакам порядка сортировки, которые хранятся в переменных-членах класса.
Можно использовать другой подход, когда ключи сортировки и признаки порядка сортировки хранятся в глобальных переменных и используется функция обычного типа comparе(). Однако связь через глобальные переменные выглядит неизящно и может быть причиной тонких ошибок. Функторы представляют собой более мощное средство связи для таких функций-шаблонов, как qStableSort().
Ниже приводится реализация функции, которая применяется для сравнения двух строк электронной таблицы.
bool SpreadsheetCompare::operator()(const QStringList &row1, const QStringList &row2) const { /*массивы keys и ascending объекта SpreadsheetCompare заполняются при работе функции MainWindow::sort(). Каждый ключ содержит индекс столбца или имеет значение -1 ("None" - нет значения)*/ for (int i = 0; i < KeyCount; ++i) { int column = keys[i]; /*сравниваем значения соответствующих ячеек двух строк, учитывая порядок ключей сортировки. Как только оказывается, что они различны, мы возвращаем соответствующее значение: true или false. Если все значения оказываются равными, мы возвращаем false. При совпадении значений функция qStableSort() coxpaняет порядок до сортировки; если строка rowl располагалась первоначально перед строкой row2 и ни одна из них не оказалась "меньше другой", то в результате строка rowl по-прежнему будет предшествовать строке row2. Именно этим функция qStableSort() отличается от своего нестабильного "родственника" qSort()*/ if (column != -1) { if (row1[column] != row2[column]) { if (ascending[i]) { return row1[column] < row2[column]; } else { return row1[column] > row2[column]; } } } } return false; }
Этот оператор возвращает true, если первая строка меньше второй; в противном случае он возвращает false. Функция qStableSort() для выполнения сортировки использует результат этой функции.
На следующем шаге рассмотрим создание подкласса QTableWidgetItem.