Продолжается подписка на наши издания! Вы не забыли подписаться?

Отображение информации в представлениях (view) Eclipse

Автор: Александр Цимбал

Представления (view) Eclipse являются основным элементом этой платформы для отображения данных самых различных видов – текстовой и графической, упорядоченной и не очень, структурированной в виде таблиц, деревьев и пр., и размещаемой задаваемым программистом (или пользователем) образом.

О том, как создавать представления с использованием точек расширения, кратко говорилось в предыдущей статье («Еще раз о разработке плагинов Eclipse»). Среда разработки плагинов содержит мастер для разработки представлений, поэтому создание нового представления как такового не вызывает никаких трудностей – разработчику, в общем, ничего особо и делать не приходится.

Другое дело – размещение в представлении визуальной информации.

SWT-компоненты и представления Eclipse

В простейшем случае представление можно рассматривать как графическое окно и контейнер для элементарных визуальных элементов, подобных графическим компонентам AWT, Swing (JFC) или SWT. При использовании Eclipse наиболее естественно использование библиотеки SWT, в силу ряда причин ставшей основной графической библиотекой компонентов на этой платформе.

Как и любая графическая среда, SWT содержит, с одной стороны, «инфраструктурные компоненты» и контейнеры, которые обеспечивают связь приложения с операционной платформой, встроенными компонентами, управление потоками и циклом жизни собственно графических компонентов и, с другой, сами элементарные визуальные компоненты. Основным пакетом библиотеки компонентов SWT является пакет org.eclipse.swt.widgets.

К основным «Инфраструктурным» компонентам библиотеки SWT можно отнести компоненты Display, Shell, Composite и Group.

Компонент Display выполняет множество функций. Он обеспечивает связь между операционной платформой, на которой выполняется данный экземпляр Eclipse, библиотекой SWT и потоком, в котором пользователь взаимодействует с визуальными компонентами SWT (и эти компоненты взаимодействуют друг с другом) – поток UI. Помимо этого, класс Display используется для управления ресурсами – шрифтами, цветами, курсорами. Наконец, Display является контейнером для хранения создаваемых разработчиком приложения произвольных свойств, определяемых в виде пары «имя_свойства – значение».

Компонент Shell является оконным фреймом – одним из возможных контейнеров визуальных компонентов. С каждым фреймом можно сопоставить свой набор графических ресурсов и свойств, т.е. свой объект Display.

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

Использование компонентов, подобных Display и Shell, различается при создании независимых SWT-приложений Eclipse и при разработке плагинов, в частности, представлений.

Для независимых приложений программист (или средства визуального проектирования) сам должен создавать SWT-среду приложения. Ниже приведен фрагмент кода такого приложения:

import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.widgets.*;

class ... 
{
  public static void main (String[] args[])
  {
    Display d = new Display();
    Shell s = new Shell (d);
    s.setText (“...”); // Заголовок
    ...
    Composite c = new Composite (s, SWT.BORDER);  // дочерний контейнер
                                                  // компонентов
    Button b = new Button (c, SWT.OK);  // кнопка под
                                        // управлением дочернего
                                        // контейнера
    ...
    s.open();
    while (!s.isDisposed()) 
    {
      if (!d.readAndDispatch())
        d.sleep(); 
    }
    d.dispose();
  }
}

При работе с представлениями инфраструктура окна уже создана, и нет необходимости создавать объекты Shell и Display – обычно вместо этого программист получает доступ к уже существующим компонентам.

Чтобы получить доступ к компоненту Display, можно использовать, например, статический метод getCurrent() класса Display, который возвращает объект класса Display, сопоставленный с текущим потоком UI (если метод вызывается не в контексте этого потока, то возвращается значение null). Получить Shell затем можно с помощью вызова метода getActiveShell().

Другой способ получения объектов Display и Shell – использование соответствующих get-методов для «комплексных» компонентов отображения данных (о них будет рассказано ниже).

Наконец, для некоторых callback-методов управления циклом жизни представления – особенно часто этот способ используется при написании кода метода createPartControl(), «отвечающего» за создание компонентов для представления – текущий контейнер компонентов передается в качестве параметра типа Composite. Для этого параметра можно вызвать метод getShell().

Библиотека SWT, помимо «элементарных компонентов» (кнопок (Button), полей (Label) и пр.), содержит более сложные компоненты, предназначенные для отображения структурированной информации, такие, как таблицы (Table) и деревья (Tree).

В принципе, набора компонентов SWT вполне достаточно для отображения графической и текстовой информации в окнах представлений.

Тем не менее, во многих случаях работать непосредственно с компонентами SWT не слишком удобно. Причина заключается в том, что они требуют от программиста перехода с уровня ориентированных на прикладную область типов, из которых строится программа, на более низкий уровень – уровень отдельных строк, чисел, графических образов, из которых нужно создать пользовательский интерфейс для данного приложения.

Кроме того, универсальные приложения давно используют классическую модель управления и отображения данных – MVC (Model – View – Controller), в которой собственно данные отделены от средств управления ими и от способа представления этих данных. Чтобы упростить работу с данными и повысить гибкость и эффективность создаваемых приложений, создана библиотека JFace, в которую, в частности, входят интегрированные компоненты структурированного отображения информации, являющиеся высокоуровневой надстройкой над более «элементарными» компонентами SWT. О представлениях JFace кратко будет рассказано в следующем разделе.

Прежде чем использовать в приложении компоненты SWT, необходимо в создаваемом проекте указать путь к библиотеке SWT. Саму библиотеку можно найти по ссылке http://www.eclipse.org/swt/. Полученный архив нужно сохранить, а затем импортировать его в workspace Eclipse c помощью команды File->Import...Existing Project into Workspace. Затем, при создании плагина или приложения Eclipse, нужно добавить в classpath путь к проекту org.eclipse.swt (команда Java Build Path в свойствах проекта) или указать зависимость создаваемого плагина от этой библиотеки (эту зависимость для создаваемого плагина эксперт создания представления создает автоматически).


Представления JFace и представления Eclipse

При создании визуальных отображений часто возникает необходимость структурированного изображения информации в виде текстов или сочетания текстов и графических изображений. Примерами такого отображения являются проекты Eclipse и их составляющие в панели Navigator перспективы Plug-in Development, список ресурсов Eclipse (папки, файлы, проекты) представления Project Explorer перспективы Resource, списки методов и атрибутов классов, табличное представление сообщений об ошибках (представление Problems) или о заданиях (представление Tasks).

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

Во всех этих случаях программы, написанные на Java, состоят из большой совокупности прикладных типов, которые к тому же объединены (с помощью наследования) в иерархию типов. Типы данных предназначены для удобного и наглядного моделирования предметной области, а не для отображения информации на экране монитора.

Отображение информации требует решения ряда задач – собственно отображение в том или ином виде (список, таблица, древовидная иерархическая структура), упорядочение данных при отображении (по уникальным индексам, названиям, дате, каким-то иным критериям), фильтрации и выборки части информации, и многих других. Для решения такого рода проблем в библиотеку JFace входят представления (viewers), которые позволяют существенно ускорить и упростить задачу универсального отображения структурированной информации по сравнению с «чистыми» компонентами SWT.

Сколько-нибудь подробное описание представлений JFace не входит в задачу статьи. Здесь мы рассмотрим основы их использования совместно с представлениями Eclipse.

Представления JFace построены по модульному принципу. Каждое из наиболее часто используемых представлений (список ListViewer, таблица TableViewer, дерево TreeViewer и др.) само по себе не содержит данных, которые нужно отображать, не завязано на режим упорядочения данных, не содержит средств выделения подмножества данных. Представления являются комплексом средств интеграции источников данных, фильтров, механизмов сортировки и обеспечения их совместной работы, включая оповещение всех, кому интересно, о происходящих на уровне представления событиях.

Основой представлений структурированной информации библиотеки JFace является класс StructuredViewer, производными от которого (прямо или косвенно) являются представления ComboViewer, ListViewer, TableTreeViewer, TreeViewer, TableViewer и некоторые другие. На этом уровне наследования классов (StructuredViewer) появляются средства фильтрации, сортировки и т.п. Функциональность более низкого уровня реализована в классах ContentViewer и в корневом класса подиерархии – классе Viewer.

Основными методами StructuredViewer являются следующие:

В приведенном ниже примере для простоты будет использоваться представление ListViewer, которое, как следует из названия, обеспечение представление информации в виде линейного списка.

Как видно, для нормальной работы представления необходимо создать и сопоставить с представлением некоторые вспомогательные объекты.

Первым из них является ContentProvider, точнее, content-провайдеры для структурированных представлений, такие, как ArrayContentProvider или TreeContentProvider. Задача провайдеров – по «источникам данных» (отдельным объектам или наборам таких объектов) подготовить информацию в виде, который понятен используемому представлению.

Базовым интерфейсом для структурированных content-провайдеров является интерфейс IStructuredContentProvider, который содержит только три метода:

Методы inputChanged() и getElements() вызываются как следствие вызова метода setInput() для структурированного представления, например, ListViewer'а.

Для нормальной работы content-провайдера, в свою очередь, необходимо обеспечить собственно входные данные, т.е. указать с помощью вызова метода представления setInput() объект, который можно трактовать как набор входных данных. Для такого content-провайдера, как ArrayContentProvider, входными данными для отображения является массив объектов, преобразуемых в строковое представление.

Наконец, необходимо обеспечить строковое представление подлежащих отображению объектов. За это отвечают так называемые Label-провайдеры. Основным Label-провайдером является класс LabelProvider, на основе которого разработаны вспомогательные классы, такие, как DecoratingLabelProvider, JavaElementLabelProvider, PerspectiveLabelProvider и др.

Разработчик label-провайдера должен реализовать интерфейс ILabelProvider, который определяет шесть методов, поэтому удобнее создавать свой класс label-провайдера на базе класса LabelProvider, для которого нужно переопределить два основных метода:

Для работы с табличным представлением информации удобно использовать класс графического представления для объекта с разбивкой по столбцам таблицы:

Таким образом, общая схема (без упорядочения, фильтрации и обработки событий) работы с представлениями JFace выглядит примерно так (полный пример будет приведен ниже):

  1. Задание объекта или совокупности объектов, содержащих данные, которые после некоторого преобразования можно преобразовать к отображаемым в представлениях JFace элементам – строкам и графическим образам.
class MyObject 
{
  ...
  Object[] getVisualItems () { ... }
}
  1. Создание экземпляра нужного представления JFace (ListViewer, TableViewer или др.)
    public void createPartControl(Composite parent) 
    {
      ...
      ListViewer lv = new ListViewer (parent, SWT.SINGLE);
  1. Создание экземпляра label-провайдера для задания отображаемой информации для каждого объекта данных из представления
      ...
      class MyLabelProvider extends LabelProvider 
      {
        ...
        Image getImage (Object o) 
        { 
          return null; // если не нужна «графика»
        } 
        String getText (Object o)
        {
          return ... ;
        }
      }

      ... 
      lv.setLabelProvider (new MyLabelProvider());
  1. Создание экземпляра нужного content-провайдера и сопоставление его с представлением
      ...
      lv.setContentProvider 
        (new ArrayContentProvider());
  1. Создание объекта с данными и сопоставление его с представлением
      ...
      MyObject mo = new MyObject();
      ...
      lv.setInput (mo.getVisualItems());

Если необходимо сортировать данные, то нужно создавать свой сортировщик (например, класс, производный от ViewerSorter с переопределением метода):

int compare (Viewer v, Object o1, Object o2);

работающий по обычным правилам Java – возвращаемое целое число меньше единицы, если объект o1 «меньше», чем объект o2, и положительное, если больше. Затем создается экземпляр этого класса, сопоставляемый с представлением (ListViewer, в нашем случае) с помощью метода setSorter();

      MySorter ms = new MySorter();
      ...
      lv.setSorter (ms);

Переопределение метода compare() – не единственный способ упорядочения данных. Данные можно делить на группы. По умолчанию все элементы представления принадлежат к одной группе с индексом 0. Сортировка с помощью метода compare() выполняется в пределах одной группы – после деления на группы.

Для деления на группы (с каждой группой сопоставлено целое число) нужно еще переопределить метод category(), который для объекта, передаваемого методу в качестве аргумента, возвращает целое число (индекс его категории).

Похожим образом выполняется фильтрация (может быть задано несколько фильтров для одного представления JFace). Разработчик создает класс, производный (как правило) от класса ViewerFilter, и переопределяет в нем (опять-таки, как правило) только один метод – select().

Метод select() имеет следующую сигнатуру:

boolean select (Viewer viewer, Object parentElement, Object element);

Если элемент element должен отображаться в представлении, то метод select для него должен возвращать значение true.

Новый фильтр добавляется к существующим для представления фильтрам с помощью вызова addFilter (ViewerFilter).

Для включения компонентов JFace в представления (view) Eclipse удобнее всего использовать callback-метод createPartControl(). Этот метод вызывается при инициализации создаваемого экземпляра представления, и его единственный аргумент (типа Composite) задает контейнер для SWT- и JFace-компонентов этого представления:

  public void createPartControl(Composite parent) 
  {
    ...
    ListViewer lv = new ListViewer (parent, SWT.SINGLE);
    ...
  }

Поскольку компоненты JFace имеют атрибуты своего визуального представления (цвет фона и текста, шрифты и т.д.) и может возникнуть необходимость изменения одного или нескольких из этих атрибутов, то после создания компонента JFace, подобного ListViewer, с помощью вызова метода getControl() можно получить доступ к «нижележащему» компоненту SWT. Например, для класса ListViewer метод getControl() возвращает ссылку на private-объект типа org.eclipse.swr.widgets.List. Получив доступ к такому объекту, программист может менять его визуальное представление на экране.

Эксперт создания представления Eclipse генерирует простейшие классы в стиле MVC для отображения данных в представлении:

package my.view_and_info;

import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;

public class View extends ViewPart 
{
  public static final String ID = "my.view_and_info.view";

  private TableViewer viewer; // представление JFace

  // класс content-провайдера

  class ViewContentProvider implements IStructuredContentProvider 
  {

    public void inputChanged(Viewer v, 
      Object oldInput, Object newInput) {  }

    public void dispose() {  }

    public Object[] getElements(Object parent) 
    {
      return new String[] { "One", "Two", "Three" };
    }
  }

  // класс простейшего label-провайдера

  class ViewLabelProvider extends LabelProvider 
    implements  ITableLabelProvider 
  {
    
    public String getColumnText(Object obj, int index) 
    {
      return getText(obj);
    }

    public Image getColumnImage(Object obj, int index) 
    {
      return getImage(obj);
    }

    public Image getImage(Object obj) 
    {
      return PlatformUI.getWorkbench().getSharedImages().
        getImage(ISharedImages.IMG_OBJ_ELEMENT);
    }
  }

  // метод, вызываемый средой Eclipse при инициализации 
  // создаваемого представления

  public void createPartControl(Composite parent) 
  {
    viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
    viewer.setContentProvider(new ViewContentProvider());
    viewer.setLabelProvider(new ViewLabelProvider());
    viewer.setInput(getViewSite());   // вызов метода getElements()
                                      // content-провайдера
  }

  public void setFocus() 
  {
    viewer.getControl().setFocus();
  }
}

Прежде, чем обсуждать ограниченность такого примитивного решения, имеет смысл сказать несколько слов о графических образах, широко используемых при отображении данных в списках или деревьях JFace.

Eclipse использует целый ряд таких образов, поэтому многие из них доступны непосредственно с помощью вызова метода getImage(), как было показано выше:

  PlatformUI.getWorkbench().getSharedImages().getImage
     (ISharedImages.IMG_OBJ_ELEMENT);

Получить некоторые образы (как объекты типа Image) можно с помощью вызова метода getSystemImage() для класса Display:

  Display d = Display.getCurrent();
  Image i = d.getSystemImage (SWT.ICON_INFORMATION);

В комплект поставки Eclipse входит также набор образов, которые можно использовать либо непосредственно, либо как основу для разработки собственных образов с помощью любого подходящего графического редактора. Для Eclipse 3.3 набор образов содержится в подкаталоге eclipse\plugins\icons\full каталога установки, в архиве с именем org.eclipse.ui_3.3.x.xxxxxxxxx.jar (точное имя файла зависит от версии релиза). При наличии файла графического образа получить образ как объект типа Image можно с помощью вызова конструкторов классов Image и/или ImageData:

  Image image = new Image (null, “D:\\my_dir\my_file.gif”); 

Общие задачи при работе с представлениями Eclipse

Непосредственное отображение информации в представлениях Eclipse – только часть дела. Помимо этого, разработчику приходится решать и другие, не менее важные и серьезные задачи.

Прежде всего, в одном и том же представлении часто приходится отображать разнотипные данные. Примером опять может служить проект Eclipse, отображаемый в панели Project Explorer (или панели Outline).

Это порождает сразу несколько проблем.

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


С точки зрения отображения информации в виде списка, таблицы или дерева, данная проблема формулируется так: нужно создать «модельный объект» («элемент проекта», например), и для него предусмотреть схему отображения – нужное представление JFace, нужный content-провайдер и label-провайдер, и т.д. Затем для реальных «типов» отображаемых данных определить процедуру «конвертации» к модельному объекту. Кроме того, необходимо также определить и обратное преобразование – от «модельного объекта» к реальному. Для этой цели в Eclipse встроен механизм так называемых «адаптеров», о которых будет рассказано в отдельном разделе.

Но само по себе использование адаптеров не решает другую проблему – проблему расширения возможностей отображения таких элементов в представлениях JFace, которые появились после разработки классов, совместно отвечающих за визуальное отображение данных. Допустим, появилась новая Java-технология, и новый вид компонента может быть добавлен в структуру проекта Java. Можно ли добавить поддержку этого нового типа (с точки зрения его отображения), не меняя уже написанного кода? Сделать это можно, используя точки расширения Eclipse.

Вторая проблема – обработка событий. Модель MVC подразумевает, в частности, что одни и те же данные могут отображаться в различном виде в различных представлениях (и в различных перспективах). Это означает, что произвольное число различных представлений должно работать синхронно при изменении данных в списке или таблице, или просто при выборе другого элемента в списке – подобно тому, как при выборе пользователем нового класса в панели Navigator меняется описывающее структуру этого класса содержимое панели Outline.

Оповещение различных компонентов в различных представлениях (и редакторах) Eclipse основывается на универсальной Java-модели событий и их листенеров (получателей оповещения о событиях). Эта модель хорошо знакома всем программистам, имевшим дело с технологией JavaBeans. Более того, поддержка событий для SWT- и JFace-компонентов реализуется существенно проще, нежели в «общем случае» – за счет того, что компоненты SWT вызывают друг друга в одном потоке, потоке UI. Вследствие этого разработчику приложений не нужно обеспечивать потокобезопасность (и эффективную работу при наличии потокобезопасности кода) при регистрации и отмене регистрации листенеров для тех или иных событий.

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

Для создания профессиональных представлений (как составной части визуального приложения) нужно решать и некоторые другие задачи, но это выходит за рамки данной статьи.

Перед тем, как рассматривать более приближенный к реальности пример отображения данных в представлении, нужно остановиться на некоторых вспомогательных темах – а именно, на адаптерах Eclipse, на обработке событий и сохранении данных в представлениях Eclipse.

Адаптеры

Использование адаптеров будет продемонстрировано на примере, здесь же я хотел бы дать самое общее представление об их назначении и правилах использования.

В Eclipse для преобразования типа, хранящегося в отображаемой коллекции, к типу, который может быть отображен представлением, используется стандартный паттерн ООП – «Адаптер».

Для поддержки этого паттерна исходный тип должен реализовывать интерфейс org.eclipse.core.runtime.IAdaptable.

Это простой интерфейс с единственным методом:

public interface IAdaptable 
{
  public Object getAdapter(Class adapter);
}

Единственный аргумент метода задает класс адаптера, т.е. тип, к которому (с точностью до дерева наследования Java) нужно «привести» объект класса, реализующего данный интерфейс. Если такое преобразование выполнить невозможно, метод getAdapter() должен возвращать значение null. Соответственно, общая схема использования «адаптируемых» объектов выглядит (в виде кода Java) примерно так:

  IAdaptable a = new MyAdaptableClass();   // Класс, реализующий
                                           // IAdaptable – преобразование
                                           // к IMyFixedInterface 
                                           // (адаптеру для 
                                           // MyAdaptableClass)
  IMyFixedInterface mi = 
    (IMyFixedInterface)a.getAdapter(IMyFixedInterface.class);
  if (mi != null)
    mi.my_method_of_MyFixedInterface();

Реализация метода getAdapter(), разумеется, зависит от прикладной задачи.

Несложно убедиться, что наличия метода getAdapter() часто бывает недостаточно. В самом деле, с помощью этого метода класса MyAdaptableClass я могу для некоторого объекта этого типа создать адаптер некоторого уже существующего «стандартного типа» – например, типа, для которого определена модель его визуального отображения в представлении Eclipse. В приведенном выше примере таким «стандартным» (точнее, неизменяемым) типом является IMyFixedInterface. Но я не могу выполнить обратную задачу – определить «преобразование» от «стандартного» типа к моему типу MyAdaptableClass – просто потому, что тип IMyFixedInterface не реализует интерфейс IAdaptable, а изменять этот интерфейс мы по какой-то причине не можем. Тем не менее, универсальную схему «преобразования типов» можно создать, только обеспечив «преобразование» в обе стороны.

Для решения только что упомянутой проблемы в платформу Eclipse была встроена специальная подсистема, работающая по принципу «фабрики адаптеров». Фабрика адаптеров должна реализовать интерфейс IAdapterFactory:

public interface IAdapterFactory 
{
  public Object getAdapter(Object adaptableObject, Class adapterType);
  public Class[] getAdapterList();
}

Первый из этих методов делает то же, что и метод getAdapter() интерфейса IAdaptable – пытается на основе исходного объекта (первый аргумент метода) создать соответствующий ему адаптер требуемого типа (этот тип задается вторым аргументом метода). Класс исходного объекта, как правило (но не обязательно), реализует интерфейс IAdaptable.

Второй метод придает фабрикам адаптеров универсальный характер (хотя он обычно не вызывается прикладными разработчиками). С его помощью можно узнать, адаптеры каких типов данная фабрика может создавать.

Созданные фабрики адаптеров должны быть доступны запущенному экземпляру платформы Eclipse (и, соответственно, установленным плагинам и библиотекам). Чтобы созданную фабрику можно было найти, ее предварительно нужно зарегистрировать. Регистрацию фабрик выполняет класс, реализующий интерфейс IAdapterManager (есть готовая реализация – класс AdapterManager). Наиболее интересными в данном случае методами этого интерфейса являются два:

public interface IAdapterManager 
{
  ...
  public void registerAdapters(IAdapterFactory factory, Class adaptable);
  public void unregisterAdapters(IAdapterFactory factory);
}

Первый аргумент метода registerAdapters() задает фабрику адаптеров, второй – тип, для которого данная фабрика создает адаптеры (т.е. «исходный» тип).

При наличии менеджера адаптеров нужную фабрику можно найти по имени класса нужного адаптера. Именно для этой цели каждая фабрика и содержит метод getAdapterList(), который возвращает список типов создаваемых адаптеров.

Ранее мы определили «преобразование» от типа MyAdaptableClass к «стандартному» типу IMyFixedInterface – за счет того, что класс MyAdaptableClass реализует интерфейс IAdaptable и, соответственно, преобразование к типу IMyFixedInterface выполняется в коде метода getAdapter():

class MyAdaptableClass implements IAdaptable 
{
  ...
  Object getAdapter (Class adapter)
  {
    If (IMyFixedInterface.class.equals (adapter))
      return new MyFixedInterface();
    ...
  } 

Теперь необходимо определить «обратное преобразование» – от IMyFixedInterface к MyAdaptableClass без изменения кода IMyFixedInterface и реализующего его класса MyFixedInterface. Для этого нужно создать следующее:

class MyAdaptableClass_AdapterFactory implements IAdapterFactory 
{
  private static Class[] adapterFor = 
    new Class[] { MyAdaptableClass.class };

  public Class[] getAdapterList () 
  {
    return adapterFor; 
  }

  public Object getAdapter (Object object, Class adapter)
  {
    if (MyAdaptableClass.class.equals(adapter)) 
      return MyAdaptableClass(object);
      ...
    return null;
  }  
}
...
  MyAdaptableClass_AdapterFactory af = 
    new MyAdaptableClass_AdapterFactory();
... 

и зарегистрировать соответствующую фабрику адаптеров:

  IAdapterManager amngr = Platform.getAdapterManager();
  amngr.registerAdapters(af, IMyFixedInterface.class);

Если адаптер создается для использования плагинами, то регистрацию фабрик удобно производить в коде метода start() активатора плагина, а отмену регистрации – в коде метода stop():

  Platform.getAdapterManager().unregisterAdapters(af);

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

Например, с учетом наличия менеджера адаптеров и зарегистрированных фабрик, код нашего метода getAdapter() класса MyAdaptableClass мог бы выглядеть теперь так:

class MyAdaptableClass implements IAdaptable 
{
  ...
  Object getAdapter (Class adapter)
  {
    // Попытка создать адаптер для объекта this непосредственно

    If (IMyFixedInterface.class.equals (adapter))
      return new MyFixedInterface();

    // если условие не соблюдается, можно попробовать обратиться
    // за помощью к зарегистрированным фабрикам – возможно, какая-то
    // из них объявляет класс adapter в списке, 
    // возвращаемом методом getAdapterList(), причем способна 
    // создать экземпляр этого класса на основе переданного 
    // первого аргумента 
    
    return Platform.getAdapterManager().getAdapter(this, adapter);
  } 
}

Адаптеры полезны во многих случаях, и возможность их использования всегда следует иметь в виду при разработке плагинов Eclipse.

Пример отображения информации в представлении

Пора перейти к более-менее реальному примеру отображения данных в преставлении Eclipse. Для «настоящего» приложения нужно писать довольно много кода, причем не всегда тривиального. Вследствие этого, во-первых, пример будет достаточно простой, а, во-вторых, даже этот простой пример создавать и описывать мы будем по частям. В первой части, т.е. в данной статье, будет рассмотрен довольно типичный подход к отображению, подчеркиваем, отображению данных. Такие вопросы, как обработка событий, сохранение и восстановление данных в представлении, будут рассмотрены в следующей статье, но основе этого же примера.

Ниже будет приведен код приложения, в котором в виде простого упорядоченного списка отображаются объекты – «цвета» и «звуки». Разумеется, в списке отображаются не сами объекты, а созданные для каждого объекта строковые представления.

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

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

Исходя из этих соображений, цвет и звук не должны звать о представлении. Кроме того, тип может быть уже разработан, и у нас может отсутствовать возможность менять его код.

Прикладной тип должен содержать «свои» данные, как-то: атрибуты задания цвета для типа «Цвет» - как именно, зависит от прикладной задачи, выбранной библиотеки работы с графикой и т.п. Объект типа «Звук» может хранить, например, ссылку на файл с записью звука или целочисленное значение соответствующей частоты звука.

В первой части статьи мы не будем окрашивать что-то в конкретные цвета или воспроизводить звуки, это произойдет при обработке событий. Следовательно, сейчас нам не нужны детали реализаций классов «Цвет» или «Звук» - можно ограничиться простыми заглушками:

public class MyColor
{

  public MyColor (String colorName) // один из конструкторов
  {
  }
}

В чем принципиальная разница между отображением просто списка строк и списка объектов предметной области, автоматически преобразуемых с помощью адаптеров к строкам?

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

Как следствие, нам нужен вспомогательный объект, предназначенный специально для выполнения «умного» отображения в окнах представлений Eclipse информации о цветах, звуках и т.п., с возможностями группировки данных, их сортировки по тому или иному принципу. Этот объект будет содержать в себе ссылки на объекты типов MyColor и MySound, но данные этих объектов ориентированы на поддержку действий со строково-графическими представлениями «цветов» и «звуков» в окнах Eclipse.

Глупо определять для типа «Цвет» правила его упорядочения среди других цветов и даже не только цветов (если в списке JFace отображаются строки для разных типов объектов) – естественно перенести это упорядочение в специальный объект – «оболочку». Представления Eclipse будут в нашем примере иметь дело не с самими цветами/звуками, а их «отображаемыми оболочками». Эти вспомогательные объекты будут задавать отображаемую строку, отображаемый графический образ (если он нужен), параметры группировки/сортировки при выводе на экран. Они будут участниками обработки событий, порождаемых пользователями (или кодом приложения) при манипулировании отображаемой для объектов «Цвет» и «Звук» информацией (например, выбор в списке, двойной щелчок мышью и т.д.). Если необходимо, этот вспомогательный «отображаемый» объект всегда может получить доступ к реальному объекту «Цвет» или «Звук» – поскольку он имеет ссылку на реальный объект. Далее везде под «объектом» применительно к примеру будем понимать этот вспомогательный объект, объект «элемент списка», созданный для унификации отображения разнородных данных («цветов и звуков», в данном случае).

Следующий момент. В списках, деревьях, таблицах JFace содержатся (и отображаются на экране) строки. Имея объект «элемент списка», мы можем получить для него строковое представление – это делается элементарно. Но в общем случае связь между объектом должна быть двухсторонней. С одной стороны, мы должны уметь вывести список названий цветов. С другой стороны, имея название цвета, мы должны иметь возможность найти объект «Цвет», соответствующий этому названию (или создать новый). Таким образом, необходимо иметь возможность определить «однозначное» соответствие между сущностью в программе и ее визуальным представлением, точнее, с несколькими возможными ее визуальными представлениями.

Если пойти немного дальше, то возникнет следующий вопрос: а почему нужно ограничиться соответствием между объектом «элемент списка» и строкой, т.е. типом String? Почему не обеспечить такие соответствия между различными объектами, имеющими то или иное визуальное представление? Представим, что в одном представлении отображаются картинки некоторых объектов программы, с которыми сопоставлены определенные цвета (например, предметы мебели). Можно ли пополнить список цветов, имеющихся в одном представлении Eclipse, просто «перетащив» (с помощью drag-and-drop) изображение предмета мебели или просто строку его описания из каталога, в список цветов? Это не составит труда, если создать схему соответствия между объектами и их строковыми и/или графическими представлениями.

В рассматриваемом примере создается сопоставление между объектом «Элемент списка» (некоторого типа) и строками. Вместо строк, разумеется, можно было взять любой другой тип Java – стандартный или созданный самим программистом. Главное, чтобы для него Eclipse знал, как создать визуальное представление того или иного вида. Например, Eclipse умеет отображать в своих view объекты, реализующие интерфейс IWorkbenchAdapter или IResource (объекты IResource используются для отображения представлений проектов, папок и файлов.)

В общем и целом в примере будет использоваться та же модель, которая была сгенерирована экспертом Eclipse при создании view, и которая была приведена выше.

Прежде всего, различные классы и интерфейсы в приложении концептуально будут разнесены по разным пакетам Java. То, что касается представления Eclipse и его класса активации, content- и label-провайдеров, сортировщиков и пр., будет находиться в одном пакете (my.view_and_info). Сами отображаемые объекты, их фабрики и прочее помещены в другой пакет (my.view_and.info.data).

Начнем с рассмотрения данных.

Сами объекты типа MyColor (и аналогичный ему MySound) – в достаточном для первой части статьи сверх-упрощенном варианте – мы уже видели. Можно перейти к оболочкам для них, предназначенным для отображения информации о цветах и звуках в списках.

Прежде всего, нужно определить универсальный интерфейс для отображаемых в списке JFace объектов. Пусть он выглядит следующим образом:

package my.view_and_info.data;

import org.eclipse.core.runtime.IAdaptable;

// Интерфейс, используемый при отображении всех элементов списка
public interface IViewElement extends IAdaptable
{
  // Строка для вывода информации об объекте  
  String getPrintName();

  // Группа, к которой относится объект  
  int getCategory ();

  // Порядковый номер в группе (например, для сортировки)  
  int getIndex();

  // Базовый объект, для которого создан данный элемент списка   
  Object getBaseObject ();
}

Наш интерфейс расширяет интерфейс IAdaptable, чтобы обеспечить возможность гибкого получения адаптера для отображаемого объекта.

Следующий класс – это класс отображаемого элемента, т.е. класс, реализующий интерфейс IViewElement.

Если условия задачи таковы, что нужно отображать информацию только для заранее известного – точнее, очень редко изменяемого набора типов объектов, причем в тех редких случаях, когда программа должна поддерживать новый тип, можно легко изменить код приложения, то возможен весьма простой (но негибкий) подход с использованием единственного типа «Элемент списка». Поддержка различных типов («Цветов», «звуков» и пр.) «зашита в коде», т.е. примерно такой класс:

package my.view_and_info.data;

import org.eclipse.core.runtime.Platform;

// Класс, реализующий универсальный интерфейс отображения данных

public class ViewElement implements IViewElement
{
  // исходный объект, для которого создается данный элемент списка
  private Object baseObject;
  private int category;     // категория элемента
  private int sortIndex;    // порядковый индекс
  ...
  
  public ViewElement (Object baseObj, int cat, int index)
  {
    inputString = str;
    category = cat;
    sortIndex = index;
  }
  
  @Override
  public String getPrintName() 
  {
    String prefix = null;
    if (category == 1)
      prefix = "Цвет: ";
    else
      if (category == 2)
        prefix = "Звук: ";
    return prefix + (String)baseObject; 
  }
  
  @Override
  public int getCategory ()
  {
    return category;
  }
  
  @Override
  public int getIndex ()
  {
    return sortIndex;
  }
  
  @Override
  public Object getBaseObject ()
  {
    return baseObj;
  }

  @Override
  public Object getAdapter(Class adapter) 
  {
    if (adapter.isInstance(inputString))
      return String (baseObject);
    return Platform.getAdapterManager().getAdapter (this, adapter);
  }
  ...
}

Обычно используется более гибкий и универсальный подход – создание иерархии классов элементов списка, с переопределением нужных виртуальных функций (подобной получению отображаемой в списке строке) для каждого класса. Это приводит в общем случае к тому, что при добавлении поддержки нового типа нет надобности менять код существующих классов – вместо этого просто создается новый класс иерархии.

Кроме того, здесь же, на уровне классов элементов списка, можно определить функции, отвечающие за группировку/сортировку таких типов. Если нужно поддерживать несколько алгоритмов и правил сортировки, то можно в классе предусмотреть возможность задания различных алгоритмов. Программистам хорошо известно, как это делается, и в нашем случае это не относится к теме статьи, поэтому реализация в примере будет единственной (и простой).

Вопросы универсализации создаваемых элементов списков с использованием возможностей Eclipse будут рассмотрены в следующей части статьи, сейчас подробно останавливаться на этом не будем.

Базовый (абстрактный) класс «элемент списка» может выглядеть примерно так:

package my.view_and_info.data;

public abstract class ViewElementAbstract implements IViewElement, Comparable
{
  // "отображаемый" в списке объект (цвет, звук и т.п.)
  protected Object baseObject;
  protected int category;     // "тип" объекта
  protected int index;        // "порядковый номер" объекта (необязательный)
  protected Object inpObject; // объект, на базе которого создан элемент
  
  protected ViewElementAbstract (Object baseObj, String name)
  {
    inpObject = name;
    baseObject = baseObj;
    category = -1;
    index = 0;
  }
  
  @Override
  public Object getBaseObject() 
  {
    return baseObject;
  }

  @Override
  public int getCategory() 
  {
    return category;
  }

  @Override
  public int getIndex() 
  {
    return index;
  }

  @Override
  abstract public String getPrintName();

  @Override
  public Object getAdapter(Class adapter) 
  {
    return null;
  }

  @Override
  public int compareTo(Object arg) 
  {
    if (arg instanceof ViewElementAbstract)
    {
      ViewElementAbstract vea = (ViewElementAbstract)arg;
      if (this.category != vea.category)
        return this.category - vea.category;
      else
        return this.index - vea.index;
    }

    return 0;
  }
}

Для поддержки цветов и звуков можно создать производные классы. В данном случае нужно переопределить только метод, возвращающий строковое представление объекта (классы ViewElementForColor и ViewElementForSound почти идентичны):

package my.view_and_info.data;

public class ViewElementForColor extends ViewElementAbstract 
{

  public ViewElementForColor (MyColor baseObj, String name, int index)
  {
    super(baseObj, name);
    this.category = 1;
    this.index = index;
  }

  @Override
  public String getPrintName() 
  {
    return "Цвет: " + (String)inpObject;
  }
}

Теперь необходимо создать элементы типа ViewElementAbstract. Для этого используется стандартный элемент шаблона проектирования программ – фабрика элементов. Заметьте, что фабрика элементов сама может быть сделана «отображаемым в списке элементом» – для этого класс фабрики должен просто реализовать интерфейс IViewElement. Такой подход имело бы смысл использовать, например, при использовании дерева JFace. В данном случае мы этого делать не будем.

Поскольку некоторая информация является общей для всех фабрик, то имеет смысл создать базовый класс «Фабрика элементов как таковая», а затем – производные от нее фабрики «Фабрика цветов», «Фабрика звуков» и т.д.

При этом нужно иметь в виду, что данный способ имеет существенное ограничение – при добавлении в программу новых типов объектов нужно изменять код класса фабрики, а также добавлять новую фабрику. Есть способы отказаться от необходимости внесения таких изменений, но об этом – как уже говорилось – в следующей статье.

Класс базовой фабрики может иметь следующий вид:

package my.view_and_info.data;

// базовый класс фабрик типов данных, отображаемых в представлении

public abstract class ViewElementFactory 
{

// Имя объекта-фабрики   
  private String factoryName;

// Созданные ниже элементы работают как фабрики создаваемых (на базе 
// входных данных) объектов типа ViewElement

  private static ViewElementFactory[] elementFactories = 
    { 
      new ViewColorElementFactory("Фабрика цветов"), 
      new ViewSoundElementFactory("Фабрика звуков")
    }; 


  public ViewElementFactory (String fname)
  {
    factoryName = fname;
  }
  
  public ViewElementFactory ()
  {
    factoryName = new String ("ViewElement factory");
  }

  public String getName ()
  {
    return factoryName;
  }
  
  public static ViewElementFactory[] getFactories()
  {
    return elementFactories;
  }
  
  public abstract IViewElement createViewElement (Object baseObj);
}

Поскольку вполне достаточно создания одной фабрики каждого типа, то их можно создать заранее (и поместить ссылку на них в статические поля), а также обеспечить к ним доступ для всех желающих.

Теперь можно приступить к созданию отдельных типов фабрик.

class ViewColorElementFactory extends ViewElementFactory 
{
  private static String[] COLORS = 
{ "red", "orange", "yellow", "green", "blue", "violet"};

  public ViewColorElementFactory () {}
  public ViewColorElementFactory (String name) 
  {
    super(name);
  }

  // Метод создания отображаемых в списке представления элементов, 
  // представляющих «объект Цвет» 
  @Override
  public IViewElement createViewElement (Object baseObj)
  {
    IViewElement res = null;
    if (baseObj instanceof String)
    {
      String str = (String)baseObj;
      int index = calculateIndex(str);
      if (index >= 0)
      {
        MyColor c = new MyColor (str);
        res = new ViewElementForColor (c, str, index);
      }
    }
    return res;
  }
  
  public int calculateIndex (String str)
  {
    int res = -1;
    for (int i = 0; i < COLORS.length; i++)
      if (str.equals(COLORS[i]))
      {
        res = i;
        break;
      }
    return res;
  }
  
}

Аналогичным образом создается фабрика «звуков»:

class ViewSoundElementFactory extends ViewElementFactory 
{
  private static String[] SOUNDS = 
{ "do", "re", "mi", "fa", "sol", "la", "si"};

  public ViewSoundElementFactory () {}
  public ViewSoundElementFactory (String name) 
  {
    super(name);
  }
  // Метод создания отображаемых в списке представления элементов, 
  // представляющих «объект Звук»
  @Override
  public IViewElement createViewElement (Object baseObj)
  {
    IViewElement res = null;
    if (baseObj instanceof String)
    {
      String str = (String)baseObj;
      int index = calculateIndex(str);
      if (index >= 0)
      {
        MySound s = new MySound (str);
        res = new ViewElementForSound (s, str, index);
      }
    }
    return res;
  }  
  public int calculateIndex(String str)
  {
    int res = -1;
    for (int i = 0; i < SOUNDS.length; i++)
      if (str.equals(SOUNDS[i]))
      {
        res = i;
        break;
      }
    return res;
  }
} 

Теперь нужно обеспечить получение исходных объектов, на базе которых будут созданы отображаемые элементы списка для цветов и звуков. Откуда берутся такие данные, и в каком виде – эта задача решается каждый раз по-своему.

В этом примере исходными данными являются строки Java с названиями цветов и нот.

Для того чтобы источник данных можно было интегрировать со списком JFace, этот источник должен иметь метод с сигнатурой getElements(), который возвращает (для выбранного JFace-провайдера) массив объектов.

Класс для управления входными данными для нашего простого примера выглядит так (список «исходных объектов» просто содержится в коде класса):

package my.view_and_info.data;

// Простой класс для создания отображаемых данных 
// (без их сохранения и обработки событий)

public class ViewElementsHolder 
{

  // Объект для управления входными данными
  private static ViewElementsHolder veHolder;

  // Список входных данных для тестирования приложения
  private static String[] custElems = 
    {"red", "fa", "blue", "violet", "do", "si", "re", "green"};

  private ViewElementsHolder () {}
  
  // Метод для доступа (или создания) объекта управления исходными данными
  public static ViewElementsHolder getElementsHolder ()
  {
    if (veHolder == null)
      veHolder = new ViewElementsHolder();
    return veHolder;
  }

  // Метод, обеспечивающий входные данные для формирования списка
  public Object[] getCustomElements()
  {
    return custElems;
  }

  // Метод, обеспечивающий взаимодействие с content-провайдером JFace
  public IViewElement[] getElements()
  {
    Object[] input = getCustomElements();
    
    IViewElement[] res = new IViewElement[input.length];
    
    ViewElementFactory[] factoryObjects = 
      ViewElementFactory.getFactories();
    
    // Для простоты считаем, что все входные данные корректны    
    for (int i = 0; i < input.length; i++)
      for (int j = 0; j < factoryObjects.length; j++)
      {
        IViewElement ve = factoryObjects[j].createViewElement(input[i]);
        if (ve != null)
        {
          res[i] = ve;
          break;
        }
      }
    return res;
  }
}

При всей простоте этого тестового метода имеет смысл обратить внимание на код метода getElements().

Его задача – входному массиву объектов типа String пытаться с помощью известных фабрик создать объекты нужного нам типа – такие, которые будут отражаться в списке представления. Входным типом для каждого создаваемого объекта ViewElementAbstract является объект типа String.

По сути, код этого метода – основа для возможного создания фабрики адаптеров, которая для строки создает адаптер типа ViewElementAbstract. Вот код фабрики адаптеров для нашего примера:

package my.view_and_info.data;

import org.eclipse.core.runtime.IAdapterFactory;

public class ViewElementFactoryAdapter implements IAdapterFactory 
{
  private static Class[] adapterTypes = { ViewElementAbstract.class };

  @Override
  public Object getAdapter(Object adaptableObject, Class adapterType) 
  {
    if (adaptableObject instanceof String) 
    {
      if (ViewElementAbstract.class.equals(adapterType))
      {
        ViewElementFactory[] factoryObjects =
          ViewElementFactory.getFactories();
        for (int j = 0; j < factoryObjects.length; j++)
        {
          IViewElement ve = factoryObjects[j]
            .createViewElement((String)adaptableObject);
          if (ve != null)
            return ve;
        }
      }
    }
    return null;
  }

  @Override
  public Class[] getAdapterList() 
  {
    return adapterTypes;
  }
}

После регистрации фабрики в методе start() нашего представления:

  private ViewElementFactoryAdapter vefa = null;
  ...
  public void start(BundleContext context) throws Exception 
  {
    super.start(context);
    plugin = this;
    
    vefa = new ViewElementFactoryAdapter();
    IAdapterManager amgr = Platform.getAdapterManager();
    amgr.registerAdapters(vefa, String.class);
  }

...код метода getElements() мог бы выглядеть так:

  public IViewElement[] getElements()
  {
    Object[] input = getCustomElements();
    IViewElement[] res = new IViewElement[input.length];

    IAdapterManager amngr = Platform.getAdapterManager(); 
    
    for (int i = 0; i < input.length; i++)
    {
      IViewElement ve = (IViewElement)
        amngr.getAdapter(input[i], ViewElementAbstract.class);
      if (ve != null)
      {
        res[i] = ve;
        break;
      }
    }
    return res;
  }

Смысл таких изменений – развязка используемых элементов и их фабрик, а также универсализация выполнения преобразования от одного типа к другому. Хотя, разумеется, вариант с использованием зарегистрированных фабрик работает существенно медленнее.

Теперь необходимо определить content- и label-провайдеры, класс, выполняющий сортировку, и объединить все это в созданном представлении.

Поскольку мы пока не работаем с событиями, код всех классов будет очень прост. Сначала – content-провайдер:

package my.view_and_info;

import org.eclipse.jface.viewers.ArrayContentProvider;
import my.view_and_info.data.*;

// Простой content-провайдер, пока без обработки событий

public class MyContentProvider extends ArrayContentProvider 
{
  public Object[] getElements (Object parent)
  {
    return ViewElementsHolder.getElementsHolder().getElements();
  }
}

Label-провайдер немного поинтереснее – именно здесь формируется выводимая в окне представления строка:

package my.view_and_info;

import org.eclipse.jface.viewers.LabelProvider;
import my.view_and_info.data.*;

public class MyLabelProvider extends LabelProvider 
{
  public String getText (Object obj)
  {
    if (obj instanceof IViewElement)
      return ((IViewElement)obj).getPrintName();
    else
      if (obj != null) 
        return (String)obj;
      else
        return "Что-то странное...";    
  }
}

Поскольку наш класс MyLabelProvider унаследован от уже вполне функционального класса LabelProvider, и мы в данном случае не работаем с отображением графических образов, достаточно просто переопределить метод getText().

Наконец, нужно обеспечить упорядочение данных. Сортировка по алфавиту нас, очевидно, не устраивает – вместо этого отображаемые объекты (а не их строковые представления на экране) сортируются сначала по категориям, а затем, уже после разбивки по категориям – по индексам.

package my.view_and_info;

import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import my.view_and_info.data.*;

// Сортировка с использованием категорий отображаемых объектов

public class MyViewerSorter extends ViewerSorter 
{
  public int category (Object obj)
  {
    int res = 0; 
    if (obj instanceof IViewElement)
      res = ((IViewElement)obj).getCategory();
    return res;
  }
  
  public int compare (Viewer v, Object o1, Object o2)
  {
    int res = 0;
    if ((o1 instanceof ViewElementAbstract) && 
(o2 instanceof ViewElementAbstract))
    {
      ViewElementAbstract od1 = (ViewElementAbstract)o1;
      ViewElementAbstract od2 = (ViewElementAbstract)o2;
      res = od1.compareTo(od2);
    }
    return res;
  }
} 

Чтение и отображение информации будет происходить уже на этапе создания самого представления, точнее, в коде метода createPartControl() класса представления (класса View):

package my.view_and_info;

import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
import my.view_and_info.data.*;


public class View extends ViewPart 
{
  public static final String ID = "my.view_and_info.view";

  private ListViewer viewer;

  public void createPartControl(Composite parent) 
  {
    viewer = new ListViewer (parent, SWT.H_SCROLL | SWT.V_SCROLL);
    viewer.setContentProvider(new MyContentProvider());
    viewer.setLabelProvider(new MyLabelProvider());
    viewer.setInput(ViewElementsHolder.getElementsHolder());
    viewer.setSorter(new MyViewerSorter());    
  }

  public void setFocus() 
  {
    viewer.getControl().setFocus();
  }
} 

Вот результат работы программы (при отображении нашего представления в отдельном окне):


Заключение

В следующей статье будут рассмотрены другие вопросы отображения информации в представлениях – обработка событий, сохранения данных в представлениях и многое другое.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.

Copyright © 1994-2016 ООО "К-Пресс"