Swing для среднего уровня

Майкл Абернети

О данном руководстве

Данное руководство предназначено для тех из вас, кто имеет опыт работы со Swing-приложениями, но хотел бы расширить знания и использовать приемы повышенной сложности - вещи, которые возможно нельзя сразу осознать, просто посмотрев на Swing API. Решив прочесть данное руководство, вы должны быть знакомы с основными концепциями Swing, такими как UI-виджеты Swing, схемы, события и модели данных. Если вы все же думаете, что вам надо освежить эти концепции, обратитесь к руководству "Введение в Swing", в котором охвачены все эти темы, а также дается необходимая информация для работы с данным руководством.

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

  • Освоение JTable и некоторых из его многочисленных запутанных и трудных концепций.
  • Написание потокозащищенного Swing-кода.
  • Создание пользовательского компонента.
  • Создание полностью измененного внешнего вида и поведения.

Загрузка инструментальных средств и исходного кода

Для работы с данным руководством вам понадобится следующее:

  • JDK 5.0.
  • IDE или текстовый редактор. Я рекомендую Eclipse.
  • swing2.jar для системы резервирования билетов.

Пример приложения

Напоминание

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

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

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

 

Ограничения приложения

Взглянув на существующее приложение, вы, возможно, полагаете, что это все, что нам нужно для работы. Возможно это так, но существует еще один взгляд - приложение имеет все необходимое для работы, но не имеет всего необходимого для хорошей работы. Именно это отличает плохие приложения от хороших - более совершенные детали, которые делают UI гармоничнее. Хотя приложение в текущем состоянии будет работать как система резервирования билетов, кто из вас, читающих сейчас это руководство, хотел бы видеть нечто подобное на вашем рабочем столе? Наверное, не многие. Оно выглядит элементарным, отсутствует изысканность, и оно вообще не очень дружественно пользователю.

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

Улучшения примера приложения

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

  • Улучшения JTable. В настоящее время таблица предоставляет пользователю результаты поиска немного неинтересно и никак с ним не взаимодействует. Результаты не упорядочены, данные нельзя отсортировать, а таблица не может быть изменена пользователем для улучшения ее внешнего вида; кроме того, она выглядит скучной, с единственным белым фоном, черным текстом и синим выделенным текстом. Мы можем улучшить все это - позволим пользователю сортировать столбцы, а также изменим внешний вид каждой ячейки и параметры таблицы.

  • Потоки. Многопоточность является одной из наиболее сложных областей для освоения в программировании; к сожалению, в Swing она не стала легче. К счастью, существую Sun-классы, не поставляемые с Swing, которые значительно облегчают работу с потоками в Swing и устраняют многие из ловушек, обычно сопутствующих программированию потоков.

  • Пользовательские компоненты. Поскольку обычные Swing-виджеты иногда очевидно не интересны или функционируют не так, как хотелось бы, мы можем создать пользовательские компоненты, которые будут в точности такими, как мы хотим. В данном приложении кнопка Purchase выглядит совершенно не интересно, хотя это важная кнопка, и она должна отражать этот факт. Так и будет после нашей работы с ней.

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

JTable в деталях

Введение

Если вы когда-либо работали с JTable, то, несомненно, сталкивались с некоторыми из трудных концепций, которые представляет JTable. Возможно, самым общим недовольством UI-разработчиков является то, что базовая версия является слишком простой и не имеет применения в реальных приложениях. Однако более сложные JTable, которые становятся полезными, добавляют много уровней сложности, с которыми хотят иметь дело не все разработчики. Да, у них нет выбора.

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

Как же все-таки насчет поведения ? Что еще не так с базовой JTable? Например, она не обеспечивает какого-либо механизма сортировки. Это большой пробел - JTable предоставляет данные пользователю, а потому она должна позволить пользователю решать, как разместить данные на экране.

Как насчет некоторых других маленьких фишек? Например, выравнивание текста? Чаще всего, привычным является выравнивание текста по левому краю, а чисел - по правому. Как насчет редактирования содержимого ячейки? Некоторые пользователи ожидают именно такого типа поведения.

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

Свойства JTable

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

  • setAutoCreateColumnsFromModel(): Эта функция позволяет вам указать таблице автоматически создавать столбцы из TableModel; обычно, она должна быть установлена в true.
  • setAutoResizeMode(): Функция изменяет поведение, когда пользователь изменяет размеры столбцов в JTable. Существует пять возможных значений, и каждое значение меняет способ изменения размеров столбцов при изменении пользователем размеров одного столбца:
    • AUTO_RESIZE_OFF: Другие столбцы не меняются.
    • AUTO_RESIZE_NEXT_COLUMN: Изменяется размер только следующего столбца.
    • AUTO_RESIZE_SUBSEQUENT_COLUMNS: Изменяются размеры каждого столбца, находящегося после изменяемого.
    • AUTO_RESIZE_LAST_COLUMN: Только последний столбец меняет размеры, гарантируя, что столбцы всегда занимают такое же пространство, что и сама таблица, при этом не требуется горизонтальная прокрутка.
    • AUTO_RESIZE_ALL_COLUMNS: Все столбцы изменяются одинаково при изменении размеров выбранного пользователем столбца.
  • setCellSelectionEnabled(): Установка в значение true позволяет пользователю выбирать одну ячейку; по умолчанию выбирается строка.
  • setColumnSelectionAllowed(): При установке в значение true и нажатии пользователем на ячейку будет выбираться весь столбец, которому принадлежит эта ячейка.
  • setGridColor(): Изменяет цвет сетки таблица.
  • setIntercellSpacing(): Изменяет расстояния между каждой ячейкой, и, следовательно, размер линий сетки.
  • setRowHeight(): Изменяет высоту строк таблицы.
  • setRowSelectionAllowed(): При установке в значение true, когда пользователь нажимает на ячейку, выбирается вся строка, содержащая эту ячейку.
  • setSelectionBackground(): Изменяет цвет фона выбранной ячейки.
  • setSelectionForeground(): Изменяет цвет переднего плана выбранной ячейки.
  • setBackground(): Изменяет цвет фона невыбранной ячейки.
  • setForeground(): Изменяет цвет переднего плана невыбранной ячейки.
  • setShowGrid(): Позволяет полностью скрывать сетку.

Как видите, без приложения значительных усилий можно сделать с объектом JTable достаточно много. Эти свойства позволяют настроить JTable в приложении под ваши собственные требования. Однако как бы ни были хороши эти маленькие свойства, они не дают ответа на некоторые более серьезные требования примера приложения.

TableRenderer

В предыдущем разделе вы видели, что можно изменить цвет переднего плана и фона ячейки при ее выборе и снятии выбора. Это прекрасная возможность, но она ограничена: Что если вы хотите менять также и шрифт? Что если вы хотите менять цвет в зависимости от конкретного значения этой ячейки?

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

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

  • Дать невыбранным строкам белый фон и черный текст.
  • Дать выбранной строке зеленый фон и черный текст.
  • Дать строке, содержащей проданные рейсы, темно-серый фон и белый текст.

Первым шагом к использованию нового TableRenderer является указание объекту JTable использовать его вместо встроенного визуализатора (JTable имеет визуализатор ячеек таблицы по умолчанию, который мы будем переопределять для выполнения собственного окрашивания). Присоединяя визуализатор к Object.class, мы, в сущности, говорим объекту JTable использовать наш пользовательский визуализатор для каждой ячейки.

 getTblFlights().setDefaultRenderer(Object.class, new FlightTableRenderer());

Теперь, после быстрого прохождения этого шага, давайте посмотрим на сам FlightTableRenderer. FlightTableRenderer использует преимущества встроенного Swing-класса DefaultCellRenderer, который предоставляет хорошую отправную точку. Мы можем расширять и переопределять единственный метод этого класса для выполнения нашего собственного окрашивания. Реальная логика окрашивания должна быть понятна без объяснений.

 public class FlightTableRenderer extends DefaultTableCellRenderer
 {	
 public Component getTableCellRendererComponent(JTable table,
                                Object value,
                                boolean isSelected,
                                boolean hasFocus,
                                int row,
                                int column)
      {
         setText(value.toString());
		
         if (((Integer)table.getValueAt(row, 3)).intValue() < 1)
         {
            setBackground(Color.GRAY);
            setForeground(Color.WHITE);
         }
         else
         {
            if (isSelected)
            {
                setBackground(Color.GREEN);
                setForeground(Color.BLACK);
            }	
            else
            {
                setBackground(Color.WHITE);
                setForeground(Color.BLACK);
            }
         }
         return this;		   
												   
      }
   }

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

 

TableModel

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

  • TableModel указывает JTable, как выравнивать данные в каждом столбце. Что именно это означает? Например, Strings обычно выравниваются по левому краю, а числа и даты по правому. Числа, представляющие цену, обычно выравниваются по десятичной точке. TableModel имеет метод, называемый getColumnClass(), который управляет всеми вопросами выравнивания. К счастью, JTable имеет множество встроенных TableRenderer, которые знают, как обрабатывать определенные классы, включая Integers, Strings и Doubles. Вы могли бы изменить тип выравнивания, предоставляя следующую функцию в вашей модели таблицы:

    public Class getColumnClass(int col)
       {
          if (col == 2 // col == 3)
             return Integer.class;
          else
             return String.class;
       }
    

    К сожалению, этот вызов конфликтует с кодом, созданным нами для FlightCellRenderer и присоединенным к Object.class. Это именно тот случай слишком большого числа примеров для ограниченного объема имеющегося у нас кода, поэтому вы должны поверить мне, что это работает, и что JTable знает, как выравнивать определенные классы. С существующим в нашем примере FlightCellRenderer с целью получения корректного выравнивания данных мы должны были бы немного изменить код и добавить строки в setHorizontalAlignment() для выравнивания данных способом, который нам нужен.

       if (column == 2 // column == 3)
          setHorizontalAlignment(SwingConstants.RIGHT);
       else
          setHorizontalAlignment(SwingConstants.LEFT);
    

  • TableModel разрешает редактирование индивидуальных ячеек. Так же как и визуализаторы ячеек по умолчанию, JTable предоставляет редакторы ячеек по умолчанию для некоторых встроенных классов, например String и Integer, во многом аналогично предоставлению визуализаторов ячеек по умолчанию. Другими словами, он знает, как предоставить возможности редактирования для этих встроенных классов. TableModel нуждается только в указании JTable того, какие ячейки разрешены для редактирования и как установить значения после завершения редактирования (а JTable обработает все остальное).

       public boolean isCellEditable(int row, int col)
       {
          return col == 3;
       }
    		
       public void setValueAt(Object value, int row, int col)
       {
          if (col == 3)
             ((Flight)data.get(row)).setRemainingTixx(new Integer(value.toString()).intValue());
       }
    

    Теперь вы можете спросить: "Что если я не хочу использовать ни один из встроенных редакторов Swing?" Моим первым ответом мог бы быть ответ: "Очень жаль". Создание пользовательского редактора (который, например, корректно редактирует даты) - очень трудная работа. Из других тем можно сделать вывод, что простота разрешения в Swing управления редактированием встроенных классов - это компенсация сложности создания новых классов для редактирования невстроенных в JTable классов. Тема создания нового класса для редактирования ячеек выходит за рамки данного руководства, и если вы решили, что вам абсолютно необходима эта функциональность, не говорите, что я вас не предупреждал. Для тех, кто заинтересовался изучением создания специализированного редактора ячеек, в разделе "Ресурсы" приведена ссылка на сайт с полезной информацией.

Сортировка

Пользователи таблиц (независимо от того, являются ли они таблицами настольного или Web-приложения) ожидают наличия возможности сортировать данные в этих таблицах по столбцу. Другими словами, они хотят нажать на заголовке столбца и увидеть перегруппировку данных либо по возрастанию, дибо по убыванию, в зависимости от нажатого столбца.

К сожалению, JTable не имеет такой функции, что действительно неприятно, поскольку почти каждому пользователю это нужно. Данная функциональность встроена в следующие версии Swing; но из-за большого количества пользователей, ожидающих ее сейчас, она уже предлагается в дополнении к JTable от сторонних производителей, которое можно довольно просто добавить в нашу систему резервирования рейсов. Иронией является то, что "приложение стороннего производителя", которое большинство пользователей использует для сортировки своих таблиц, предоставляется самой Sun в документации по JTable. Вызывает удивление, почему Sun просто не включила его в Swing автоматически вместо того, чтобы заставлять людей рыться в документации, не так ли?

Предоставляемый фирмой Sun класс называется TableSorter, и его крайне легко использовать в любой JTable. Класс TableSorter сам управляет всем автоматически, сортируя данные в возрастающем/убывающем порядке в зависимости от нажатой кнопки мыши, а также используя стрелку для указания направления сортировки.

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

TableSorter sorter = new TableSorter(flightModel, getTblFlights().getTableHeader());
   getTblFlights().setModel(sorter);

На приведенном ниже рисунке показано, как выглядит сортировка в нашем приложении. Обратите внимание на стрелку после слова "Tixx". Она указывает на то, что столбец отсортирован, а также направление сортировки - по возрастанию или по убыванию:

 

Резюме по JTable

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

  • Вы можете вызвать методы set объекта JTable для быстрого изменения некоторых простых свойств самой таблицы. Некоторыми из этих свойств являются цвет сетки, размер сетки, высота строки и другие "украшательства" в JTable.
  • Вы можете изменить внешний вид каждой ячейки, создав новый TableRenderer. Создавая TableRenderer, мы также принимаем на себя всю ответственность по окрашиванию ячейки, в сущности, указывая объекту JTable, что мы будем управлять всем окрашиванием. Помните об этом, когда создаете ваш собственный ТableRenderer - любые ошибки быстро станут очевидными. Также, создавая ваш собственный TableRenderer, вы принимаете ответственность за выравнивание данных, которое в противном случае управляется TableModel.
  • Вы можете использовать TableModel для изменения способа выравнивания данных в столбце (при условии, что вы не переопределили TableRenderer), а также для разрешения редактирования в самих ячейках.
  • JTable обрабатывает многие типы данных автоматически, включая String, Integer и Double. Вы можете воспользоваться этим преимуществом, используя встроенные визуализаторы и редакторы для этих классов. К сожалению, для тех классов, которые не поддерживаются автоматически в JTable, вы должны предоставить ваш собственный визуализатор (который мы здесь рассматривали) и редактор (который является сложным, а его рассмотрение выходит за рамки данного руководства).
  • Наконец, вы узнали о том, как добавить возможность сортировки в ваши объекты JTable, используя класс TableSorter фирмы Sun. Хотя Sun предоставляет этот класс в документации по Swing, он является не поддерживаемым Swing-классом и должен поставляться с любыми классами, которые вы создаете. В следующих версиях Swing сортировка будет встроенной.

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

 

Потокозащищенный Swing

Когда UI застывает

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

Концепции Swing, по крайней мере, намного легче понять и закодировать. Например, предположим (в нашей системе резервирования билетов), что пользователь нажал на кнопку для покупки билетов - в текущей версии приложения этот процесс завершается быстро, поскольку мы используем фиктивную базу данных. Но представим себе, что мы обращаемся к реальной базе данных, расположенной где-нибудь еще, как в классическом приложении клиент-сервер. Когда пользователь нажимает кнопку Purchase, мы должны вызвать и обновить базу данных и ожидать результат. Вы действительно собираетесь заставить пользователя ждать столько времени (возможно, до минуты), пока обработаете эту покупку? Надеюсь, что нет!

Существует множество проблем, возникающих в Swing при кодировании подобного действия. Пользователь заметит сразу, что он (или она) заблокирован в приложении и не может взаимодействовать больше ни с чем; Swing выполняется в одном потоке (поток распределения событий - event-dispatch thread). Это означает, что поскольку поток сидит и ждет завершения вызова базы данных, любое другое пользовательское взаимодействие (например, попытка выбора другого города прибытия) помещается в очередь потока для завершения и, следовательно, ожидает завершения вызова базы данных. Мы не можем блокировать наших пользователей из их собственного приложения!

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

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

Работа с Event-Dispatch Thread

В Swing есть метод SwingUtilities.invokeLater(), который на самом деле имеет дело с проблемой, являющейся, в некотором роде, зеркальным отображением той, которую мы рассмотрели в предыдущем разделе. Вместо поиска решения для выполнения затратных по времени операций в отельном потоке от event-dispatch thread (EDT), эта функция разрешает не UI потокам выполнять задачи в event-dispatch thread. С какой стати кому-то понадобилось делать это? Хорошо, самым простым примером для понимания является загрузка GUI-приложения. Подумайте о том, что приложение обычно делает во время загрузки: открывает соединения с сервером, загружает предпочтительные настройки, создает GUI и т.д. Это прекрасная возможность делегировать создание GUI потоку EDT, разрешая главному потоку приложения выполнять другие задачи, в то время как EDT заботится о Swing-задачах. Это решение улучшает производительность загрузки приложения.

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

 public static void main(String[] args)
 {
   FlightReservation f = new FlightReservation();
   f.setVisible(true);
  }

Однако мы должны воспользоваться возможностью улучшить производительность, предлагаемую методом SwingUtilities.invokeLater(), и позволить EDT создать Swing-компоненты (в данном простом приложении улучшение производительности незначительно, потому вы должны использовать здесь ваше воображение).

   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            FlightReservation f = new FlightReservation();
            f.setVisible(true);
         }
      }
      );
      // возможно открыть файлы, получить соединения с DB
   }

Однако это все равно не решает нашу первоначальную проблему: перемещение медленных процессов из EDT. Мы рассмотрим это далее.

Решения Swing-многопоточности сторонних производителей

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

Класс называется SwingWorker и, в своей основе, работает аналогично любому другому потоку. Однако он специально заказан для работы со Swing-приложениями, четко отделяя логику, которая должна выполняться вне EDT, от логики, которая должна выполняться в EDT, но зависит от результатов логики, выполняющейся вне EDT. Запутались? Это пройдет, когда вы все увидите в действии.

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

 // должен происходить в EDT, поскольку работает со Swing
   final String dest = getComboDest().getSelectedItem().toString();
   final String depart = getComboDepart().getSelectedItem().toString();

   // НЕ должен происходить в EDT, поскольку может занимать значительное время
   List l = DataHandler.searchRecords(depart, dest);

   // должен происходить в EDT, но зависит от результатов из предыдущей строки
   flightModel.updateData(l);

Класс SwingWorker требует, чтобы весь код, который не должен выполняться в EDT, помещался в его метод construct(). Класс также требует, чтобы весь код, который должен выполняться в EDT (но зависящий от результатов метода construct()), помещался в метод finished(). Ключевым звеном между двумя методами является то, что метод construct() возвращает Object, к которому метод finished() может обратиться при помощи вызова get(). Зная это, мы можем изменить приведенный выше код, реализуя SwingWorker для удаления блокирующего кода из EDT, гарантируя, что наше приложение не будет мешать взаимодействию пользователя.

   final String dest = getComboDest().getSelectedItem().toString();
   final String depart = getComboDepart().getSelectedItem().toString();
   SwingWorker worker = new SwingWorker()
   {
      public Object construct()
      {
         List l = DataHandler.searchRecords(depart, dest);
         return l;
      }
				
      public void finished()
      {
         flightModel.updateData((List)get());
      }
   };
   worker.start();


Пользовательские компоненты

Введение

У вас было когда-нибудь чувство, что иногда вам недостаточно того, что у вас есть? Думаете ли вы, что Swing предоставляет все, что вам может понадобиться для создания желаемого UI? Скорее всего, ответом на первый вопрос будет да , а на второй - нет . Хотя Swing предоставляет десятки компонентов и виджетов, творческому человеку всегда нужно больше.

К счастью, Swing предоставляет средства для решения этой проблемы. Вы хотите новый компонент? Нет проблем. Вы можете собрать его, используя всего лишь несколько строк кода. Хотите изменить поведение уже существующего компонента, приспособив его к стилю вашего собственного приложения? Это тоже можно легко сделать. Используя иерархию Java-классов, изменить существующий Swing-виджет для создания пользовательского компонента так же просто, как создать подкласс существующего виджета. Если вы хотите создать полностью новый пользовательский компонент, просто скомбинируйте существующие виджеты в один большой виджет (как строительные блоки).

В данном разделе мы создадим пользовательский компонент обоими способами, начиная с изменения существующего Swing-компонента.

Изменение существующего компонента

Прежде всего, перед изменением существующего компонента, естественно, нужно решить, что же, собственно, мы собираемся в нем менять. В данном примере для системы резервирования билетов мы будем изменять кнопку Purchase, поскольку очень важно, чтобы она выглядела аналогично другим кнопкам в приложении. Мы создадим некий объект, называемый JCoolButton, в котором граница будет скрыта до тех пор, пока над ним не появится курсор мыши; тогда граница отобразится.

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

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

 public JCoolButton()
 {
 super();
      init();
   }
	
   private void init()
   {
      setBorderPainted(false);
      addMouseListener(new MouseAdapter()
      {	
         public void mouseEntered(MouseEvent arg0)
         {
            setBorderPainted(true);
         }

         public void mouseExited(MouseEvent arg0)
         {
            setBorderPainted(false);
         }
      }
      );
   }

Вот кнопка без mouseover:

А вот кнопка с mouseover:

 

Дополнительные изменения в JCoolButton

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

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

В документации по paint() указано, что при вызове метода он, в свою очередь, вызывает три других метода JComponent: сначала paintComponent(), затем paintBorder() и, наконец, paintChildren(). Из порядка вызова методов вы можете увидеть, что Swing рисует себя снизу вверх. Важным уроком является то, что для переопределения внешнего вида компонента вы должны переопределить метод paintComponent(), который непосредственно несет ответственность за рисование конкретного компонента на экране.

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

 public void paintComponent(Graphics g)
   {
      super.paintComponent(g);
      if (!mouseOver)
      {
         g.setColor(getParent().getBackground());
         g.fillRect(0,0,getSize().width, getSize().height);	
         g.setColor(getForeground());
         int width = SwingUtilities.computeStringWidth(getFontMetrics(getFont()), getText());
         int height = getFontMetrics(getFont()).getHeight();
         g.drawString(getText(), getWidth()/2 - width/2, getHeight()/2 + height/2);	
      }
   }

Вот как выглядит наша новая измененная кнопка без mouseover:

А вот как она выглядит с mouseover:

 

Создание абсолютно нового компонента

Ваша творческая натура может быть стеснена существующими Swing-компонентами - не только их внешним видом, но и тем, как они работают. Вы можете решить, что нет существующих компонентов, которые могут делать то, что вы хотите. Вы имеете образ и хотите реализовать его в вашем UI.

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

  1. Указать существующие Swing-виджеты, которые вам нужно скомбинировать для создания нового виджета. Существующие Swing-компоненты обеспечивают строительные блоки, которые могут быть использованы для новых виджетов. Их преимущество очевидно - они являются законченными компонентами, которые были тщательно протестированы.
  2. Скомбинировать виджеты визуально, для того чтобы сформировать желаемый внешний вид компонента. Самым лучшим способом это сделать - добавить выбранные компоненты в JPanel и использовать менеджер схем (потому что вы не знаете, насколько большим или маленьким захотят его видеть другие пользователи).
  3. Создать корректное взаимодействие компонентов для получения желаемого поведения компонентов. Сюда может входить создание частных (private) функций для обработки событий, направление данных из одной части в другую, правильное рисование всей системы - в общем, все, что вам нужно для создания желаемого поведения.
  4. Заключить в общедоступный API законченный компонент, для того чтобы другие смогли понять, как его использовать.

JMenuButton

Хорошо, достаточно абстрактных описаний. Давайте перейдем к примерам! Мы создадим новый компонент и выполним четыре шага, описанных в предыдущем разделе, чем покажем вам, как можно создать качественный новый компонент.

Компонентом, который будет создаваться, является JMenuButton, новый компонент, работающий как нормальный JButton, но с дополнительной функциональностью: пользователь может нажать кнопку со стрелкой с краю JButton для показа дополнительных вариантов. Если это описание запутанно (а я уверен, что да), подумайте о кнопке Back вашего Web-браузера: ее нажатие направляет вас назад к последней прочитанной вами Web-странице, но ее нажатие и удержание вызывает появления списка страниц, имеющихся в вашей истории посещений. Это, по существу, JMenuButton, и мы будем создавать одну из них в Swing. Вы увидите, что ничего похожего не существует в текущей версии Swing, но все строительные блоки имеются для создания этого нового компонента.

Вот как выглядит законченный продукт, цель нашей работы:

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

Шаг 1: Выбор компонентов

Для создания JMenuButton нам нужны четыре компонента:

  • JButton, служащая главной кнопкой (Back-часть компонента). Она служит начальным выбором в компоненте.
  • JButton, служащая в качестве кнопки со стрелкой. Кнопка со стрелкой отображает всплывающее меню, содержащее альтернативные варианты в компоненте.
  • JPopupMenu, который будет отображаться при нажатии кнопки со стрелкой, и в котором появятся альтернативные варианты.
  • JMenuItems, которые размещаются в JPopupMenu и служат в качестве альтернативных вариантов.

Шаг 2: Расположение компонентов

Как я подчеркивал в разделе "Создание абсолютно нового компонента", простейший способ создать новый компонент - добавить существующие компоненты в JPanel. В нашем примере мы будем использовать JPanel в качестве базы для компонентов и поместим две кнопки в верхней части. Фактически, наши JMenuButton являются подклассами JPanel, а не какого-либо компонента, которые он содержит. Выбор схемы для наших компонентов достаточно понятен, и мы можем просто использовать BorderLayout.

 this.setLayout(new java.awt.BorderLayout());
   this.add(getBtnMain(), java.awt.BorderLayout.CENTER);
   this.add(getBtnArrow(), java.awt.BorderLayout.EAST);

Шаг 3: Внутреннее взаимодействие

Третьим шагом в создании нового компонента является обеспечение его правильной работы. В данном примере нам нужно получить кнопку со стрелкой для запуска всплывающего меню при каком-либо нажатии на нее. Обратите внимание на то, что главная кнопка не обязательно должна что-либо делать внутри. Снаружи ее необходимо прослушивать, но внутри она не меняет своего состояния или внешний вид компонентов и, таким образом, может быть проигнорирована на этом шаге. Также могут быть проигнорированы индивидуальные JMenuItems в JPopupMenu - мы даже не добавили ни одного из них при старте, а потому они не меняют компонент.

Вот код, который будет обрабатывать внутреннее взаимодействие компонентов:

   getBtnArrow().addActionListener(new ActionListener()
   {
      public void actionPerformed(ActionEvent e)
      {
         getPopup().show(getBtnMain(), 0, getHeight());
      }
   }		
   );

Шаг 4: Создание общедоступного API

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

Каждый раз, создавая общедоступный API-конверт (wrapper) вокруг вашего компонента, вы должны стремиться сохранить формат таким же, как существующие Swing-компоненты - использовать методы get/set и попытаться не менять любое поведение, которое UI-разработчики ожидали бы от Swing-компонента (например, не меняйте каких-либо метод в JComponent, от которого UI-разработчики будут ожидать одинаковых действий от компонента к компоненту).

Существует две области, которыми нам необходимо интересоваться в нашем JMenuButton. Во-первых, мы должны иметь возможность добавлять альтернативные варианты в JMenuButton. Мы можем создать новый метод для этого:

   public void add(JMenuItem item)
   {
      getPopup().add(item);
   }

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

   public void addActionListener(ActionListener l)
   {
      getBtnMain().addActionListener(l);
      for (int i=0; i<getPopup().getSubElements().length; i++)
      {
         JMenuItem e = (JMenuItem)getPopup().getSubElements()[i];
         e.addActionListener(l);
      }		
   }
	
   public void removeActionListener(ActionListener l)
   {
      getBtnMain().removeActionListener(l);
      for (int i=0; i<getPopup().getSubElements().length; i++)
      {
         JMenuItem e = (JMenuItem)getPopup().getSubElements()[i];
         e.removeActionListener(l);
      }		
   }

Все. Это конец процесса создания JMenuButton. В течение каких-нибудь нескольких секунд мы создали новый, функциональный Swing-компонент. Следуя четырем шагам, описанным в данном разделе, вы тоже можете создать ваш собственный компонент.

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

Настройка внешнего вида и поведения

Исходные данные

Одной из прекраснейших возможностей Swing является его способность легко изменять внешний вид и поведение приложения. Внешний вид и поведение приложения - это комбинация двух вещей (запомните эти термин, так как я буду ссылаться на них во всем разделе):

  • Внешний вид - это то, как компоненты выглядят визуально: какие цвета используют, какие шрифты, оттенки и т.д.
  • Поведение - это то, как компоненты взаимодействуют с пользователями: как они реагируют на нажатие правой кнопкой мышки, перемещение курсора мышки и т.д.

Внешний вид и поведение приложения управляется Swing-классом javax.swing.LookAndFeel; мы будем ссылаться на его экземпляр как LookAndFeel . Есть маленькое, но заметное отличие между видом и поведением, которое мы исследуем подробно при рассмотрении Synth.

Еще одной иронией является работа пользовательских LookAndFeel в Swing: необходимые для создания нового класса javax.swing.LookAndFeel концепции и код являются сложными (и выходят за рамки данного руководства), но как только javax.swing.LookAndFeel получен и спакетирован, чрезвычайно легко использовать его в вашем приложении.

Фактически, вы можете изменить внешний вид и поведение вашего приложения полностью, добавив только одну строку кода!

UIManager.setLookAndFeel(new WindowsLookAndFeel());

LookAndFeel системы Swing

Swing устанавливается с несколькими предварительно установленными LookAndFeel. Они соответствуют распространенным на рынке операционным системам, но имеют печальный побочный эффект: LookAndFeel, подходящий для Windows XP, доступен только на Windows, Macintosh LookAndFeel доступен только на Mac OS, а GTK LookAndFeel доступен только на Linux-системах. Sun также создала Ocean LookAndFeel - попытка предоставить хорошо смотрящийся кросс-платформенный внешний вид и поведение.

Давайте посмотрим, как наша система резервирования билетов выглядит в различных установленных LookAndFeel (сюда не входят Mac или GTK, поскольку я пишу это руководство на Windows-машине). Помните, что только одна строка кода должна быть изменена для полного изменения внешнего вида нашей системы резервирования билетов.

Windows LookAndFeel:

Motif LookAndFeel:

И Ocean LookAndFeel:

 

UIManager

Самым простым способом начать изменять внешний вид и поведение ваших приложений является изучение использования UIManager. UIManager обеспечивает доступ ко всему, что имеет дело с установленным внешним видом и поведением. Я имею в виду все . Каждый возможный цвет, каждый возможный шрифт и каждая возможная граница может быть изменена и обработана UIManager. UIManager действует как HashTable, содержащий все эти значения и связывающий их в String, которые являются ключами во взаимосвязи hashtable.

Здесь находится трудная часть работы с UIManager - эти String, выступающие как ключи, не документированы где-либо на сайте Sun. К счастью, исследуя установленные LookAndFeels, поставляемые с Swing, вы можете увидеть, какие ключи использует Sun, и использовать их самостоятельно. Плохо, что эти ключи нигде не документированы, поскольку это только повышает сложность создания нового пользовательского внешнего вида и поведения.

Мы будем использовать UIManager для изменения цветов меток с синего на зеленый, а также шрифта, который используется во всем приложении:

 Font font = new Font("Courier", Font.PLAIN, 12);
 UIManager.put("Button.font", font);
   UIManager.put("Table.font", font);
   UIManager.put("Label.font", font);
   UIManager.put("ComboBox.font", font);
   UIManager.put("TextField.font", font);
   UIManager.put("TableHeader.font", font);
   UIManager.put("Label.foreground", Color.GREEN);

Вот наше приложение:

 

Упаковка всего этого в LookAndFeel

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

Решением Sun является предоставление класса javax.swing.LookAndFeel, который позволяет вам легко упаковать всю информацию, необходимую для создания внешнего вида и поведения для вашего приложения. Он также позволяет описать, как объединить все фрагменты для создания внешнего вида и поведения, которое вы хотите распространять.

Существует два способа создать LookAndFeel. Первый - создать подкласс самого класса javax.swing.LookAndFeel, хотя это более трудный вариант. Лучшее решение - создать подкласс одного из существующих LookAndFeel, предоставляемых Swing: либо javax.swing.plaf.metal.MetalLookAndFeel, либо javax.swing.plaf.basic.BasicLookAndFeel (строительный блок для внешнего вида и поведения, не имеющий каких-либо визуальных элементов, а служащий в качестве основы для создания других LookAndFeel).

Имеется также некоторая базовая информация, которую должен содержать каждый LookAndFeel. Это информация, рассказывающая Swing о LookAndFeel и позволяющая другим знать о нем, если вы решите упаковать его и распространять.

  • getDescription(): Описание внешнего вида и поведения.
  • getID(): Уникальный ID, который может быть использован для идентификации внешнего вида и поведения.
  • getName(): Название внешнего вида и поведения.
  • isNativeLookAndFeel(): Указывает, являются ли этот внешний вид и поведение родными для OS; любой пользовательский внешний вид и поведение должны возвращать значение true из этой функции.
  • isSupportedLookAndFeel(): Тоже должна возвращать значение true для любого пользовательского внешнего вида и поведения.

Взгляните на этот код в нашем новом пользовательском классе LookAndFeel, FlightLookAndFeel.

 public String getDescription()
   {
      return "The Flight Look And Feel is for the Intermediate Swing tutorial";
   }
	
   public String getID()
   {
      return "FlightLookAndFeel 1.0";
   }
	
   public String getName()
   {
      return "FlightLookAndFeel";
   }
	
   public boolean isNativeLookAndFeel()
   {
      return true;
   }
	
   public boolean isSupportedLookAndFeel()
   {
      return true;
   }

Дополнительная информация о FlightLookAndFeel

Последним шагом в создании класса FlightLookAndFeel является описание того, что должны менять внешний вид и поведение. В примере, рассмотренном в разделе "UIManager", мы просто переопределили определенные цвета и шрифты, которые уже были в MetalLookAndFeel. Мы будем повторно использовать этот код для создания нашего внешнего вида и поведения.

Существует класс, работающий аналогично UIManager, называемый UIDefaults. Различие между ними едва различимо - UIManager должен быть использован вне классов LookAndFeel, поскольку он представляет все значения внешнего вида и поведения после своей загрузки. UIDefaults представляет эти же значения во время загрузки.

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

   public UIDefaults getDefaults() 
   {
      UIDefaults def = super.getDefaults();
      Font font = new Font("Courier", Font.PLAIN, 12);
      def.put("Button.font", font);
      def.put("Table.font", font);
      def.put("Label.font", font);
      def.put("ComboBox.font", font);
      def.put("TextField.font", font);
      def.put("TableHeader.font", font);
      def.put("Label.foreground", Color.GREEN);
      return def;
   }

Дополнительная информация о пользовательских LookAndFeel

Хотя предыдущих разделов было достаточно для создания пользовательского внешнего вида, имеющего дело только с цветом и шрифтами, на самом деле, это не совсем полный урок по созданию завершенного пользовательского внешнего вида и поведения, которые можно было бы сравнить с WindowsLookAndFeel или MotifLookAndFeel. Они выходят далеко за рамки простого изменения цвета и шрифтов; они изменяют практически все в реализации по умолчанию - способ прорисовки каждого компонента, как он реагирует на события от мышки, где размещаются компоненты при добавлении элементов и т.д. Создание завершенного внешнего вида и поведения - это не простая задача.

Данное руководство не будет рассматривать большое количество шагов, необходимых для создания пользовательского внешнего вида и поведения. Фактически, для обучения созданию пользовательского вида и поведения могло бы быть написано отдельное руководство; к сожалению, это сложное предприятие. Чтобы вы понимали смысл вещей, отмечу, что эта задача требует от пользователя создания нового класса для каждого Swing-компонента с указанием того, как он должен выглядеть и вести себя. Это примерно 60 классов, которые вы должны создать, - совсем не то, что можно сделать после обеда, и конечно же не то, что можно было бы кратко изложить в нескольких разделах данного руководства.

Дальнейшим доказательством сложности создания завершенных пользовательского вида и поведения является то, что количество примеров, доступных в Интернете, удивительно не велико. Всего лишь от 20 до 30 коммерческих LookAndFeel доступны для загрузки - и это для Swing, который присутствует на рынке семь лет.

К счастью, в J2SE 5.0 Swing представил новый класс, который делает процесс намного проще и уменьшает время, необходимое для создания пользовательского внешнего вида и поведения с трех месяцев (как это требовалось в 1.4) до трех недель (как это требуется сейчас). Мы обсудим это в следующем разделе.

Synth

Synth - это новейшее LookAndFeel-дополнение к Swing, но есть определенного рода искажение в наименовании. Он, на самом деле, вовсе не LookAndFeel-компонент, что вы обнаружили бы, если бы пытались добавить его в ваше приложение: оно стало бы абсолютно белым. При использовании Synth вы даже не сможете полностью изменить вид и поведение приложения. Synth позволяет вам изменять только внешний вид приложения - границы, цвета, шрифты. Он не позволяет изменять поведение приложения.

Synth, в двух словах, является наружной оболочкой, которую вы устанавливаете в вашем приложении. Эта оболочка содержит информацию, описывающую, как использовать внешние изображения и пользовательский код рисования для создания внешнего вида, выделяя эту информацию из одиночного XML-файла, загружаемого тогда, когда Synth LookAndFeel устанавливается в приложение. Самым большим его преимуществом является предлагаемая пользователям экономия времени - вместо необходимости создавать подклассы для 60 Java-классов вы просто должны создать один XML-файл и некоторые графические элементы. Результат - потенциальное взрывоподобное увеличение количества пользовательских LookAndFeel, доступных разработчикам для выбора, что является конечной целью Sun при выпуске этого нового класса. В случае если вы не следили за драмой, знайте, что Swing годами критиковался за ужасный внешний вид. Ocean LookAndFeel отчасти смягчил это недовольство, а Synth предлагает творческим разработчикам увеличить их потенциал для создания очень красивых LookAndFeel в ближайшем будущем.

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

Резюме

В данном руководстве рассмотрены некоторые проблемы среднего уровня сложности, с которыми вы можете столкнуться при разработке UI-приложений с использованием Swing. Руководство основано на материалах руководства "Введение в Swing", либо на уже имеющихся у вас знаниях Swing. Рассматриваются некоторые общие вопросы, которые возникают у разработчиков UI-приложений. Я попытался выбрать комбинацию наиболее интересных и наиболее важных проблем.

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

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

И преобразовали его в такое:

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

  • Intermediate-level JTable: Кроме информации о том, как много свойств таблицы может быть изменено для быстрого изменения внешнего вида таблицы, вы узнали также следующие вещи о JTable:
    • TableRenderers: Вы узнали, что вы не ограничены встроенной в JTable схемой цветов для ее ячеек. Создавая ваш собственный TableRenderer, вы можете управлять внешним видом каждой ячейки таблицы, когда она выбрана или не выбрана, и даже тем, как предоставить информацию о приложении пользователю (например, когда мы затемнили недоступные рейсы).
    • TableModel: Кроме управления данными TableModel может также управлять выравниванием данных в столбцах и возможностью редактирования ячейки.
    • TableEditors: JTable имеет много встроенных редакторов для традиционных классов, появляющихся в данных (String, Integer), но создание редактора для других данных является трудной задачей и выходит за рамки данного руководства.
    • TableSorter: Пользователи ожидают, что у них будет возможность сортировать данные в их таблицах путем нажатия на заголовки столбцов. Поставляемая в Swing JTable не поддерживает сортировку вообще, но Sun предоставляет класс TableSorter (не поставляемый с JDK), который берет на себя заботу о сортировке.
  • Thread safety: Это важная тема в Swing, поскольку плохое управление потоками может привести к блокированию пользователя в приложении и к появлению неприятного серого прямоугольника вместо UI приложения.
    • SwingUtilities.invokeLater() должен использоваться всегда, когда UI-работу должен выполнить поток, отдельный от EVT (event-dispatch thread).
    • Класс SwingWorker (еще один класс, не поставляемый с JDK, но опубликованный Sun) должен всегда использоваться с EVT при выполнении затратного по времени действия (например запроса к базе данных).
  • Custom components: Хотя Swing предоставляет практически любой компонент, который вы захотели бы использовать, иногда эти компоненты работают не так, как вы хотите, или даже множества Swing-компонентов недостаточно для обеспечения способа работы Swing-виджета в вашем приложении. Наилучшим способом сделать это - создать подкласс существующего Swing-виджета и переопределить метод paintComponent() для адаптации компонента к вашим требованиям. Возможность создания нового пользовательского компонента позволяет вам создавать оригинальные необычные компоненты, отсутствующие в Swing. Для создания нового компонента необходимо выполнить несколько действий: указать нужные вам компоненты, расположить их соответственно, настроить их на совместную работу друг с другом для корректной работы всего нового компонента, и, наконец, заключить его в общедоступный API для упрощения работы с ним.
  • Custom look and feel: Создание пользовательского внешнего вида и поведения является очень трудным, комплексным и длительным по времени процессом, и, вероятно, заслуживает отдельного руководства. Однако, есть простые способы быстро изменить поведение в вашем приложении, о которых вы узнали в данном руководстве:
    • Используйте UIManager для переопределения свойств, используемых в существующем LookAndFeel, и укажите свои собственные цвета, шрифты и границы для адаптации существующего внешнего вида и поведения под ваши требования.
    • Используйте новый LookAndFeel Synth - дополнение к Swing, позволяющее вам создавать пользовательские внешние виды и поведения намного проще и быстрее, чем прежде.

Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=3988