WWW.DISS.SELUK.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА
(Авторефераты, диссертации, методички, учебные программы, монографии)

 

Pages:     | 1 || 3 | 4 |   ...   | 5 |

«Swing ЭФФЕКТНЫЕ ПОЛЬЗОВАТЕЛЬСКИЕ ИНТЕРФЕЙСЫ Java Foundation Classes Москва • Санкт-Петербург • Нижний Новгород • Воронеж • Новосибирск • Ростов-на-Дону • Екатеринбург • Самара • Киев • Харьков • ...»

-- [ Страница 2 ] --

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

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

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

Все ли так хорошо в MVC?

Ответ кажется очевидным: «Конечно, все просто замечательно!» Действительно, архитектура MVC хороша — гибкая, легко настраиваемая, расширяемая и простая в понимании, она позволяет делать многое. Однако хоть это и кажется удивительным, использовать ее в оригинальном варианте оказывается не совсем удобно. Проблема здесь кроется не только и не столько в самой архитектуре — скорее накладывают свои ограничения те задачи, которые мы хотим решить с ее помощью, и те инструменты, которыми мы эти задачи собираемся решать.

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

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

То же самое можно сказать и о связи контроллера и вида. Эта связь сильная: вид хранит ссылку на контроллер, а контроллер на вид. Можно попытаться избежать подобной связи, полагаясь только на связь контроллера с моделью и дальнейшее оповещение моделью вида. Но такое взаимодействие сделает интерфейс неудобным: оно практически не в состоянии учитывать некоторые операции при изменении данных, которые также нужно отображать: например, модель текстового документа не должна во избежание излишней сложности хранить информацию о мерцающем курсоре, выделенном тексте, операциях перетаскивания и других подобных вещах. Все это должны решить между собой контроллер и вид, и без отражения подобных операций интерфейс будет крайне неудобным. В итоге мы получаем, что хотя формально MVC отделяет контроллер, последний фактически «намертво»

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

Решение напрашивается само собой — надо просто соединить в единое целое контроллер и вид, образовав визуально-поведенческую (look and feel) часть компонента. Это целое и будет общаться с моделью. Такой подход не просто больше подходит для Java, но еще и более удобен для программирования: например, если компонент нужно отключить, то логичнее вызвать какой-то метод компонента (который сразу отключит обработку событий и сменит внешний вид), а не тасовать контроллеры и виды.

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

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

Решение Swing — представители пользовательского интерфейса Что же, мы выяснили, что для Java классическая архитектура MVC подходит не очень хорошо:

разделение контроллеров, моделей и видов не совсем оправдано. Гораздо более простым в реализации и дальнейшем использовании оказывается решение, совмещающее вид и контроллер в одно целое. Именно такое решение было выбрано разработчиками Swing. Давайте посмотрим, что у них получилось (рисунок 1.4).

Как видно из рисунка, разработчики Swing объединили вид и контроллер в новый элемент, который назвали представителем (delegate) пользовательского интерфейса (User Interface, UI). Теперь все действия пользователя поступают не в контроллер, определяющий реакцию на них, а в этот новый элемент, в котором происходит значительная часть работы. Он определяет, нужно ли реагировать на них (так как контроллер теперь находится внутри, исчезает необходимость переделывать его, чтобы отключить реакцию на действия пользователя — свойство «включено/выключено» стало свойством представителя), и если нужно, то сразу же без генерации каких-либо событий и изменения данных меняет вид (это происходит быстро — вид и контроллер находятся в одном месте и имеют исчерпывающую информацию друг о друге, благодаря этому пользовательский интерфейс быстро реагирует на любые изменения и позволяет легко воспроизвести самые сложные операции по изменению данных), а уже после этого представитель говорит модели о том, что данные изменились и их необходимо обновить. Модель обновляет хранящиеся в ней данные и оповещает заинтересованных субъектов (чаще всего того же представителя) об изменениях. В ответ внешний вид компонента окончательно обновляется, чтобы соответствовать новым данным.

';

Если рассматривать диаграмму подробней, оказывается, что схема работы новой системы очень проста, но весьма эффективна. Отдельного контроллера теперь нет, и ему не придется прикладывать титанических усилий, чтобы оставаться многократно используемым и полезным элементом системы, способным работать с любой моделью. Вид четко знает, какой элемент он представляет на экране, то есть данные модели, как бы она ни была реализована, он выведет на экран надлежащим образом (с видом вообще нет проблем). Находящийся теперь с ним в «одном флаконе» контроллер может проще решить свою задачу обработки событий пользователя: когда нужно, он сразу изменяет вид (это требуется для смены каких-то аспектов вида, не связанных с данными модели). Взаимодействие контроллера и вида реализовано внутри представителя пользовательского интерфейса, поэтому происходит быстро. Завершающим этапом является отправка совмещенным видом-контроллером сообщения модели о необходимости обновления данных (представитель знает, как работает отображаемая им модель) и рассылка моделью оповещения о новых данных. Это оповещение дает возможность всем присоединенным к модели видам прорисовать новые данные, а остальным слушателям узнать, что пользователь внес в данные изменения.

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

Таким образом, слегка изменив и во многом упростив изначальную архитектуру MVC, создатели Swing сумели сохранить ее основные достоинства: простое изменение внешнего вида и поведения компонентов (это осуществляется заменой UI-представителя компонента) и мощь модельного программирования (программист все также может использовать различные модели для одного компонента, менять данные и манипулировать ими, не заботясь об обновлении вида и типе компонента). Немного неправильно говорить, что в Swing задействована архитектура MVC (в библиотеке реализована архитектура из двух частей, а не из трех), поэтому часто говорят об использовании в Swing отношения модель-представитель (model-delegate), или об архитектуре с разделенной моделью (separable model architecture).

Как все работает Кажется, мы дошли до сути механизма, обеспечивающего компонентам Swing различные поведение и внешний вид. Имеется UI-представитель, обрабатывающий события пользователя и рисующий компонент на экране; есть модель, хранящая данные компонента. Непонятно одно — как этот механизм взаимодействует с классами библиотеки Swing, такими как кнопки (JButton) или списки (JList). Работать с ними просто — вы создаете экземпляр класса кнопки и добавляете его в контейнер.

Где же UI-представитель? Очевидно, что не в классах компонентов, иначе менять их внешний вид и поведение было бы невозможно (пришлось бы переписывать все эти классы для поддержки другого внешнего вида). Рисунок 1.5 иллюстрирует роль, которую играет класс компонента во взаимоотношениях UI-представителя, модели и конечных пользователей (к ним относятся программисты-клиенты Swing). Можно сказать, что класс компонента — это точка приложения сил архитектуры «модель-представитель», в нем сосредоточивается информация о том, как UIпредставитель взаимодействует с некоторой моделью. Модель не знает, с каким UI-представителем она сотрудничает и какой компонент ее использует, все, что известно о модели, — это то, что она есть. Раз модель существует, на нее должна быть ссылка. Хранится эта ссылка в классе компонента.

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

Следует четко осознавать, что именно классы компонентов (такие как JButton и JTable) являются основной частью библиотеки Swing. Может показаться, что они не так уж и важны: ведь в них не происходит ни прорисовки компонента, ни обработки событий, ни манипуляции данными. Однако это не так: UI-представители и модели являются лишь частью внутреннего механизма библиотеки, сами по себе они не представляют большого интереса. Компонент просто делегирует к ним запросы:

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

Если использовать терминологию шаблонов проектирования, то можно сказать, что с точки зрения UI-представитслей и моделей классы компонентов Swing действуют как посредники (mediators), обеспечивая слабую связанность системы. С точки же зрения программистов-клиентов Swing классы компонентов являются фасадами (facade) для архитектуры «модель-представитель»

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

Рисунок 1.5. Взаимоотношения UI-представителя и модели Управление внешним видом и поведением программы В библиотеке Swing довольно много компонентов и каждый из них имеет своего UI-представителя, ответственного за обработку событий и прорисовку компонента на экране. Рано или поздно настает момент, когда внешний вид и поведение вашего Java-приложения приходится менять (например, чтобы оно выглядело одинаково с приложениями той платформы, на которой ему приходиться работать). Если бы разработчику пришлось менять UI-представителя индивидуально для каждого компонента, это было бы не только утомительно и долго, но и внесло бы множество ошибок.

Поэтому в Swing управление внешним видом осуществляется в специальном классе UIManager. Он позволяет вам установить внешний вид и поведение для всех компонентов библиотеки сразу. Для этого нужно лишь вызвать статический метод этого класса setLookAndFeel() и передать в него объект класса LookAndFeel. Объект LookAndFeel — это хранитель информации об определенном внешнем виде и поведении программы, в нем содержится информация о UI-представителях, название внешнего вида, а также методы, упрощающие работу класса UIManager. По умолчанию компоненты автоматически «выбирают» себе UI-представителя именно с помощью класса UIManager.

Внешние виды для компонентов Swing, которые поставляются с пакетом разработки JDK 1.4 (так же как и с JDK 1.3), перечислены в таблице 1.1.

Таблица 1.1. Доступные внешние виды компонентов Swing приложений на Java (внешний вид javax.swing.plaf.metal Swing по умолчанию (если вы явно не зависящий от платформы) MetalLookAndFeel разработан создателями Swing, для того приложений com.sun.java.swing.plaf.windows эмуляции Windows-приложений. Его приложений com.sun.java.swing.plaf.motif выглядеть аналогично Unix-приложениям Кроме перечисленных внешних видов, входящих в стандартный инструментарий JDK, компания Sun также разработала внешний вид Macintosh (Mac Look & Feel). Если ваше приложение будет работать на платформе Mac и вы хотите, чтобы оно выглядело соответственно, то можно загрузить этот внешний вид с сайта java.sun.com. Однако внешний вид Mac, так же как и внешний вид Windows, можно использовать только на соответствующей платформе (таковы требования корпораций Apple и Microsoft). Специально для получения внешнего вида, соответствующего платформе, на которой работает приложение, в классе UIManager определен метод getSystemLookAndFeel(). При работе под управлением Windows этот метод вернет вам внешний вид Windows, а при работе под Unix — внешний вид Unix. Лучше всего менять внешний вид и поведение перед тем, как на экране появится окно вашего приложения. Хотя никто не запрещает вам менять внешний вид прямо во время работы программы, в таком случае компоненты не изменятся автоматически, и вам придется вызывать специальный метод класса SwingUtilities, чтобы обновить их. К тому же при изменении внешнего вида прямо во время работы программы могут возникнуть проблемы с размерами компонентов и их расположением в контейнере — разные внешние виды придают компонентам разные размеры, и то, что прекрасно смотрится во внешнем виде Metal, может выглядеть ужасным при переходе к внешнему виду Motif. Кстати, это типичная ситуация для начинающих работать со Swing программистов: они настолько воодушевляются возможностью тасовать внешние виды и менять поведение своего приложения, что поначалу только этим и занимаются, не обращая внимания на то, что в результате внешний вид приложения сильно страдает.

В принципе, оптимальным для приложения является использование одного внешнего вида и одного варианта поведения. Такое заявление может показаться странным: как же отказываться от великолепного механизма, позволяющего одной строчкой кода полностью сменить внешность и реакцию приложения? Разнообразие еще никому не вредило, но как показывают время и уже созданные Java-приложения, широкий выбор внешних видов не позволяет получать по настоящему эффектные приложения. Дело в том, что при разработке пользовательского интерфейса первоклассных программ учитываются рекомендации создателей компонентов этого интерфейса — именно это дает возможность добиваться наилучших результатов. С помощью внешних видов для конкретных платформ сделать то же невозможно: если вы создадите эффектное приложение с внешним видом Windows, полностью следуя рекомендациям Microsoft, вы не сможете перенести его на Unix, потому что использовать внешний вид Windows на других платформах запрещено (а рекомендации Microsoft для интерфейса Unix не подходят, и это еще мягко сказано).

Здесь на передний план выходит внешний вид Metal, специально созданный для Java-приложений (он как бы символизирует, что Java представляет собой именно платформу, а не просто язык). Компания Sun разработала для него ряд рекомендаций, выполняя которые можно получить по-настоящему красивые интерфейсы. Мы подробно рассмотрим эти рекомендации и этапы воплощения их в жизнь в главе 5, когда будем говорить о размещении компонентов в контейнере. Создав интерфейс специально для внешнего вида Metal, вы с легкостью перенесете его на любую платформу. Все сказанное не стоит воспринимать, как совет отказаться от внешних видов, эмулирующих известные платформы, но, как показывает практика, их использование все равно не обеспечивает полного соответствия «родным» приложениям этих платформ. Дело в том, что Swing всегда находится на шаг позади (сначала меняется интерфейс конкретной платформы, команда Swing разрабатывает внешний вид, эмулирующий этот интерфейс, обновленный внешний вид выходит в новом пакете JDК, а в это время интерфейс конкретной платформы опять меняется, пусть даже и ненамного). За изменения же внешнего вида Java можно не беспокоиться, потому что он меняется одновременно со Swing. Вы вовсе не ограничены внешними видами, созданными в Sun. На данный момент имеется умопомрачительное количество разнообразных внешних видов, некоторые их которых определенно стоят того, чтобы на них обратили внимание.

Часто программистам и пользователям не нравится внешний вид Metal, используемый в Java по умолчанию (следует признать, что причины для недовольства имеются — слишком уж «топорно»

выглядит этот лаконичный интерфейс по сравнению с современными системами пользовательских интерфейсов). Вы вполне можете задействовать вместо него один из интерфейсов от стороннего производителя, по-прежнему выполняя рекомендации для интерфейсов Metal, потому что большинство сторонних внешних видов являются просто производными от Metal, оставляя без изменения поведение, размеры компонентов и пр. и меняя лишь изображения. Для начала можно посетить сайт www.jars.com, где найдутся все наиболее популярные внешние виды для Swing, например внешний вид Alloy, очень популярный и используемый во многих коммерческих продуктах, созданных с помощью Swing. Если при разработке интерфейса учитывать рекомендации для внешнего вида Metal, внешний вид Alloy позволит получать приложения, способные конкурировать с самыми продуманными и изысканными пользовательскими интерфейсами. Кроме того, в качестве неплохой бесплатной замены внешнего вида Metal хорошо подходит внешний вид Kunststoff.

Вообще говоря, подключаемые внешний вид и поведение (Pluggable Look And Feel, PLAF) — одно из самых мощных свойств Swing. Никакая другая библиотека или операционная система не позволяет осуществить такие масштабные действия так просто и быстро. Вы можете разработать для своего приложения любой вид, не задумываясь о платформах и их различиях, создать совершенно особый колорит, подчеркивающий предназначение вашего приложения (конечно, это довольно долго и недешево, но всегда можно использовать внешний вид от стороннего производителя). Никто не запрещает вам создать абсолютно новаторский трехмерный интерфейс или интерфейс, основанный только на системе синтеза и распознавания речи. При всем при этом вам не нужно будет изменять ни строчки кода в вашем приложении.

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

Надо сказать, что, создавая приложение с помощью Swing, вы уже выполняете большую часть работы, необходимой для поддержки специальных средств. Разработчики Swing учли важность этой поддержки и встроили во все компоненты особую информацию. Эта информация (она может быть передана специальным средствам, которые обработают ее надлежащим образом) описана во внутренних классах компонентов, использующих библиотеку Accessibility (эта библиотека относится к набору Java Foundation Classes).

Итак, в каждом компоненте библиотеки Swing имеется внутренний класс, имя которого составляется из названия класса компонента и слова «Accessible», например, в классе кнопки (JButton) имеется внутренний класс AccessibleJButton. В классах AccessibleXXX содержится исчерпывающая информация о компонентах Swing (набор «ролей», которые исполняют графические компоненты в пользовательском интерфейсе; действия, которые можно совершать над компонентом, основные свойства компонента, например текст надписи). Если на компьютере установлено специальное средство, оно находит эту информацию (вызывая для этого метод getAccessibleContext(), встроенный в общий класс всех компонентов Swing - Component) и выводит ее в надлежащем виде (например, для человека с расстройством зрения интерфейс будет «прочитан» путем синтеза речи).

Вся работа фактически уже сделана разработчиками Swing: к примеру, если вы создаете кнопку с надписью (не предпринимая никаких дополнительных усилий), специальное средство сразу получит информацию о том, что в пользовательском интерфейсе программы имеется кнопка, получит название кнопки и данные о том, что кнопку можно «нажать». Что делать с этой информацией дальше, зависит уже от ситуации и того, как пользователю удобно информацию получать. Самое главное здесь — в том, что вы просто пишете приложение, совершенно не задумываясь, что в дальнейшем оно может быть использовано человеком с ограниченными возможностями, и тем не менее вся необходимая информация будет на месте. Учитывая и другие достоинства Swing, независимость от конкретной платформы, способность динамически менять внешний вид и поведение (что особенно важно для специальных средств, при использовании которых может понадобиться увеличивать размеры компонентов, по-особому обрабатывать действия, озвучивать их), простую локализацию и поддержку Unicode, можно сказать, что для специальных средств библиотека Swing подходит как нельзя кстати. Если вам вдруг понадобится проверить, как Swing справляется с поддержкой специальных средств, это всегда можно сделать. Загляните на официальный сайт java.sun.com, там вы найдете несколько инструментов для тестирования, а также ссылки на реальные специальные средства с поддержкой библиотеки Accessibility.

Резюме Итак, мы узнали, что основой Swing является библиотека AWT. Тем не менее возможности библиотеки Swing гораздо богаче, к тому же она по праву «носит звание» библиотеки, полностью написанной на Java. Благодаря технологии JavaBeans в визуальных средствах разработки программ удалось упростить использование компонентов Swing и классов, поля и методы которых формируются по простым правилам. Подключаемые внешний вид и поведение компонентов, встроенная поддержка специальных возможностей придают Swing необычайную гибкость и избавляют нас от многих проблем и рутинной работы.

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

Для любой библиотеки пользовательского интерфейса очень важно качество используемой в ней системы обработки событий. Как бы ни была хороша внешне или функционально библиотека, неудачно реализованная обработка событий сведет все ее преимущества «на нет». Библиотека AWT из первого выпуска JDK — хороший пример. Несмотря даже на то, что она не блистала качеством и внешним видом компонентов, основным нападкам подвергалась именно неудачная система обработки событий, и будь она получше, возможно, у AWT было бы более светлое будущее. Однако система обработки событий AWT была слишком неуклюжа — код, обрабатывающий события, находился прямо в классах компонентов: приходилось наследовать от них, искать нужное событие, пользуясь уже набившими оскомину операторами if и switch, и смешивать пользовательский интерфейс с деловой логикой программы. В итоге получалась программа, которую было крайне тяжело поддерживать и обновлять. Все это предрешило неудачу библиотеки AWT. К счастью, нам не придется возвращаться во времена старой системы обработки событий, и вспомнили мы ее лишь из уважения к истории развития библиотек пользовательского интерфейса в Java. Библиотека Swing использует систему обработки событий JavaBeans, и система эта действительно хороша. С одной стороны, она проста и понятна, с другой — предоставляет множество способов обработки событий, и вам остается лишь выбрать из них наиболее подходящий. Основным достоинством системы обработки событий Swing следует признать то, что как бы вы ни писали свою программу, какие бы способы создания интерфейса ни применяли, код, отвечающий за создание интерфейса, будет отделен от кода, обрабатывающего события. Это позволит и легко обновлять программу, и легко понимать ее.

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

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

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

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

Наблюдатели проще субъектов: они определяют методы, которые следует вызывать субъекту, для того чтобы сообщить о своих изменениях (рисунок 2.1).

Рисунок 2.1. Взаимоотношения наблюдателей и субъектов Как видно из рисунка, субъект обладает тремя методами: метод add() позволяет добавить очередного наблюдателя (как правило, наблюдатели хранятся в виде списка, что позволяет иметь произвольное их количество и легко манипулировать ими); метод remove() позволяет удалить ранее добавленного наблюдателя; метод notify() сообщает наблюдателям, добавленным ранее методом add(), о смене состояния субъекта. Для этого он вызывает определенный во всех объектах-наблюдателях специальный метод, в нашем случае это метод update(). В свою очередь, наблюдатели знают, что очередной вызов метода update() означает смену состояния субъекта, и выполняют в этом методе все необходимые действия. Чтобы субъекты и наблюдатели смогли работать друг с другом, их функции описываются в базовых классах (или интерфейсах), и объекты, которым необходимо знать друг о друге, но которым нежелательно быть сильно связанными, наследуют от этих базовых классов или реализуют интерфейсы и начинают работать как субъекты и наблюдатели.

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

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

Самое интересное, что вся библиотека Swing буквально «напичкана» субъектами и наблюдателями.

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

Но вернемся к системе обработки событий Swing. Она на самом деле основана на отношении вида субъект-наблюдатель. Субъектами являются компоненты Swing (кнопки JButton, списки JList и т. п.), а наблюдателями — специальные объекты, которые называют слушателями. Для того чтобы узнать о каком-либо событии, надо написать соответствующего слушателя и присоединить его к компоненту.

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

Источником события (event source) в Swing может быть любой компонент, будь то кнопка, надпись с текстом или диалоговое окно. Для того чтобы узнавать в своей программе о происходящих в компоненте событиях, нам необходимо сообщить компоненту о своей заинтересованности. Сделать это можно, передав компоненту слушателя (listener) определенного события. Слушатель — этот тот самый наблюдатель (разница только в названиях), которому компонент будет сообщать о происходящих в нем событиях.

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

// FirstEvents.java // События - нажатия клавиш на клавиатуре import javax.swing.*;

import java.awt.event.*;

public class FirstEvents extends JFrame { setDefaultCloseOperation(EXIT_ON_CLOSE);

public static void main(String[] args) { // этот класс будет получать извещения о событиях class KeyL implements KeyListener { public void keyTyped(KeyEvent k) { public void keyPressed(KeyEvent k) { // отпускание нажатой клавиши public void keyReleased(KeyEvent k) { Пример очень прост — мы создаем класс, унаследованный от окна JFrame, устанавливаем для него размер методом setSize(), указываем, что при закрытии окна следует завершить работу приложения (методом setDefaultCloseOperation(), подробнее об этом методе мы узнаем в главе 4, посвященной окнам) и выводим окно на экран. Гораздо интереснее посмотреть, как создается слушатель события.

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

keyPressed() и keyReleased() — при нажатии и отпускании клавиши, keyTyped() — при печати символа (когда нажимается и отпускается клавиша, соответствующая печатному символу 12). Как параметр каждому методу передается объект KeyEvent, который используется для получения дополнительной информации о событии (кода клавиши, источника события и т. д.). Чтобы сделать программу максимально простой, мы просто передаем эту информацию в стандартный поток вывода.

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

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

Схема именования событий JavaBeans Система обработки событий в Swing является частью архитектуры JavaBeans, которая позволяет создавать переносимые и легко используемые графические компоненты для визуальных средств разработки программ. В главе 1 мы узнали, что все компоненты Swing являются компонентами JavaBeans. Как вы помните, основой JavaBeans является соглашение об именах, которое позволяет визуальным средствам легко узнавать, какими свойствами обладает компонент. Для этого компонент определяет набор методов со специальными именами get/set. Методы эти служат для считывания и записи значений свойств компонента.

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

У каждого события есть имя. Например, в примере из предыдущего раздела этим именем было слово «Key» (клавиша) — неудивительно, ведь это событие происходит при нажатии клавиш на клавиатуре.

События, которые происходят в окнах, называются «Window» (окно); в общем случае будем считать, что названием события являются просто символы XXX. Чтобы событие стало доступно визуальному средству разработки, необходимо проделать описанную ниже процедуру.

1. Определить класс, в котором будет храниться информация о произошедшем событии (что это будет за информация, определяет создатель события). Класс должен быть унаследован от базового класса Java.util.EventObject и иметь название вида XXXEvent, где XXX — это название нашего события. В предыдущем примере мы видели, что информация о событиях от клавиатуры хранилась в классе с названием KeyEvent.

Метод keyTyped() — вообще очень таинственный метод, и своей таинственностью он целиком обязан невразумительной интерактивной документации Java. Иногда создается впечатление, что этот метод работает без всякой логики. Однако на самом деле все просто: он сообщает программе, что пользователь нажал и отпустил клавишу с печатным символом (печатные символы — это все буквы, цифры, а также пробел и клавиши Enter и Esc).

2. Создать интерфейс слушателя, в который будет приходить информация о событии. Это должен быть именно интерфейс, а не класс. Название интерфейса должно иметь следующий вид: XXXListener — в предыдущем примере мы использовали слушателя с именем KeyListener. Этот интерфейс должен быть унаследован от базового интерфейса всех слушателей событий Java.util.EventListener (это пустой интерфейс без методов, он просто помечает то, что унаследованный от него интерфейс является слушателем). В интерфейсе может быть определено сколь угодно много методов, единственное требование к этим методам — наличие параметра типа XXXEvent. Никаких других параметров у методов быть 3. Включить поддержку события в класс компонента, в котором это событие может происходить. Чтобы сделать это, необходимо определить два метода: один для присоединения слушателей, другой для их отсоединения. Названия методов должны выглядеть следующим образом: addXXXListener() — для метода, присоединяющего слушателей, и removeXXXListener() — для метода, отсоединяющего слушателей. Если вспомнить пример, то там присоединение слушателя происходило как раз с помощью метода addKeyListener(), определенного в классе окон JFrame (на самом деле этот метод определен в базовом классе всех компонентов Component, и добавлять слушателей клавиатуры можно к любому компоненту).

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

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

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

Надо сказать, что события в Java условно разделяются на низкоуровневые (low-level events) и высокоуровневые (high-level events). К низкоуровневым событиям относят те, что происходят непосредственно в результате действий пользователя: это движения мыши, передача фокуса ввода от одного приложения другому, нажатия клавиш и т. п. 13. Они поступают в Java-программу от операционной системы или от внутренних механизмов виртуальной машины. Высокоуровневые события происходят в результате изменения состояния компонента. Такие события поступают не от операционной системы, а создаются самим компонентом. Процесс создания события еще называют запуском (fire). Во многих компонентах Swing вы можете увидеть методы с именами вида fireXXX();

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

Начнем мы с низкоуровневых событий (таблице 2.1). Эти события могут возникать в любом графическом компоненте, унаследованном от класса java.awt.Component (правда, есть несколько исключений).

Низкоуровневые события легко отличить от высокоуровневых — все низкоуровневые события унаследованы от особого базового класса AWTEvent.

Таблица 2.1. Основные низкоуровневые события Описано в классе KeyEvent. keyReleased(KeyEvent), Возникает, когда пользователь keyTyped(KeyEvent) нажимает клавишу Нажатия и отпускания кнопок мыши. mouseClicked(MouseEvent), Все компоненты Класс MouseEvent позволяет следить за состоянием кнопок мыши и контролировать нахождение указателя мыши в определенной области Класс события тот же — MouseEvent, mouseMoved(MouseEvent) но вот слушатель называется подругому — MouseMotionListener Прокрутка колесика мыши. mouseWheelMoved(MouseWheelEvent) Все компоненты Класс MouseWheelEvent Добавление или удаление componentAdded(ContainerEvent), Все контейнеры (наследники компонентов в контейнере. componentRemoved(ContainerEvent) класса java.awt.Container) Класс ContainerEvent Класс Window/Event Позволяет windowClosed(WindowEvent), узнать о перемещении, свертывании, windowClosing(WindowEvent), закрытии окна и т. д windowDeactivated(WindowEvent), Вы можете видеть, что в таблице нет названий слушателей и методов, предназначенных для добавления и удаления этих слушателей. Такие названия легко получить самим: достаточно вспомнить простые правила именования, которые мы рассмотрели в предыдущем разделе. Используя данную таблицу, можно написать слушатель для любого низкоуровневого события (в таблице описаны не все, а только наиболее важные низкоуровневые события; компоненты поддерживают еще несколько событий, которые используются крайне редко, вы можете познакомиться с ними в интерактивной документации). Чтобы окончательно убедиться в том, что события эти на самом деле возникают и компоненты сообщают - о них слушателям, рассмотрим небольшой пример.

// LowLevelEvents.java // Наблюдение за основными низкоуровневыми событиями import javax.swing.*;

import java.awt.event.*;

public class LowLevelEvents extends JFrame { setDefaultCloseOperation(EXIT_ON_CLOSE);

getContentPane().add(new JScrollPane(out = new JTextArea()));

// сюда мы будем выводить информацию // внутренний класс - слушатель событий class OurListener implements MouseListener, KeyListener, MouseMotionListener, MouseWheelListener, FocusListener { Это единственное исключение из правила именования событий JavaBeans. По идее, для класса MouseEvent слушатель должен был бы называться MouseListener (как в предыдущей строке таблицы), но разработчики решили разбить этот слушатель на два.

Это событие поддерживается, только начиная с пакета JDK 1.4.

public void mouseWheelMoved(MouseWheelEvent e) public static void main(String[] args) { В этом примере мы создаем окно, добавляем в центр текстовое поле (помещенное в панель прокрутки JScrollPane, подробнее о ней мы узнаем в главе 11), а в нижнюю часть окна помещаем простую кнопку JButton, которая и будет служить источником событий. Далее к кнопке присоединяются слушатели разнообразных событий. Интерфейсы слушателей реализованы в классе OurListener. В примере этот класс реализует обязанности сразу пяти слушателей, так что методов в нем довольно много. Каждый из этих методов просто выводит информацию о происшедшем событии в текстовое окно, где вы и можете ее изучить. Запустив приложение и совершая с кнопкой различные действия, вы сможете своими глазами увидеть, когда, как и какие события в ней происходят (это как раз тот случай, когда лучше не изучать исходный текст примера, а посмотреть, как он работает). После того как вы насладитесь этим примером, мы познакомимся с самыми важными высокоуровневыми событиями. Они обрабатываются точно так же, как низкоуровневые (помните, разделение событий на две категории условно, их обработка ничем не отличается, разными являются лишь источники этих событий). Высокоуровневые события создаются (запускаются) самими компонентами и обозначают наиболее важные изменения в этих компонентах. У каждого компонента чаще всего имеются собственные события (у кнопки — это нажатие, у флажка — установка, у списка — выбор нового элемента), поэтому высокоуровневых событий очень много. Рассматривать их в отдельности от компонентов не имеет смысла, так что каждый раз при знакомстве с новым компонентом мы будем говорить и о том, какие события могут в нем возникать.

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

Таблица 2.2. Наиболее часто используемые высокоуровневые события.

Событие PropertyChangeEvent. propertyChange(PropertyChangeEvent) Практически все графические Событие ActionEvent. actionPerformed(ActionEvent) Компоненты Swing, у которых есть Описанные в таблице события PropertyChangeEvent и ChangeEvent в обычных программах почти не используются, однако именно с их помощью происходит взаимодействие между компонентами, их UI-представителями и моделями. Событие PropertyChangeEvent — это основополагающее событие архитектуры JavaBeans, оно позволяет следить за тем, как и какие свойства меняются в компоненте (так называемые привязанные свойства). Мы уже упоминали в главе 1, что все свойства компонентов Swing являются привязанными. Это не только позволяет использовать их в визуальных средствах, но и дает возможность моделям и UI-представителям эффективно взаимодействовать друг с другом.

Событие ChangeEvent — это более простое событие, которое также дает знать об изменениях состояния компонента или модели. Довольно часто модели запускают это событие, сообщая об изменениях в данных.

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

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

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

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

В этом разделе мы рассмотрим наиболее популярные варианты обработки событий с помощью слушателей.

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

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

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

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

Попробуем использовать адаптеры в следующем примере.

// Adapters.java // Использование адаптеров вместо интерфейсов import javax.swing.*;

import java.awt.event.*;

public class Adapters extends JFrame { setDefaultCloseOperation(EXIT_ON_CLOSE);

// наследуем от адаптера class MouseL extends MouseAdapter { public static void main(String[] args) { В примере создается небольшое окно, к которому добавляется слушатель событий от мыши MouseListener. Однако реализовывать интерфейс слушателя мы не стали, а создали новый внутренний класс MouseL, унаследованный от класса адаптера MouseAdapter. Вместо того чтобы определять все методы слушателя (а их ни много, ни мало — целых пять, можете убедиться в этом, заглянув в таблицу 2.1), мы переопределили только один. Здесь нас интересовало только, когда пользователь щелкает кнопками мыши в окне, так что нам пригодился метод mouseClicked(). Остальные четыре метода остались в стороне, как будто их и не было.

Для всех низкоуровневых событий из пакета java.awt.event, слушатели которых состоят более чем из одного метода, имеются адаптеры. Чаще всего они и используются при обработке событий, и только в тех редких случаях, когда программу интересуют все события определенного рода, задействуются интерфейсы. Узнать название класса адаптера очень просто: если слушатель называется XXXListener, то имя адаптера выглядит как XXXAdapter 16. Но вот для высокоуровневых событий компонентов Swing имеются только слушатели, адаптеров для них вы не найдете. Видимо, разработчики решили, что если хоть какое-то событие от компонента обрабатывается, то информация о нем нужна полная.

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

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

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

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

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

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

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

// InnerClassEvents.java // Внутренние классы для обработки событий import javax.swing.*;

import java.awt.*;

import java.awt.event.*;

public class InnerClassEvents extends JFrame { public InnerClassEvents() { setDefaultCloseOperation(EXIT_ON_CLOSE);

getContentPane().setLayout(new FlowLayout());

getContentPane().add(text = new JTextField(10));

getContentPane().add(button = new JButton("Нажмите"));

// класс - слушатель нажатия на кнопку class ButtonL implements ActionListener { public static void main(String[] args) { В примере показана классическая ситуация: имеется текстовое поле и кнопка - мы добавили их в панель содержимого (content pane) нашего окна, предварительно установив для нее последовательное расположение компонентов (подробнее про расположение компонентов будет рассказано в главе 5).

Пользователь что-то вводит в поле и щелкает на кнопке, а программа должна обработать введенные им данные. Использование внутреннего класса для обработки события щелчка на кнопке (слушателя ActionListener) дает нам возможность без помех получить доступ к текстовому полю text и содержащийся в нем текст. Используй мы отдельный класс, нам пришлось бы каким-то образом заполучить ссылку на объект нашего окна, более того, в классе окна InnerClassEvents нам пришлось бы либо объявить текстовое поле открытым для доступа (public), либо добавить новый метод, возвращающий текст, набранный в текстовом поле.

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

«Быстро и грязно»

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

Давайте рассмотрим пример.

// AnonymousClassEvents.java // Анонимные классы для обработки событий import javax.swing.*;

import java.awt.event.*;

public class AnonymousClassEvents extends JFrame { public AnonymousClassEvents() { button.addActionListener(getButtonL());

// этот метод создает слушателя для кнопки public ActionListener getButtonL() { public static void main(String[] args) { В этом очень простом примере создается окно, в которое помещается кнопка. Для обработки закрытия окна мы создаем собственного слушателя оконных событий WindowEvent и делаем это с помощью анонимного класса, который наследует от класса WindowAdapter и при закрытии окна (методом windowClosing()) завершает работу приложения. Все происходит прямо на месте: и регистрация слушателя, и создание слушателя, и его описание. Пожалуй, быстрее обработать событие невозможно. Однако легко видеть, что получавшийся код весьма запутан и плохо управляем: нет никакой возможности получить ссылку на объект-слушатель, нельзя унаследовать от него, анонимный класс не может получить доступ к членам класса, которые не были объявлены неизменными (final). Есть немного более удобный способ работы с анонимными слушателями — их можно создавать в специальных методах. В нашем примере — это метод getButtonL(), возвращающий слушателя нажатий кнопки (который просто выводит сообщение о нажатии в стандартный поток вывода). Здесь уже чуть больше возможностей и удобства: класс находится в отдельном методе, метод легко найти, его можно переопределить.

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

Фабрика классов — повышение гибкости Мы уже видели, что использование анонимных классов запутывает и усложняет код. Именованные внутренние классы позволяют более четко структурировать код: они имеют имена и отделены от основного класса. Обновлять их также не сложно: Java позволяет наследовать от внутренних классов и переопределять их методы. Однако наследование от внутренних классов не так просто и понятно, как наследование от обычных. Оно имеет довольно необычный синтаксис и может ввести в заблуждение человека, поддерживающего код, к тому же между внутренним и внешним классами могут нарушиться некоторые связи, которые «снаружи» просто не видны.

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

// FactoryEvents.java // Использование фабрики классов повышает гибкость программы import javax.swing.*;

import java.awt.event.*;

public class FactoryEvents extends JFrame { private ListenerFactory factory = new ListenerFactory();

public FactoryEvents() { addWindowListener(factory.getWindowL());

button.addActionListener(factory.getButtonL());

public static void main(String[] args) { // фабрика классов class ListenerFactory { // этот метод создает слушателя для кнопки public ActionListener getButtonL() { // слушатель оконных событий public WindowListener getWindowL() { class WindowL extends WindowAdapter { Пример практически полностью повторяет предыдущий: у нас имеется окно и в нем кнопка, только на этот раз слушатели событий не создаются в месте создания пользовательского интерфейса. Вместо этого используется фабрика классов ListenerFactory, которая и снабжает класс нашего окна всеми необходимыми слушателями. Для того чтобы изменить поведение приложения, нам необходимо изменить методы фабрики, не заботясь о том, как и где реализованы слушатели и пользовательский интерфейс. Вы можете резонно возразить: «Если фабрика полностью отделена от пользовательского интерфейса, как же получать информацию о состоянии компонентов интерфейса, выделенных элементах и прочих вещах?» Но не забывайте о моделях — мощном средстве Swing, позволяющем полностью отделять места создания и настройки пользовательского интерфейса от мест, в которых обрабатываются данные. Модели дают возможность работать с данными и не думать об интерфейсе, вы можете передавать ссылки на используемые вашей программой модели в фабрику слушателей.

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

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

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

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

Чтобы все стало окончательно ясно, рассмотрим небольшой пример.

// ForwardingEvents.java // Техника диспетчеризации событий import javax.swing.*;

import java.awt.*;

import java.awt.event.*;

public class ForwardingEvents extends JFrame { public ForwardingEvents() { setDefaultCloseOperation(EXIT_ON_CLOSE);

getContentPane().setLayout(new FlowLayout());

button1.addActionListener(forwarder);

button2.addActionListener(forwarder);

JButton button1, button2;

// класс - слушатель нажатия на кнопку class Forwarder implements ActionListener { // обработка события от кнопки "ОК" public void onOK(ActionEvent e) { // обработка события от кнопки "Отмена" public void onCancel(ActionEvent e) { public static void main(String[] args) { В данном примере создается окно, в которое помещено две кнопки. К каждой кнопке присоединен слушатель событий Forwarder, следящий за нажатиями кнопок, причем слушатель этот создается только один раз (что без сомнений позволяет экономить память). В самом слушателе проделывается немудреная работа: при возникновении события от кнопки выясняется, в какой именно кнопке произошло это событие, после чего вызывается метод с соответствующим названием. Слушатель Forwarder можно расширить, чтобы он поддерживал гораздо большее число кнопок, и при этом не придется создавать новые классы — достаточно будет лишь определить новые методы. Если в дальнейшем понадобится модифицировать работу приложения, это будет несложно сделать: надо унаследовать новый класс от класса окна и переопределить интересующие нас методы, например onОК().

Диспетчеризация имеет свои преимущества: код получается более компактным и в некотором смысле более привычным для тех программистов, что перешли на Java с других языков программирования, где обработка событий осуществляется именно в методах, а не в отдельных классах. Именно такая иллюзия и создается в результате использования такой техники: мы видим несколько методов, вызываемых при возникновении событий, и реализуем обработку этих событий, переопределяя методы. Однако есть здесь и свои ограничения: то, как события рассылаются по методам, целиком зависит от классов слушателей, подобных Forwarder, и если событие от какого-то компонента не обрабатывается этим классом, вам остается лишь развести руками и писать слушатель самому. Если компонентов в интерфейсе достаточно много и для каждого из них создается свой метод, обрабатывающий некоторое событие, получится гигантский класс, «битком набитый» методами, а работать с такими классами всегда неудобно; более того, появление таких классов свидетельствует о нарушении основополагающего правила объектно-ориентированного программирования: каждый класс решает собственную небольшую задачу.

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

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

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

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

Теперь надо вспомнить, как в Java работает сборщик мусора. Хорошо известно, что созданные объекты в Java не нужно явно удалять: об этом заботится сборщик мусора. Он работает в фоновом режиме параллельно с программой, периодически включается и производит удаление объектов, на которые не осталось явных ссылок. Здесь-то нас и поджидает сюрприз — все те графические компоненты, которые визуальное средство удалило из контейнера, не удаляются сборщиком мусора, потому что в них еще имеются явные ссылки на слушателей событий, ранее присоединенных визуальным средством. И чем больше будет работать программа, чем интенсивнее пользователь будет создавать интерфейсы, тем меньше останется памяти, и, в конце концов, все может завершиться аварийным завершением программы с потерей несохраненных данных.

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

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

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

Впрочем, как бы ни были хороши стандартные компоненты библиотеки, рано или поздно возникают ситуации, когда нужные нам возможности они обеспечить не могут. В таком случае придется создать собственный компонент, унаследовать его от какого-либо компонента библиотеки, или написав «с нуля» (то есть, унаследовав от базового компонента JComponent библиотеки Swing, или если вы хотите создать компонент «с чистого листа», от базового класса Component библиотеки AWT). У вашего нового компонента, если он выполняет не самые простые функции, наверняка будут какие-то события, и программистам-клиентам компонент, (если компонент окажется удачным, возможности для его многократного использования обязательно найдутся) необходимо предоставить способ обработки этих событий. Для компонента, соответствующего архитектуре JavaBeans, это означает наличие интерфейса слушателя, класса события и пары методов для присоединения и удаления слушателей. Чуть раньше мы кратко обсудили систему именования событий JavaBeans и правила, которым подчиняются слушатели и классы событий. Давайте попробуем создать свой компонент и новый тип события. Следование архитектуре JavaBeans к тому же позволит использовать новый компонент и его события в визуальных средствах разработки.

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

Прежде всего, необходимо создать класс события. Как вы помните из описания схемы событий JavaBeans, этот класс должен быть унаследован от класса java.util.EventObject и иметь название вида XXXEvent, где XXX — название события. Вот что получается для нашей кнопки:

// com/porty/swing/event/ButtonPressEvent.java // Класс события для кнопки SimpleButton package com.porty.swing.event;

import java.util.EventObject;

public class ButtonPressEvent extends EventObject { // конструктор. Требует задать источник события public ButtonPressEvent(Object source) { В нашем событии не будет храниться никакой дополнительной информации, так что класс события чрезвычайно прост. Заметьте, что конструктор класса требует указать источник события; как правило, это компонент, в котором событие произошло. Источник события нужно задавать для любого события, унаследованного от класса EventObject, а получить его позволяет метод getSource() того же базового класса. Таким образом, при обработке любого события JavaBeans вы можете быть уверены в том, что источник этого события всегда известен. Напоследок обратите внимание, что класс нашего нового события разместился в пакете com.porty.swing.event, так проще организовать к нему доступ.

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

Далее нам нужно описать интерфейс слушателя нашего события. Данный интерфейс будут реализовывать программисты-клиенты компонента, заинтересованные в нажатиях кнопки. Интерфейс слушателя, следующего стандарту JavaBeans, должен быть унаследован от интерфейса Java.util.EventListener. В последнем нет ни одного метода, он служит «отличительным знаком», показывая, что наш интерфейс описывает слушателя событий. Итак:

// com/porty/swing/event/ButtonPressListener.java // Интерфейс слушателя события нажатия кнопки package com.porty.swing.event;

import java.util.EventListener;

public interface ButtonPressListener extends EventListener { // данный метод будет вызываться при нажатии кнопки void buttonPressed(ButtonPressEvent e);

В интерфейсе слушателя мы определили всего один метод buttonPressed(), который и будет вызываться при нажатии нашей кнопки. В качестве параметра этому методу передается объект события ButtonPressEvent, так что заинтересованный в нажатии кнопки программист, реализовавший интерфейс слушателя, будет знать подробности о событии. Интерфейс слушателя, так же как и класс события, разместился в пакете com.porty.swing.event.

Теперь нам остается включить поддержку события в класс самого компонента. Для этого в нем нужно определить пару методов для присоединения и отсоединения слушателей ButtonPressListener, эти методы должны следовать схеме именования событий JavaBeans. В нашем случае методы будут именоваться addButtonPressListener() и removeButtonPressListener(). Слушатели, которых программисты регистрируют в данных методах, будут оповещаться о нажатии кнопки. Существует два основных способа регистрации слушателей в компоненте и оповещения их о происходящих событиях.

• Регистрация единичного (unicast) слушателя. В классе компонента определяется единственная ссылка на слушателя события, так что узнавать о событии может только один слушатель одновременно. При присоединении нового слушателя старый слушатель, если он был, перестает получать оповещения о событиях, так как ссылка на него теряется.

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

// com/porty/swing/SimpleButton.java // Пример компонента со своим собственным событием package com.porty.swing;

import javax.swing.*;

import java.awt.*;

import java.awt.event.*;

import java.util.*;

import com.porty.swing.event.*;

public class SimpleButton extends JComponent { private ArrayList listenerList = new ArrayList();

// один объект-событие на все случаи жизни private ButtonPressEvent event = new ButtonPressEvent(this);

// конструктор - присоединяет к кнопке слушателя события от мыши // присоединяет слушателя нажатия кнопки public void addButtonPressListener(ButtonPressListener l) { // отсоединяет слушателя нажатия кнопки public void removeButtonPressListener(ButtonPressListener l) { public void paintComponent(Graphics g) { g.draw3DRect(0, 0, getWidth(), getHeight(), true);

// оповещает слушателей о событии protected void fireButtonPressed() { while ( i.hasNext() ) ((ButtonPressListener)i.next()).buttonPressed(event);

// внутренний класс, следит за нажатиями мыши class PressL extends MouseAdapter { Мы создаем очень простой компонент SimpleButton: это прямоугольник с рамкой, который, тем не менее, обладает собственным событием ButtonPressEvent. Компонент унаследован нами от базового класса JComponent библиотеки Swing, так что все действия, связанные с его прорисовкой, мы поместили в метод paintComponent() (подробнее о базовом компоненте Swing и реализованной в нем системе прорисовки мы узнаем в главе 3). В конструкторе происходит настройка основных механизмов компонента: мы присоединяем к нему слушателя событий от мыши, реализованного во внутреннем классе PressL, а также задаем размер нашего компонента методом setPreferredSize() (по умолчанию размеры компонента считаются нулевыми). В слушателе PressL мы будем отслеживать нажатия кнопок мыши, для этого нам понадобится метод mousePressed(). Как только пользователь щелкнет кнопкой мыши в области, принадлежащей нашему компоненту, будет вызван метод fireButtonPressed(), обязанностью которого является оповещение слушателей о событии. Кстати, название вида fireXXX() (или fireXXXEvent()) является неофициальным стандартом для методов, «запускающих» высокоуровневые события, хотя такие методы и не описаны в спецификации JavaBeans. Мы еще не раз встретим методы с такими названиями при работе с событиями различных компонентов Swing и их моделями.

Слушатели ButtonPressListener будут храниться в списке ArrayList. Благодаря мощи стандартного списка ArrayList методы для присоединения и отсоединения слушателей совсем просты: им нужно лишь использовать соответствующие возможности списка. Также просто и оповещение слушателей о событиях: мы получаем итератор Iterator для перемещения по списку и для каждого элемента списка (а в нем, как мы знаем, хранятся только слушатели) вызываем метод buttonPressed(), передавая ему в качестве параметра объект-событие. Объект-событие у нас один на компонент, и именно он передается всем слушателям: на самом деле, зачем создавать для каждого слушателя новое событие, если в нем не хранится ничего, кроме ссылки на источник события, то есть на сам компонент. Мы не включили в компонент поддержку многозадачности: если регистрировать слушателей будут несколько потоков одновременно, у нашего компонента могут возникнуть проблемы (для эффективной работы список ArrayList рассчитан на работу только с одним потоком в каждый момент времени). Но исправить это легко: просто объявите методы для присоединения и отсоединения слушателей как synchronized. Аналогичное действие проделайте и с методом fireButtonPressed() (он тоже работает со списком). Есть и еще один способ включить для компонента поддержку многозадачного окружения: с помощью класса Java.utilCollections и статического метода synchronizedList(). Последний метод вернет вам версию списка ArrayList со встроенной поддержкой многозадачности.

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

// SimpleButtonTest.java // Обработка события нового компонента import javax.swing.*;

import com.porty.swing.*;

import com.porty.swing.event.*;

public class SimpleButtonTest extends JFrame { public SimpleButtonTest() { setDefaultCloseOperation(EXIT_ON_CLOSE);

button.addButtonPressListener(new ButtonL());

JPanel contents = new JPanel();

contents.add(button);

class ButtonL implements ButtonPressListener { public static void main(String[] args) { В примере мы создаем небольшое окно, в панели содержимого которого разместится наш новый компонент, простая «кнопка» SimpleButton (в качестве панели содержимого используется отдельная панель JPanel). К нашей кнопке мы присоединяем два слушателя ButtonPressListener: один слушатель описан прямо на месте, в виде анонимного класса, а второй реализован во внутреннем классе. После добавления кнопки в окно последнее выводится на экран. Запустив программу с примером, вы увидите (посмотрев на консольный вывод), как слушатели оповещаются о событии. Подобная цепочка действий повторяется для любого нового события JavaBeans: вы описываете класс события и интерфейс его слушателя, следуя хорошо известным правилам, и добавляете в компонент пару методов для регистрации слушателей и их отсоединения. Для хранения слушателей используется подходящий список, все хранящиеся в нем слушатели оповещаются о возникновении события.

Список EventListenerList В пакете javax.swing.event, который предназначен для хранения событий и слушателей компонентов библиотеки Swing, есть интересный инструмент — специализированный список EventListenerList.

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

Для того чтобы можно было легко различать слушателей разных типов, при добавлении в список каждому слушателю требуется сопоставить его «тип», который представлен объектом Class. Таким образом, список EventListenerList состоит из пар вида «объект Class — слушатель EventListener». Для добавления в список новой такой пары служит метод add(). Если бы мы в только что разобранном нами примере использовали для хранения слушателей вместо ArrayList список EventListenerList, то для присоединения слушателей нам понадобилось бы написать следующий код:

listenerList.add(ButtonPressListener.class, 1);

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

EventListener[] listeners = listenerList.getListeners(ButtonPressListener.class);

С полученным массивом слушателей легко работать: для каждого элемента массива (все элементы массива имеют базовый тип EventListener) вы можете смело провести преобразование к указанному вами в методе getListeners() типу слушателя (у нас это ButtonPressListener) и вызвать определенные в интерфейсе слушателя методы, которые и сообщат слушателям о том, что в компоненте произошли некоторые события. Помимо метода getListeners() можно также использовать метод getListenersList(), возвращающий все содержащиеся в списке пары Class — EventListener в виде массива. С массивом, полученным из метода getListenersList(), работать сложнее: приходится самостоятельно проверять, принадлежит ли слушатель нужному нам типу, и учитывать тот факт, что каждый слушатель хранится вместе с объектом Class. Метод getListeners() без сомнения проще и удобнее.

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

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

За кулисами системы обработки событий Давайте подведем маленький итог: события могут возникать в любом компоненте Swing; для того чтобы узнать о них, надо создать соответствующего слушателе событий и зарегистрировать его в компоненте. В большинстве случаев обработка событий не заходит дальше этой простой цепочки действий. Однако иногда возникают более сложные задачи, требующие более изощренного контроля над происходящими событиями, например, если вам понадобится отфильтровать некоторые события или перехватить их раньше, чем они дойдут до слушателей. В этом разделе мы познакомимся с «начинкой» системы обработки событий Swing и посмотрим, как она функционирует.

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

Давайте посмотрим, как низкоуровневые события попадают к компонентам Java (к которым относятся и компоненты Swing).

Поток EventDispatchThread и очередь событий EventQueue Вспомним, как запускается Java-программа: вызывается статический метод main(). При этом виртуальная машина создает новый поток выполнения (который так и называется «main») и передает ему управление. Самое интересное происходит, когда в методе main() создается объект, каким-либо образом связанный с графической системой Java, то есть унаследованный от базового класса java.awt.Component. При этом происходит целая череда событий.

1. Инициализируется динамическая библиотека Java, связывающая графическую подсистему Java с операционной системой.

2. Создаются необходимые помощники компонентов AWT, отвечающие за их связь с операционной системой.

3. Создается очередь событий EventQueue, хранящая происходящие в программе события.

4. Запускается еще один поток выполнения EventDispatchThread, который связывается с очередью событий EventQueue и начинает рассылать события, хранящиеся в этой очереди событий, по соответствующим компонентам AWT.

Если с первыми двумя пунктами все более или менее понятно (если вы помните, мы обсуждали помощников компонентов AWT в главе 1), то последние два пункта стоит рассмотреть поподробнее.



Pages:     | 1 || 3 | 4 |   ...   | 5 |
Похожие работы:

«РОССИЙСКИЙ ГОСУДАРСТВЕННЫЙ ОТКРЫТЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ ПУТЕЙ СООБЩЕНИЯ 9/1/1 Одобрено кафедрой Утверждено Эксплуатация деканом факультета железных дорог Управление процессами перевозок ЕДИНАЯ ТРАНСПОРТНАЯ СИСТЕМА Рабочая программа для студентов V курса специальности 190701 ОРГАНИЗАЦИЯ ПЕРЕВОЗОК И УПРАВЛЕНИЕ НА ТРАНСПОРТЕ (ЖЕЛЕЗНОДОРОЖНЫЙ ТРАНСПОРТ) (Д) Москва – 2008 Программа составлена на основании примерной учебной программы данной дисциплины, составленной в соответствии с...»

«Государственное образовательное учреждение высшего профессионального образования МОСКОВСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ЛЕСА А.С. Летин, О.С.Летина МАШИННАЯ ГРАФИКА АвтоКАД Рекомендовано к изданию УМО по образованию в области лесного дела в качестве учебника для студентов вузов, обучающихся по направлениям подготовки 250400.62 Технология лесозаготовительных и деревоперерабатывающих производств, 151000.62 Технологические машины и оборудование. 2-е издание Издательство Московского...»

«Марышева Татьяна Николаевна АРХИТЕКТУРНО-ПЛАНИРОВОЧНЫЕ ОСНОВЫ ФОРМИРОВАНИЯ И РАЗВИТИЯ МЕТРОПОЛИТЕНА В ГОРОДЕ РОСТОВЕ-НА-ДОНУ направление 27300.68 Архитектура, магистерская программа Теория градостроительства и районная планировка Автореферат диссертации на соискание академической степени магистра архитектуры Ростов-на-Дону 2011 Работа выполнена в институте архитектуры и искусства ФГАОУ ВПО Южный федеральный университет на кафедре градостроительства. Научный руководитель :...»

«ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ БЕЛГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ УНИВЕРСИТЕТ (НИУ БелГУ) ФАКУЛЬТЕТ БИЗНЕСА И СЕРВИСА УТВЕРЖДАЮ Зав. кафедрой технологии продуктов питания и сферы услуг наименование кафедры Н.И. Мячикова подпись расшифровка подписи _._._ ПРОГРАММА государственного экзамена по специальности Технология продуктов общественного питания 260501. шифр наименование специальности /...»

«ДЕПАРТАМЕНТ ОБРАЗОВАНИЯ ГОРОДА МОСКВЫ ЗАПАДНОЕ ОКРУЖНОЕ УПРАВЛЕНИЕ ОБРАЗОВАНИЯ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ГОРОДА МОСКВЫ СРЕДНЯЯ ОБЩЕОБРАЗОВАТЕЛЬНАЯ ШКОЛА № 1471 РАБОЧАЯ ПРОГРАММА Предмет: биология Класс: 6-9 Учитель: Спирина Елена Александровна Учебный год: 2014-2015 Пояснительная записка Программы составлены в соответствии с требованиями федерального компонента государственного образовательного стандарта начального общего, основного общего, среднего (полного) общего...»

«ДЕПАРТАМЕНТ ВНУТРЕННЕЙ И КАДРОВОЙ ПОЛИТИКИ БЕЛГОРОДСКОЙ ОБЛАСТИ ПРИКАЗ 03 Q 2014 г. О конкурсе на соискание гранта на разработку научной технологии реализации Стратегии Формирование регионального солидарного общества на 2011-2025 годы В целях стимулирования фундаментальных и прикладных научных исследований, направленных на исследование технологий улучшения социального самочувствия населения Белгородской области, п р и к а з ы в а ю : 1. Управлению массовых коммуникаций и общественных отношений...»

«СРЕДНЕЕ ПРОФЕССИОНАЛЬНОЕ ОБРАЗОВАНИЕ А. П. ПАНФИЛОВА ПСИХОЛОГИЯ ОБЩЕНИЯ Рекомендовано Федеральным государственным автономным учреждением Федеральный институт развития образования (ФГАУ ФИРО) в качестве учебника для использования в учебном процессе образовательных учреждений, реализующих программы СПО по укрупненной группе специальностей 050000 Образование и педагогика Регистрационный номер рецензии 654 от 18 декабря 2012 г. ФГАУ ФИРО УДК 159.9(075.32) ББК 88.53я723 П167 Рецензент —...»

«Практические вопросы подтверждения легальности происхождения древесины и рекомендации по усовершенствованию процедур (на примере цепочек поставок из России в Китай) ЛЕСНАЯ ПРОГРАММА WWF РОССИИ Москва, 2008 Практические вопросы подтверждения легальности происхождения древесины и рекомендации по усовершенствованию процедур (на примере цепочек поставок из России в Китай) Москва Июнь 2008 Лесная программа WWF России С 1988 года WWF ведет проекты по сохранению лесов и устойчивому лесоуправлению...»

«ПРОГРАММА вступительного экзамена по образовательным программам высшего образования– программам подготовки научно-педагогических кадров в аспирантуре по направлению подготовки - 14.06.01 Ядерная, тепловая и возобновляемая энергетика и сопутствующие технологии (очная и заочная форма обучения) направленность (профиль): 05.14.02 Электрические станции и электроэнергетические системы Содержание вступительного экзамена № п/п Наименование раздела дисциплины Содержание Раздел 1. Электрическая часть...»

«ПРОГРАММА вступительного экзамена в аспирантуру по специальностям 05.13.05 - Элементы и устройства вычислительной техники и систем управления 05.13.15 - Вычислительные машины, комплексы и компьютерные сети 05.13.17 - Теоретические основы информатики 1. Теоретические основы проектирования элементов, устройств, систем и сетей ВТ 1.1. Математические методы описания и анализа дискретных процессов функционирования элементов и устройств. Алгебраические системы. Множества и операции над ними....»

«МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ (Минобрнауки России) ПРИКАЗ от 06 мая 2005 г. № 137 Об использовании дистанционных образовательных технологий Опубликовано 16 августа 2005 г. Вступает в силу с 27 августа 2005 г. Зарегистрирован в Минюсте РФ 2 августа 2005 г. Регистрационный N 6862 В соответствии с Законом Российской Федерации от 10 июля 1992 года N 3266-1 Об образовании (в редакции Федерального закона от 13 января 1996 г. N 12-ФЗ) (Ведомости Съезда народных депутатов...»

«CBD Distr. GENERAL КОНВЕНЦИЯ О БИОЛОГИЧЕСКОМ UNEP/CBD/MYPOW/4 РАЗНООБРАЗИИ 7 January 2003 RUSSIAN ORIGINAL: ENGLISH МЕЖСЕССИОННОЕ СОВЕЩАНИЕ ОТКРЫТОГО СОСТАВА ПО МНОГОЛЕТНЕЙ ПРОГРАММЕ РАБОТЫ КОНФЕРЕНЦИИ СТОРОН НА ПЕРИОД ДО 2010 ГОДА Монреаль, 17 - 20 марта 2003 года Пункт 5 предварительной повестки дня* МНОГОЛЕТНЯЯ ПРОГРАММА РАБОТЫ КОНФЕРЕНЦИИ СТОРОН НА ПЕРИОД ДО 2010 ГОДА Записка Исполнительного секретаря I. ВВЕДЕНИЕ 1. В пункте 1 своего решения VI/28 Конференция Сторон поручила Исполнительному...»

«МИНИСТЕРСТВО СЕЛЬСКОГО ХОЗЯЙСТВА РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования Саратовский государственный аграрный университет имени Н.И. Вавилова СОГЛАСОВАНО УТВЕРЖДАЮ Заведующий кафедрой Декан факультета /Морозов А.А./ /Гиро Т.М./ 30 августа 2013 г. 30 августа 2013 г. РАБОЧАЯ ПРОГРАММА ДИСЦИПЛИНЫ (МОДУЛЯ) Дисциплина ОБЩАЯ ТЕХНОЛОГИЯ ОТРАСЛИ Направление 260200.62 Продукты питания животного подготовки...»

«Утверждаю Директор школы М.П. Кокина Основная образовательная программа начального общего образования муниципального бюджетного общеобразовательного учреждения Елизаветинская средняя общеобразовательная школа ( с изменениями согласно приказа Министерства образования и науки Российской Федерации от 22 сентября 2011г. № 2357) Рассмотрена Утверждена на заседании Методического совета школы на заседании Педагогического совета школы (протокол № 1 от 29.08.2012 г.) (протокол № 1 от 30.08.2012 г.)...»

«ФЕДЕРАЛЬНОЕ АГЕНТСТВО ВОЗДУШНОГО ТРАНСПОРТА ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ МОСКОВСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ ГРАЖДАНСКОЙ АВИАЦИИ (МГТУ ГА) УТВЕРЖДАЮ Проректор по УМР В.В. Криницин _2010 г. РАБОЧАЯ УЧЕБНАЯ ПРОГРАММА ДИСЦИПЛИНЫ Конструкция и прочность летательных аппаратов (СД.05) (наименование, шифр по ГОС) Направление 160900 – Эксплуатация и испытания авиационной (шифр по ГОС) и космической техники Факультет...»

«Частное учреждение образования Минский институт управления УТВЕРЖДАЮ Ректор Минского института управления _Суша Н.В. _ 2010 г. Регистрационный № УД-_ЭУП/р Экономика малого бизнеса Учебная программа для специальности 1-25 01 07 Экономика и управление на предприятии Факультет экономики Кафедра экономики и управления производством Курс 5 Семестр 9 Лекции - 34 часов Экзамен – 9 семестр Практические (семинарские) занятия - 20 часа Зачет - нет Всего аудиторных часов по дисциплине – 54 часа Курсовой...»

«Региональный научный форум Мать и дитя 20 - 22 марта 2007 КАЗАНЬ, Казанская ярмарка Care ОРГАНИЗАТОРЫ Mother & Child Министерство здравоохранения и социального развития Российской Федерации ФГУ Научный центр акушерства, гинекологии и РЕГИОНАЛЬНЫЙ НАУЧНЫЙ ФОРУМ перинатологии Федерального агентства по высокотехнолоМАТЬ И ДИТЯ гичной медицинской помощи Российское общество акушеров-гинекологов Международная медицинская Министерство здравоохранения республики Татарстан специализированная выставка...»

«Приложение 8Б: Рабочая программа факультативной дисциплины Психология образовательной среды ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ ПЯТИГОРСКИЙ ГОСУДАРСТВЕННЫЙ ЛИНГВИСТИЧЕСКИЙ УНИВЕРСИТЕТ Утверждаю Проректор по научной работе и развитию интеллектуального потенциала университета профессор З.А. Заврумов _2012 г. Аспирантура по специальности 19.00.07 Педагогическая психология отрасль науки: 19.00.00 Психологические науки Кафедра...»

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

«Пресс-релиз 11 июля 2012 года в 12.00 час.в Кабинете Министров Республики Татарстан (зал заседаний, 3 этаж) в режиме видеоконференции состоится заседание антинаркотической комиссии в Республике Татарстан.В заседании комиссии принимают участие члены антинаркотической комиссии в Республике Татарстан, руководители федеральных и республиканских министерств и ведомств, руководители исполнительных комитетов муниципальных районов и городских округов, руководители ссузов и лидеры добровольческих...»






 
2014 www.av.disus.ru - «Бесплатная электронная библиотека - Авторефераты, Диссертации, Монографии, Программы»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.