Просто вывод на экран панели не представляет особого интереса. Наша основная цель показать пользователю данные о файлах и папках выбранной файловой системы. Ограничимся для начала отображением фиксированного пользовательского каталога (выбор нужной файловой системы и ресурсов для просмотра оставим на будущее).
К сожалению, проект Commons VFS не содержит готового подключаемого модуля eclipse, так что создадим его самостоятельно. На данном этапе это будет просто модуль “пустышка”, который является лишь контейнером для необходимых библиотек. В дальнейшем создадим точки расширения для обеспечения возможности создания собственных независимых реализаций виртуальной файловой системы.
Проект apache commons VFS (текущая версия 1.0-RC8) зависит от ряда дополнительных библиотек:
Таблица 4.1. Зависимость проекта apache commons VFS (текущая версия 1.0-RC8) от дополнительных библиотек:
Библиотека | Требуется для: |
---|---|
Jakarta Commons Logging версии 1.0.4 или выше | все |
Jakarta Commons Collections версии 3.1 | LRU Cache (опционально) |
Jakarta Commons Net версии 1.4.1 или выше | FTP |
Jakarta ORO версии 2.0.8 | |
Jakarta Commons Httpclient версии 2.0 | WebDAV HTTP, URI Utils |
jdom.org JDom . версии 1.0. Требуется только для webdav 2.2+ | WebDAV |
Jakarta Slide версии 2.2pre1 (20050629.002841) Требуется если будет использоваться RandomAccessContent с webdav | |
jCIFS версии 0.8.3 или выше | CIFS |
JSch версии 0.1.23 или выше | SFTP |
Commons Compress Nightly build 20050911 | tar, bz2 |
В простейшем случае можно собрать все требуемые библиотеки, сложить их в папку проекта и прописать зависимости в настройках проекта. Но модульная структура eclipse предполагает создание отдельных модулей для обеспечения повторного использования кода разными проектами. Причем, желательно создавать отдельные модули для каждой независимой части, в данном случае библиотеки. Разбивка на отдельные независимые модули, позволяет так же снизить затраты Интернет трафика при автоматическом обновлении приложения в случае выпуска новых версий модулей.
Процесс создания модулей на основе внешних jar файлов в Eclipse IDE автоматизирован (это не запрещает создание таких проектов вручную). Воспользуемся мастером автоматического создания проекта, вызвав меню “ File->New->Project->Plug-in development->Plug-in from existing JAR archives ”.
Во многих диалогах и формах выбора элементов из списка или деревьев с большим количеством элементов доступно поле фильтра отбора данных. Список допустимых значений автоматически фильтруется при наборе текста критерия фильтра, что позволяет быстро находить требуемые элементы.
Первым шагом создания проекта будет диалог выбора исходных JAR архивов (можно указать несколько файлов). Далее, будет предложено ввести наименование модуля и других реквизитов проекта.
Обращаем внимание на галочку “ Unzip the Jar archives into the project ”. Если данный пункт будет включен, то содержимое архивов будет распаковано, в противном случае архивы будут просто скопированы в каталог проекта. Первый вариант дает преимущество при развертывании (deployment) модуля, так как при этом создается упакованный в jar архив проекта. При втором варианте проект создается в виде каталога.
При “ручном” создании содержащих библиотеки проектов, для того чтобы данные jar архивы библиотек были доступны другим модулям, на страничке “ Runtime ” редактора манифеста следует указать список экспортируемых библиотек. На той же закладке редактора манифеста, в блоке " Exported packages " надо указать какие пакеты будут доступны извне. Для зависящих от других библиотек модулей надо дополнительно прописать список зависимостей в закладке “ Dependences ” редактора манифеста.
Панель навигатора файлов выступает в качестве “вида” шаблона MVC, а в качестве модели данных выступает виртуальная файловая система проекта Apache Commons VFS. Панель навигатора включает в себя компоненты org.eclipse.jface.viewers.TreeViewer для отображения дерева каталогов и org.eclipse.jface.viewers.TableViewer для отображения списка файлов. В дальнейшем реализуем еще и компоненту отображения текущего адреса.
Компоненты TreeViewer и TableViewer (вьюверы) предназначены для отображения структурированных данных (наследники абстрактного класса ContentViewer). Для отображения данных данными компонентами требуется создать классы посредники, которые будут являться связующим звеном между моделью и представлением. Для получения списка отображаемых объектов служит провайдер контента (content provider), а за формат отображения в понятном человеку виде отвечает провайдер меток (текста и изображений (label provider)).
Многие задаются вопросом: “Зачем разделять провайдеры контента и меток?”. Разработчики jface решили, что это позволяет повторно использовать программный код, так как провайдер контента обычно имеет аналогичную реализацию для разных вьюверов.
При указании текущего объекта вьюверу, или смене его на новый в методе setInput(Object input), вьювер запрашивает у провайдеров какую информацию и в каком виде отображать. Для сложных древовидных структур данных запрашивается так же информация о дочерних и родительских объектах.
Кроме предоставления информации об объектах модели провайдеры так же информируют вьюверы о том, что данные были изменены извне, и необходимо обновить отображение данных вьювером. В абстрактных классах вьюверов для этого зарезервированы специальные методы:
refresh - просто указывает на необходимость обновления данных. Это самая простая, но в то же время самая требуемая ресурсоемкая операция;
add , для указания, что был добавлен элемент или группа элементов;
remove , для указания, что был удален элемент или группа элементов;
update , для указания, что был изменен элемент. Опционально может быть указан список свойств, которые могут влиять на отображение, сортировку и фильтрацию.
Так как провайдеры относятся больше к уровню бизнес логики, то есть смысл выделить вновь создаваемые классы работы с виртуальной файловой системой в отдельный модуль расширения com.berdaflex.filearranger.vfs.
Создаем класс VfsTreeContentProvider реализующий интерфейс ITreeContentProvider. Метод getChildren(..) должен возвращать список дочерних объектов используемой модели данных, если они есть у текущего элемента. Воспользуемся готовым методом getChildren (..), доступным для объектов типа FileObject:
public Object[] getChildren(Object parentElement) { if ((parentElement != null) && (parentElement instanceof FileObject)) try { if (((FileObject) parentElement).getType().hasChildren()) { return ((FileObject) parentElement).getChildren(); } } catch (FileSystemException e) { . . . } return new Object[0]; }
Аналогичный метод есть и для получения родительского элемента:
public Object getParent(Object element) { if ((element != null) && (element instanceof FileObject)) { try { return ((FileObject) element).getParent(); } catch (FileSystemException e) { . . . } } return null; }
Для получения информации о наличии дочерних элементов так же есть аналогичный метод:
public boolean hasChildren(Object element) { if ((element != null) && (element instanceof FileObject)) { try { return ((FileObject) element).getType().hasChildren(); } catch (FileSystemException e) { . . . } } return false; }
Для получения списка элементов относительно корневого элемента воспользуемся реализованным методом получения дочерних элементов getChildren(..):
public Object[] getElements(Object inputElement) {
if (inputElement != null) {
return getChildren(inputElement);
}
return null;
}
Для реализации провайдера меток создадим наследник VfsTreeLabelProvider класса LabelProvider, который является базовой реализацией интерфейса ILabelProvider. На данном этапе метод получения изображения опустим:
public String getText(Object element) {
if ((element != null) && (element instanceof FileObject) {
return ((FileObject) element).getName().getBaseName();
} else {
element.toString();
}
}
Для простоты тестирования созданных провайдеров назначим их пока в методе создания панели проводника файлов, а точнее создадим метод инициализации и добавим его вызов:
private void createVfsExplorerComposite() { . . . initialize(); } private void initialize() { getSite().setSelectionProvider(vfsExplorerComposite.getTreeViewer()); try { VfsTreeContentProvider vfsTreeContentProvider = new VfsTreeContentProvider(); vfsExplorerComposite.getTreeViewer().setContentProvider( vfsTreeContentProvider); vfsExplorerComposite.getTreeViewer().setLabelProvider( new VfsTreeLabelProvider()); setInput(System.getProperty("user.home")); } catch (FileSystemException e) { . . . }
Для того, чтобы определить опорную точку (в данном случае это адрес в файловой системе) предназначен метод settInput(..). Для тестирования укажем пока жесткий путь к каталогу пользователя:
protected void setInput(String filePath) throws FileSystemException { FileSystemManager fsManager = VFS.getManager(); if (fsManager != null) { currentFileObject = fsManager.resolveFile(filePath); vfsExplorerComposite.getTreeViewer().setInput(currentFileObject); } else { . . . } }
Теперь можно пробовать запускать наше приложение. Как видно из рисунка наш проводник успешно отображает содержимое каталога.
Статья A. O. Van Emmenis из цикла “Using JFace and SWT in stand-alone mode” http://www-106.ibm.com/developerworks/opensource/library/os-ecgui1/
Для представления табличных данных в навигаторе файлов используется jface TableViewer.
Следующим этапом процесса связывания модели с представлением будет связь данных с табличным вьювером VfsTableView. Создадим провайдер контента, класс VfsTableContentProvider реализующий интерфейс ITableContentProvider. Интерес представляет реализация метода getElements(Object inputElement). Особенность реализации данного метода состоит в анализе наличия потомков у выбранного корневого элемента. Если потомков нет, то возвращается список элементов для родительского элемента:
public Object[] getElements(Object inputElement) {
if (inputElement instanceof FileObject) {
try {
if (((FileObject) inputElement).getType().hasChildren()) {
return getChildren(inputElement);
} else {
return getChildren(((FileObject) inputElement).getParent());
}
} catch (FileSystemException e) {
. . .
}
}
return getChildren(inputElement);
}
Для исключения дублирования кода введен вспомогательный метод поиска дочерних элементов getChildren():
private Object[] getChildren(Object parentElement) {
if (parentElement instanceof FileObject) {
try {
FileObject fileObject = (FileObject) parentElement;
if (fileObject.getType().hasChildren()) {
return fileObject.getChildren();
}
} catch (FileSystemException e) {
. . .
}
}
return new Object[0];
}
Провайдер меток, реализующий интерфейс ITableLabelProvider класс VfsTableLabelProvider для табличного представления будет несколько сложнее древовидного, так как на экран выводится несколько колонок свойств объекта. Для простоты построения прототипа, будем выводить фиксированный набор колонок: “Имя”, “Тип”, “Размер” и “Дата последнего изменения”. Для улучшения читабельности кода создадим отдельные методы для отображения значений каждого из атрибутов. Нумерация колонок метода getColumnText() начинается от 0.
public String getColumnText(Object element, int columnIndex) { if ((element != null) && (element instanceof FileObject)) { // name if (columnIndex == 0) { return getFileObjectName((FileObject) element); } // size if (columnIndex == 1) { return getFileObjectSize((FileObject) element); } // type if (columnIndex == 2) { return getFileObjectType((FileObject) element); } // date if (columnIndex == 3) { return getFileObjectDate((FileObject) element); } } return "???"; }
В связи с ограничениями операционной системы Windows нулевую колонку нельзя будет сортировать. В дальнейшем изменим реализацию данного метода, и будем использовать ее для вывода иконки.
Отображение имени файла реализовано простейшим способом:
private String getFileObjectName(FileObject fileObject) {
return fileObject.getName().getBaseName();
}
Реализация виртуальной файловой системы позволяет получать размер содержимого файлового объекта, так что данный метод тривиален:
private String getFileObjectSize(FileObject fileObject) {
try {
if (((FileObject) fileObject) != null) {
if (((FileObject) fileObject).getType().equals(FileType.FOLDER)) {
return "";
} else if (((FileObject) fileObject).getType().hasContent()) {
return "" + ((FileObject) fileObject).getContent().getSize();
}
}
} catch (FileSystemException e) {
. . .
}
return "???";
}
Реализация получения типа файла более интересна, так как здесь используется системный объект Program, при помощи которого мы пытаемся получить информацию о файловом объекте по его расширению:
private String getFileObjectType(FileObject fileObject) {
try {
if (fileObject != null) {
if (fileObject.getType().equals(FileType.FOLDER)) {
return "Folder";
} else {
if (fileObject.getType().equals(FileType.FILE)) {
int dotPos = fileObject.getName().getBaseName()
.lastIndexOf('.');
if (dotPos != -1) {
String extension = fileObject.getName()
.getBaseName().substring(dotPos);
Program program = Program.findProgram(extension);
if (program != null) {
return program.getName();
} else {
return extension.toUpperCase();
}
} else {
return "Unknown";
}
}
}
}
} catch (FileSystemException e) {
. . .
}
return "???";
}
Получение форматированной даты не представляет особых проблем. Воспользуемся стандартным DateFormat форматом. Возможность настройки отображаемого формата отложим на будущее.
private DateFormat dateFormat = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM); . . . private String getFileObjectDate(FileObject fileObject) { try { return dateFormat.format(new Date(fileObject.getContent() .getLastModifiedTime())); } catch (FileSystemException e) { . . . } return "???"; }
Для отображения колонок в панели проводника, требуется создать и назначить вьюверу колонки. За этот процесс будет отвечать метод setTableColumns():
private void setTableColumns() {
//Создаем колонки
TableColumn column = new TableColumn(vfsExplorerComposite
.getTableViewer().getTable(), SWT.LEFT);
column.setText("Name");
column.setWidth(150);
TableColumn columnSize = new TableColumn(vfsExplorerComposite
.getTableViewer().getTable(), SWT.LEFT);
columnSize.setText("Size");
columnSize.setWidth(100);
TableColumn columnType = new TableColumn(vfsExplorerComposite
.getTableViewer().getTable(), SWT.LEFT);
columnType.setText("Type");
columnType.setWidth(150);
TableColumn columnDate = new TableColumn(vfsExplorerComposite
.getTableViewer().getTable(), SWT.LEFT);
columnDate.setText("Date Modified");
columnDate.setWidth(150);
// назначим созданные колонки вьюверу
vfsExplorerComposite.getTableViewer().getTable().setHeaderVisible(true);
}
Осталось назначить провайдеры табличному вьюверу. А также создать обработчик изменений в дереве файловых объектов для обеспечения синхронных изменений в табличном вьювере. Метод инициализации примет вид:
private void initialize() { . . . vfsExplorerComposite.getTableViewer().setContentProvider( new VfsTableContentProvider()); vfsExplorerComposite.getTableViewer().setLabelProvider( new VfsTableLabelProvider()); setTableColumns(); setInput(System.getProperty("user.home")); vfsExplorerComposite.getTreeViewer().addSelectionChangedListener( new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection) event .getSelection(); vfsExplorerComposite.getTableViewer().setInput( selection.getFirstElement()); } }); } catch (FileSystemException e) { . . . } }
Связь данных с табличным вьювером VfsTableView теперь наглядно видна при запуске нашего проводника файлов. При навигации по дереву объектов в табличной форме автоматически отображается список файлов с атрибутами.