Обработка событий

В

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

■ Основы обработки событий: вы освоите механизм, с помощью которого JBuilder позволяет обрабатывать самые основные события, генерируемые кон­кретными классами. Например, вы узнаете, как можно отреагировать на щел­чок на компоненте JButton. Что еще более важно, вы узнаете, как устроен код, поддерживающий этот тип простых обработчиков событий, и ознакоми­тесь с различными опциями, необходимыми для их реализации. В частности, вы сможете исследовать синтаксис и анонимных, и стандартных классов- адаптеров.

■ Поддержка интерфейсов слушателей: вы познакомитесь с классами, поддержи­вающими интерфейс, подобными MouseListener, KeyListener ИЛИ классу MouseWheelListener Из JDK 1.4. C помощью этих интерфейсов классы могут реагировать на многие виды событий.

■ Генерация событий: вы создадите класс, который может не только реагировать на события, но и генерировать их. Эта технология может применяться как ва­риант слабо связной коммуникации (loosely coupled communication) между классами программы. Такого рода слабая связность высоко ценится в мире объектно-ориентированного программирования, т. к. она способствует по­вторному использованию кода и разработке устойчивых архитектур.

■ События пользователя: вы узнаете, как создавать свои события, предназначен­ные для передачи специальных типов информации.

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

События в Java не всегда легки для понимания, особенно для новичков в языке. Уровень сложности бывает довольно значительным, но все же не непреодолимым. Скромные усилия в этой области дадут в дальнейшем богатые результаты. Более того, JBuilder значительно упрощает эту тему по сравнению с ручным написанием кода обработки событий.

примечаниеНе все технологии, рассматриваемые в данной главе, поддерживаются в JBuiIder редакции Personal. Но я покажу весь код, генерируемый мастерами JBuiIder. При необходимости вы сможете просто ввести требуемый код вручную. Класс MouseWheelListenec присутствует только в JDK 1.4.

Основы обработки событий

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

примечаниеВ данной главе вы узнаете, как заставить реагировать на события объект JButton. Вы также познакомитесь с объектом ActionEvent. Но здесь не будут даны описания, как сделать так, чтобы JButton реагировал на нажатия клавиш или их комбина­ций, наподобие Alt+X. Я также не коснусь изменения надписи на кнопке, использования HTML-кода с кнопками и добавления на кнопки пиктограмм. Все эти темы будут рас­Смотрены позже в данной части книги, в частности, в главе 18.

рис. 14.1. элементарный проект jbuilder с кнопкой, помещенной на framelСоздайте новый проект и поместите в него новое приложение, содержащее JMenuBar. Возможность создания строки меню в составе приложения встроена в ма­стер создания приложения JBuilder, описанный в главе 3.

Проверьте, что компоновка объекта Framel Установлена по умолчанию в BorderLayout. Поместите на Framel Кнопку JButton, И проверьте, что ее привязка установлена в Center. Теперь ваше приложение состоит из одной сплошной кнопки. При его запуске оно должно выглядеть, как показано на рис. 14.1.

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

Существует два способа связать с кнопкой обработчик события:

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

2. ,Перейдите на страницу Events инспектора и щелкните на свойстве actionPerformed. Если вы щелкнете один раз, то вы сможете изменить имя ва­шего обработчика события. Пользуйтесь этим способом для задания имени
создаваемого вами обработчика события. Вы можете воспользоваться и име­нем, присвоенным по умолчанию, но лучше воспользоваться этой возможно­стью и назвать обработчик события более осмысленно. Двойной щелчок или нажатие клавиши Enter переведет вас в режим редактирования исходного кода и создаст заготовку метода.

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

Void JButtonl-actionPerfoπned(ActionEvent Е)

{

)

Поместите в этот метод какой-нибудь простой код, чтобы можно было увидеть его действие:

Void JButtonl_actionPerfonned(ActionEvent Е)

(

JOptionPane.ShowMessageDialog(This,

"Ненависть не победить ненавистью, ненависть побеждается любовью!”) ;

)

Если пользователь выбрал jButtonl, то автоматически создается метод

примечаниеJButtonl ActionPerfoπned.

примечаниеКласс JOptionPane предназначен для создания всплывающих диало­говых окон различных типов. Он содержит методы ShowMessageDialog, 3howInputDialog, ShowConfinnDialog и т. д. Эта книга посвящена JBuiIder, а не JDKf поэтому, если вы хотите узнать про этот класс больше, я предлагаю вам воспользоваться оперативной справкой или посетить сайт компании Sun. В сопровождающих книгу мате­риалах находится много примеров применения этого класса, поэтому если вы хотите Увидеть, как он работает, можете просмотреть код этих примеров.

Прежде чем перейти к следующей теме, мне придется упомянуть, что бывают случаи, что при создании обработчика событий возникают сообщения об ошиб­ках. Например, вы можете щелкнуть на jButtθ∏1 и получить совершенно бессмысленное сообщение о том, что не найдена какая-то скобка. Если вы натолкнетесь на что-либо по­добное, попробуйте закрыть файл, с которым вы работаете, и затем открыть его снова из Списка недавно открытых файлов. У меня это обычно решало проблему.

Как вызываются события

Метод JButtoni-SctionPerfonned из предыдущего раздела вызывается не не­посредственно из JDK, а из класса, встроенного в Framel. Пришло время погово­рить об этом классе.

Класс, вызывающий JButtonlSctionPerformed, Может появиться в вашем коде в одном из двух мест. Точное место его нахождения зависит от установок, выбран­ных в диалоговом окне Project Properties (Свойства проекта), которое вызывается че­рез пункт меню Project ∣ Project Properties ∣ Code Style. Это диалоговое окно, упоми­навшееся еще в главе 5, показано на рис. 14.2.

чтобы точно понять, что сейчас происходит, необходимо посмотреть на две копии полного исходного кода для jframe с расположенной на ней кнопкой и одним методом обработки события. первая версия приведена в листинге 14.1, а вторая — в листинге 14.2.
листинг 14.1. так выглядит исходный код для framel при использовании стандартных адаптеров
fr, projett f iopertws
pβthβ j oenprai j ruftj bund fcode b⅛te iirtparibv cljttor, uml, betver j
-bn>ces-i-`bemple:-. ,, t..,,. ʌ ⅛ √„, ⅛
г endofllne
,ff neritlne,'p'jpul>li< vel< feed
s 1<
∙'1<i
>
j j
rc*enthand∣lnjr -—
i c anonymoueadapter
.
<≈, standard adapter
,example:,{class Γ∙e <
j >rtvas∙ veil jbinito (
j j »
p
,mattheristingcode =ι^∙∙ <
•’ — ∙ - «...

untitled32; java.awt.*; javax.swing.*; java.awt.event.*; class frame2 extendsvlslbllttyorinstancevarlable*: {package jΓ uβegeenβ.lnstan⅛ste(..}Package import import import

Public class Frame2 extends JFrame 1

JButton JButtonl = new JButton();

Public Frame2 ()

{

Try {

Jblnit() ;

}

Catch(Exception e)

1

E. printStackTrace();

1

)

Private void jblnit() throws Exception

{

JButtonl. setText("JButtonl") ;

JButtonl. addActionLiβtener(new

Frame2_JButtonl_actionAdapter(this));

This. getContentPane().add(JButtonl, BorderLayout. CENTER);

1

Void JButtonl_actionPerformed(ActionEvent e)

{

)

ok i c⅜nce∣ ⅜ he⅜ jрис. 14.2. выбор стиля адаптеров обработки событий в диалоге project properties}

Class Frame2_JButtonl_actionAdapter implements java. awt. event. ActionListener <

Frame2 adaptee;

Frame2_JButtonl_actionAdapter(Frame2 adaptee)

{

This. adaptee = adaptee;

)

Public void actionPerformed(ActionEvent e)

1

Adaptee. JButtonl_actionPerformed(e);

)

}

Листинг 14.2. Так выглядит исходный код для Framel при использовании

Анонимных адаптеров

Package Margieapp;

Import J Ava.Awt.*;

Import j avax. swing.*;

Import java. awt. event.;

∕**

* <p>Title: <∕p>

* <p>Description: <∕p>

* <pXopyright: Copyright (c) 2002<∕p>

* <p>Company: <∕p>

* @author υnascribed

* Sversion 1.0 */

Public class Frame2 extends JFrame

{

JButton JButtonl = new JButton(); public Frame2()

(

Try

I

Jbɪnit () ;

J

Catch(Exception e)

{

E. printStackTrace();

}

}

Private void jbɪnit() throws Exception {

JButtonl. setText("JButtonl");

JButtonl. SddActionListener(new java. awt. event. ActionListener()

{

Public void actionPerformed(ActionEvent e)

{

JButtonl_actionPerformed(e);

J

>) ;

This. getContentPane().add(JButtonlr BorderLayout. CENTER);

}

Void JButtonl_actionPerformed(ActionEvent e)

{

1

Эти два исходных файла созданы так: сначала в JBuilder был выбран пункт меню File I New. Затем на “странице General браузера объектов я выбрал Frame и таким об­разом создал экземпляр класса JFrame. В листинге 14.1 в качестве стиля кода обра­ботки событий (Code Style for Event Handling) был выбран Standard Adapter, а в лис­тинге 14.2 — Anonymous Adapter. Затем на каждый фрейм была помещена кнопка, а для нее создан обработчик события.

Я создал потомков класса JFrame Вместо работы с экземпляром Framei Для того, чтобы эти экземпляры не содержали излишнего кода, наподобие метода ProcessWindowEvent. Данный код несколько усечен, чтобы он содержал только ин­тересующий нас текст.

В листинге 14.1 обратите внимание на присутствующий там класс Fr ame2_ JBu t ton 1_ас t ionAdap ter:

Class Frame2_JButtonl_actionAdapter implements java. awt. event. ActionListener 1

Frame2 adaptee;

Frame2_JButtonl_actionAdapter (Frame2 adaptee)

{

This. adaptee = adaptee;

1

Public void actionPerformed(ActionEvent e)

1

Adaptee. JButtonl_actionPerformed(e);

1

1

Вы видите, что этот класс вызывает созданный ранее метод

JButtonl_actionPerformed:

Adaptee. JButtonl_actionPerformed(e);

Теперь мы узнали, что в коде имеется класс, называемый адаптером, который вы­зывает обработчик события кнопки. Упомянутый обработчик — как раз тот код, ко­торый вызывается при щелчке на кнопке.

А как JDK знает, что при нажатии кнопки нужно вызвать ваш адаптер? Частично ответ на этот вопрос может быть получен из следующего кода метода jbɪnit:

JButtonl. BddActionListener(new Frame2_JButtonl_actionAdapter(this)) ;

Метод addActionListener находится глубоко в JDK. Наш метод вызывает его и присваивает класс Frame2_JButtoni_actionAdapter экземпляру класса JButton. Класс jButtonl хранит список всех actxo∏Adapter, зарегистрированных в нем. Ког­да происходит щелчок на кнопке, он просматривает список и вызывает метод actionPerformed этого класса:

Public void actionPerformed(ActionEvent e)

{

Adaptee. JButtonl_actionPerformed(e);

)

примечаниеТеперь мы полностью разобрались с этим. Мы обнаружили, что JDK знает, что необходимо вызвать класс-адаптер, потому что вы вызвали addActionListener, а мы уже видели, что класс-адаптер вызывает наш метод JButtonl_actionPerf ormed. Hy хорошо, поехали дальше.

В следующей главе вы узнаете об этом процессе больше. Там вы со­здадите свои собственные экземпляры метода addActionListener. Но пока получен­Ной информации достаточно, чтобы понять, как работает система.

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

Frame2_JButtonl_actionAdapter(Frame2 adaptee)

{

This. adaptee = adaptee ;

)

Этот метод является конструктором. Он возвращает методу jbinit вашего при­ложения экземпляр главного фрейма:

JButtonl. SddActionLxatener(new

Frame2_JButtonl_actxonAdapter(this));

Здесь слово "this" относится к Frame2, передаваемому конструктору адаптера. Затем фрейму присваивается переменная Adaptee. S Га переменная применяется

примечаниеВ методе SctionPerformed ДЛЯ вызова JButtonl_actior Performed.

Iiiii■ Если вам интересно мое мнение, я думаю, что "adaptee" — это не ан­глийское слово. Это слово имеется во французском языке. Видимо, разработчики JBuiIder думали по-французски, когда выбирали имя для этой переменной.

Теперь вы знаете все о том, как работают обработчики событий, если вы исполь­зуете в приложении JBuilder стандартные адаптеры. По-моему, весь код в данной книге использует стандартные адаптеры.

Анонимные адаптеры

Анонимные адаптеры, приведенные в листинге 14.2, используют не стандартный класс Java, а анонимный класс. Этот анонимный класс создается в методе jbɪnit:

JButtonl. SddActionListener(new java. awt. event. ActionListener()

{

Public void actionPerformed (ActionEvent e)

{

JButtonl_actionPerformed(e);

1

1) ;

Анонимные классы создаются, объявляются и реализуются в параметре другого метода. Метод в этом случае выглядит так:

JButtonl. SddActionListener ( — Здесь вставлен класс — )

Класс, вставленный в параметр данного метода, выглядит следующим образом:

New j ava. awt. event. ActionListener()

{

Public void actionPerformed(ActionEvent e)

{

JButtonlsSCtionPerformed(e);

1

1

Обратите внимание, что перед объявлением класса находится слово New, И что есть имя класса-предка, но у самого класса имени нет: он анонимен. Класс является производным от ActionListener, Но сам имени не имеет.

Как видите, этот класс очень похож на класс-адаптер из листинга 14.1. У него нет конструктора и франкоязычной переменной Adaptee, Но все-таки его структура очень знакома. Переменная Adaptee Здесь не нужна, поскольку этот класс известен только внутри Frame2 И поэтому может вызывать его методы напрямую. На самом деле ЭТОТ Класс содержит метод ActionPerformed, Напрямую вызывающий JButtonl actionPerformed:

Public void actionPerformed(ActionEvent Е)

{

JButtonl_actionPerformed(е);

}

Как и ранее, метод JButtonKaddActionListener вставляет дескриптор ЭТОГО Анонимного класса в список, который хранится в jButtonl. При щелчке на jButtonl вызывается метод actionPerformed всех классов из этого списка. А что делается внутри этого метода — это уже наше дело. В данном случае вызывается ме­тод JButtoni_actionPerformed. Или, скорее, JBuilder автоматически генерирует вызывающий его код.

Сравнение анонимных и стандартных адаптеров

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

Почему я принял такое решение? Я считаю, что стандартные адаптеры легче вос­принимаются визуально. Кроме того, применение стандартных адаптеров позволя­ет не загромождать методы jbɪnit

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

Класс ActionEvent

Как вы помните, в методы ActionPerformed передается параметр типа ActionEvent:

Void JButtonl_actionPerformed(ActionEvent Е)

Класс ActionEvent содержит большой объем информации о состоянии мыши и клавиатуры на момент возникновения события. В следующей главе, "Генерация со­бытий", у вас будет возможность исследовать множество важных свойств этого клас­са. А в данной главе вы узнаете, как создавать экземпляры класса ActionEvent и как применять многие из его методов. Но пока еще вам необходимо узнать много базо­вых вещей об этом классе.

Посмотрите на методы класса ActionEvent, перечисленные в таблице 14.1.

Таблица 14.1. Методы класса ActionEvent

метод
e.getactioncommand() ; е.getmodifiers(); е. getid () ; е .getwhen () ; е.getsource();
Описание

Получить строку, связанную с командой. Получить состояние клавиш Alt И Ctrl. Получить целое число, связанное с командой. Получить время запуска команды.

Какой элемент управления вызвал событие? (В нашем случае — jButtonl.)

примечание

Метод e. getWhen() является новой возможностью, введенной в JDK 1.4. Это значит, что если вы не перейдете на JDK 1.4.0, то не сможете пользоваться этой частью кода. Если вам нужно перейти на другой JDK, то инструкции для этого были при­ведены в главе 1 и в ReadMe-файле с именем index. html на инсталляционном ком­пакт-диске JBuiIder. Если вы пользуетесь JBuiIder Personal, у вас может не быть возможно­сти смены JDK, поэтому вам необходимо закомментировать код, относящийся к этому Методу. В общем, читайте главу 1 и ReadMe-файл.

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

Рассмотрим следующий метод обработки щелчка на кнопке:

Void JButtonl_actionPerformed(ActionEvent е)

{

System. out. println("Строка параметра: " + e. paramString());

System. out. println("Запускаемая команда: " + e. getActionCommand()); int modifiers = е. getModifiers () ;

System. out. println("Модификаторы: " +

KeyEvent. getKeyModifiersText(modifiers));

System. out. println("ID: " + e. getID()); long a = e. getWhen() ;

Date d = new Date (a) ;

Sys tern. out. println ("Время: " + d) ;

System. out. println("Источник: " + e. getSource().getClass().getName());

}

Этот метод выдает следующую информацию:

Строка параметра: ACTION_PERFORMED, Cmd=JButtonl, when=1019943559903, modi f iers=Alt+Shi ft+Buttonl

Запускаемая команда: JButtonl

Модификаторы: Alt+Shi ft+Buttonl

ID: IOOl

Время: Sat Oct 25 14:39:19 PDT 2003

Источник: javax. swing. JButton

Метод paramstring() события дает некоторую общую информацию о состоя­нии кнопки, сгенерировавшей событие. ActionCommand — это строка, связанная C кнопкой. Вы можете изменить ее во время конструирования с помощью инспектора.

Модификаторы определяют, какие специальные клавиши были нажаты в момент щелчка на кнопке. Здесь для определения состояния этих клавиш я пользуюсь ме­тодом KeyEvent. getKeyModifIersText. Это можно сделать с помощью примерно такого кода:

Void JButtonBitMasks_actionPerformed(ActionEvent e)

{

String StateStr = "";

Int keyState = e. getModifiers () ;

If ((keyState & InputEvent. ALT_MASK) != O) {

StateStr += "Нажата клавиша Alt∖n" ;

If ((keyState & InputEvent. SHIFT_MASK) ∙= O) {

StateStc += "Нажата клавиша Shif tN";

}

If ((keyState & InputEvent-CTRLJMASK) != О) {

StateStr += "Нажата клавиша CtrlN";

}

If (stateStr. length() == 0) {

StateStr = "Не нажата ни одна из клавиш Alt, Shift Или Ctrl";

}

JTextAreal. setText(stateStr);

}

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

Int keyState = Е.getModifiers();

If ((keyState & InputEvent. ALT_MASK) ∙= 0)

Идентификационный номер (ID), возвращаемый методом ActionEvent. getID О, является уникальным номером, связанным с конкретным событием.

Вызов метода ActionEvent. getwhen О Определяет время возникновения собы­тия. Вот код, преобразующий возвращаемое число типа long в строку:

Long А = e. getWhen();

Date d ≈ new Date (а) ;

System. out. println("Время: " + d) ;

Результат выглядит так:

Время: Sat Oct 25 14:39:19 PDT 2003

И, наконец, метод Getsource Возвращает экземпляр элемента управления, запу­стившего данный обработчик события. Вызов Getsource. getciass. getName Дает тип класса, запустившего событие. Вот код получения текста кнопки, на которой был выполнен щелчок:

JTextAreal. append("\пНадпись на кнопке: " +

((JButton).getsource())).getText());

Использование других стандартных событий

Ясно, что JBuilder поддерживает не только событие ActionPerformed. Если вы выберете какой-либо компонент и перейдете на страницу Events инспектора, то, скорее всего, вы увидите целый набор обработчиков событий, которые может со­здать для вас JBuilder. Например, JButton Поддерживает события AncestorAdded, ancestorRemoved, itemStateChanged, StateChanged И Т. Д. Изучение всех ЭТИХ Об­работчиков событий — серьезная задача, касающаяся JDK, а не JBuilder. Поэтому изучение этих различных обработчиков событий и результатов их действий возлага­ется на вас.

Настройка механизма обработки событий JBuiIder

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

■ Существуют и другие способы обработки событий, кроме рассмотренных в данной главе.

■ JBuilder может не помочь вам создать эти альтернативные виды обработчиков событий, но он и не будет мешать вам делать это.

В программе StringTestOl, находящейся в подкаталоге ComplexProject В со­провождающих книгу материалах, показан альтернативный способ обработки собы­тий. Код обработчика события в этой программе частично создан JBuilder, а частич­но вручную.

B самом конце файла Framel. java Вы найдете такой класс:

/** В данном приложении я использую один и тот же ActionListener

* Для всех элементов меню. Более того, большую их часть я отсылаю

* Обратно тому же самому обработчику событий в главной программе.

*/

Class MainListener implements ActionListener

{

Framel adaptee;

MainListener(Framel adaptee)

{

This, adaptee = adaptee;

)

Public void actionPerformed(ActionEvent e)

{

If (e. getSource() == adaptee. jMenuFileExit)

{

Adaptee. jMenuFileExit_actionPerformed(e);

}

Else if (e. getSource() == adaptee. jMenuHelpAbout)

{

Adaptee. jMenuHelpAbout_actionPerformed(e);

}

Else if (e. getSource() == adaptee. jMenuItemReverse)

{

Adaptee. jMenuItemReverse_actionPerformed(e);

)

Else if (e. getSource() = adaptee. jMenuItemGetFirstWord)

{

Adaptee. jMenuItemReverse_actionPerformed(e);

}

adaptee.jmenu!temreverse_actionperformed(e);

Else if (e. getSource() = adaptee. jMenuItemStripFirstWord) t

}

}

}

Этот класс ПОХОЖ На класс Frame2_JButtonl_actionAdapter, Который МЫ уже видели в данной главе:

Class Frame2_JButtonl_actionAdapter implements java. awt. event. ActionListener

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

Если вы уже некоторое время программировали на Java, то наверняка сталкива­лись с подобным кодом. Он использует метод объекта ActionEvent, Передаваемый обработчику, чтобы определять, из какого элемента Framel На самом деле произо­шел вызов. Используемый метод называется Getsource, И он возвращает экземпляр класса С именем MainListener.

После того как класс определил, какой объект вызвал его, он использует эту ин­формацию для обратного вызова соответствующего метода из Framei:

Else if (Е.GetSourСе() == adaptee. jMenuItemStripFirstWord)

{

Adaptee. jMenuItemReverse_actionPerformed(e);

}

Существует несколько различных способов связать объект MainListener С раз­личными компонентами Framei, Генерирующими события. Способ, применяемый в данной программе, выглядит следующим образом:

MainListener listen = new MainListener(this); JMenuFileExit. addActionListener(listen);

JMenuItemReverse. addActionListener(listen); jMenuItemGetFirstWord. addActionListener(listen); И т. д.

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

Очевидно, что способ, показанный здесь, использует меньше ресурсов, чем спо­соб, применяемый JBuilder. В частности, в нем используется только один класс- адаптер, в то время как код JBuilder для той же ситуации генерирует несколько адап­теров. C другой стороны, чистый объектно-ориентированный язык вроде Java опти­мизирован так, что создание классов выполняется максимально экономично. Издержки, связанные с классами-адаптерами, не настолько велики, чтобы суще­ственно повлиять на большинство программ. Поэтому обычно не стоит тратить вре­мя и делать то, что делал я. Тем не менее, возможность такая есть.

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

Интерфейсы слушателей и классы-адаптеры

примечаниеОчень немногие классы автоматически получают необработанные события мыши. Если в этих немногих классах вы перейдете на страницу Events инспектора, то сможете создать обработчики событий мыши точно так же, как вы создаете обра­ботчики события ActχonEvent, Возникающего при щелчке пользователя на кнопке.

Конечно, событие ActionPerfornied класса JButton вызывается щел­чком мыши, но в строгом смысле оно не является событием кнопки. Оно сообщает, что кнопка выбрана, и, исследуя параметр ActionEvent, передаваемый этому методу, вы можете узнать состояние мыши и клавиатуры на момент выбора кнопки. Но все же назна­чение этого события состоит не в уведомлении о состоянии мыши, а лишь в том, что пользо­Ватель хочет, чтобы сработало действие, закодированное в обработчике этого события.

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

До сих пор в данной главе вы имели дело с генерацией и получением событий. Рассмотренные нами события были довольно просты. Некоторые события, вроде тех, что связаны с интерфейсом MouseListener, Несколько более сложны:

Public interface MouseListener extends EventListener

{

Public void mouseClicked(MouseEvent e) ; public void mousePressed(MouseEvent e) ; public void mouseReleased (MouseEvent e) ; public void XnouseEntered (MouseEvent e) ; public void mouseExited(MouseEvent e) ;

}

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

Событие щелчка происходит, когда кнопка мыши быстро нажата и от­Пущена, а событие нажатия происходит, когда кнопка только нажата.

KeyListener, MouseListenerl MouseWheeiListener и MouseMotionListener

Сейчас я покажу вам программу, входящую в состав сопровождающих матери­алов под названием MouseKey, Которая демонстрирует, как создавать экземпляр класса JFrame, Поддерживающий интерфейсы KeyListener, MouseListener, MouseWheelListener И MouseMotionListener. Внешний ВИД Окна программы пред­ставлен на рис. 14.3.

рис. 14.3.
программа mousekey поддерживает m d !-интерфейс.
в ней
продемонстрирована техника ввода информации с мыши и клавиатуры
,интерфейс mousemotionlistener, описываемый в данной главе, является мастью jdk 1.4. если вам необходимо сменить jdk, вы можете найти соответствующие инструкции в главе 1 и на инсталляционном компакт-диске jbuiider, в readme-фай- ле с именем index.html. если вы имеете дело с jbuiider professional, то у вас может не быть возможности использовать в jbuiider интерфейс mousemotionlistener. короче говоря, читайте главу 1 и readme-файл
примечание

В программе MouseKey Реализован M Dl-интерфейс. Ближе к концу текущей гла­вы я дам точные инструкции, как создавать программы с таким интерфейсом. Пока же вам достаточно знать, что в программе имеется главное окно, в котором находят­ся три экземпляра JPanei. Нижний из них называется InputPanel; На этой панели можно вводить текст. Две верхних панели предназначены для отображения вводи­мого в InputPanel Текста В частности, после щелчка мышью на InputPanel В MousePanei Должно появиться сообщение об этом событии. Когда пользователь вводит текст на InputPanel, В панели KeyPanel Ведется протокол нажатий клавиш

Разбор программы MouseKey

Вы можете последовать нижеприведенному тексту и создать свою собственную копию программы MouseKey. Я не буду подробно пояснять, как это сделать, однако постараюсь дать достаточно информации для того, чтобы вам нетрудно было вос­становить недостающие детали. Основа, на которой собрана вся программа — это MDI-интерфейс, или виртуальный рабочий стол. Я объясню принципы его работы в самом конце этой главы.

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

284

Чтобы самостоятельно создать программу, сначала создайте новый проект и новое при­ложение, как описано в главе 3. В JBuilder выберите пункт меню File ∣ New Class и создай­те класс, производный от JPanel, В файле InputPanei. java. На рис. 14.4 показано, как выбрать базовый класс, а на рис. 14.5 — как правильно заполнить поля мастера создания класса. Закройте мастер, щелкнув на кнопке OK, и выберите в редакторе созданный класс.

Если вы пользуетесь JBuilder редакции SE или Enterprise, выберите пункт меню Wizards I Implement Interface. Найдите java. awt. event и выберите интерфейсы MouseMotionListener,

MouseListener, KeyListener и (если у вас JDK 1.4)

MouseWheeIListener, как показано на рис. 14.6.

В начало файла InputPanel. java Будет добавлен следующий код:

Public class InputPanel extends JFrame

Implements MouseListener7 MouseMotionListener7 MouseHheelListener7

KeyListener

Если в вашей версии продукта нет мастера реализации интерфейса, вам придет­ся ввести этот код вручную.

В реализации вашего класса мастер создания интерфейса добавит код вроде при­веденного в листинге 14.3. Если вы пользуетесь JBuilder редакции Personal, то вам потребуется ввести этот код вручную. Вообще-то код содержит еще некоторые мето­ды и выдает имя метода, в котором возникла исключительная ситуация, но я чуть — чуть сократил его, чтобы он стал более читабельным.

C ⅛⅜ I BftatttCfas

Browse Search

Class name: JavaitswirsJPaneI

S & JEditorPane!~i⅛ JFileChooser ISJFormafledTexlFieId ∙-A JFrame I A JlntemalFrame 1- & JLabei i A JLayeredPane i~∙⅛ JUSl i — A JMenu t∙ A JMenuBar! A JMenuItem i A J. PtIonPane

∙~ A JPasswordField t∙ A JPopupMenu i⅛ JProoressBar,ɪ — A JRadioButton I-■<& JRadioButtonMenultem AJRftOtPline

Рис. 14.4. Выбор базового класса для InputPanel

Рис. 14.5. Заполнение полей мастера создания класса, чтобы поместить в пакет Untitled34 Новый класс InputPanel, Производный от JPanel

Г© ‘.Inss Wizard .

, , CreateanewJawacIass

Γ⅛ΛJ

’—* Fill in Ihellelds belowto set the package, name, base class, and other Optionsforthe

Java class which will be created.

• vιαooιιιιuι∣ιιαuuιι …………………

I Package: Untitled34

Uɪll

Class name: LnputPane(

I Baseclass. Ijavaxswing JPaneI

3 — π

Г Options————-

) F Public

F Oeneratedefaultconstructor

I I” Generatemainmethod

F Override abstract methods

I FGenerateheadercomments

OK Cancel Help j

рис. 14.6. выбор интерфейсов в мастере реализации интерфейса

Листинг 14.3. Реализация по умолчанию интерфейсов MouseListener, MouseMotionListener И MouseWheeiListener, Обеспечиваемая мастером создания интерфейса

Pxιblic void moυseClicked (MouseEvent Е)

(

∕**gtodo Implement this java. awt. event. MouseListener method*/

Throw new java. Iang. UnsupportedoperationBxception("not yet implemented.");

}

Public void mousePressed(MouseEvent e)

{

∕**6todo Implement this java. awt. event. MouseListener method*/

Throw new java. Iang-UnsupportedoperationException("not yet inplemented.");

}

Public void mouseReleased(MouseEvent e)

{

∕**ρtodo Implement this java. awt. event. MouseListener method*/

Throw new java. Iang. UnsupportedoperationException("not yet implemented.");

)

Public void mouseEntered(MouseEvent e)

(

/**0todo Implement this java. awt. event. MouseListener method*/

Throw new java. Iang. UnsupportedoperationException(‘not yet inplemented.");

)

Public void mouseExited(MouseEvent e)

{

∕**θtodo Implement this java. awt. event. MouseListener method*/

Throw new java lang. UnsupportedoperationException("not yet inplemented.»);

)

Public void mouseDragged(MouseEvent e)

{

∕**6todo Implement this java. awt. event. MouseMotionListener method*/ throw new java. Iang. UnsupportedoperationException("not yet inplemented.");

)

Public void mouseMoved(MouseEvent e)

{

/**0todo Implement this java. awt. event. MouseMotionListener method*/ throw new java. Iang-UnsupportedoperationException("not yet inplemented.");

)

Public void mouseWheelMoved(MouseWheelEvent e)

{

∕**θtodo Implement this java. awt. event. MouseWheelListener method*/ throw new java. Iang-UnsupportedoperationException("not yet implemented.");

}

Fð ,IBhHder 7 — G;/Src JftvaZAUtHιtHbdWuι∏Hif∙d3d∕brc∕untHlβ(134∕lπpιrtP^πeljavΛ l,*J. fΠ*J1>C,’

File Edit Search View Project Run Team Wizards ɪools Window

4elp

D £ ⅛* ∙ ⅝ B 9 ⅛i> *∙><‘*⅝β M j MjseiTexi

⅞⅜ ⅞* Tft j¾⅛ ’ j□3 > ’ Λ* ’ £♦ ’

<ft ` i φ

Ft & * ið Φ U∏titlβd34 Px

×⅛⅛ Framel X⅜ nputPanel J

3p u∩titlβd34.jpx -J

jJ

..lΠ-∙I.- —1.O ∙aλ-> -,ι.→.. ………………………. ………..∙..∙∙.n….∙………∙.∙∙∙.w∙.∙…………………………… -……………………….. ………………. …………………. …4Mm4

Isport Javex-Swlno-JPanel; (

H ⅛jToDo ~

F Ixport Java. awt. event. KeyEvent;

Qtodo: Implementthisjava awtevenlkeytjstenermethod

LR>ort java. awt. event. KeyListance;

Qtodo: Implementthisjava awtevenlKeyLJsteneimethod

Isport J ava. awt. event. HouseEvent;

-Qtodo: Implementthisjava awleveniKeytJetenermethod

Isport java. awt. event. HouseListener;

∙l Qtodo *mplementthιsAva. awleventMouseListenermethod 1

Isport java. βwt. event. IlouseHotlonllstener;

, Qtodo: Impiementthislava-SwlevenlMouseLIstenermethod a⅛

Isport Java. awt. event. HousetIheelEvent;

Qtodo: Implementthisjava awt event MouseLlstenermethod

Isport java. awt. event. HouseIIheelLlstener;

J I Qlodo: ImplementthisjavaawleventMouseListenermethod

4

Qtodo: Implementthisjava awleventMousebstenermtfhoa

Q todo: Implementthis Iava awlevenlMousetfotionUstenermeIhod

■ l . Qlodo: Implementthisjava awlevenlMouseMotionListenermethod SS

. InputPaneIjava ⅛ 1:1 ‘i Modiflsd i Insert

Qtodo: ImpiementthlsjavaiSwleventMouseWheeIListenermethod J-j

Source (Design f Bsan J UMl} DocJTiistoiyl

Bulld sutteedɔd with 1 tile compiled. Build took O seconds. ~’ ~~r ~

Рис. 14.7. Элементы @todo И операторы импорта, сгенерированные мастером
создания интерфейса, отображаются на панели структуры

Все элементы etodo будут отображены вверху панели структуры под узлом То Do, как показано на рис. 14.7.

Интерфейс KeyListener

Интерфейс KeyListener Также содержится В программе MouseKey, Хотя он и не показан в листинге 14.1. Интерфейс KeyListener Выглядит следующим образом:

Public interface KeyListener extends EventListener

{

Public void keyTyped(KeyEvent e) ; public void keyPressed (KeyEvent e) ; public void keyReleased (KeyEvent e) ;

}

Вы не увидите этот интерфейс в программе MouseKey Именно в таком виде. Но все эти методы реализованы в классе InputPanel, В силу соглашения, по которому класс реализует интерфейс.

События KeyTyped Не предоставляют такую подробную информацию о событиях клавиатуры, как события KeyPressed. Они лишь сообщают, какая клавиша была на­жата, но не предоставляют код клавиши. Позже в данной главе вы узнаете больше о кодах клавиш. В Документации компании Sun для ввода символов предлагается ис­пользовать события KeyPressed, Поскольку они более независимы от используемой платформы. C другой стороны, если на платформе Windows или Linux вы хотите уз­нать несколько больше о том, какая клавиша была нажата, то следует воспользовать­ся KeyTyped.

Создание KeyPaneI и MousePaneI

После создания InputPanei Необходимо создать еще два класса, дочерних по от­ношению к JPanel: KeyPanel И MousePanel. Придайте им вид двух верхних окон, показанных на рис. 14.3. Чтобы сэкономить место, я не буду слишком вдаваться в подробности этих классов. Если вам необходима помощь при их создании, посмот-

Рите код программы MouseKey в сопровождающих книгу материалах, но вы должны уметь сами восстановить их общий вид на основе того, что вы видите на рис. 14.3.

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

Регистрация слушателя

Давайте остановимся на минутку и посмотрим, что произошло. Вы объявили, что ваш класс InputPanei поддерживает интерфейсы, созданные для обработки ввода с мыши и с клавиатуры. Объявление этих интерфейсов дает возможность по­лучать соответствующие события.

Чтобы перевести потенциальный слушатель в реальный приемник событий, не­обходимо зарегистрироваться в классе, который будет генерировать эти события. Лучше это сделать в методе jbinit класса InputPanel:

This. addKeyListener(this); this. addMouseListener(this); this. SddMouseMotionListener(this); this. SddMouseWheelListener(this);

После добавления этих методов остается только одно: ввести код методов, полу­чающих события. В нашем случае достаточно сообщить, что событие получено. По­этому можно написать что-нибудь вроде

System. out. println("Произошел щелчок кнопкой мыши.");

Но лучше сделать что-нибудь более интересное. В частности, мы хотим отобра­жать вводимую информацию в созданных классах KeyPanel и MousePanel. В следу­ющем разделе вы узнаете, как это сделать.

Использование KeyPaneI и MousePaneI

Как вы знаете, информация, вводимая с мыши и клавиатуры на InputPanel, бу­дет отображаться с помощью интерфейсов для классов KeyPanel и MousePanel. Пе­редайте в конструктор InputPanei в качестве параметров экземпляры mousePanel и keyPanel, чтобы к ним был возможен доступ:

MousePanel mousePanel;

KeyPanel keyPanel;

Public InputPanel(MousePanel mousePanel, KeyPanel keyPanel)

{

This. mousePanel = mousePanel;

This. keyPanel = keyPanel;

This. addKeyListener(this);

This. addMouseListener(this);

This. SddMouseMotionListener(this);

This. addMouseWheelListener(this);

}

Теперь InputPanel При вводе информации сможет посылать этим панелям сооб­щения:

Public void keyPressed(KeyEvent Е)

{

KeyPanel. keyTyped.getKeyChar(), Е.getKeyCode());

Framel. say("Метод KeyPressed() .");

}

Public void mouseMoved(MouseEvent Е)

{

MousePanel. setMouseX.getX ()) ;

RiousePanel. SetMouseY . getY ()) ;

}

Методы KeyPanel. keyTyped, mousePanel. SetMouseX И MousePanel. SetMouseY Просто отображают передаваемые им данные. Вот метод KeyTyped Из KeyPanel. java:

Public void keyTyped (char key, int code)

(

JTextFieldl. setText(new String() + key); jTextField2.setText(new String () + code); if (code == 8)

{

String s = jTextAreal. getText(); s = s. substring(O, s. length() -1); jTextAreal. setText(s);

)

Else

{

JTextAreal. append(new String() + key);

}

)

А вот метод SetMouseX Из файла MousePanel. java:

Public void setMouseX(int mouseX)

{

This. mouseX = mouseX;

JTextField2.setText(String. valueθf(mouseX));

)

Элементы JTextFieid И JTextArea, Отображающие данные в панелях MousePanel И KeyPanel, Получают ЭТИ Данные ИЗ ВЫЗОВОВ Методов SetMouseX, SetMouseY И KeyTyped. Если вам не очень понятно, что делают эти методы, посмот­рите на рис. 14.3.

Вы, видимо, уже поняли, как с помощью методов классов KeyEvent И MouseEvent Получить конкретную информацию о состоянии клавиатуры и мыши. Например, метод MouseEvent. getx() Сообщает текущую координату X мыши. Ме­тод KeyEvent. getKeyChar () Выдает символ клавиши, нажатой пользователем, на­пример, "А” Или "В". Более подробную информацию, такую как ASCII-код клави­ши, можно получить с помощью метода GetκeyCode. Конечно, у всех этих объектов имеются и другие методы, которые вы также можете использовать.

И, наконец, необходимо в класс InputPanel Добавить метод, снабжающий MousePanel Более конкретной информацией:

Protected void say(String OutString)

{

MousePanel. addString(outString);

}

C помощью метода Say Можно быстро вывести большой объем подробной ин­формации. Например, следующие три строки кода с помощью класса MouseWheeiEvent, Объявленного здесь как Е, Сообщают состояние колеса, которым укомплектованы современные модели мышей:

SayCmouseWheelMoved, Поворот колесика: " + Е.getWheelRotation());

Framel. sayCmouseWheelMoved, Пробег: " + Е.GetScrolIAmount());

примечаниеFramel. sayCmouseWheelMoved, UnitsToScroll: " + Е. geWnitsToScroll ()) ;

Классы, имеющие дело с колесом мыши, являются частью JDK 1.4.

Виртуальные рабочие столы: JDesktop, JInternaIFrame и

MDI-программы

Вы, видимо, уже заметили, что программа MouseKey Имеет необычный интер­фейс. Она принимает информацию от мыши и клавиатуры в одном окне и позволя­ет наблюдать события мыши во втором окне, а события клавиатуры — в третьем окне, как показано на рис. 14.3. Для этого я с помощью классов JDesktopPane И JinternalFrame Создал виртуальный рабочий стол. В мире Windows это называется MDI (Multiple Document Interface — многодокументный интерфейс).

На рис. 14.3 вы можете увидеть, что эти три окна являются дочерними по отно­шению К главному окну, т. е. Framel Владеет тремя другими окнами — InputPanel, MousePanel И KeyPanel. Эти окна не являются диалогами, отображаемыми поверх Framel; Они представляю собой дочерние объекты Framei И существуют на его по­верхности, подобно тому как JButton И JiextField находятся на поверхности JFrame. Существенное отличие состоит в том, что элемент JButton находится на одном месте и не может изменять своих размеров, а эти окна подвижны в положе­нии и размере. Но вы не сможете вытащить их за пределы Framel.

Для поддержки MDI-интерфейса сначала необходимо объявить переменную класса JDeskTopPane.’

JDesktopPane desktop;

Объявите ее в начале кода Framel, Там, где JBuilder помещает объявления эле­ментов JButton, JTextField и других членов класса. В методе jbinit нужно объя­вить, что создаваемый экземпляр JDesktopPane будет находиться на поверхности Framel:

Desktop = new JDesktopPaneO ‘∙

CgntentPane. add(desktop);

Объект Desktop — Это та поверхность, на которой будут находиться MDI-окна.

Вы можете поместить на desktop один из созданных нами потомков JPanei с помощью такого кода:

Private void CreateFrame(int Х, Int У, JPanel panel, int width,

String caption)

{

JInternalFrame j = new JInternalFrame(caption, true, ∕∕ Oxho Может менять размер True, ∕∕ Окно содержит значок "Закрыть"

True, // Окно содержит значок "Развернуть на весь экран" True) ; // Окно содержит значок "Свернуть на панель задач"

J. SetBounds(х, у, Width, 250); j. SetContentPane(panel); Desktop. add(j); j. show();

)

JinternalFrame — Это упрощенный контейнер для окон, которые будут нахо­диться на экземпляре JDesktopPane. В первом параметре конструктора JinternaiFrame Передается имя, которое вы даете этому окну. Это имя будет ото­бражаться в строке заголовке окна. Следующие четыре параметра являются логичес­кими значениями. C их помощью можно задать, сможет ли окно изменять размеры, быть закрытым, развернутым, минимизированным и отображаемым в виде пиктог­раммы.

Затем в коде задаются границы окна. Следующим шагом будет помещение эк­земпляра JPanel, С которым мы работаем, в JinternalFrame, И добавление его в эк­земпляр JDesktopPane:

J. SetContentPane(panel); Desktop. add(j);

B метод jbinit добавьте следующий код вызова только что рассмотренного нами метода CreateFrame:

Desktop = new JDesktopPane();

ContentPane. add(desktop);

CreateFrame(10, 10, mousePanel, 250, "Панель мыши");

CreateFrame(500, 10, keyPanel, 250, "Панель клавиатуры"); InputPanel = new InputPanel(mousePanel, keyPanel, "Панель ввода"); CreateFrame(150, 275, inputPanel, 400);

Адаптеры мыши

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

Возможным решением этой проблемы может служить JDK-класс J ava. awt. event. MouseAdapter:

Public abstract class MouseAdapter implements MouseListener

{

Public void mousedicked (MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseEntered (MouseEvent e) ( } public void mouseExited (MouseEvent e) { }

)

Этот класс обеспечивает самую компактную реализацию интерфейса MouseListener. Все методы абсолютно пусты, хотя все-таки и существуют. C помо­щью этого класса вы можете очень просто реализовать обработчик Moused icked:

Class MyClass extends MouseAdapter

{

Public void mouseClicked (MouseEvent e)

{

∕∕ Здесь находится код обработки события.

}

}

Резюме

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

Далее были рассмотрены интерфейсы слушателей событий, с помощью кото­рых класс может получать различные виды событий, не предусмотренные в инс­пекторе JBuilder. В этом разделе основное внимание было уделено слушателям MouseListener, MouseMotionListener, MouseHheelListener И KeyListener.

Попутно вы узнали, как с помощью классов JDesktopPane И JInternalFrame Со­здавать MDI-приложения, или виртуальные рабочие столы. Оказалось, что эта тема очень проста для понимания.

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

Чарли Калверт

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *