Шаг 179.
Библиотека Qt.
Реализация пунктов меню Инструменты и Настройки

    На этом шаге рассмотрим реализацию пунктов меню Инструменты и Настройки в приложении Электронная таблица.

    Вид меню 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.




Предыдущий шаг Содержание Следующий шаг