Статьи

Настройка TreeViewer с привязкой EMF-данных


В этой статье мы начинаем писать наше RCP-приложение, которое использует Eclipse-Databinding.
Я не буду вдаваться в подробности того, как вы создаете RCP-приложение (я просто использую PDE-Wizard), но предполагаю, что вы знакомы с такими вещами.

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

Заблуждение, которое я часто вижу, когда говорю, что мои RCP-приложения свободны от редакторов, заключается в том, что люди спрашивают: «Как вы собираетесь стать частью Workbench-Dirty / Save-Lifecyle, если вы используете ViewPart?».

Ответ довольно прост, единственное, что вам нужно сделать, это заставить ваш ViewPart реализовать ISaveablePart2, и тогда ваше представление станет частью жизненного цикла состояния рабочего места.

Части применения

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

  • ProjectAdminViewPart:
    это основная область пользовательского интерфейса, которая является подклассом ViewPart и зарегистрирована в рабочей среде с помощью views-Extension-Point.
  • ProjectExplorerPart:
    этот класс составляет левую часть приложения, показывая TreeViewer для выбора проекта, создания подпроектов,…. Внутренние части этой части более подробно описаны в этом посте позже.
  • ProjectFormAreaPart:
    этот класс составляет правую верхнюю часть, показывающую набор элементов управления вводом, которые действуют как подробные части ProjectExplorerPart . Внутренние части этой части подробно объясняются в части 4 этой серии блогов.
  • ProjectCommittersPart:
    этот класс составляет правую нижнюю часть, показывающую TableViewer . Внутренние части этой части подробно объясняются в части 5 этой серии блогов.

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

Восстановление информации о состоянии просмотра

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

Eclipse-RCP-Framework предоставляет возможность сделать это, используя IMemento для сохранения информации в запусках приложения, единственное, что вам нужно сделать, это использовать API:

public class ProjectAdminViewPart
extends ViewPart implements ISaveablePart2
{
private float divider = 0.2f;
private SashForm sashForm;

@Override
public void init(final IViewSite site, IMemento memento)
throws PartInitException
{
super.init(site, memento);
if (memento != null
&& memento.getFloat(DIVIDER_KEY) != null)
{
divider = memento.getFloat(DIVIDER_KEY);
}

listener = new PartListenerImpl(site);
site.getPage().addPartListener(listener);
}

@Override
public void saveState(IMemento memento)
{
super.saveState(memento);
int total = sashForm.getWeights()[0]
+ sashForm.getWeights()[1];
memento.putFloat(
DIVIDER_KEY,
sashForm.getWeights()[0] * 1.f / total);
}

@Override
public void createPartControl(Composite parent)
{
// ...
sashForm = new SashForm(parent, SWT.HORIZONTAL);
// ...
int left = (int)(100 * divider);
sashForm.setWeights(new int []{ left, 100 - left });
}
}

Избегайте утечки слушателя

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

Исключением из этого являются наблюдаемые, созданные для SWT-виджетов, потому что они располагаются при удалении SWT-виджета, но для всех остальных это правило — тот, кто его создал, должен его утилизировать. Привязка данных предоставляет класс с именем ObservablesManager, который помогает вам в этом.

В 3.5 EMF-Databinding добавлен новый API-интерфейс в ObservableManager, который сделает сбор данных-наблюдений очень простым, но, по-видимому, ошибка была обнаружена очень поздно в цикле выпуска, поэтому API в настоящее время не используется. Рассматриваемый API используется следующим образом:

ObservablesManager mgr = new ObservablesManager();
mgr.runAndCollect(new Runnable()
{
public void run()
{
// your code which creates observables
}
});

Хотя API-интерфейс по умолчанию не работает для привязки EMF-данных, мы можем добавить простое исправление, чтобы API по крайней мере собирал все IEMFObservable, чтобы нам приходилось иметь дело только с такими вещами, как ComputedValue и такими вещами, потому что, как указано выше, SWT-Observables располагаются, когда SWT-Widget утилизируется.

Итак, наш код выглядит так:

  @Override
public void createPartControl(Composite parent)
{
// ...
/*
* Track the creation of observables so that we
* don't leak listeners when the view part is closed
*/
mgr = new EMFObservablesManager();
defaultMgr = new ObservablesManager();
mgr.runAndCollect(new Runnable()
{

public void run()
{
projectExplorer = new ProjectExplorerPart(
getViewSite(),
sashForm,
toolkit,
resource.getFoundation(),
defaultMgr);

projectDataForm = new ProjectFormAreaPart(
getViewSite(),
sashForm,
toolkit,
resource,
defaultMgr,
projectExplorer.getProjectObservable());
}
});
// ...
}

Настройка TreeViewer
Достаточно об общих вещах, и давайте теперь сосредоточимся на основной теме этого блога.
Project Explorer
Хотя я думаю, что большинство из вас знает, как настроить стандартный TreeViewer, вот краткое описание:

  1. Установите CellLabelProvider : отвечает за перевод объекта модели в визуальное представление (текст, цвета, изображение и т. Д.). CellLabelProvider-API был представлен в версии 3.3, а поверх него JFace предоставляет множество интересных новых функций, таких как StyledText-Support (разные шрифты и цвета в одной ячейке) и ToolTip-Support. Стоит пересмотреть старый код и заменить LabelProvider через подклассы CellLabelProvider.
  2. Установите ITreeContentProvider : отвечает за перевод входных данных в древовидную структуру.
  3. Установить вход: любой вход, который может обработать ITreeContentProvider

Создание провайдера контента
Eclipse-RCP-Platform поставляется с плагином org.eclipse.jface.databinding, который обеспечивает поддержку связывания данных и классы реализации для элементов управления SWT и JFace. Одним из этих классов является ObservableListTreeContentProvider, который является классом, реализующим ITreeContentProvider, и позволяет довольно легко настроить наблюдаемое дерево.

ObservableListTreeContentProvider не может сделать всю работу самостоятельно , но ожидает от нас , чтобы обеспечить его вспомогательные классы:

  • TreeFactory : отвечает за создание списков IObservableList для наблюдения дочерних элементов триода
  • TreeStructureAdvisor : отвечает за предоставление информации о том, как получить доступ к родительскому элементу модели элемента, и помогает принять решение, если элемент имеет дочерние элементы.

Глядя на рисунок выше, мы видим, что наша структура является немного виртуальной, когда мы сравниваем ее с экземпляром домена, где мы увидим только отношение проект-подпроекты на первый взгляд.
Класс проекта
Однако наш просмотрщик отображает различные многозначные объекты как дочерние элементы в дереве:

  • подпроекты, элементы которых имеют тип Project
  • коммиттерсы, элементы которых имеют тип CommitterShip

Отображение различных типов объектов не было поддержано в 3.4 и является новой функцией в новой реализации привязки данных, а также возможностью объединить 2 многозначных объекта в один наблюдаемый список, что лично для меня является одной из самых крутых функций в свойствах -API.

  private static class TreeFactoryImpl
implements IObservableFactory
{
private IEMFListProperty multi = EMFProperties.multiList(
ProjectPackage.Literals.PROJECT__SUBPROJECTS,
ProjectPackage.Literals.PROJECT__COMMITTERS);

public IObservable createObservable(final Object target)
{
if (target instanceof IObservableList)
{
return (IObservable)target;
}
else if (target instanceof Project)
{
return multi.observe(target);
}

return null;
}
}

private static class TreeStructureAdvisorImpl
extends TreeStructureAdvisor
{
@Override
public Object getParent(Object element)
{
if (element instanceof Project)
{
return ((Project)element).getParent();
}

return null;
}

@Override
public Boolean hasChildren(Object element)
{
if (element instanceof Project
&& (
((Project)element).getCommitters().size() > 0
|| ((Project)element).getSubprojects().size() > 0
)
)
{
return Boolean.TRUE;
}
return super.hasChildren(element);
}
}

и используется как это

private TreeViewer init(Composite parent,
Foundation foundation)
{
TreeViewer viewer = new TreeViewer(parent);
ObservableListTreeContentProvider cp =
new ObservableListTreeContentProvider(
new TreeFactoryImpl(),
new TreeStructureAdvisorImpl()
);
viewer.setContentProvider(cp);

// rest of viewer setup
}

Создание CellLabelProvider

Чтобы получить такое красивое дерево, мы не можем использовать простой CellLabelProvider (например, ColumnLabelProvider — его простая реализация), но нам нужно одно с большим количеством функций. Другой реализацией CellLabelProvider является StyledCellLabelProvider, который использует владелец-рисование для рисования StyledText-Strings в дереве.

JFace-Databinding не предоставляет реализацию StyledCellLabelProvider из коробки, поэтому мне пришлось написать свою собственную, но это не так уж сложно. Единственное, что нужно сделать, это подключить слушатель к IObservableMap (s), чтобы наблюдать атрибуты всех элементов дерева и обновлять средство просмотра, если один из них изменяется.

private class TreeLabelProviderImpl
extends StyledCellLabelProvider
{
private IMapChangeListener mapChangeListener =
new IMapChangeListener()
{
public void handleMapChange(MapChangeEvent event)
{
Set<?> affectedElements =
event.diff.getChangedKeys();
if (!affectedElements.isEmpty())
{
LabelProviderChangedEvent newEvent =
new LabelProviderChangedEvent(
TreeLabelProviderImpl.this,
affectedElements.toArray()
);
fireLabelProviderChanged(newEvent);
}
}
};

public TreeLabelProviderImpl(
IObservableMap... attributeMaps)
{
for (int i = 0; i < attributeMaps.length; i++)
{
attributeMaps[i].addMapChangeListener(
mapChangeListener
);
}
}

@Override
public String getToolTipText(Object element)
{
return "#dummy#";
}

@Override
public void update(ViewerCell cell)
{
if (cell.getElement() instanceof Project)
{
Project p = (Project)cell.getElement();

StyledString styledString = new StyledString(
p.getShortname()!=null ? p.getShortname():"*noname*",
null
);
String decoration = " (" +
p.getCommitters().size() + " Committers)";
styledString.append(
decoration,
StyledString.COUNTER_STYLER
);
cell.setText(styledString.getString());
cell.setImage(projectImage);
cell.setStyleRanges(styledString.getStyleRanges());
}
else if (cell.getElement() instanceof CommitterShip)
{
Person p = (
(CommitterShip)cell.getElement()
).getPerson();
String value = "*noname*";
if (p != null)
{
value = p.getLastname() + ", " + p.getFirstname();
}
StyledString styledString = new StyledString(
value, null);
cell.setText(styledString.getString());
cell.setForeground(
cell.getControl().getDisplay().getSystemColor(
SWT.COLOR_DARK_GRAY
)
);
cell.setImage(committerImage);
cell.setStyleRanges(styledString.getStyleRanges());
}
}
}

и используется как это

private TreeViewer init(Composite parent,
Foundation foundation)
{
TreeViewer viewer = new TreeViewer(parent);
ObservableListTreeContentProvider cp =
new ObservableListTreeContentProvider(
new TreeFactoryImpl(),
new TreeStructureAdvisorImpl()
);
viewer.setContentProvider(cp);

IObservableSet set = cp.getKnownElements();
IObservableMap[] map = new IObservableMap [4];

map[0] = EMFProperties.value(
ProjectPackage.Literals.PROJECT__SHORTNAME
).observeDetail(set);

map[1] = EMFProperties.value(
ProjectPackage.Literals.PROJECT__COMMITTERS
).observeDetail(set);

map[2] = EMFProperties.value(
FeaturePath.fromList(
ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
ProjectPackage.Literals.PERSON__FIRSTNAME)
).observeDetail(set);

map[3] = EMFProperties.value(
FeaturePath.fromList(
ProjectPackage.Literals.COMMITTER_SHIP__PERSON,
ProjectPackage.Literals.PERSON__LASTNAME)
).observeDetail(set);

viewer.setLabelProvider(new TreeLabelProviderImpl(map));
// Further viewer setup

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

IEMFListProperty projects = EMFProperties.list(
ProjectPackage.Literals.FOUNDATION__PROJECTS
);
viewer.setInput(projects.observe(foundation));

В ProjectExplorerPart содержится еще больше кода, например настраиваемый ColumnViewerToolTipSupport для создания приятных на вид подсказок при наведении на элементы дерева, а также некоторый RCP-код, позволяющий plugin.xml добавлять контекстное меню, но это стандартный код JFace и RCP.

От http://tomsondev.bestsolution.at

 

 

d