Жасмин Бланшет - QT 4: программирование GUI на С++
Нам не нужно реализовывать специальную функцию для пункта меню Options | Show Grid, поскольку в QTableWidget уже содержится слот setShowGrid(), который наследуется от базового класса QTableView. Остается только реализовать функцию Spreadsheet::sort(), которая вызывается из MainWindow::sort():
01 void Spreadsheet::sort(const SpreadsheetCompare &compare)
02 {
03 QList<QStringList> rows;
04 QTableWidgetSelectionRange range = selectedRange();
05 int i;
06 for (i = 0; i < range.rowCount(); ++i) {
07 QStringList row;
08 for (int j = 0; j < range.columnCount(); ++j)
09 row.append(formula(range.topRow() + i,
10 range.leftColumn() + j));
11 rows.append(row);
12 }
13 qStableSort(rows.begin(), rows.end(), compare);
14 for (i = 0; i < range.rowCount(); ++i) {
15 for (int j = 0; j < range.columnCount(); ++j)
16 setFormula(range.topRow() + i, range.leftColumn() + j, rows[i][j]);
17 }
18 clearSelection();
19 somethingChanged();
20 }
Сортировка работает на текущей выделенной области и переупорядочивает строки в соответствии со значениями ключей порядка сортировки, хранящимися в объекте compare. Мы представляем каждую строку данных в QStringList, а выделенную область храним в виде списка строк. Мы используем алгоритм Qt qStableSort() и для простоты сортируем по выражениям формул, а не по их значениям. Стандартные алгоритмы и структуры данных Qt рассматривается в главе 11 («Классы—контейнеры»).
Рис. 4.8. Хранение выделенной области в виде списка строк.
В качестве аргументов функции qStableSort() используются итератор начала, итератор конца и функция сравнения. Функция сравнения имеет два аргумента (оба имеют тип QStringLists), и она возвращает true, когда первый аргумент «больше, чем» второй аргумент, и false в противном случае. Передаваемый как функция сравнения объект compare фактически не является функцией, но он может использоваться и в таком качестве, в чем мы вскоре сможем убедиться.
Рис. 4.9. Помещение данных в таблицу после сортировки.
После выполнения функции qStableSort() мы помещаем данные обратно в таблицу, сбрасываем выделение области и вызываем функцию somethingChanged(). Класс SpreadsheetCompare в spreadsheet.h определен следующим образом:
01 class SpreadsheetCompare
02 {
03 public:
04 bool operator()(const QStringList &row1,
05 const QStringList &row2) const;
06 enum { KeyCount = 3 };
07 int keys[KeyCount];
08 bool ascending[KeyCount];
09 };
Класс SpreadsheetCompare является специальным классом, реализующим оператор (). Это позволяет нам применять этот класс в качестве функции. Такие классы называются объектами функций или функторами (functors).
Для лучшего понимания работы функторов мы сначала разберем простой пример:
01 class Square
02 {
03 public:
04 int operator()(int x) const { return x * x; }
05 }
Класс Square содержит одну функцию operator()(int), которая возвращает квадрат переданного ей значения параметра. Обозначая функцию в виде operator()(int), а не в виде, например, compute(int), мы получаем возможность применения объекта типа Square как функции:
Square square;
int у = square(5);
Теперь рассмотрим пример с применением объекта SpreadsheetCompare:
QStringList row1, row2;
QSpreadsheetCompare compare;
…
if (compare(row1, row2)) {
// строка row1 меньше, чем row2
}
Объект compare можно использовать так же, как если бы он был обычной функцией compare(). Кроме того, он может быть реализован таким образом, что будет осуществлять доступ ко всем ключам сортировки и всем признакам порядка сортировки, которые хранятся в переменных—членах класса.
Можно использовать другой подход, когда ключи сортировки и признаки порядка сортировки хранятся в глобальных переменных и используется функция обычного типа compare(). Однако связь через глобальные переменные выглядит неизящно и может быть причиной тонких ошибок. Функторы представляют собой более мощное средство связи для таких функций—шаблонов, как qStableSort().
Ниже приводится реализация функции, которая применяется для сравнения двух строк электронной таблицы:
01 bool SpreadsheetCompare::operator()(const QStringList &row1,
02 const QStringList &row2) const
03 {
04 for (int i = 0; i < KeyCount; ++i) {
05 int column = keys[i];
06 if (column != -1) {
07 if (row1[column] != row2[column]) {
08 if (ascending[i]) {
09 return row1[column] < row2[column];
10 } else {
11 return row1[column] > row2[column];
12 }
13 }
14 }
15 }
16 return false;
17 }
Этот оператор возвращает true, если первая строка меньше второй; в противном случае он возвращает false. Функция qStableSort() для выполнения сортировки использует результат этой функции.
Массивы keys и ascending объекта SpreadsheetCompare заполняются при работе функции MainWindow::sort() (она приводится в главе 2). Каждый ключ содержит индекс столбца или имеет значение —1 («None» — нет значения).
Мы сравниваем значения соответствующих ячеек двух строк, учитывая порядок ключей сортировки. Как только оказывается, что они различны, мы возвращаем соответствующее значение: true или false. Если все значения оказываются равными, мы возвращаем false. При совпадении значений функция qStableSort() сохраняет порядок до сортировки; если строка row1 располагалась первоначально перед строкой row2 и ни одна из них не оказалась «меньше другой», то в результате строка row1 по-прежнему будет предшествовать строке row2. Именно этим функция qStableSort() отличается от своего нестабильного «родственника» qSort().
Теперь мы закончили класс Spreadsheet. В следующем разделе мы рассмотрим класс Cell. Этот класс применяется для хранения формул ячеек и обеспечивает переопределение функции QTableWidgetltem::data(), которая вызывается в Spreadsheet через функцию QTableWidgetItem::text() для отображения результата вычисления формулы ячейки.
Создание подкласса QTableWidgetltem
Класс Cell наследует QTableWidgetltem. Этот класс спроектирован для удобства работы с Spreadsheet, но он не имеет никаких особых связей с данным классом электронной таблицы и теоретически может применяться для любого объекта QTableWidget. Ниже приводится заголовочный файл:
01 #ifndef CELL_H
02 #define CELL_H
03 #include <QTableWidgetItem>
04 class Cell : public QTableWidgetltem
05 {
06 public:
07 Cell();
08 QTableWidgetltem *clone() const;
09 void setData(int role, const QVariant &value);
10 QVariant data(int role) const;
11 void setFormula(const QString &formula);
12 QString formula() const;
13 void setDirty();
14 private:
15 QVariant value() const;
16 QVariant evalExpression(const QString &str, int &pos) const;
17 QVariant evalTerm(const QString &str, int &pos) const;
18 QVariant evalFactor(const QString &str, int &pos) const;
19 mutable QVariant cachedValue;
20 mutable bool cacheIsDirty;
21 };
22 #endif
Класс Cell расширяет QTableWidgetltem, добавляя две закрытые переменные:
• переменная cachedValue кэширует значение ячейки в виде значения типа QVariant;
• переменная cacheIsDirty принимает значение true, если кэшируемое значение устарело.
Мы используем QVariant, поскольку некоторые ячейки имеют тип числа двойной точности double, а другие имеют тип строки QString.
При объявлении переменных cachedValue и cacheIsDirty используется ключевое слово mutable языка С++. Это позволяет нам модифицировать эти переменные в функциях с модификатором const. Мы могли бы поступить по-другому и заново выполнять расчет при каждом вызове функции text(), но эта неэффективность будет не оправдана.
Следует отметить, что в определении класса не используется макрос Q_OBJECT. Класс Cell является «чистым» классом С++, который не имеет сигналов и слотов. На самом деле из-за того, что QTableWidgetltem не является наследником QObject, мы не можем использовать в Cell как таковые сигналы и слоты. Классы элементов Qt не наследуют QObject, чтобы свести к минимуму затраты на их обработку. Если сигналы и слоты необходимы, они могут быть реализованы в виджете, содержащем элементы, или (в виде исключения) при помощи множественного наследования класса QObject.