Статьи

От боли к выгоде: Swing и платформа NetBeans в реальном мире

Разработка богатых настольных Java-приложений исторически была болезненным опытом; но теперь у вас есть новые усовершенствованные компоненты Swing и полная платформа приложений на платформе NetBeans. Посмотрите, как платформа NetBeans значительно упростила разработку сложного настольного приложения и какие уроки были извлечены при его создании.

Как и большинство людей, работавших с Java с самого начала, мой первый опыт работы с этой технологией был связан с (небольшими) настольными приложениями: некоторыми исследованиями в моей докторантуре и простой панелью управления для медицинского колл-центра. Это был век AWT, и ты действительно не мог сделать намного больше. Поэтому вскоре я перешел на сторону сервера, где все оказалось более надежным и перспективным. Они действительно были, и я остался там надолго и стал архитектором J2EE.

Несколько лет спустя меня снова привлекла настольная система из-за растущей страсти к цифровой фотографии. Я все еще сталкивался со многими проблемами, но незадолго до того, как я бросил полотенце, Sun и сообщество разработчиков пришли на помощь с SwingLabs , java.net и новыми версиями платформы NetBeans. Теперь я наслаждаюсь (возможно) многообещающим приложением с открытым исходным кодом — blueMarine — которое основано на платформе NetBeans .

В этой статье я расскажу вам больше об истории BlueMarine и рассмотрю некоторые основные API-интерфейсы NetBeans . Я покажу, как эти API используются и настраиваются, и покажу, с какими проблемами я столкнулся и как они были решены. Если вы мало знаете о платформе NetBeans и занимаетесь разработкой настольных систем, думаю, вам понравится эта статья, которая переиздана из журнала NetBeans.

Содержание

Начало

Первый раз, когда я написал некоторый Java-код для управления моими фотографиями, был около 2001 года, после того, как мне стало скучно с таблицей OpenOffice, которую я использовал. Я экспортировал все в XML и с помощью XSLT-преобразования определил свой собственный формат базы данных, которым управлял очень простой графический интерфейс на основе Swing.

Летом 2003 года я совершил «большой прыжок» в мир цифровых камер, купив Nikon D100 (профессиональную зеркальную фотокамеру). Это было одно из самых жарких летнего столетия в Италии, поэтому я был вынужден свести к минимуму количество фотопутешествий: прогулка на улице была просто болью. Будучи вынужденным оставаться дома, хотя в расслабляющей обстановке сельской местности Тосканы я провел большую часть своего отпуска, изучая формат NEF .

NEF — это «формат файла RAW», который в то время в основном не имел документов. Формат файла RAW хранит необработанные данные прямо с датчика CCD камеры и требует обработки перед преобразованием в качественное изображение; эту обработку часто считают цифровым аналогом старой фотолаборатории мокрой темной комнаты. Никогда не владея влажной темной комнатой, я был заинтригован возможностью «цифровой разработки» моих фотографий и начал писать для этого некоторый Java-код.

В конце лета я создал простой миниатюрный навигатор с возможностью показа моих собственных фотографий — и родился BlueMarine. Год спустя у проекта появилась возможность отмечать фотографии с помощью каталога и публиковать галереи в Интернете.

Однако меня раздражал тот факт, что для выполнения таких задач, как редактирование, печать, каталогизация, архивирование и веб-публикация, мне требовалось более одного программного обеспечения. Поэтому я приступил к реализации всего этого рабочего процесса в одном приложении. Кроме того, я решил, что пришло время публично выпустить blueMarine, и поэтому первый альфа-релиз вышел на SourceForge под лицензией GPL (позже был изменен на Apache 2.0). Вы можете увидеть одну из первых версий на рисунке 1 .

Рисунок 1. Старое главное окно blueMarine, разработанное на простом Swing.

Меня подтолкнула другая сила: задача попробовать Java для обработки цифровых изображений на настольном компьютере. Для меня уже было очевидно, что Java хороша для манипулирования научными изображениями; Одним из примеров было то, что инженеры НАСА успешно использовали JAI, передовой API визуализации. Но как насчет обработки рабочего стола для случайного фотографа? Я всегда стремился продемонстрировать, что Java хорош для широкого круга приложений, так как я начал работать консультантом по Java более десяти лет назад.

Разочарование

Несмотря на первоначальный энтузиазм, в конце 2005 года я был довольно разочарован проектом. Производительность не была большой проблемой, но я столкнулся с трудностями при разработке многофункционального графического приложения с использованием простого Swing. Swing — отличный API, но когда вы начнете использовать его для создания сложного приложения, вы обнаружите, что еще есть что добавить.

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

До недавнего времени было мало библиотек с открытым исходным кодом, занимающихся такими проблемами, и большинство из них были неудовлетворительными и громоздкими для интеграции. Были также ранние выпуски NetBeans, но я был недоволен их производительностью. Eclipse и SWT были вариантом, но я решил, что на самом деле я не собираюсь изучать полностью альтернативный и нестандартный API с очень низким ROI обучения и громоздким способом интеграции с Swing.

Подводя итог, я серьезно думал о том, чтобы отказаться от blueMarine — может быть, Java еще не была готова к разработке десктопов.

Ренессанс

Однако было несколько одновременных событий, которые спасли проект: мое участие в JavaPolis в конце 2005 года и выпуск NetBeans 5.0 в начале 2006 года.

В JavaPolis я вдохнул атмосферу сообщества, о которой больше всего забыл (прошло три года с момента моего последнего JavaOne). Это возродило мой энтузиазм, который был подкреплен презентацией Ромена Гая, показывающей, как с помощью Swing можно создавать эффективные графические интерфейсы. Я начал просматривать блог Ромена и переходя по ссылкам, которые я получил, к другим блогам, таким как Джошуа Мариначчи, и оттуда ко всем материалам java.net и JavaDesktop. Я обнаружил, что в Swing появилось много нового интереса; Качественные компоненты Swing, такие как SwingLabs, классные демонстрации — много материала, который я мог бы использовать. Но мне все еще нужна платформа.

Через несколько недель вышел NetBeans 5.0. Новая версия выглядела так, словно наконец исправила традиционные проблемы платформы NetBeans, поэтому я решил попробовать. Я начал разбирать blueMarine, извлекать только код изображения и перепроектировать его для использования платформы NetBeans. Через несколько месяцев первые сборки раннего доступа были готовы к поставке, и я начал использовать этот инструмент для собственного управления фотографиями. Между тем, переход с нуля с моего бывшего PPC Apple iBook на новый Intel MacBook Pro стал явным признаком того, что мой выбор был верным.

Сегодня я работаю над тем, чтобы сделать новый blueMarine стабильным и удобным. Доступны новые ранние сборки доступа, и я провожу необходимые тесты качества (полная модернизация явно нарушила стабильность предыдущего выпуска; это была цена, которую нужно было заплатить). На рисунке 2 показана версия blueMarine на платформе NetBeans.

Рисунок 2. Главное окно нового приложения blueMarine, разработанного на платформе NetBeans.

Сила платформы NetBeans

Теперь, когда вы знаете происхождение blueMarine, я кратко расскажу о многих преимуществах, которые NetBeans Platform и Swing принесли его разработке, а также о некоторых проблемах, с которыми я столкнулся, и о том, как я их решал.

Первый момент: это Swing!

Для меня тот факт, что платформа NetBeans основана на обычном Swing, является огромным преимуществом перед конкурентами, такими как Eclipse RCP. Если вы посмотрите вокруг, вы можете найти гораздо более широкий выбор компонентов Swing (включая «крутые», которые реализуют анимацию и эффекты).

Я конкретно осознал это преимущество в июне прошлого года, когда Джошуа Мариначчи выпустил источник компонента Aerith Swing, способного отображать карты, под названием JXMapViewer (Aerith была одной из самых популярных демонстраций на JavaOne 2006). Я ждал этого момента в течение нескольких недель, так как одной из особенностей blueMarine является геотегирование (привязка географического положения к каждой фотографии, чтобы их можно было показать на карте). Интеграция JXMapViewer в blueMarine потребовала всего нескольких часов работы; Вы можете увидеть результат на рисунке 3 . Выбор Swing был действительно полезным.

Рисунок 3. Вьюер карт с фотографиями с геотегами использует компонент JXMapViewer.

Модульная система

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

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

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

DataObjects, Nodes и ExplorerManager

ExplorerManager , Node и DataObject , вероятно, являются наиболееполезными API в платформе NetBeans. С DataObject вы можете реализовать свои специфичные для приложения сущности, которые отображаются в файл на диске. Например, основным объектом BlueMarine является PhotoDataObject , который представляет фотографию в базе данных.

В то время как объекты DataObject содержат все состояние и поведение ваших объектов, узлы могут быть связаны с ними для целей визуализации. Их также можно объединять различными способами (в виде коллекций или графиков). Платформа NetBeans предоставляет компоненты графического интерфейса, такие как таблицы и списки, которые могут использовать набор объектов Node в качестве своей модели; Среди наиболее распространенных — BeanTreeView , ContextTreeView и ListView . Наконец, ExplorerManager контролирует выбор и древовидную навигацию.

Да, это не что иное, как сложная реализация MVC (см. Рисунок 4 ), но реализация, в которой для вас уже написано много стандартного кода. Например, API-интерфейсы платформы NetBeans заботятся о таких вещах, как поддержка перетаскивания (с такими мелкими деталями, как визуальные подсказки во время операции перетаскивания), операции вырезания и вставки и контекстные меню.

Рисунок 4. Компоненты NetBeans MVC.

API поиска

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

Например, давайте предположим, что у нас есть модуль it.tidalwave.catalog.CatalogImpl (реализующий интерфейс it.tidalwave.catalog.Catalog ). Сначала вы «регистрируете» модуль, помещая специальный файл в путь к классам в каталоге META-INF / services . Файл должен быть назван в честь реализованного интерфейса и содержать имя класса реализации. Всякий раз, когда модуль загружается, платформа NetBeans сканирует эти специальные файлы, создает экземпляры объектов и помещает их в объект Lookup «по умолчанию» , откуда любой другой фрагмент кода может впоследствии извлечь его.

Обычно я обертываю код поиска с помощью шаблона Locator, как показано в листинге 1 , а затем выполняю поиск следующим образом:

Каталог-каталог = CatalogLocator.findCatalog ();

Листинг 1. Локатор, который использует класс Lookup.

public class CatalogLocator {

public static final synchronized Catalog findCatalog() {
final Lookup lookup = Lookup.getDefault();
final Catalog catalog = (Catalog)lookup.lookup(Catalog.class);
if (catalog == null) {
throw new RuntimeException("Cannot lookup Catalog");
}
return catalog;
}

}

Этот механизм не только поддерживает разделение, но и создает подключаемое поведение. Например, давайте посмотрим на возможность рендеринга карты в blueMarine. Как вы, возможно, знаете, вокруг много поставщиков карт, таких как Google Maps, Microsoft Visual Earth, NASA и других. Я хочу, чтобы люди могли расширять blueMarine, добавляя в него новый код для работы с дополнительными поставщиками карт. Решение простое: сначала определите интерфейс — MapProvider — который объявляет все необходимые возможности, затем напишите альтернативные реализации, каждая в своем собственном модуле, например, GoogleMapProvider , MicrosoftVisualEarthMapProvider и т. Д.

Каждая реализация регистрируется в экземпляре Lookup по умолчанию с использованием одного и того же «имени»: MapProvider ( допускается несколько зарегистрированных объектов для одного и того же имени). Теперь поиск объектов становится легкой задачей. Пример показан в листинге 2 . Вы можете добавить модули с новыми поставщиками карт, и код поиска найдет их во время выполнения.

Листинг 2. Получение зарегистрированных объектов.

private DefaultComboBoxModel  mapProvidersModel = new DefaultComboBoxModel();

private void searchMapProviders() {
Result result = Lookup.getDefault().lookup(new Template(MapProvider.class));
for (Object provider : result.allInstances()) {
  mapProvidersModel.addElement(provider);
}
}

Lookup API также способствует развязке

Экземпляр поиска по умолчанию также содержит текущий набор выбранных объектов Node . Это позволяет разработать эффективный и слабо связанный механизм также для межмодульной связи. Он основан на шаблоне Observer: некоторые модули публикуют их выбор узлов по умолчанию в Lookup , в то время как остальные слушают изменения. А благодаря реализации некоторой фильтрации, связанной с видом информации, связанной с измененными узлами, мы получаем шаблон проектирования «Публикация / подписка».

Например, в blueMarine есть много способов навигации по базе данных фотографий и отображения набора миниатюр — путем просмотра папок, календаря, фотографий с общим тегом, фотографий в той же галерее и т. Д. «Проводник» модули просто опубликовать выбор узла s связан с PhotoDataObject s по умолчанию подстановок ; Thumbnail Viewer получает уведомления и обновляет себя соответствующим образом (см. рисунок 5 ).

Рисунок 5. Роль объектов Node для межмодульного взаимодействия.

Компоненты проводника не зависят от Thumbnail Viewer. На самом деле они полностью отделены от него (здесь мы применяем Inversion of Control). С этим дизайном я могу добавить столько исследователей, сколько захочу, даже в независимых модулях, которые могут быть установлены как дополнения. Я также могу легко добавить новых зрителей. Например, мне удалось включить средство просмотра киноленты в качестве полностью отделенного компонента, который можно использовать сам по себе или вместе с исходным средством просмотра эскизов (см. Рисунки 6 и 7 ).

Рис. 6. Средство просмотра миниатюр и средство просмотра кинопленки должны показывать одинаковые узлы — с одинаковым выбором.

Рисунок 7. Несколько синхронизированных представлений реализуются простым прослушиванием изменений одного и того же узла.

Lookup API имеет много других применений, как многие типы объектов (включая Node s сам) имеет свой собственный локальный Уточняющий экземпляр. Я показал только «верхушку айсберга» здесь.

FileSystem API

Java предлагает простой подход к управлению файлами через класс java.io.File , который упаковывает имя файла и обеспечивает доступ к атрибутам, основные операции и список каталогов. Этот подход действительно плох, так как нет концепции файловой системы; Более того, объекты File связаны с локальными физическими файлами / каталогами. Что если вам нужно представить виртуальное или удаленное дерево каталогов? Я столкнулся с этой проблемой несколько лет назад, и в конце концов я решил ее, расширив подклассы File, а не аккуратный дизайн, хотя он и работал.

API-интерфейс FileSystem NetBeans восполняет этот пробел. Существует целый набор классов FileSystem, которые можно использовать для представления различных типов файловых систем: локальных, удаленных или даже виртуальных. (Внутренние параметры конфигурации NetBeans, включая параметры вашего пользовательского кода, хранятся в такой виртуальной файловой системе.) Единственное предостережение заключается в том, что вы должны использовать специфичный для NetBeans экземпляр FileObject вместо File , но преобразование одного в другое легко:

FileObject fileObject = …;
Файл файл = …;
file = FileUtil.toFile (fileObject);
fileObject = FileUtil.toFileObject (file);

blueMarine has a strict requirement for managing files. Each file must be associated with a unique id stored in a local database. The id is later used to build relationships between each photo and other entities such as thumbnails, metadata, galleries, editing settings, and so on. It’s a rather common design for this kind of application, allowing you to later move or rename photos without too many changes in the database. It also lets you work with remote volumes such as external disks and DVDs. In other words, even when you don’t have the file available in the system, you can look at its thumbnails and metadata.

A good starting point for tweaking the filesystem management is the LocalFileSystem class, which represents a tree of files with a single root (for systems with multi-root hierarchies like Windows you just need to put a few LocalFileSystem objects together).

The LocalFileSystem class includes AbstractFileSystem.Attr and AbstractFileSystem.List. Attr lets you manipulate a set of attributes for each FileObject, and List lets you customize a directory listing. (Attributes are just simple properties which are bound to a FileObject and can be manipulated with getters and setters.)

I started by writing a simple subclass of LocalFileSystem that installs decorators of Attr and List, as shown in Listing 3. The AttrDecorator class retrieves the unique id for each file path (the FileIndexer is just a sort of DAO for this data), and makes it available as a special attribute of FileObject (ID_ATTRIBUTE). The code is shown in Listing 4.

Listing 3. Plugging decorators into the LocalFileSystem class.

class LocalIndexedFileSystem extends LocalFileSystem {
public LocalIndexedFileSystem() {
attr = new AttrDecorator(attr, this);
list = new ListDecorator(list, this);
}
}

Listing 4. Retrieving registered objects.

class AttrDecorator implements AbstractFileSystem.Attr {

private static final FileIndexer fileIndexer = FileIndexerLocator.findFileIndexer();
private AbstractFileSystem.Attr peer;
private LocalIndexedFileSystem fileSystem;

public AttrDecorator(AbstractFileSystem.Attr peer, LocalIndexedFileSystem fileSystem) {
this.peer = peer;
this.fileSystem = fileSystem;
}

public Object readAttribute (String path, String name) {
if (IndexedFileSystem.ID_ATTRIBUTE.equals(name)) {
String path2 = fileSystem.findCompletePath(path);
Serializable id = fileIndexer.findIdByPath(path2);
if (id == null) {
fileIndexer.createIndexing(path2, false);
id = fileIndexer.findIdByPath(path2);
}
return id;
}
else {
return peer.readAttribute(path, name);
}
}

...
...
...

}

While AttrDecorator would be enough to satisfy the functional specs, there’s still the problem of batch loading. The readAttribute() method would be called quite randomly, thus preventing any effective batch policy (FileIndexer is able to do batching by lazy loading, but to be effective it needs to have a good number of entries to batch!).

Here ListDecorator helps us, as it intercepts children files after they are listed from a parent directory (see Listing 5). Calling createIndexing() immediately on the set of listed files allows the FileIndexer to batch the retrieval of their ids.

Listing 5. Decorating directory scanning.

class ListDecorator implements AbstractFileSystem.List {

private static final FileIndexer fileIndexer = FileIndexerLocator.findFileIndexer();
private AbstractFileSystem.List peer;
private LocalIndexedFileSystem fileSystem;

public ListDecorator (AbstractFileSystem.List peer, LocalIndexedFileSystem fileSystem)
{
this.peer = peer;
this.fileSystem = fileSystem;
}

public String[] children (String path) {
String[] result = peer.children(path);
if (!fileSystem.disableChildrenIndexing) {
String path2 = fileSystem.findCompletePath(path);
if (result != null) {
for (String child : result) {
fileIndexer.createIndexing(path2 + child, true);
}
}
}
return result;
}

}

Actions and Menus

Actions and Menus (together with auxiliary components such as toolbars) are the main facilities for interacting with the user. Swing provides basic support for them, but you soon realize that this is not enough, especially if you are designing a modular application.

Menus are organized hierarchically and grouped according to intuitive criteria. So a pluggable module will need to place its own menu items in the proper places (for instance, under a global “Edit” or “View” item in the menu bar), and possibly respect some meaningful sequence (e.g. “menu items of module C should appear between those of modules A and B”). Also you might like to introduce menu dividers to group some menu items together.

Swing actions can be enabled and disabled with a simple attribute change; but the implementation of the decision whether to enable or disable them is up to you. Often this is done by a special method that computes the states of a set of actions and is invoked after any change made by the user, as in:  

private void setEnablementStatus() {
myAction1.setEnabled(/* condition 1 */);
myAction2.setEnabled(/* condition 2 */);
...
}

This approach works, but it’s neither modular nor easily maintainable. And  one must consider that in most cases the boolean conditions (condition 1, 2, etc. in the previous code) are just a function of the set of objects currently selected by the user – e.g., you can run “Edit” or “Print” only if a photo is selected.

Managing Menus and Actions with plain Swing in a clever way doesn’t require rocket science, but you’ll get a headache if it needs to be done from scratch for a sophisticated, modularized application. Fortunately, the NetBeans Platform also helps you in this area.

First, the NetBeans Platform provides richer classes than Swing’s Action. Some of the most commonly used are:

  • NodeAction – A generic action that changes state when a new set of Nodes is selected. The programmer must subclass it and override an enable(Node[]) method, which evaluates the proper boolean expression for activating the action.
  • CookieAction – This is an action whose state depends on the currently selected Node, and on it being bound to a given object (usually a specific DataObject). It also deals with different selection modes such as “exactly one”, “at least one”, etc.

  After having implemented your action using the proper class, you declare it in your module’s layer.xml, which acts as a generic configuration file (it models a “virtual file system” structured as the contained XML DOM).

Note: Usually you don’t have to do this manually: the NetBeans IDE offers a “New action” wizard that asks for the required information and both generates skeleton Java code and updates the relevant part of layer.xml. In fact, most of the XML in the following examples can be generated or manipulated through the IDE.

This approach works for both actions that should appear on contextual menus (see Figure 8) and for actions that need to be attached to a “regular” menu. In layer.xml you can also declare toolbars, where groups of buttons are logically bound to actions, and define keyboard shortcuts.

Figure 8. Each thumbnail is a rendered Node object bound to a PhotoDataObject and can pop up a menu with contextual actions. The grid arrangement and the look of thumbnails are implemented by means of a customized ListView.

The Window System API

The NetBeans Platform provides a specific windowing component named TopComponent. This component models a rectangular portion of the main window, which can be resized and docked in different areas of the screen (these areas are called “modes”). For instance, the Explorer mode is a vertical column at the left side; the Properties mode is a vertical column at the right side; the Editor mode is the remaining space at the center (see Figure 9).

Figure 9. Modes in the main window of blueMarine – “strip” is a custom mode; each mode contains a TopComponent.

Also, a TopComponent can be activated or deactivated, has its own mechanism for keeping persistent state (which is automatically restored on restart), and can request attention by flashing its tab.

Docking areas can be resized with the mouse, or programmatically. You can assign TopComponents to different areas by dragging them with the mouse or through code. You can define your own docking areas as well. For instance, I needed a component called “Film Strip” that should be placed at the bottom of the window. So I defined a docking area called “strip” and bound the Film Strip to it (see Figure 9 again).

While this flexibility is great for some types of applications, such as an IDE or a CAD system, so much control could be distracting to some classes of users. For blueMarine I prefer not to have such flexible docking: a fixed scheme is used, offering just a bit of control through menu commands that let you swap the components in the Explorer and Properties modes.

The tabs and the control code which allows docking with the mouse has been removed in blueMarine by specifying a special TabDisplayerUI (the visual component for each mode). I implemented programmatic control with the code in Listing 6.

Listing 6. Programmatic component docking.

    TopComponent topComponent = ...;
String newMode = "explorer";
Mode targetMode = WindowManager.getDefault().findMode(newMode);

if (targetMode != null) {
component.close();
targetMode.dockInto(component);
component.open();
component.requestVisible();
}

Note: Indeed it was not hard at all to implement the TabDisplayerUI trick, but only because I discovered the solution on Geertjan Wielenga’s blog; without that help it would have taken much longer. I found that programmers can enjoy excellent support from the NetBeans community, both in the mailing list and in the evangelists’ blogs – I recommend you to bookmark these!

The Look & Feel

The predefined Java look and feels are getting better with each JDK release, but sometimes you need a special LAF. For instance, dealing with photography you need a clean GUI that doesn’t distract the user, and a darkish theme (where all the used colors are rigorously shades of gray), so as not to disturb a correct perception of colors.

Since JDK 1.4, the UIManager class allows you to plug different look and feels with minimal or no impact on existing code. As the class is a part of the standard Swing API, there are a lot of compatible LAFs that can be easily plugged into your app.

If you find a look and feel that you like, you can install it into NetBeans (and of course into your NetBeans Platform application) with a simple command-line switch:  

—look-and-feel <name of the L&F class>

After some tests, I decided to keep the native look and feel for every piece of the GUI except for the main window, where I just changed the component colors (Mac OS X is a special case; see below). The typical blueMarine look and feel is illustrated in Figure 10.

Figure 10. Using a dark color scheme in the main window and a regular scheme in popups.

As you know, changing the colors of a Swing component is usually a matter of c.setForeground() and c.setBackground(). Since the NetBeans Platform is Swing-based, things aren’t much different. But there are a few exceptions. For instance, these standard methods don’t work on ListView (one of the most used view components for Node objects). In blueMarine, this was solved with the code in Listing 7, which first retrieves the inner JList and then changes its properties as needed. Similar code works with tree-based components (which share the same problem).

Listing 7. An enhanced ListView.

public class EnhancedListView extends ListView {

protected JList jList;

@Override
protected JList createList() {
jList = super.createList();
jList.setOpaque(isOpaque());
jList.setBackground(getBackground());
jList.setForeground(getForeground());
return jList;
}

@Override
public void setBackground (Color color) {
super.setBackground(color);
if (jList != null) {
jList.setBackground(color);
}
}

@Override
public void setForeground (Color color){
super.setForeground(color);
if (jList != null) {
jList.setForeground(color);
}
}

@Override
public void setOpaque (boolean opaque){
super.setOpaque(opaque);
if (jList != null) {
jList.setOpaque(opaque);
}
}

}

I found another problem with tree-based components: even with the code shown before, tree cells were rendered in black and white. Again, inspecting the sources, it was easy to find the cause: NetBeans’ trees usually have a special cell renderer which does many things, such as supporting HTML rendering (so you can use multiple text styles); the cell renderer also chooses a color scheme that contrasts well between the foreground and the background. This is a clever idea in most cases, but not when you want to fine-tune your colors. The workaround was implemented by the few lines shown in Listing 8. Here’s how you install the patched renderer:

PatchedNodeRenderer nodeRenderer = new PatchedNodeRenderer(tree.getCellRenderer());
tree.setCellRenderer(nodeRenderer);

Listing 8. A patched cell renderer for controlling colors in JTree’s.

class PatchedNodeRenderer extends DefaultTreeCellRenderer {

private TreeCellRenderer peer;

public PatchedNodeRenderer (final TreeCellRenderer peer) {
this.peer = peer;
}

@Override
public Component getTreeCellRendererComponent (final JTree jTree,
final Object object, final boolean selected, final boolean expanded,
final boolean leaf, final int row, final boolean hasFocus)
{

final Component component = peer.getTreeCellRendererComponent(
jTree, object, selected, expanded, leaf, row, hasFocus);
component.setBackground(
selected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor());
component.setForeground(
selected ? getTextSelectionColor() : getTextNonSelectionColor());
return component;
}

}

 

Regarding the look and feel, Mac OS X raised some particular issues. Mac users, who are really picky about aesthetics, noticed some time ago that even the Java implementation created by Apple does not accurately reproduce the operating system look and feel. This prompted the creation of a third-party product, named Quaqua, which fixes all the problems and implements a pixel-accurate Aqua GUI. (Actually the problems go beyond pixel accuracy: for instance the Java JFileChooser under Apple’s Mac OS LAF is terrible in comparison to the native one.) As Quaqua is a regular look and feel handled by the UIManager class, its integration in blueMarine was not a problem, with the exception of a few Quaqua issues that were quickly fixed by the project’s developer.

Other extensions needed

A little more work was necessary with ListView, since the thumbnail-rendering components required a custom renderer (to provide the “slide” look and for overlaying extra information). Also needed was a grid layout with a predefined cell size. See Figure 9.

The standard JList makes these things quite easy to achieve. It’s a matter of setting a few properties:  

jList.setCellRenderer(…);
jList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
jList.setFixedCellWidth(150);
jList.setFixedCellHeight(150);

But unfortunately NetBeans Platform developers left these methods out of ListView (much like the control of colors). With a similar workaround to the one used for the colors problem (i.e. accessing the inner JList), it was easy for me to add the missing methods to EnhancedListView, as you see in Listing 9.

Listing 9. New methods in ListView.

public class EnhancedListView extends ListView {

...
...
...

public void setCellRenderer (ListCellRenderer listCellRenderer) {
jList.setCellRenderer(listCellRenderer);
}

public void setFixedCellWidth (int size) {
jList.setFixedCellWidth(size);
}

public void setFixedCellHeight (int size) {
jList.setFixedCellHeight(size);
}

public void setLayoutOrientation (int layoutOrientation) {
jList.setLayoutOrientation(layoutOrientation);
}

}


Listing 10. Retrieving the Node object from a custom cell renderer.

import org.openide.explorer.view.Visualizer;

public class ThumbnailRenderer extends JComponent implements ListCellRenderer {

final public Component getListCellRendererComponent(
JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
this.hasFocus = cellHasFocus;
this.selected = isSelected;
this.index = index;
Node node = Visualizer.findNode(value);
thumbnail = (Thumbnail)node.getLookup().lookup(Thumbnail.class);
return this;
}

...
...
...

As a final point, custom ListCellRenderers must add an extra step through a utility class, named Visualizer, to retrieve the Node associated to the current object to render, as shown in Listing 10 (the usual JList approach, that is a cast applied to the value parameter, would not work).

Conclusions

Well, what can I say… blueMarine has survived its crisis, and its open-source “advertising” has already brought me a business opportunity for an application related to photo management (which reuses some of the back-end components of blueMarine – track my blogs if you want to know more about it). And without NetBeans, I would have dropped blueMarine and missed this opportunity.

What about blueMarine itself? Redesigning around the NetBeans Platform required some time, of course. It’s likely that you’ll need to deeply redesign an existing application if you want to take full advantage of all NetBeans Platform features (even though you could choose a lower-impact, more incremental way of working than I did). But I’m quite pleased with the results, and now I can add new features much faster than before.

To me, this means that Swing and the NetBeans Platform are now mature technologies for the desktop. I’m no longer forced to waste time reinventing the wheel. On the contrary: I can move very rapidly from an idea for my application, to an effective implementation.