На этом шаге рассмотрим использование диалогового окна сортировки (шаг 159).
Теперь мы перейдем к созданию диалогового окна Сортировать. Это диалоговое окно является модальным и позволяет пользователю упорядочить текущую выбранную область, задавая в качестве ключей сортировки определенные столбцы. На рисунке 1 показан пример сортировки, когда в качестве главного ключа сортировки используется столбец A, а в качестве вторичного ключа сортировки используется столбец B (в обоих случаях сортировка выполняется по возрастанию значений).
Рис.1. Сортировка выделенной области электронной таблицы
Порядок действий при программировании функции sort() аналогичен порядку действий, применяемому при программировании функции goToCell():
void MainWindow::sort() { SortDialog dialog(this); QTableWidgetSelectionRange range = spreadsheet->selectedRange(); /*Вызов setColumnRange() задает столбцы, выбранные для сортировки. Например, при выделении области, показанной на рисунке 67, функция range.leftColumn() возвратит 0, давая в результате 'А' + 0 = 'A', a range.rightColumn() возвратит 2, давая в результате 'А' + 2 = 'С'*/ dialog.setColumnRange('A' + range.leftColumn(), 'A' + range.rightColumn()); if (dialog.exec()) { /*В объекте compare хранятся первичный, вторичный и третичный ключи, а также порядок сортировки по ним. Этот объект используется функцией Spreadsheet::sort() для сортировки строк*/ SpreadsheetCompare compare; /*В массиве keys содержатся номера столбцов ключей. Например, если выбрана область с С2 по Е5, то столбец С будет иметь индекс 0. Функция QComboBox::currentIndex() возвращает индекс текущего элемента (начиная с 0). Для вторичного и третичного ключей мы вычитаем единицу из текущего элемента, чтобы учесть значение "None" (отсутствует)*/ compare.keys[0] = dialog.primaryColumnCombo->currentIndex(); compare.keys[1] = dialog.secondaryColumnCombo->currentIndex() - 1; compare.keys[2] = dialog.tertiaryColumnCombo->currentIndex() - 1; /*В массиве ascending в переменных типа bool хранятся значения направления сортировки для каждого ключа*/ compare.ascending[0] = (dialog.primaryOrderCombo->currentIndex() == 0); compare.ascending[1] = (dialog.secondaryOrderCombo->currentIndex() == 0); compare.ascending[2] = (dialog.tertiaryOrderCombo->currentIndex() == 0); spreadsheet->sort(compare); } }
Функция sort() сделает свою работу, но она не совсем надежна. Она предполагает определенный способ реализации диалогового окна, а именно использование выпадающих списков и элементов со значением "None". Это означает, что при изменении дизайна диалогового окна Sort нам, возможно, потребуется изменить также программный код. Такой подход можно использовать для диалогового окна, применяемого только в одном месте; однако это может вызвать серьезные проблемы сопровождения, если это диалоговое окно станет использоваться в различных местах.
Более надежным будет такой подход, когда класс SortDialog делается более "разумным" и может создавать свой собственный объект SpreadsheetCompare, доступный вызывающему его компоненту. Это значительно упрощает функцию MainWindow::sort():
void MainWindow::sort() { SortDialog dialog(this); QTableWidgetSelectionRange range = spreadsheet->selectedRange(); dialog.setColunnRange('A' + range.leftColumn(), 'A' + range. rightColumn()); if (dialog.exec()) spreadsheet->performSort(dialog.comparisonObject()); }
Такой подход приводит к созданию слабо связанных компонентов, и выбор его почти всегда будет правилен для диалоговых окон, которые вызываются из нескольких мест.
Более "радикальный" подход мог бы заключаться в передаче указателя на объект Spreadsheet при инициализации объекта SortDialog и разрешении диалоговому окну работать непосредственно с объектом Spreadsheet. Это значительно снизит универсальность диалогового окна SortDialog, поскольку оно будет работать только с виджетами определенного типа, но это позволит еще больше упростить программу из-за возможности исключения функции SortDialog::setColumnRange(). В этом случае функция MainWindow::sort() примет следующий вид:
void MainWindow::sort() { SortDialog dialog(this); dialog.setSpreadsheet(spreadsheet); dialog.exec(); }
Этот подход является зеркальным отражением первого: вместо знания вызывающим компонентом характерных особенностей диалогового окна теперь само диалоговое окно должно иметь представление об особенностях структур данных, передаваемых вызывающим компонентом. Этот подход полезно применять, когда диалоговому окну требуется отслеживать изменения. В то время как при первом подходе не надежен код вызвавшего компонента, третий подход перестает работать при изменении структуры данных.
На следующем шаге рассмотрим создание диалогового окна справки о программе.