Статьи

Расширенная настройка сортировки в OutlineView платформы NetBeans

На практике существуют интерфейсные приложения, в которых весь набор данных не может быть доступен сразу из базовой службы (например, поисковой системы), или нецелесообразно ждать весь набор данных — например, большая и довольно удаленная база данных. В этих случаях, если базовый сервис поддерживает разбиение на страницы, мы можем использовать « OutlineView», отображающий движущееся окно записей, и добавить внешние элементы управления разбиением на страницы. Или, в зависимости от требований приложения, может отображаться только первые 1000 записей, а остальные игнорируются.

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

Но нам нравится естественный способ » OutlineView«, который ведет себя при сортировке локальных данных по столбцу заголовка и их комбинациям, поэтому мы считаем, что конечный пользователь получит лучший опыт при запуске сортировки службы с использованием элементов управления » OutlineView» заголовка таблицы. Поэтому мы решили, что мы хотим вызвать сортировку службы с помощью OutlineViewзаголовка таблицы » » и отобразить ответ службы на эту сортировку в » OutlineView«.

Для простоты, предполагая, что нумерация страниц 10 в представлении клиента с 57 записями, если вы сортируете по возрастанию по «Фамилии», на первой странице мы хотели видеть первые 10 записей на основе порядка фамилий из всего набора:

при переходе на 6-ю страницу мы ожидаем увидеть последние записи, включая » null«, без каких-либо дальнейших действий с заголовком таблицы:

Возвращаясь к первой странице, когда мы переключаемся на сортировку по убыванию, мы не хотим, чтобы эти 10 записей с первой страницы были перевернуты, потому что в базе данных намного больше записей — мы хотим видеть последние 10 записей (с не- » null» Фамилия) обратная, т.е. те, которые начинаются с китайского и Z:

Поскольку « OutlineView» будет сортировать только локальный фрагмент записей с использованием внутреннего алгоритма сортировки, было выявлено и решено несколько деликатных аспектов, и вкратце это то, что мы достигли:

  1. Перехват действий сортировки заголовка столбца.
  2. Получение всей информации сортировки (столбец, порядок) для создания запроса на обслуживание.
  3. Воссоздание дерева и таблицы с полученными данными. Данные на текущей странице могут полностью изменяться при полной (внешней) сортировке.
  4. То, что происходит с локальной сортировкой указанных выше данных в качестве алгоритма сортировки / компараторов, может отличаться, и мы не хотели изменять OutlineViewкод » «. Нам нужно сделать локальную сортировку неизменной (функция Identity, использующая пустой компаратор) … но все равно » null» значения были показаны первыми в » OutlineView» — мы также нашли решение для этого.
  5. Постоянство — что происходит после того, как мы закрываем и перезагружаем OutlineViewкомпонент » «, поскольку свойства сортировки сохраняются.

В качестве примера мы рассмотрим SQL-подобный синтаксис для запросов к удаленному сервису относительно параметров сортировки, то есть через API сервиса, который мы отправим
» ORDER BY COL1 ASC, COL2 DESC, COL3 ASC ...«

Например, на снимке ниже, где мы группируем три столбца для сортировки,

мы хотим собрать "last_name ASC NULLS LAST, first_name DESC NULLS LAST, company DESC NULLS LAST"в качестве опции сортировки, где » last_name«, » first_name» и » company» являются именами столбцов в базовой модели сервиса.

Затем мы отправляем это как часть запроса в службу и заполняем » OutlineView» ответом. Однако, так как « OutlineView» снова выполняет сортировку, могут быть различия, такие как в понимании кейса, « nullсортировке, сопоставлении и т. Д., Следовательно, мы задействуем вложенный компаратор идентичности (равенства) и для« null»значений в дочерней фабрике мы превратить их в пустую строку.

Теперь давайте объясним с помощью кода, как мы достигли шагов.

Прежде всего » OutlineView» и свойства сортировки столбцов должны быть установлены на » true«.

Мы установили » MouseListener» в заголовке таблицы с помощью » OutlineView» метода » getOutline().getTableHeader().addMouseListener(...)«. Обратите внимание, что recordsTable — это наше » org.openide.explorer.view.OutlineView«.

Метод «» слушателя mouseClickedбудет вызван после того, как внутреннее » Etable MouseListener» выполнит свою работу и решит, как будет отсортирован каждый столбец, и мы фактически моделируем то, что происходит внутри, получая атрибуты сортировки. Вот как мы решаем пункт № 1 — перехватывая действия сортировки заголовка столбца. Мы также устанавливаем компаратор идентификации, если он еще не установлен.

recordsTable.getOutline (). getTableHeader (). addMouseListener (new MouseAdapter () {
      @Override
      public void mouseClicked (MouseEvent e) {
        if (e.getButton () == MouseEvent.BUTTON3 || e.getClickCount ()! = 1) {
          // Другие кнопки используются для сортировки; мы не заинтересованы.
          возвращение;
        }

        int column = recordsTable.getOutline (). columnAtPoint (e.getPoint ());

        if (столбец <0) {
          возвращение;
        }
        // Хотя мы можем получить событие для изменения размера столбца, ничего не изменится, поскольку столбец не найден как отсортированный.
        TableColumnModel tcm = recordsTable.getOutline (). GetColumnModel ();
        if (tcm instanceof ETableColumnModel) {
          ETableColumnModel etcm = (ETableColumnModel) tcm;
          TableColumn tc = tcm.getColumn (column);
          if (tc instanceof ETableColumn) {
            ETableColumn etc = (ETableColumn) tc;
            if (etc.getNestedComparator ()! = равенстваComparator) {
              etc.setNestedComparator (equalityComparator);
            }
            // В будущем мы можем улучшить и сохранить данные только о сортировочных столбцах, не просматривая их каждый раз.
            customizeSorting ();

            // Пока не стоит проделывать работу по сохранению выделения, поскольку мы многостраничны.
            . RecordsTable.getOutline () clearSelection ();
          }
        }

        // Здесь мы вызываем внешний сервис, выполняющий сортировку и воссоздающий узлы для предоставления нового среза.
        applyOptions ();
      }
    });

Важный метод в приведенном выше коде, который получает параметры сортировки (элемент № 2), это » customizeSorting()» — он изображен ниже. Помимо вызова его из » MouseListener«, мы также вызываем его, когда открывается наш Верхний компонент (» componentOpened()«), вместе с вызовом метода для установки настраиваемого вложенного разделителя равенства, после чтения настроек персистентности OutlineView, чтобы гарантировать, что OutlineView после повторного открытия остается отсортированным в этой конкретной конфигурации (элемент № 5). По сути, мы определяем, что по столбцу щелкают для целей сортировки, видим его состояние сортировки и ранжирование и добавляем его в предыдущие состояния сортировки. На данный момент в первой версии мы фактически перебираем все видимые столбцы и делаем это для всех столбцов с isSorted()«возвращением true».

  protected void customizeSorting () {
    // Идентифицируем столбцы во внешнем сервисе
    String [] columns = getColumnOrderBy ();
    // Столбец по умолчанию, используемый для сортировки
    int sortedColumnIndex = getDefaultOrderByColumn ();
    // Столбец по умолчанию, используемый для сортировки
    String sortedOrderType = getDefaultOrderType ();
    // Переменная-член, используемая также методом applyOption ().
    orderBy = "";
    логическое sortedFound = false;    
    пытаться {
      int numCols = recordsTable.getOutline (). getColumnModel (). getColumnCount ();
      ETableColumn [] orderByCols = new ETableColumn [numCols + 1];
      // Скрытых столбцов здесь нет. Возможно, в будущих версиях NB.
      for (int i = 0; i <numCols; i ++) {
        ETableColumn etc = (ETableColumn) recordsTable.getOutline (). GetColumnModel (). GetColumn (i);
        if (etc.isSorted ()) {
          if (etc.getSortRank () <orderByCols.length) {
            // Мы переназначаем столбец, чтобы учесть реальный рейтинг.
            orderByCols [etc.getSortRank ()] = etc;
          }          
        }
      }

      for (int i = 1; i <orderByCols.length; i ++) {
        if (orderByCols [i]! = null) {
          // Это означает, что столбец отсортирован.
          ETableColumn etc = orderByCols [i];
          sortedColumnIndex = и т. д. getModelIndex ();
          if (etc.isAscending ()) {
            // sortedOrderType = "ASC NULLS FIRST";
            sortedOrderType = "ASC NULLS LAST";
          } еще {
            sortedOrderType = "DESC NULLS LAST";
          }
          if (sortedFound) {
            orderBy + = ",";
          } еще {
            sortedFound = true;
          }
          if (sortedColumnIndex <columns.length) {            
            // Поскольку в реальной модели может быть несколько столбцов, ограниченных одним и тем же столбцом, нам нужно добавить sortedOrderType для каждого.
            // Для обычного случая достаточно строки, указанной ниже
            // orderBy + = columns [sortedColumnIndex] + "" + sortedOrderType;
            orderBy + = DatabaseLicenseOperations.createCorrectOrderBy (columns [sortedColumnIndex], sortedOrderType);
          }
        }
      }

      if (! sortedFound) {
        // Используется столбец по умолчанию (обычно это столбец узла).
        if (sortedColumnIndex <columns.length) {
          // Поскольку в реальной модели может быть несколько столбцов, ограниченных одним и тем же столбцом, нам нужно добавить sortedOrderType для каждого.
          // Для обычного случая достаточно строки, указанной ниже
          // orderBy = columns [sortedColumnIndex] + "" + sortedOrderType;
          orderBy = DatabaseLicenseOperations.createCorrectOrderBy (columns [sortedColumnIndex], sortedOrderType);          
        }
      }
    } catch (LicenseException ex) {
      ex.printStackTrace ();
    }
  }

columnsМассив » » сохраняет соответствие между индексом столбца таблицы и столбцами в соответствующей службе / базе данных. Исходя из этого, » ORDER BY» значение создается с помощью вспомогательного DatabaseLicenseOperations.createCorrectOrderBy(...)метода » «, чтобы в конечном итоге разделить многопольные столбцы и добавить orderType к каждому столбцу базы данных. Например, в представлении для версии продукта у нас может быть один логический столбец с именем » Version«, состоящий из "major, minor"полей уровня базы данных. Мы должны преобразовать это в "major ASC NULLS LAST, minor ASC NULLS LAST".

Внутренняя сортировка таблиц будет производиться визуально и эффективно после того, как мы вернемся из MouseListenerметода » «. Поскольку мы воссоздаем узлы и свойства, используя срез новых записей во внешнем отсортированном порядке, посредством вызова applyOptions()метода » » в методе прослушивателя мыши, эти новые значения используются » OutlineView» для его сортировки и отображения. Таким образом, мы решаем пункт № 3). Единственный недостаток (который мы можем себе позволить с точки зрения времени выполнения) состоит в том, что сортировка также происходит в « OutlineView» — поэтому есть две операции сортировки данных, внутренняя — только для точного среза записей (не для весь набор данных). Поскольку внутренний порядок сортировки может отличаться по порядку внешней сортировки от службы / базы данных, мы устанавливаем пользовательский вложенный компаратор, определенный ниже,разрешение пункта № 4:

  / **
   * Компаратор равенства, чтобы порядок сортировки оставался неизменным 
   * как один из службы / базы данных.
   * /
  статический защищенный класс EqualityComparator <T> реализует Comparator <T> {

    @Override
    public int сравнивать (T o1, T o2) {     
      // Всегда o1 = o2, чтобы использовался заказ из сервиса / БД.
      вернуть 0;
    }
  }
  / **
   * Используется для ссылки на один и тот же вложенный компаратор для всех столбцов.
   * /
  статическая защита EqualityComparator <Object> равенстваComparator = new EqualityComparator <Object> ();

Мы также установили «Обновить выделение при сортировке» на « false», так как теперь могут присутствовать строки с других страниц, и выбор не имеет большого смысла между страницами.

. RecordsTable.getOutline () setUpdateSelectionOnSort (ложь);

Оставалось решить еще одну вещь, а именно « null» значения. В случае значения » null» в столбце, внутренняя сортировка возвращается из метода компаратора перед вызовом вложенного компаратора при просмотре » null» значений. Следовательно, » null» всегда будут добавлять первые позиции при сортировке по возрастанию, соответственно в конце при сортировке по убыванию, и мы хотели иметь возможность перечислять » null» значения в конце для любого порядка сортировки, фактически контролируя это из внешней службы / сортировка базы данных.

Если вместо « null» мы использовали пустую строку, то будет вызван вложенный компаратор, и через наш компаратор равенства мы можем сохранить порядок в базе данных. Следовательно, мы фактически преобразовали значение » null» в пустые строки в методе » getValue()» нашей » PropertySupport.ReadOnly.getValue()» реализации, из которой сделан наш набор свойств.

Эту настройку можно также использовать без разбивки на страницы или с другими сценариями доступности набора данных в случае, если внешняя сортировка необходима так же, как сама по себе потребность, например, связанная с такими аспектами i18n, как сопоставление, транслитерация. Существуют также случаи, когда сортировка, например, ранжирование выполняется с помощью внутренних атрибутов в базовом сервисе, и они не публикуются. Для получения правильного порядка сортировки необходимо использовать сервис сортировки.