Какой параметр в обработчике события передает объект события которое обрабатывается
Для взаимодействия с пользователем в Windows Forms используется механизм событий. События в Windows Forms представляют стандартные события на C#, только применяемые к визуальным компонентам и подчиняются тем же правилам, что события в C#. Но создание обработчиков событий в Windows Forms все же имеет некоторые особенности.
Прежде всего в WinForms есть некоторый стандартный набор событий, который по большей части имеется у всех визуальных компонентов. Отдельные элементы добавляют свои события, но принципы работы с ними будут похожие. Чтобы посмотреть все события элемента, нам надо выбрать этот элемент в поле графического дизайнера и перейти к вкладке событий на панели форм. Например, события формы:
Чтобы добавить обработчик, можно просто два раза нажать по пустому полю рядом с названием события, и после этого Visual Studio автоматически сгенерирует обработчик события. Например, нажмем для создания обработчика для события Load :
И в этом поле отобразится название метода обработчика события Load. По умолчанию он называется Form1_Load .
Если мы перейдем в файл кода формы Form1.cs, то увидим автосгенерированный метод Form1_Load:
public partial class Form1 : Form < public Form1() < InitializeComponent(); >private void Form1_Load(object sender, EventArgs e) < >>
И при каждой загрузке формы будет срабатывать код в обработчике Form1_Load.
Как правило, большинство обработчиков различных визуальных компонентов имеют два параметра: sender — объект, инициировавший событие, и аргумент, хранящий информацию о событии (в данном случае EventArgs e ).
Но это только обработчик. Добавление же обработчика, созданного таким образом, производится в файле Form1.Designer.cs:
namespace HelloApp < partial class Form1 < private System.ComponentModel.IContainer components = null; protected override void Dispose(bool disposing) < if (disposing && (components != null)) < components.Dispose(); >base.Dispose(disposing); > private void InitializeComponent() < this.SuspendLayout(); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(284, 261); this.Name = "Form1"; // добавление обработчика this.Load += new System.EventHandler(this.Form1_Load); this.ResumeLayout(false); >> >
Для добавления обработчика используется стандартный синтаксис C#: this.Load += new System.EventHandler(this.Form1_Load)
Поэтому если мы захотим удалить созданный подобным образом обработчик, то нам надо не только удалить метод из кода формы в Form1.cs, но и удалить добавление обработчика в этом файле.
Однако мы можем добавлять обработчики событий и програмно, например, в конструкторе формы:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace HelloApp < public partial class Form1 : Form < public Form1() < InitializeComponent(); this.Load += LoadEvent; >private void Form1_Load(object sender, EventArgs e) < >private void LoadEvent(object sender, EventArgs e) < this.BackColor = Color.Yellow; >> >
Кроме ранее созданного обработчика Form1_Load здесь также добавлен другой обработчик загрузки формы: this.Load += LoadEvent; , который устанавливает в качестве фона желтый цвет.
Введение в браузерные события
Событие – это сигнал от браузера о том, что что-то произошло. Все DOM-узлы подают такие сигналы (хотя события бывают и не только в DOM).
Вот список самых часто используемых DOM-событий, пока просто для ознакомления:
События мыши:
- click – происходит, когда кликнули на элемент левой кнопкой мыши (на устройствах с сенсорными экранами оно происходит при касании).
- contextmenu – происходит, когда кликнули на элемент правой кнопкой мыши.
- mouseover / mouseout – когда мышь наводится на / покидает элемент.
- mousedown / mouseup – когда нажали / отжали кнопку мыши на элементе.
- mousemove – при движении мыши.
События на элементах управления:
- submit – пользователь отправил форму .
- focus – пользователь фокусируется на элементе, например нажимает на .
Клавиатурные события:
- keydown и keyup – когда пользователь нажимает / отпускает клавишу.
События документа:
- DOMContentLoaded – когда HTML загружен и обработан, DOM документа полностью построен и доступен.
CSS events:
- transitionend – когда CSS-анимация завершена.
Существует множество других событий. Мы подробно разберём их в последующих главах.
Обработчики событий
Событию можно назначить обработчик, то есть функцию, которая сработает, как только событие произошло.
Именно благодаря обработчикам JavaScript-код может реагировать на действия пользователя.
Есть несколько способов назначить событию обработчик. Сейчас мы их рассмотрим, начиная с самого простого.
Использование атрибута HTML
Обработчик может быть назначен прямо в разметке, в атрибуте, который называется on .
Например, чтобы назначить обработчик события click на элементе input , можно использовать атрибут onclick , вот так:
При клике мышкой на кнопке выполнится код, указанный в атрибуте onclick .
Обратите внимание, для содержимого атрибута onclick используются одинарные кавычки, так как сам атрибут находится в двойных. Если мы забудем об этом и поставим двойные кавычки внутри атрибута, вот так: onclick=»alert(«Click!»)» , код не будет работать.
Атрибут HTML-тега – не самое удобное место для написания большого количества кода, поэтому лучше создать отдельную JavaScript-функцию и вызвать её там.
Следующий пример по клику запускает функцию countRabbits() :
Как мы помним, атрибут HTML-тега не чувствителен к регистру, поэтому ONCLICK будет работать так же, как onClick и onCLICK … Но, как правило, атрибуты пишут в нижнем регистре: onclick .
Использование свойства DOM-объекта
Можно назначать обработчик, используя свойство DOM-элемента on .
К примеру, elem.onclick :
Если обработчик задан через атрибут, то браузер читает HTML-разметку, создаёт новую функцию из содержимого атрибута и записывает в свойство.
Этот способ, по сути, аналогичен предыдущему.
Обработчик всегда хранится в свойстве DOM-объекта, а атрибут – лишь один из способов его инициализации.
Эти два примера кода работают одинаково:
Так как у элемента DOM может быть только одно свойство с именем onclick , то назначить более одного обработчика так нельзя.
В примере ниже назначение через JavaScript перезапишет обработчик из атрибута:
Кстати, обработчиком можно назначить и уже существующую функцию:
function sayThanks() < alert('Спасибо!'); >elem.onclick = sayThanks;
Убрать обработчик можно назначением elem.onclick = null .
Доступ к элементу через this
Внутри обработчика события this ссылается на текущий элемент, то есть на тот, на котором, как говорят, «висит» (т.е. назначен) обработчик.
В коде ниже button выводит своё содержимое, используя this.innerHTML :
Частые ошибки
Если вы только начинаете работать с событиями, обратите внимание на следующие моменты.
Функция должна быть присвоена как sayThanks , а не sayThanks() .
// правильно button.onclick = sayThanks; // неправильно button.onclick = sayThanks();
Если добавить скобки, то sayThanks() – это уже вызов функции, результат которого (равный undefined , так как функция ничего не возвращает) будет присвоен onclick . Так что это не будет работать.
…А вот в разметке, в отличие от свойства, скобки нужны:
Это различие просто объяснить. При создании обработчика браузером из атрибута, он автоматически создаёт функцию с телом из значения атрибута: sayThanks() .
Так что разметка генерирует такое свойство:
button.onclick = function() < sayThanks(); // содержимое атрибута >;
Используйте именно функции, а не строки.
Назначение обработчика строкой elem.onclick = «alert(1)» также сработает. Это сделано из соображений совместимости, но делать так не рекомендуется.
Не используйте setAttribute для обработчиков.
Такой вызов работать не будет:
// при нажатии на body будут ошибки, // атрибуты всегда строки, и функция станет строкой document.body.setAttribute('onclick', function() < alert(1) >);
Регистр DOM-свойства имеет значение.
Используйте elem.onclick , а не elem.ONCLICK , потому что DOM-свойства чувствительны к регистру.
addEventListener
Фундаментальный недостаток описанных выше способов назначения обработчика – невозможность повесить несколько обработчиков на одно событие.
Например, одна часть кода хочет при клике на кнопку делать её подсвеченной, а другая – выдавать сообщение.
Мы хотим назначить два обработчика для этого. Но новое DOM-свойство перезапишет предыдущее:
input.onclick = function() < alert(1); >// . input.onclick = function() < alert(2); >// заменит предыдущий обработчик
Разработчики стандартов достаточно давно это поняли и предложили альтернативный способ назначения обработчиков при помощи специальных методов addEventListener и removeEventListener . Они свободны от указанного недостатка.
Синтаксис добавления обработчика:
element.addEventListener(event, handler, [options]);
- once : если true , тогда обработчик будет автоматически удалён после выполнения.
- capture : фаза, на которой должен сработать обработчик, подробнее об этом будет рассказано в главе Всплытие и погружение. Так исторически сложилось, что options может быть false/true , это то же самое, что .
- passive : если true , то указывает, что обработчик никогда не вызовет preventDefault() , подробнее об этом будет рассказано в главе Действия браузера по умолчанию.
Для удаления обработчика следует использовать removeEventListener :
element.removeEventListener(event, handler, [options]);
Удаление требует именно ту же функцию
Для удаления нужно передать именно ту функцию-обработчик которая была назначена.
Вот так не сработает:
elem.addEventListener( "click" , () => alert('Спасибо!')); // . elem.removeEventListener( "click", () => alert('Спасибо!'));
Обработчик не будет удалён, т.к. в removeEventListener передана не та же функция, а другая, с одинаковым кодом, но это не важно.
Вот так правильно:
function handler() < alert( 'Спасибо!' ); >input.addEventListener("click", handler); // . input.removeEventListener("click", handler);
Обратим внимание – если функцию обработчик не сохранить где-либо, мы не сможем её удалить. Нет метода, который позволяет получить из элемента обработчики событий, назначенные через addEventListener .
Метод addEventListener позволяет добавлять несколько обработчиков на одно событие одного элемента, например:
Как видно из примера выше, можно одновременно назначать обработчики и через DOM-свойство и через addEventListener . Однако, во избежание путаницы, рекомендуется выбрать один способ.
Обработчики некоторых событий можно назначать только через addEventListener
Существуют события, которые нельзя назначить через DOM-свойство, но можно через addEventListener .
Например, таково событие DOMContentLoaded , которое срабатывает, когда завершена загрузка и построение DOM документа.
document.onDOMContentLoaded = function() < alert("DOM построен"); // не будет работать >;
document.addEventListener("DOMContentLoaded", function() < alert("DOM построен"); // а вот так сработает >);
Так что addEventListener более универсален. Хотя заметим, что таких событий меньшинство, это скорее исключение, чем правило.
Объект события
Чтобы хорошо обработать событие, могут понадобиться детали того, что произошло. Не просто «клик» или «нажатие клавиши», а также – какие координаты указателя мыши, какая клавиша нажата и так далее.
Когда происходит событие, браузер создаёт объект события, записывает в него детали и передаёт его в качестве аргумента функции-обработчику.
Пример ниже демонстрирует получение координат мыши из объекта события:
Некоторые свойства объекта event :
event.type Тип события, в данном случае «click» . event.currentTarget Элемент, на котором сработал обработчик. Значение – обычно такое же, как и у this , но если обработчик является функцией-стрелкой или при помощи bind привязан другой объект в качестве this , то мы можем получить элемент из event.currentTarget . event.clientX / event.clientY Координаты курсора в момент клика относительно окна, для событий мыши.
Есть также и ряд других свойств, в зависимости от типа событий, которые мы разберём в дальнейших главах.
Объект события доступен и в HTML
При назначении обработчика в HTML, тоже можно использовать объект event , вот так:
Это возможно потому, что когда браузер из атрибута создаёт функцию-обработчик, то она выглядит так: function(event) < alert(event.type) >. То есть, её первый аргумент называется «event» , а тело взято из атрибута.
Объект-обработчик: handleEvent
Мы можем назначить обработчиком не только функцию, но и объект при помощи addEventListener . В этом случае, когда происходит событие, вызывается метод объекта handleEvent .
Как видим, если addEventListener получает объект в качестве обработчика, он вызывает object.handleEvent(event) , когда происходит событие.
Мы также можем использовать класс для этого:
Здесь один и тот же объект обрабатывает оба события. Обратите внимание, мы должны явно назначить оба обработчика через addEventListener . Тогда объект menu будет получать события mousedown и mouseup , но не другие (не назначенные) типы событий.
Метод handleEvent не обязательно должен выполнять всю работу сам. Он может вызывать другие методы, которые заточены под обработку конкретных типов событий, вот так:
Теперь обработка событий разделена по методам, что упрощает поддержку кода.
Итого
Есть три способа назначения обработчиков событий:
- Атрибут HTML: onclick=». » .
- DOM-свойство: elem.onclick = function .
- Специальные методы: elem.addEventListener(event, handler[, phase]) для добавления, removeEventListener для удаления.
HTML-атрибуты используются редко потому, что JavaScript в HTML-теге выглядит немного странно. К тому же много кода там не напишешь.
DOM-свойства вполне можно использовать, но мы не можем назначить больше одного обработчика на один тип события. Во многих случаях с этим ограничением можно мириться.
Последний способ самый гибкий, однако нужно писать больше всего кода. Есть несколько типов событий, которые работают только через него, например, DOMContentLoaded . Также addEventListener поддерживает объекты в качестве обработчиков событий. В этом случае вызывается метод объекта handleEvent .
Не важно, как вы назначаете обработчик – он получает объект события первым аргументом. Этот объект содержит подробности о том, что произошло.
Мы изучим больше о событиях и их типах в следующих главах.
Процедуры-обработчики событий
Особенностью обработки событий среде 1С:Предприятия 8 является то, что имя процедуры-обработчика в одних случаях должно совпадать с именем события, а в других случаях может от него отличаться. Данная статья написана, чтобы внести ясность в этом вопросе.
Обратите внимание, что термин «Предопределенная процедура», который использовался в версии 7.х, теперь заменен на «процедура-обработчик события» или просто «обработчик события».
ПРАВИЛО №1. Если процедура-обработчик события относится к форме или элементу управления, то ее обязательно нужно указывать в палитре свойств для формы или элемента управления. |
Ниже показана палитра свойств для формы элемента справочника «Номенклатура» с несколькими назначенными обработчиками событий:
За информацией о приемах работы с этой частью палитры свойств обращайтесь к документации: книга «Конфигурирование и администрирование», «Глава 3. Объекты конфигурации => Свойства элементов управления => Категория свойств События» (стр. 1 — 204)
Обратите внимание на важный момент, имя процедуры-обработчика событий может не совпадать с именем события . Для элементов управления чаще всего так и бывает, например, процедура «ТипЦенПриИзменении» обрабатывает событие «ПриИзменении» поля ввода для реквизита «ТипЦен», как показано на следующем рисунке:
Как правило, процедура-обработчик имеет тот же набор параметров, что и событие. Если у нее нет соответствующих параметров, то обработка события может получиться неполной. Поэтому рекомендуется создавать процедуры-обработчики конструктором через палитру свойств, нажимая кнопку с лупой или выбирая процедуру из выпадающего списка.
Есть еще одна интересная возможность: одна и та же процедура может «обслуживать» несколько событий формы или элементов управления, в том числе от разных источников. Элемент управления, который инициировал событие, передается в качестве первого параметра в эту процедуру-обработчик (параметр «Элемент»), и при необходимости алгоритм может проанализировать, откуда пришло событие, и выполнить соответствующие действия.
ПРАВИЛО №2. Процедуры-обработчики событий, расположенные в модуле приложения, модуле внешнего соединения, модуле прикладного объекта должны называться точно так, как называются соответствующие события. |
Поясним это правило на конкретных примерах:
1. Процедуры-обработчики событий, расположенные в модуле приложения или модуле внешнего соединения, совпадают с именами событий:
- ПередНачаломРаботыСистемы
- ПриНачалеРаботыСистемы
- ПриЗавершенииРаботыСистемы
- ПередЗавершениемРаботыСистемы
- ОбработкаВнешнегоСобытия
2. Имена процедур-обработчиков событий, расположенных в модуле объекта, тоже строго соответствуют именам событий:
для модуля документа (события объекта типа «ДокументОбъект»)
- ПередЗаписью
- ПриЗаписи
- ПриУдалении
- ПриКопировании
- ОбработкаЗаполнения (для обработки «ввода на основании»)
- ОбработкаПроведения
- ОбработкаУдаленияПроведения
- ПриУстановкеНовогоНомера
Аналогичные обработчики событий могут располагаться в модуле справочника и модулях других прикладных объектов.
3. Есть также модуль набора записей для всех видов регистров, который подобен модулям прикладных объектов. Модуль набора записей может содержать следующие процедуры-обработчики событий (имена процедур должны совпадать с именами событий):
- ПередЗаписью
- ПриЗаписи
Ниже приведены несколько важных моментов, которые полезно помнить при работе с событиями:
Примечание 1. Событие ПередЗаписью прикладного объекта отличается от события ПередЗаписью формы, связанной с этим прикладным объектом. Обработчик события в модуле формы вызывается при интерактивной записи, а обработчик в модуле объекта при любом способе записи элемента в базу данных.
Примечание 2. Если в процедурах-обработчиках модуля объекта нужно обратиться к самому объекту (текущий элемент справочника, текущий документ и т.д.), то для этого можно использовать свойство ЭтотОбъект. Оно содержит объект типа «СправочникОбъект», «ДокументОбъект» и т.д.
Примечание 3. Считается грубой ошибкой в процедурах-обработчиках событий объектов вызывать такие интерактивные команды, как Вопрос и Предупреждение. Эти команды показывают на экране диалоговое окно и ждут реакции пользователя. Так как событие обрабатывается в рамках транзакции, то это вызовет значительную задержку в обработке события и часть данных (или вся таблица) будет заблокирована на время ожидания.
Работа в среде, управляемой событиями
Обработка событий — фундаментальный механизм, на котором основана работа программ wxPython. Такие программы называют программами, управляемыми событиями. В этой главе, мы обсудим, чем управляемое событиями приложение отличается от традиционного. Мы дадим краткий обзор понятий и терминологии, используемой в программирование GUI. Мы также расскажем о жизненном цикле типичной управляемой событиями программы.
Событие – это то, что случается в вашей системе, и на что ваше приложение может отреагировать, вызывая определенную функцию. Событие может быть низкоуровневым действием пользователя, типа перемещения мыши или нажатия клавиши, или высокоуровневым, типа выбора из меню. Событие может также быть создано операционной системой. Созданные вами объекты также могут создавать собственные события. Приложение wxPython работает, связывая определенный вид события с определенным программным кодом, который должен быть выполнен в ответ на событие. Такой программный код, связанный с событием, называется обработчиком события.
Эта глава рассказывает о событиях, о том как писать код для обработки событий, и о том как система wxPython вызывает ваш код, когда событие произошло. Мы также покажем вам, как добавить собственные события к библиотеке wxPython, которая содержит список стандартных пользовательских и системных действий.
Терминология для понимания событий
Эта глава содержит много терминов, большая часть которых начинается со слова событие (event). Таблица 3.1 — справочник терминов, которые мы будем использовать.
Таблица 3.1 Термины, связанные с событием
Мы надеемся, что эта таблица не позволит вам перепутать обработчики событий с биндерами. Обращайтесь к этой таблице по мере необходимости. Мы начнем с краткого обзора управляемого событиями программирования, затем мы обсудим специфические особенности того, как все это реализовано в wxPython.
Программирование управляемое событиями
Программа, управляемая событиями, имеет структуру управления, которая, главным образом, получает события и отвечает на них. Структура программы wxPython (или любой другой программы, управляемой событиями) существенно отличается от структуры обычного сценария Python. Типичный сценарий Python имеет определенную отправную точку и определенный пункт окончания, и программист управляет порядком выполнения, используя условные операторы, циклы и функции. Программа не линейна, но ее порядок часто независим от пользовательских действий.
С точки зрения пользователя, программа wxPython большую часть времени ничего не делает. Она ожидает пока пользователь или система проявят свою активность. Структура программы wxPython – это пример архитектуры программы, управляемой событиями. На рисунке 3.1 показаны главные части программы, управляемой событиями.
Рисунок 3.1 Схематичное изображение цикла обработки событий. Показан жизненный путь программы, пользовательские события и вызов обработчиков.
Цикл обработки событий можно сравнить с оператором в сервис-центре. Пока нет входящих звонков оператор ожидает. Наконец происходит событие — телефонный звонок. В процессе общения с клиентом, оператор должен получить достаточно информации, чтобы знать к кому переадресовать клиента для ответа. После пересылки, оператор ждет следующего звонка.
- После начальной установки, большую часть времени программа проводит в цикле, ожидая события. Вход в этот цикл показывает начало пользовательской интерактивной части программы, и выход из цикла показывает ее конец. В wxPython, этот цикл – метод wx.App.MainLoop(). Он должен быть явно вызван в вашем сценарии. Выход из цикла происходит, когда все окна верхнего уровня закрыты.
- Программа реагирует на события, которые могут произойти в среде программы. Обычно, события вызываются пользовательской деятельностью, но могут также быть результатом деятельности системы, или произвольного кода в другом месте программы. В wxPython, все события – объекты класса wx.Event или производного от него. Каждое событие имеет атрибут тип события (event type) (см. таблицу 3.1), который позволяет различать виды событий.
- В цикле, программа периодически проверяет, случилось ли что-нибудь требующее ответа. Есть несколько механизмов, которыми система, управляемая событиями, может получать информацию о событиях. Наиболее популярный метод, используемый wxPython: события помещаются в общую очередь по мере поступления, а затем извлекаются из нее и обрабатываются.
- Когда событие происходит, система его обрабатывает, вызывая программный код, связанный с этим событием. В wxPython, родные системные события переведены в объекты wx.Event и созданы методы wx.EvtHandler.ProcessEvent() для того, чтобы послать события надлежащему обработчику. На рисунке 3.3 схематически изображен этот процесс. Составляющие части механизма обработки событий – биндеры (event binder) и обработчики событий. Биндер (event binder) — предопределенный объект wxPython. Есть отдельный биндер для каждого типа события. Обработчик (event handler) — функция или метод, который принимает объект события как параметр. Обработчик вызывается, когда пользователь вызывает соответствующее событие.
Далее, мы подробно обсудим как это реализовано в wxPython. И начнем с обработчиков событий.
Программирование обработчиков событий
В вашем коде wxPython, события и обработчики событий должны быть связаны с соответствующими виджетами. Например, событие нажатия кнопки будет послано определенному обработчику, связанному с этой кнопкой. Для связи события, виджета и обработчика используете специальный объект — биндер. Например,
1 self.Bind(wx.EVT_BUTTON, self.OnClick, aButton)
используется предопределенный объект биндер wx.EVT_BUTTON для связи события нажатия кнопки aButton с методом self.OnClick. Метод Bind() – это метод класса wx.EvtHandler, который является базовым классом всех отображаемых объектов. Поэтому, этот пример кода может быть применен к любому виджету.
Вам кажется, что программа wxPython пассивно ждет событие, но на самом деле она выполняет метод wx.App.MainLoop(). MainLoop() может быть переведен в упрощенный псевдокод Python:
1 while True: 2 while not self.Pending(): 3 self.ProcessIdle() 4 self.DoMessage()
Другими словами, пока нет событий, система простаивает. При появлении события, посылается сообщение соответствующему обработчику.
Проектирование программ управляемых событиями
Природа управляемой событиями программы wxPython предполагает определенные способы проектирования и кодирования. Так как неизвестно, когда произойдет событие, программист уступает большую часть управления программой пользователю. Большая часть кода в вашей программе wxPython выполняется как прямой или косвенный результат действий пользователя или системы. Например, сохранение документа в вашей программе происходит после того, как пользователь выберет пункт меню, нажмет кнопку панели инструментов или нажмет горячую клавишу. Любое из этих событий может вызвать обработчик, который сохраняет документ пользователя.
Архитектура программ, управляемых событиями, является распределенной. Код, который вызывается в ответ на событие, обычно не определяется виджетом, который вызвал это событие. Например, программный код, вызванный в ответ на нажатие кнопки, не обязан быть частью определения кнопки. Он может быть определен во фрейме или любом другом месте. Объединение такой архитектуры с объектно-ориентированным дизайном позволяет создавать программный код многократного использования. Вы найдете, что гибкая природа Python делает особенно простым повторное использование общих обработчиков событий в различных приложениях wxPython. С другой стороны, распределенная природа программы, управляемой событиями, затрудняет ее понимание и поддержку. Иногда бывает трудно разыскать метод, вызываемый в ответ на событие. (В некоторой степени, эта проблема верна для всего объектно-ориентированного программирования). В главе 5 мы дадим рекомендации, позволяющие упорядочить код программ, управляемых событиями.
Генерация событий
В wxPython, большинство виджетов генерирует высокоуровневые события в ответ на события более низкого уровня. Например, щелчок мыши на кнопке wx.Button генерирует событие wx.CommandEvent типа EVT_BUTTON. Точно так же при перетаскивании мышью угла окна, wxPython автоматически создаст событие wx.SizeEvent. Преимущество высокоуровневых событий состоит в том, что они позволяют сосредоточиться на самих событиях, вместо того, чтобы отслеживать каждый щелчок мыши. Высокоуровневые события могут также содержать полезную информацию о событии. Поскольку вы создаете ваши собственные виджеты, вы можете определить ваши собственные события.
В wxPython cобытия – это объекты класса wx.Event или производного от него. Базовый класс wx.Event – это небольшой абстрактный класс, содержащий методы чтения и записи для свойств, определенных для всех событий, типа EventType, EventObject, и !Timestamp. Различные подклассы wx.Event добавляют дополнительную информацию. Например, wx.MouseEvent содержит информацию о координатах курсора миши и нажатой кнопке.
В wxPython определено несколько различных подклассов wx.Event. Таблица 3.2 содержит список некоторых из классов событий, с которыми вы скорее всего столкнетесь. Помните, один класс события может иметь много типов. Каждый тип события соответствует различным действиям пользователя.
Таблица 3.2 Важнейшие подклассы wx.Event
Как правило, объекты событий сами ничего не делают. Событие передается как параметр соответствующему обработчику, используя биндер и систему обработки событий.
Как связать событие с обработчиком?
Биндеры состоят из объектов класса wx.PyEventBinder. Объекты wx.PyEventBinder определены для всех типов поддерживаемых событий. Вы можете создать ваши собственные биндеры для ваших собственных типов событий, когда это необходимо. Для каждого типа события определен свой биндер. Типы событий более детализированы чем подклассы wx.Event. Например, класс wx.MouseEvent имеет четырнадцать отдельных типов событий.
В wxPython, имена объектов биндеров глобальны. Чтобы ясно связать типы объектов с обработчиками, эти имена начинаются с wx.EVT_ и соответствуют названиям макроопределений, используемых в коде C++ wxWidgets. В коде wxPython, имя биндера используются вместо типа события. В результате, и это стоит подчеркнуть, имя биндера – это не целочисленный код, который вы получили бы, вызывая метод GetEventType() объекта wx.Event. Челочисленные коды типов событий имеют полностью различный набор глобальных названий, и не часто используются практически.
Для примера рассмотрим типы событий wx.MouseEvent. Как мы упоминали, их – четырнадцать. Девять из них описывают нажатия кнопок мыши. Вот их имена:
1 wx.EVT_LEFT_DOWN 2 wx.EVT_LEFT_UP 3 wx.EVT_LEFT_DCLICK 4 wx.EVT_MIDDLE_DOWN 5 wx.EVT_MIDDLE_UP 6 wx.EVT_MIDDLE_DCLICK 7 wx.EVT_RIGHT_DOWN 8 wx.EVT_RIGHT_UP 9 wx.EVT_RIGHT_DCLICK
Дополнительно, событие типа wx.EVT_MOTION происходит при перемещении мыши. Собития типов wx.ENTER_WINDOW и wx.LEAVE_WINDOW возникают, когда курсор мыши входит в область виджета и выходит из нее. Событие типа wx.EVT_MOUSEWHEEL происходит из-за движения колесика мыши. Наконец, вы можете связать все события мыши с единственной функцией, используя тип wx.EVT_MOUSE_EVENTS.
Аналогично, класс wx.CommandEvent имеет 28 различных типов событий, связанных с ним (хотя некоторые из них — только для последних версий Windows). Большинство из них определено для единственного виджета, например, wx.EVT_BUTTON для кнопки и wx.EVT_MENU для выбора пункта меню. События команды описываются вместе со своими виджетами во второй части книги.
Работа с методами wx.EvtHandler
Класс wx.EvtHandler определяет множество методов, которые не вызываются при нормальных обстоятельствах. Часто используется только метод Bind(). Он создает биндеры событий, которые мы обсуждали. Метод принимает следующие параметры:
1 Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)
Функция Bind() связывает событие, объект и обработчик. Параметр event обязательный. Это объект класса wx.PyEventBinder как описано в разделе 3.3. Параметр handler, также обязательный. Это объект, поддерживающий вызов, обычно это метод или функция с единственным параметром – объектом событием. Параметром обработчика может быть None, если событие не связано с обработчиком. Параметр source – это виджет, который является источником события. Параметр используется, когда виджет, вызывающий событие не тот, который используется как обработчик. Как правило, обработчики событий — это методы вашего класса wx.Frame. С этими методами вы связываете события от виджетов, содержащихся в окне. Однако, если родительское окно содержит больше чем один источник события нажатия кнопки (например в окне есть две кнопки OK и Cancel), параметр source используется, чтобы различить какой объект является источником события. Следующий пример демонстрирует это:
1 self.Bind(wx.EVT_BUTTON, self.OnClick, button)
Связываются событие, объект button (и только button) и метод OnClick(). В листинге 3.1 дан пример использования метода Bind() с параметром source и без него. Вы не обязаны называть ваши обработчики событий On, но это — общее соглашение.
Листинг 3.1 Пример использования метода Bind() с параметром source и без него
1 def __init__(self, parent, id): 2 wx.Frame.__init__(self, parent, id, 'Frame With Button', 3 size=(300, 100)) 4 panel = wx.Panel(self, -1) 5 button = wx.Button(panel, -1, "Close", pos=(130, 15), 6 size=(40, 40)) 7 # (1) Binding the frame close event 8 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 9 # (2) Binding the button event 10 self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) 11 12 def OnCloseMe(self, event): 13 self.Close(True) 14 15 def OnCloseWindow(self, event): 16 self.Destroy()
(1) В этой строке событие закрытия фрейма связывается с методом self.OnCloseWindow. Так как событие и вызвано и связано с фреймом, нет необходимости передавать параметр source.
(2) Эта строка связывает событие нажатия кнопки с кнопкой и методом self.OnCloseMe. В этом случае, кнопка, генерирующая событие, это не фрейм. Поэтому, кнопку нужно передать как параметр методу Bind, чтобы wxPython мог различать события нажатия этой кнопки и события нажатия других кнопок фрейма.
Вы можете также использовать параметр source, чтобы идентифицировать элемент, даже если элемент не является источником события. Например, вы можете связать события меню с обработчиками даже при том, что события меню технически вызваны фреймом. Листинг 3.2 иллюстрирует пример связывания событий меню.
Листинг 3.2 Связывание событий меню
1 #!/usr/bin/env python 2 3 import wx 4 5 class MenuEventFrame(wx.Frame): 6 7 def __init__(self, parent, id): 8 wx.Frame.__init__(self, parent, id, 'Menus', 9 size=(300, 200)) 10 menuBar = wx.MenuBar() 11 menu1 = wx.Menu() 12 menuItem = menu1.Append(-1, "&Exit. ") 13 menuBar.Append(menu1, "&File") 14 self.SetMenuBar(menuBar) 15 self.Bind(wx.EVT_MENU, self.OnCloseMe, menuItem) 16 17 def OnCloseMe(self, event): 18 self.Close(True) 19 20 if __name__ == '__main__': 21 app = wx.PySimpleApp() 22 frame = MenuEventFrame(parent=None, id=-1) 23 frame.Show() 24 app.MainLoop()
Параметры id и id2 метода Bind() определяют источник события, используя число ID, а не виджет непосредственно. Как правило, id и id2 не требуются, так как идентификатор (ID) источника события может быть извлечен из параметра source. Однако, иногда использование ID непосредственно имеет смысл. Например, если вы используете предопределенные ID для диалогового окна, легче использовать число ID, чем использовать виджет. Если вы используете оба параметра (и id и id2), то вы можете связать с событием несколько виджетов, идентификаторы которых находятся в диапазоне от id до id2. Это полезно, когда идентификаторы виджетов последовательны. Благодаря использованию типов событий, wxPython может очень точно управлять событиями, все еще позволяя подобным событиям быть объектами того же самого класса, и совместно использовать данные и функциональные возможности. Это делает написание обработчиков событий намного более чистыми в wxPython, чем в других инструментариях для создания интерфейса.
Биндеры позволяют связать виджет, событие и функцию обработчик. Эта связь позволяет системе wxPython отвечать на события виджета, выполняя код функции обработчика. В wxPython, любой объект, который может ответить на событие, является подклассом wx.EvtHandler. Все отображаемые объекты – это подклассы wx.EvtHandler, следовательно каждый виджет в приложении wxPython может отвечать на события. Класс wx.EvtHandler может использоваться не только с виджетами, например его использует wx.App. Таким образом объекты, отвечающие на события, не ограничены виджетами. Фраза: виджет может ответить на события, означает, что виджет может создать биндер, который wxPython использует для диспетчеризации событий. Программный код функции обработчика не обязан располагаться в классе wx.EvtHandler.
Старый стиль связывания
Метод Bind() появился в wxPython начиная с версии 2.5. В предыдущих версиях wxPython, имена EVT_* использовались как функции, и связывание выглядело следующим образом:
1 wx.EVT_BUTTON(self, self.button.GetId(), self.OnClick)
Недостаток старого стиля в том, что он не выглядит и не действует как объектно-ориентированный метод. Однако, старый стиль все еще работает в версии 2.5 (потому что объекты wx.EVT * поддерживают вызов), и вы, возможно, будете встречать его в коде wxPython.
В таблице 3.3 представлен список некоторых методов класса wx.EvtHandler, которые вы можете использовать для управления процессом обработки событий.
Таблица 3.3 Обычно используемые методы класса wx.EvtHandler
Как wxPython обрабатывает события?
Ключевой компонент системы, основанной на событиях, — это процесс диспетчеризации событий. В этом разделе, мы рассмотрим все шаги обработки поступающих событий. Для этого мы используем небольшой пример, чтобы проследить шаги в процессе. На рисунке 3.2 изображено окно с единственной кнопкой, которая будет использоваться для генерации событий.
Рисунок 3.2 Окно для тестирования событий
Листинг 3.3 содержит код, который создает это окно. В этом коде, события генерируются при щелчке на кнопке и при перемещении курсора мыши над кнопкой.
Листинг 3.3 Различные виды событий мыши
1 #!/usr/bin/env python 2 3 import wx 4 5 class MouseEventFrame(wx.Frame): 6 7 def __init__(self, parent, id): 8 wx.Frame.__init__(self, parent, id, 'Frame With Button', 9 size=(300, 100)) 10 self.panel = wx.Panel(self) 11 self.button = wx.Button(self.panel, 12 label="Not Over", pos=(100, 15)) 13 # (1) Binding the button event 14 self.Bind(wx.EVT_BUTTON, self.OnButtonClick, 15 self.button) 16 # (2) Binding the mouse enter event 17 self.button.Bind(wx.EVT_ENTER_WINDOW, 18 self.OnEnterWindow) 19 # (3) Binding the mouse leave event 20 self.button.Bind(wx.EVT_LEAVE_WINDOW, 21 self.OnLeaveWindow) 22 23 def OnButtonClick(self, event): 24 self.panel.SetBackgroundColour('Green') 25 self.panel.Refresh() 26 27 def OnEnterWindow(self, event): 28 self.button.SetLabel("Over Me!") 29 event.Skip() 30 31 def OnLeaveWindow(self, event): 32 self.button.SetLabel("Not Over") 33 event.Skip() 34 35 if __name__ == '__main__': 36 app = wx.PySimpleApp() 37 frame = MouseEventFrame(parent=None, id=-1) 38 frame.Show() 39 app.MainLoop()
Фрейм MouseEventFrame содержит одну кнопку в середине. Нажатие на мышь изменяет цвет фона фрейма на зеленый. Клик мышкой связывается с обработчиком в строке (1). Когда курсор мыши входит в область кнопки, измененяется заголовок кнопки, связь с обработчиком в строке (2). Когда курсор мыши покидает кнопку, заголовок меняется на прежний, связь с обработчиком в строке (3).
При рассмотрении этого примера, возникают некоторые вопросы об обработке событий в wxPython. В строке (1), событие кнопки, генерируется кнопкой, расположенной на фрейме. Откуда wxPython знает, что искать биндер нужно в объекте frame, а не объекте button? В строках (2) и (3), события связываются непосредственно с объектом button. Почему эти события не могут также быть связаны с фреймом? На оба этих вопроса мы ответим после исследования процедуры обработки событий wxPython.
Понимание процесса обработки событий
Процедура обработки событий в wxPython была спроектирована так, чтобы сделать ее простой для программиста, чтобы обрабатывать высокоуровневые события и игнорировать низкоуровневые. Далее, мы проследим процедуру обработки событий для нажатия кнопки и щелчка мышью.
На рисунке 3.3 изображена основная блок-схема процесса обработки событий. Прямоугольники указывают начало и конец процесса, круги указывают различные объекты wxPython, которые являются частью процесса, ромбы указывают пункты решения, и прямоугольники с полосами указывают фактические методы обработчики событий.
Процесс начинается с объекта, который вызвал событие. Как правило, wxPython ищет сначала у этого объекта обработчик соответствующего типа события. Если обработчик найден, то он выполняется. В противном случае wxPython проверяет, распространять ли событие по контейнерной иерархии. Если да, то проверяется родительский контейнер и все контейнеры до окна верхнего уровня. Если событие не распространяется, то wxPython проверяет прикладной объект (wx.App) для поиска метода обработчика. После выполнения обработчика процесс обычно заканчивается. Однако, обработчик может сказать wxPython продолжать искать обработчики.
Рисунок 3.3
Давайте подробно рассмотрим каждый шаг процесса. Перед обсуждением каждого шага, мы отобразим эскиз для соответствующего фрагмента рисунка 3.3.
Шаг 1 Создание события
Процесс начинается, когда событие произошло.
Рисунок 3.4
Большинство существующих типов событий создается в ответ на определенные пользовательские действия или системные уведомления. Например, событие mouse entering создается, когда wxPython замечает вход мыши в границы нового виджета, и событие button click создается после событий left mouse down и left mouse up на кнопке.
Событие сначала передается объекту, ответственному за создание события. Для button click, объект — кнопка, для mouse enter event, объект – виджет, в который входит курсор мыши.
Шаг 2 Определяет, разрешено ли объекту обрабатывать события
Следующий шаг процесса проверяет, может ли виджет ответственный за событие обработать это события.
Окну можно разрешить или запретить обработку события, вызывав метод wx.EvtHandler SetEvtHandlerEnabled(boolean). Эффект запрещения обработки события состоит в том, что виджет полностью исключается из процесса обработки события.
Разрешение (enable) или заперщение (disable) виджета на уровне обработки события не нужно путать с запрещением виджета на уровне пользовательского интерфейса (UI). Отключение виджета на уровне UI можно выполнить, используя методы Disable() и Enable() класса wx.Window. Отключение виджета в UI означает, что пользователь не может взаимодействовать с заблокированным виджетом. Обычно, заблокированный виджет показан на экране серым цветом, чтобы указать на его состояние. Окно, которое было заблокировано на уровне UI, не будет в состоянии произвести любые события; однако, если оно находится на контейнерной иерархии для других событий, оно все еще обрабатывает события, которые получает. До конца этого раздела, мы будем использовать термины enabled и disabled имея в виду уровень обработки события.
Рисунок 3.5
Проверка состояния объекта enabled/disabled выполняется в методе ProcessEvent(), который вызывает система wxPython, чтобы запустить механизм диспетчеризации событий. Мы будем встречать метод ProcessEvent() снова и снова при рассмотреднни процесса обработки событий. Он фактически осуществляет большую часть процесса, изображенного на рисунке 3.3. Метод ProcessEvent() возвращает True, если обработка события выполнена. Обработку считают выполненой, если найдена функция обработчика для объекта и обрабатываемого события. Функция обработчика может явно просить продолжить обработку, вызвав метод Skip() класса wx.Event. Кроме того, если объект — подкласс wx.Window, он может фильтровать события, используя специальный объект — validator. Validators рассматриваются более подробно в главе 9.
Шаг 3 Определение расположения обработчика
Метод ProcessEvent() ищет объект binder, который связывает тип события и текущий объект.
Рисунок 3.6
Если binder не найден для объекта непосредственно, обработка идет по иерархии класса, чтобы найти binder в родительском классе объекта — это отличается от обхода в контейнерной иерархии, которая выполняется в следующем шаге. Если объект binder найден, wxPython вызывает связанную функцию обработчика. После того, как обработчик вызван, обработка этого события завершается, если только функция обработчика явно не запрашивает дальнейшую обработку.
В листинге 3.3 событие mouse enter перехватывается, потому что определена связь между кнопкой, биндером wx.EVT_ENTER_WINDOW и методом OnEnterWindow(), который и вызывается. Так как мы не связывали событие mouse button click и wx.EVT_LEFT_DOWN, wxPython продолжил бы поиск в этом случае.
Шаг 4 Определяет продолжать ли обрабатывать событие
Рисунок 3.7
После вызова первого обработчика, wxPython проверяет требуется ли дальнейшая обработка. Обработчик запрашивает дальнейшую обработку, вызывая метод Skip() класса wx.Event. Если метод Skip() вызывается, то обработка продолжается, и любые обработчики, определенные в суперклассе выполняются в этом шаге. Метод Skip() можно вызвать в любом месте обработчика, или любом коде, вызванном обработчиком. Метод Skip() устанавливает в объекте события флаг, который wxPython проверяет после выполнения метода обработчика. В листинге 3.3 OnButtonClick() не вызывает Skip(), таким образом процесс обработки события завершается в конце метода обработчика. Другие два обработчика события вызывают Skip(), поэтому система продолжит искать соответствующий биндер для события, в конечном счете вызывая функцию обработки события по умолчанию.
Шаг 5 Определяет распространять ли событие В конечном счете wxPython определяет, должен ли процесс распространять событие по контейнерной иерархии, чтобы найти обработчик. Контейнерная иерархия – это путь от определенного виджета до фрейма верхнего уровня, проходящий от каждого виджета вверх к его родительскому контейнеру.
Рисунок 3.8
Если текущий объект не имеет обработчика для события, или если обработчик вызывает метод Skip(), wxPython определяет, должено ли событие распростанятся по контейнерной иерархии. Если ответ «Нет», процесс еще раз ищет обработчик, в объекте wx.App, и затем останавливается. Если ответ «Да», процесс переходит к контейнеру виджета. Процесс продолжается пока wxPython не находит соответствующий биндер, или не достигает фрейма верхнего уровня, или объекта wx.Dialog (даже если диалог не окно верхнего уровня). Считают, что событие нашло соответствующий биндер, если ProcessEvent () для этого объекта возвращает True, указывая, что обработка завершена.
Должено ли событие распространятся по контейнерной иерархии определяет динамическое свойство каждого объекта события, хотя на практике почти всегда используется значение по умолчанию. По умолчанию, только объекты класса wx.CommandEvent и производные от него, распространяются по контейнерной иерархии. Все другие события этого не делают.
В листинге 3.3 так обрабатывается нажатие кнопки. Щелчок мышью на wx.Button генерирует событие command типа wx.EVT_BUTTON. После того, как wxPython не в состоянии найти биндер в объекте кнопки, он передает событие родителю, который в этом случае является панелью. У панели нет соответствующего биндера, далее проверяется родитель панели — фрейм. Так как фрейм имеет соответствующий биндер, ProcessEvent() вызывает соответствующую функцию — OnButtonClick().
Шаг 5 также объясняет, почему события mouse enter и mouse leave должны быть связаны с кнопкой а не с фреймом. Так как события мыши не подкласс wx.CommandEvent, события mouse enter и mouse leave не распространяются вверх родителю, и wxPython не может найти соответствующий биндер.
Событиям command даются такие привилегии, потому что это высокоуровневые события, указывающие, что пользователь делает что-то в приложении, а не в конкретном окне. Предполагается, что события окна прежде всего представляют интерес для виджета, который первоначально их получает, в то время как события прикладного уровня могут представлять интерес выше в иерархии. Это правило не препятствует нам определять обработчик события в другом месте. Например, даже при том, что событие mouse click связано с кнопкой, сам биндер определен в классе фрейма, и вызывает метод класса фрейма. Другими словами, события низкого уровня non-command обычно используются для вещей, которые случаются с виджетом типа щелчка мыши, нажатия клавиши, запроса на перерисовку, изменение размера или перемещение. С другой стороны, события command, типа нажатия кнопки или выбора из списка, обычно производятся и испускаются непосредственно виджетом. Например, события команды кнопки генерируются после нажатия и отпускания кнопки мыши на соответствующем виджете.
Наконец, если событие не обработано после прохождения через контейнерную иерархию, ProcessEvent() вызывается для прикладного объекта wx.App. По умолчанию он ничего не делает, однако, вы можете добавить биндеры событий к вашему wx.App, чтобы обработать события некоторым нестандартным способом. Например, если вы пишите систему для создания GUI, вы можете захотеть, чтобы события в вашем окне построителя интерфейса распространялись и в окно редактирования кода, даже при том, что это оба окна верхнего уровня. Один из способов это сделать — фиксировать события в прикладном объекте и передавать их в окно кода.
Использование метода Skip()
Первый обработчик, найденный для события останавливает процесс обработки события, если только в обработчике не вызван метод Skip(). Метод Skip() позволяет продолжить поиск обработчиков в родительских классах и родительских контейнерах. В некоторых случаях, объект должен продолжить обрабатывать событие, чтобы позволить поведение по умолчанию наряду с вашим собственным обработчиком. В листинге 3.4 показан пример использования метода Skip(), который позволяет программе отвечать и на событие left button down и на событие button click в той же самой кнопке.
Листинг 3.4 Обработка mouse down и button click в то же самое время
1 #!/usr/bin/env python 2 3 import wx 4 5 class DoubleEventFrame(wx.Frame): 6 7 def __init__(self, parent, id): 8 wx.Frame.__init__(self, parent, id, 'Frame With Button', 9 size=(300, 100)) 10 self.panel = wx.Panel(self, -1) 11 self.button = wx.Button(self.panel, -1, "Click Me", pos=(100, 15)) 12 # (1) 13 self.Bind(wx.EVT_BUTTON, self.OnButtonClick, self.button) 14 # (2) 15 self.button.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) 16 17 def OnButtonClick(self, event): 18 self.panel.SetBackgroundColour('Green') 19 self.panel.Refresh() 20 21 def OnMouseDown(self, event): 22 self.button.SetLabel("Again!") 23 # (3) 24 event.Skip() 25 26 if __name__ == '__main__': 27 app = wx.PySimpleApp() 28 frame = DoubleEventFrame(parent=None, id=-1) 29 frame.Show() 30 app.MainLoop()
(1) Эта строка связывает кнопку, событие button click и обработчик OnButtonClick(), который изменяет цвет фона фрейма.
(2) Эта строка связывает событие left mouse button down и обработчик OnMouseDown(), который изменяет текст кнопки. Так как событие left mouse button down не событие command, это событие должно быть связано с кнопкой, а не с фреймом.
Когда пользователь щелкает мышью по кнопке, сначала система генерирует событие left mouse button down. При нормальных обстоятельствах, событие left mouse button down изменяет состояние кнопки таким образом, что последующее событие left button up, создает событие wx.EVT_BUTTON. Класс DoubleEventFrame сохраняет это поведение благодаря вызову метода Skip() в строке (3). Без Skip() алгоритм обработки события находил бы первым биндер, созданный в строке (2), и останавливался прежде, чем кнопка могла создать событие wx.EVT_BUTTON. С использованием метода Skip(), обработка события продолжается, и button click создается.
Связывая свои обработчики с низкоуровневыми событиями, такими как mouse up/down, помните, что wxPython тоже ожидает эти события, чтобы обработать или сгенерировать дальнейшие высокоуровневые события. Если вы не вызываете метод Skip(), то вы рискуете блокировать ожидаемое поведение. Например, вы можете не увидеть как нажимается кнопка.
Методы wx.App для управления событиями
Вы можете непосредственно управлять главным циклом обработки событий, используя некоторые методы wx.App. Например, вы можете захотеть начать обрабатывать следующее доступное событие в вашем собственном списке, вместо того, чтобы ждать пока wxPython начнет процесс обработки. Эта особенность необходима, если вы запускаете долгую процедуру, и не хотите, чтобы казалось, что GUI застыл. Вы не должны часто использовать методы из этого раздела, но иногда важно иметь такие возможности.
В таблице 3.4 содержится список методов wx.App, которые вы можете использовать для воздействия на цикл обработки событий.
Таблица 3.4 Методы wx.App для цикла обработки событий
Другой метод для управления событиями состоит в том, чтобы создать свои собственные типы событий, которые соответствуют специфическим особенностям вашего приложения и виджетов. В следующем разделе, мы обсудим, как создать собственные события.
Перевод: Володеев Сергей
WxPython_in_Action/Работа_в_среде_управляемой_событиями (последним исправлял пользователь alafin 2010-05-19 08:08:27)
- MoinMoin Powered
- Python Powered
- GPL licensed
- Valid HTML 4.01