к оглавлению   к 4GL - визуальному программированию

Концепции программирования на базе WFC

Windows Foundation Classes for Java (WFC) образует каркас, который состоит из Java-пакетов и поддерживает компоненты, предназначенные для операционной системы Windows и модели объектов Dynamic HTML. WFC тесно интегрирован со средой программирования Visual J++ и предоставляет полный набор элементов управления Windows, написанных на Java. Благодаря этому — а также поддержке средств типа Intelli-Sense, Forms Designer, Application Wizard и Object Browser — значительно упрощается создание Windows-приложений на Java. Но как бы средства Visual J++ ни облегчали разработку приложений, Вы, наверное, все же захотите разобраться в структуре и логике пакетов и классов, из которых состоит WFC.

Цель этой главы — представить концептуальную структуру пакетов и классов WFC, а также разъяснить некоторые из его фундаментальных моделей. Многие пакеты образуют инфраструктуру модели компонентов, и разработчики, сосредоточенные на использовании WFC-элемен-тов, могут их игнорировать. К остальным пакетам проще всего обращаться из Forms Designer среды Visual J+ + . Изучая библиотеку WFC, Вы поймете, какие пакеты и классы нужны для Вашего приложения. В разделах этой главы рассматриваются следующие вопросы.

Пакеты WFC

Фундаментальные блоки WFC — Windows DLL и Dynamic HTML. WFC базируется на Win32-мoдeли программирования, что позволяет создавать на Java Windows-приложения, способные задействовать все элементы пользовательского интерфейса, события и системные сервисы Windows. WFC построен также и на модели объектов Dynamic HTML, что дает возможность создавать на Java как клиентские, так и серверные HTML-страницы, и работать с Dynamic HTML непосредственно из Java. В основе этих технологий лежат DLL-библиотеки, которые предоставляют для инфраструктуры WFC ключевые API-функции. Эти DLL стали доступными в Java благодаря двум технологиям: JActiveX и J/Direct. Если DLL представляет COM/ActiveX-компонент, JActiveX создает классы-оболочки, преобразующие СОМ-объекты в Java-объекты. А если DLL основана не на СОМ, то средствами J/Direct можно напрямую обращаться к ее функциям и обеспечить маршалинг типов данных между Java и языком, на котором была создана эта DLL (например, С или C++). Обе технологии используют все возможности компилятора JVC и Microsoft Virtual Machine for Java (Microsoft-реализации виртуальной машины Java).

Об этом важно знать в основном потому, что некоторые пакеты WFC целиком состоят либо из классов-оболочек СОМ (формируемых JActiveX), либо из классов J/Direct. У этих классов есть методы, непосредственно связанные с API-функциями, — они не документированы в WFC Reference, так как обычно не вызываются напрямую, а предназначены для поддержки классов из других пакетов.

Кроме пакетов поддержки API конкретной операционной платформы, WFC включает семь основных пакетов.

Пакет

Описание

com.ms.wfc.app

Базовые классы, инкапсулирующие поддержку Windows-приложений. Сервисы этих классов используются шаблоном форм в Visual J++. Кроме базовой структуры обработки сообщений Windows, они поддерживают такие элементы Windows, как буфер обмена, реестр, потоки, описатели окон, системная информация и т. д.

com.ms.wfc.core


Базовые классы модели компонентов. Этот пакет поддерживает контейнеры, события, исключения, свойства и инфраструктуру, обеспечивающую взаимодействие с такими средствами Visual J++, как Forms Designer.

com.ms.wfc.data

Java-классы ADO (ActiveX Data Objects), поддерживающие доступ к данным и связывание с данными. Кроме того, он включает пакет com. ms.wfc. data. ui, который предоставляет базовые классы для WFC-элементов, связанных с доступом к данным.

com.ms.wfc.html

Классы, используемые для реализации Dynamic HTML в Java.

com.ms.wfc.io

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

com.ms.wfc.ui

Базовые классы элементов управления, поставляемых с WFC. Эти классы обеспечивают также доступ к графическим API-функциям.

com.ms.wfc.util

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

Для поддержки API конкретной операционной платформы в WFC имеются следующие основные классы.

Пакет

Описание

com.ms.wfc.ax Классы-оболочки Java для ActiveX-интерфейсов.
com.ms.wfc.html.om Классы-оболочки Java для модели объектов Dynamic HTML.
com.ms.wfc.ole32 Классы-оболочки Java для сервисов OLE.
com.ms.wfc.win32 Классы-оболочки Java для Win32 API.

 

Работа с визуальными WFC-компонентами

Хотя визуальные WFC-компоненты для приложений Win32 и Dynamic HTML (DHTML) имеют некоторые различия, общих характеристик у них все же больше, что позволяет адаптировать WFC-модель компонентов к потребностям приложений обоих типов. Например, и Win32-, и DHTML-модели включают основные типы элементов управления (поля, флажки, кнопки, переключатели и поля со списками). У многих распространенных элементов управления Win32 есть свои аналоги в DHTML — они и называются так же, только перед именем ставится префикс Dh (так, классу com.ms.wfc.ui.Edit соответствует класс com.ms.wfc.html.DhEdit). Кроме того, шрифты, цветовые константы и большинство типов событий одинаковы в обеих моделях. Конечно, существуют и компоненты, характерные лишь для какой-то одной из этих моделей, например таблицы в DHTML или элементы управления со списочным представлением (list view controls) в Win32. Вероятно, самое заметное различие между WFC-компонентами для Win32 и DHTML в том, что компоненты для DHTML недоступны в Forms Designer. Это означает, что Вы должны создавать, добавлять и изменять элементы DHTML в редакторе исходного кода, но Java-код, реализующий такие компоненты, в обеих моделях выглядит практически одинаково.

Визуальные компоненты Windows

WFC интегрирован с Visual J++инструментом визуальной разработки и является надстройкой над Win32 API операционной системы Windows. Основной пакет WFC, поддерживающий визуальные компоненты, — com.ms.wfc.ui, а базовый класс большинства визуальных WFC-компонентов — класс com.ms.wfc.ui.Control. Многие элементы управления WFC (WFC-элементы) расширяют и этот класс, и,класс Form (который представляет визуальный контейнер для элементов управления).

Класс Control

Класс Control содержит все основные свойства и методы, необходимые для управления Win32-OKHOM. Методы можно сгруппировать по назначению.

Control расширяет класс com.ms.wfc.core.Component — базовый для всех WFC-компонентов.

Использование форм

Форма — главный визуальный элемент приложения или нестандартное диалоговое окно, связанное с приложением. Основой всех форм в WFC служит класс com.ms.wfc.ui.Form.

Forms Designer в Visual J++ начинает свою работу с вывода шаблона формы, который предоставляет класс, расширяющий com.ms.wfc.ui.-Form, помогает настроить свойства формы и добавить на нее элементы управления. Класс, производный от Form, добавляет метод main, отсутствующий в Form. Форма запускается вызовом из main метода com.ms.wfc. арр.Application, run с передачей ему нового экземпляра класса, основанного на Form (этот код уже присутствует в шаблоне). Подробнее см. раздел «Запуск и завершение приложений» далее в этой главе. Основанный на Form класс, используемый в качестве модального диалогового окна, можно запустить вызовом Form. showDialog (showDialog запускает и модальные диалоговые окна, основанные на классе com.ms.wfc.ui.CommonDialog). Немодальные диалоговые окна, основанные на Form, можно открывать методом show формы, который делает ее видимой.

Form расширяет класс com.ms.wfc.ui.Control, поэтому у него есть все методы класса Control плюс множество собственных, поддерживающих его функциональность как окна и контейнера элементов управления. К ним относятся методы для:

Этот список неполон, но дает общее представление о формах. Еще один класс из пакета com.ms.wfc.ui, расширяющий класс Form, — UserCont-rol. Он предназначен для создания нестандартных элементов управления, основанных на Form; Вы можете помещать их в Toolbox.

Обзор WFC-элементов

Все видимые WFC-элементы содержатся в пакете cpm.ms.wfc.ui. Так как в нем более 240 классов, сразу найти нужные довольно трудно. К счастью, они разбиты на несколько основных категорий.

Классы, показанные в следующей таблице, содержатся в пакете com.ms.wfc.ui и расширяют непосредственно Control. Элементы управления, которые расширяют эти классы, перечислены во второй графе таблицы.

Класс

Описание

Animation

Инкапсулирует Windows-элемент управления Animation, который воспроизводит анимационные файлы формата AVI (Audio- Video Interleaved).

AxHost

Оболочка ActiveX-элемента, позволяющая обращаться с нему как к WFC-элементу.

Button

Инкапсулирует Windows-элемент управления Button — кнопку.

CheckBox

Инкапсулирует Windows-элемент управления CheckBox — флажок с меткой.

ComboBox

Инкапсулирует Windows-элемент управления ComboBox — поле со списком.

DateTimePicker

Инкапсулирует Windows-элемент управления DateTimePicker, позволяющий вводить дату и время.

Edit

Инкапсулирует Windows-элемент управления Edit, позволяющий вводить текст.

Form

Представляет окно верхнего уровня.

GroupBox

Инкапсулирует элемент управления GroupBox — прямоугольник, включающий другие элементы управления.

Label

Инкапсулирует Windows-элемент управления Label, который отображает строку текста, не редактируемую пользователем.

ListBox

Инкапсулирует Windows-элемент управления ListBox — список, из которого пользователь выбирает один или несколько элементов. Используется совместно с Listltem. Элемент управления CheckedListBox расширяет класс ListBox.

ListView

Инкапсулирует Windows-элемент управления ListView, отображающий набор элементов, каждый из которых состоит из значка (он берется из списка изображений) и метки.

MDIClient

Представляет окно, содержащее дочерние MDI-окна.

MonthCalendar

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

Panel

Представляет контейнер для других элементов управления. Его расширяет класс TabPage.

PictureBox

Инкапсулирует Windows-элемент управления PictureBox, используемый для вывода растровых изображений.

ProgressBar

Инкапсулирует Windows-элемент управления ProgressBar, который динамически отображает ход выполнения операции, заполняя полоску крошечными «кирпичиками».

RadioButton

Инкапсулирует Windows-элемент управления RadioButton — переключатель.

Rebar

Инкапсулирует элемент управления Rebar — контейнер для других элементов управления; его можно перемещать и масштабировать. Используется совместно с RebarBand.

RichEdit

Инкапсулирует Windows-элемент управления RichEdit.

ScrollBar

Представляет базовый класс для полос прокрутки. HScrollBar и VScrollBar расширяют этот класс.

Splitter

Инкапсулирует элемент управления Splitter, позволяющий изменять размер1 стыкуемых элементов управления в период выполнения.

StatusBar

Инкапсулирует Windows-элемент управления StatusBar. Используется совместно с классом StatusBarPanel.

TabBase

Определяет базовый класс, содержащий основную функциональность для элементов управления типа «вкладка». TabControl (использующий TabPage) и TabStrip (использующий Tabltem) расширяют этот класс.

ToolBar

Инкапсулирует нестандартный элемент управления ToolBar. Используется совместно с классом ToolbarButton.

TrackBar

Инкапсулирует Windows-элемент управления TrackBar (также известный как Slider) — линейку с ползунком для выбора значения из заданного диапазона.

TreeView

Инкапсулирует Windows-элемент управления TreeView. Используется совместно с классом TreeNode.

UpDown

Инкапсулирует элемент управления UpDown (иногда называемый Spinner) — наборный счетчик.

Доступ к графическим средствам

WFC-приложения выполняют графические операции, используя объект Graphics, который инкапсулирует встроенные графические средства операционной системы Windows. Этот объект обеспечивает гибкую поддержку большинства распространенных графических операций, включая вывод изображений и значков, а также прорисовку линий, многоугольников и текста.

Объект Graphics выполняет свою работу, создавая оболочку контекста устройства Windows — структуры данных, определяющей системные графические объекты, связанные с ними атрибуты и графические режимы. Вы можете получить контекст устройства, скрываемый объектом Graphics, и использовать объект Graphics параллельно с графическими функциями Win32 API.

Все WFC-объекты, расширяющие объект Control, поддерживают создание объекта Graphics методом createGraphics, а объекты, расширяющие объект Image, — например, Bitmap, Icon и Metafile, - методом getGraphics. Подробнее об использовании этого объекта см. главу 15 «Графика».

Визуальные компоненты Dynamic HTML

Элементы Dynamic HTML образуют второй набор визуальных компонентов в WFC. Элементы управления из пакета com.ms.wfc.html базируются на модели объектов Dynamic HTML. Классы этого пакета применяются при создании новых элементов и связывании с существующими на HTML-странице. Такие компоненты создаются и используются в клиентских браузерах или на сервере, передающем их клиентским браузерам. Модель объектов Dynamic HTML существует на нескольких платформах. Она не привязана исключительно к Win32, хотя элементы пользовательского интерфейса обеих моделей весьма схожи, потому что стандартный набор кнопок, списков, переключателей и др. имеется как в Win32, так и в DHTML.

Оба набора WFC-элементов (для Win32 и Dynamic HTML) построены на аналогичной основе, так как оба в конечном счете производны от класса com.ms.wfc.core.Component. Компоненты могут быть включены в контейнер и поддерживают интерфейс IComponent, имеющий методы для «посадки» (siting) компонентов. Вопрос о том, как связываются компоненты и контейнеры, большинству программистов, использующих WFC, мало интересен; но поскольку элементы и из com.ms.wfc.html, и из com.ms.wfc.ui базируются на компонентах, они обладают близкими характеристиками. Так, все компоненты добавляются в свои родительские контейнеры методом add.

Подробнее об использовании пакета com.ms.wfc.html см. главу 14 «Программирование Dynamic HTML в Java».

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

Базовый класс Control и классы, его расширяющие (например, кнопки и поля), предоставляют стандартные Windows-события: click, keyPress, mouseMove, dragDrop и др. Вы работаете с событиями в своем приложении, используя делегаты (delegates). Для написания обработчиков событий в приложении не обязательно досконально разбираться в делегатах. Но понимание того, как создаются и применяются делегаты, очень важно при разработке элементов управления, взаимодействии с другими приложениями, генерирующими события, или использовании WFC-компонентов в потоках. Без него сложно вникнуть и в детали Java-кода, генерируемого Forms Designer. Поэтому в данном разделе сначала даются общие сведения о делегатах, а потом рассматриваются практические аспекты обработки событий.

Что такее делегат? Объявление delegate определяет класс, расширяющий com.ms.lang.Delegate. Компилятор JVC, тоже распознает ключевое слово delegate, упрощая создание класса, основанного на Delegate. Экземпляр делегата может вызвать какой-нибудь метод объекта и передать ему данные. Делегат — что очень важно — изолирован от объекта, на который он ссылается, и ему не нужно ничего о нем знать. Поэтому он идеально подходит для «анонимных вызовов». В других языках подобная функциональность реализуется процедурными указателями. Но в отличие от них делегаты объектно-ориентированны, обеспечивают полный контроль типов и безопасны.

В WFC делегаты чаще всего применяются для связывания событий с методами-обработчиками, например, событие click кнопки Вы связываете с методом его обработки в своем классе. При возникновении события элемент управления вызывает делегат, передавая ему всю доступную информацию, связанную с событием. Делегат в свою очередь вызывает зарегистрированный метод-обработчик и передает данные ему. Вы можете использовать делегаты и для связывания одного события с несколькими методами — такая организация называется многоканальным вещанием (multicasting); когда возникает событие, вызывается каждый делегат из списка в том порядке, в котором они были добавлены. И наоборот, делегатам нескольких разных событий можно присвоить один и тот же метод-обработчик (например, кнопка панели инструментов и элемент меню могут вызывать один обработчик). Работая с событиями, Вы регистрируете делегат, чтобы Ваша программа получала уведомление об определенном событии в конкретном элементе управления. Для этого Вы должны вызвать метод addOn<co6uтие> элемента управления, где вместо <событие> подставляется имя нужного события. Так, чтобы зарегистрировать делегат для события click кнопки, вызовите метод addOnClick объекта кнопки. Метод addOn<co6ыmue> принимает в качестве параметра экземпляр делегата; обычно это существующий делегат WFC, сопоставленный с данными конкретного события. При вызове addOn<co6bimue> создается экземпляр делегата со ссылкой на метод, с которым Вы хотите связать событие. Вот как связать обработчик события btnOK_Click (в текущем классе) с событием click кнопки (btnOK):

Button btnOK = new Button();

btnOk.addOnMouseClick(new EventHandler(this.btnOK_Click)); Для большинства событий Вы можете создавать и передавать экземпляр универсального делегата EventHandler, который возвращает универсальный объект Event. Однако некоторые события, несущие дополнительную информацию, требуют специальных классов обработчиков. Например, события, сообщающие о перемещении мыши, обычно содержат такую информацию, как позиция курсора мыши. Чтобы получить ее, Вы создаете и передаете экземпляр класса MouseEventHandler, а тот возвращает обработчику объект MouseEvent. Для получения информации о состоянии клавиш Shift и др. от событий, связанных с клавиатурой, нужен класс KeyEventHandler — он возвращает объект KeyEvent. В WFC все классы делегатов обработчиков событий расширяют класс com.ms.lang.Delegate. Большинство из них содержится в пакете com.ms.wfc.ui, их имена заканчиваются на EventHandler. События WFC (пакет com.ms.wfc.ui) расширяют класс com.ms.wfc.core.Event, их имена заканчиваются на Event.

Совет Forms Designer-позволяет связывать событие с определенным методом через окно Properties в режиме Events. При этом Forms Designer создает соответствующий метод addOn<co6ытие> и заготовку обработчика этого события.

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

private void btnOK_Click(Object source, Event e)

{

if (source instanceof Button)

{

String buttonName = ((Button)source).getText();

MessageBox.show("You clicked button " + buttonName);

} }

Если Вы связали событие со своим методом через универсальный класс EventHandler, объект Event в Вашем обработчике не даст никакой полезной информации. Но если событие несет такую информацию, Вы получаете ее от объекта конкретного события. Ниже показано, как могут выглядеть делегат и обработчик для события, связанного с перемещением мыши. Объект MouseEvent предоставляет свойства, позволяющие узнать позицию курсора мыши.

// запрос на уведомление

Button btnTest = new Button();

// метод addOn<coбытие> использует класс

MouseEventHandler btnTest.addOnMouseMove(new

MouseEventHandler(this.btnTestMouseMove));

// обработчик события "перемещение мыши"

private void btnTestMouseMove(Object source, MouseEvent e)

{

edit1.setText(e.x + ", " + e.y);

}

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

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

private void initEventHandlers()

{

Button buttonNew = new Button();

Button buttonSave = new Button();

Button buttonExit = new Button();

// все события направляются одному обработчику

buttonNew.addOnClick(new MouseEventHandler(this.toolbarClick));

buttonSave.addOnClick(new MouseEventHandler(this.toolbarClick));

buttonExit.addOnClick(new MouseEventHandler(this.toolbarClick));

// общий обработчик события

srivate void toolbarClick(Object source, Event e)

{

String buttonName;

if (source instanceof Button)

{

buttonName = new String((Button)source). getText());

MessageBox.show("You clicked button " + buttonName);

} }

Локализация приложений

WFC и Forms Designer среды Visual J + + упрощают разработку приложений с многоязыковой поддержкой, WFC-приложение можно создать в нескольких языковых версиях, различающихся лишь двоичным файлом ресурсов. Правила именования файлов ресурсов требуют указывать поддерживаемый язык в имени файла, а это позволяет при запуске приложения загружать нужные ресурсы в соответствии с региональными стандартами и языком, выбранными на компьютере пользователя. Локализация требует соответствующей реализации на этапе разработки и поддержки в период выполнения. Ряд свойств визуальных компонентов (форм и элементов управления) Visual J + + рассматривает как локализуемые. При разработке приложения Visual J++ помещает значения таких свойств в двоичный файл ресурсов. Например, текст, шрифт и размер элемента управления могут изменяться в зависимости от языка.

Чтобы создать локализованную версию приложения, сконструируйте его визуальное представление в Visual J + +, установите свойство localizable формы как true и сохраните ее. Тогда Visual J++ автоматически создаст двоичный файл ресурсов и поместит в него все локализуемые свойства.

Когда свойство localizable формы установлено как true, Visual J++ всегда сохраняет ресурсы в единственном файле ресурсов с именем Форма.resources, где Форма — имя главной формы (например, Forml.resources). Каждая создаваемая Вами версия будет сохраняться под этим именем. А Вы, заканчивая подготовку каждой новой языковой версии, дол-

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

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

Допустим, Вам надо создать американо-английскую, французскую и японскую версии своего приложения, главная форма которого называется Zippo.java. Предположим также, что Вы начинаете работу с английской версии (но это необязательно). Создайте сначала форму на английском языке и установите ее свойство localizable как true. Сохранив форму, Вы создадите файл Zippo.resources. Теперь в Windows Explorer или командой MS-DOS сделайте копию файла Zippo.resource и переименуйте ее в Zippo_enu_enu.resources (enu — стандартный суффикс для американо-английских версий, и в данном случае английский будет как основным, так и дополнительным языком).

Затем в Forms Designer поменяйте язык на французский и соответственно модифицируйте элементы управления и их свойства. Закончив, снова сохраните форму — эта версия перезапишет существующий файл Zippo.resources. Вновь сделайте копию этого файла и переименуйте ее в Zippo_fra_fra.resources.

Чтобы протестировать эту версию, откройте в Control Panel (Панель управления) диалоговое окно Regional Settings Properties (Свойства: Язык и стандарты) и выберите нужный региональный стандарт — в данном случае French (Standard) [Французский (стандартный)]. Перезагружать систему после этой операции не требуется — региональный стандарт будет изменен только для локального потока.

Использование прикладных сервисов WFC

Пакет com.ms.wfc.app содержит много классов, реализующих прикладные сервисы WFC. Большая их часть принадлежит самому объекту Application. Эти сервисы главным образом создают потоки, запускают приложения, обрабатывают события приложений и т. д. О Java-потоках см. раздел «Использование Java-потоков с WFC» далее в этой главе. Другие прикладные сервисы связаны с Win32-функция. Они обеспечивают доступ к реестру Windows, буферу обмена и системной информации.

Запуск и завершение приложений

WFC-приложение запускается методом Application, run. Он обычно помещается в метод main класса, основанного на Form и образующего главную форму приложения. У Application, run имеются переопределенные методы, которые либо не принимают параметры, либо принимают

один параметр — класс формы, представляющий главное окно приложения. Его вызов, как правило, выглядит так:

public static void main(String args[])

{

Application. run(new MyMainWindow());

}

Если форма передается методу run, ее свойство visible автоматически устанавливается как true, а к форме добавляется обработчик события onClosed. Последний при закрытии формы вызывает метод Application. exitThread. А если методу run форма не передается, приложение работает, пока не будет вызван метод Application.exit, завершающий все потоки и закрывающий окна приложения, или exitThread, завершающий только текущий поток.

Обработка событий приложений

Вы используете объект Application, чтобы закрепить обработчики за пятью разными событиями, возникающими в контексте приложения: applicationExit, idle, settingChange, systemShutdown и threadException. Сделать это позволяют следующие addOn-методы.

Метод Application

Описание

addOnApplicationExit

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

addOnldle

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

addOnSettingChange

Задает обработчик, вызываемый при изменении пользователем параметров окна.

addOnSystemShutdown

Задает обработчик, вызываемый непосредственно перед завершением работы системы инициированным пользователем. Это дает возможность сохранить пользовательские данные.

addOnTh read Except ion

Задает обработчик, вызываемый при возникновении неперехваченного исключения Java, что позволяет приложению корректно его обработать. Обработчик принимает в качестве параметра объект com.ms.wfc.app.Thread-ExceptionEvent, у которого есть поле, идентифицирующее возникшее исключение.

У addOn-методов имеются парные removeOn-методы, удаляющие обработчики событий.

Доступ к системной информации

Win32-система содержит большой объем информации, доступной WFC-приложениям и компонентам. Доступ в основном осуществляется через классы, входящие в пакет com.ms.wfc.app. Большая часть этой информации хранится в реестре Windows, и Вы обращаетесь к ней через классы RegistryKey и Registry. Другую системную информацию, например размеры экранных элементов Windows, параметры операционной системы, сведения о сетевых и аппаратных возможностях, можно получить, вызывая статические методы класса com.ms.wfc.app.Systemlnformation. Системное время доступно через класс com.ms.wfc.app.Time. В этом разделе дан обзор способов, которыми WFC-приложение может получить эту системную информацию.

Информация, хранящаяся в реестре Windows

Класс RegistryKey из пакета com.ms.wfc.app содержит методы для доступа к системному реестру Windows. Используйте его методы, чтобы создавать и удалять подразделы реестра, подсчитывать число подразделов в текущем разделе и получать их имена, а также чтобы получать, устанавливать и удалять параметры в подразделах. Класс com.ms.wfc.app.Registry имеет поля, содержащие объекты RegistryKey, которые представляют корневые разделы реестра (они начинаются с HKEY_). (Экземпляры корневых объектов RegistryKey можно создавать и методом getBaseKey.) Методы объектов RegistryKey позволяют перечислять и манипулировать разделами и параметрами в подразделах данного корневого объекта. Так, следующий код получает массив имен подразделов раздела HKEY_CURRENT_USER и количество имен в этом массиве:

int subKeyCount; String[] subKeyNames;

subKeyNames = Registry.CuRRENT_USER.getSubKeyNames();

subKeyCount = Registry. CURRENTJJSER.getSubKeyCount();

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

String path; // для строки пути

String[] valueNames; // для массива имен недавно использовавшихся файлов

int valueCount; // для количества имен файлов в valueNames

path = new String("Software\\Microsoft\

\VisualStudio\\6.0\\FileMRUList");

RegistryKey subKey = registry. CURRENTJJSER.getSubKey(path);

// получаем имена файлов и их количество

valueNames = subKey.getValueNames();

valueCount = subKey.getValueCount();

if (valueCount > 0)

for (Int i=0; i < valueCount; ++i)

{

// получаем параметр, т. е. имя файла

String value = new String((String)subKey.getValue(valueNames[i]));

// конкатенация имени ("1", "2", и т. д.) и значения параметра

String valString = new String(valueNames[i] + " " + value);

// добавляем результат в поле

edit1.setTSxt(edit1.getText() + valString + "\r\n"); }

Кроме того, Вы можете создавать новые разделы методом createSubKey и устанавливать значения параметров в этих разделах, используя метод setValue.

Информация о национальных настройках

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

Все они обычно задаются в диалоговом окне Regional Settings Properties, открываемом из Control Panel, но обращаться к ним можно и программно. WFC обеспечивает доступ к этой информации через методы класса com.ms.wfc.app.Locale и подклассы Locale с константами, которые используются методами Locale. О том, как считывать и устанавливать эту информацию, см. методы класса Locale.

Информация о времени

Существует еще одна категория системной информации — время. Класс com.ms.wfc.app.Time предоставляет объект Time, который позволяет получать в том числе и системное время: конструктор по умолчанию создает объект Time с системными датой и временем. Кроме того, объект Time может пригодиться в решении многих других задач — например, в сравнении объектов Date и Time, преобразовании времени в различные форматы и сохранении данных объекта Time для последующего использования.

Созданные объекты Time изменять нельзя. Однако класс Time предоставляет методы для создания новых объектов с временным смещением (например, addSeconds, addMinutes, addHours, addDays и addYears). Существуют и методы для получения лишь какого-то одного из свойств объекта Time: секунд, минут, часов, дней и т. д.

Объект Time в WFC хранит время как число стонаносекундных единиц, отсчитываемое от 1 января 100 г. нашей эры. Максимальное значение, которое может хранить WFC-объект Time, — 31 декабря 10 000 г. нашей эры. Преобразование WFC-объектов Time в другие форматы (String, Variant, SYSTEMTIME и т. п.) может вызвать потерю точности, да и не все форматы способны хранить столь большой диапазон значений. Не путайте класс Time с другим классом пакета com.ms.wfc.app — Timer. Последний на самом деле является элементом управления; единственная причина, по которой он не включен в пакет com.ms.wfc.ui, — отсутствие у него пользовательского интерфейса.

Операции drag-and-drop и работа с буфером обмена

Поддержка drag-and-drop в WFC основана на модели Win32 (OLE), которая ускоряет копирование и вставку данных. А используя буфер обмена, приходится выполнить несколько операций: выделить данные в источнике, выбрать из контекстного меню команду Cut или Сору, переключиться на приемник (файл, окно или приложение) и выбрать из контекстного меню команду Paste.

Поддержка drag-and-drop избавляет от необходимости пользоваться контекстным меню. Вместо этого предлагается такой сценарий: Вы нажимаете левую кнопку мыши для захвата данных, выделенных в источнике, и отпускаете ее после перемещения данных в приемник (на мишень). Операции drag-and-drop позволяют переносить любые данные, которые можно поместить в буфер обмена; следовательно, drag-and-drop работает с теми же форматами данных, что и буфер обмена. Форматы определяют вид данных: текст, растровое изображение, HTML, звук (WAV) и т. д. Класс com.ms.wfc.app.DataFormats содержит поля, относящиеся к каждому формату данных, используемому буфером обмена. Имена этих полей (например, CF_TEXT) связаны непосредственно с именами Win32-KOHCTАHT.

Данные для операций drag-and-drop и с буфером обмена хранятся в классе DataObject (пакет com.ms.wfc.app), который реализует интерфейс IDataObject. Последний определяет методы для записи и чтения данных, получения списка форматов в объекте данных и запроса о наличии определенного формата.

Чтобы программно обмениваться данными с .буфером, используйте статические методы класса com.ms.wfc.app.Clipboard. Метод Clipboard.setDataObject принимает IDataObject и помещает'его в буфер обмена Windows, a Clipboard.getDataObject возвращает IDataObject из этого буфера. Приемник должен поддерживать формат данных, помещенных в буфер обмена. Для проверки запросите объект данных методом IDataObject. getDataPresent, передав формат, который может принять мишень, — getDataPresent возвращает true, если такой тип данных имеется.

Реализация источника в операции drag-and-drop

Операция drag-and-drop для любого WFC-элемента (основанного на com.ms.wfc.ui.Control) начинается с вызова метода Control.doOragDrop. Обычно это делается в ответ на перемещение пользователем мыши при нажатой левой кнопке.:Поэтому в обработчик события mouseMove помещается код, проверяющий, не нажата ли левая кнопка (нажатие этой кнопки — сигнал к началу операции перетаскивания). Пример такого обработчика для списка с именами файлов:

private void listFiles_mouseMove(Object source, MouseEvent e)

{

// если нажата левая кнопка, начинаем операцию drag-and-drop

if(this.getMouseButtons()==MouseButton.LEFT)

{

String dafa = (String)listFiles.getSelectedltem();

HstFiles.doDragDrop(data, DragDropEf feet. ALL);

} }

Метод doDragDrop принимает подлежащие передаче данные и объект класса com.ms.wfc.ui.DragDropEffect. Класс DragDropEffect содержит константы, которые можно комбинировать побитовой операцией «OR» для выполнения операции drag-and-drop в требуемом режиме.

Метод DragDropEffect Описание
COPY После передачи данные не удаляются из источника.
MOVE После передачи данные удаляются из источника.
SCROLL После передачи данные прокручиваются в окне мишени.
ALL После передачи данные удаляются из источника и прокручиваются в окне мишени (то же, что COPY | MOVE | SCROLL).
NONE Никакие операции не выполняются.

Мишень, принимающая данные в результате операции drag-and-drop, получает событие dragDrop — оно содержит объект DragDropEffect, что позволяет легко определить режим операции.

Реализация мишени в операции drag-and-drop

Часть операции drag-and-drop, когда данные «опускаются» на мишень, обрабатывается как событие. Класс Control создает инфраструктуру обработчиков для следующих событий drag-and-drop: dragDrop, dragEnter, dragLeave и dragOvcr. Чтобы назначить этим событиям обработчики, используйте следующие методы.

Метод Control

Описание

addOnDragDrop

 

addOnDragEnter

 

addOnDragLeave

addOnDragOver

Задает обработчик для данных, отпускаемых в . Вашем элементе управления или окне (при отпускании левой кнопки мыши).

Задает обработчик, вызываемый, когда в процессе перетаскивания данных в окно попадает курсор.

Задает обработчик, вызываемый, когда в процессе перетаскивания данных курсор покидает окно.

Задает обработчик, вызываемый, когда в процессе перетаскивания данных в окне перемещается курсор.

У каждого addOn-метода есть парный removeOn-метод, удаляющий обработчик события. Как и все addOn- и removeOn-методы в WFC, добавляющие и удаляющие обработчики, эти методы принимают делегат (в данном случае — DragEventHandler), имя которого формируется по имени метода-обработчика. Например, следующая строка добавляет метод txtFile_dragDrop как обработчик события dragDrop:

txtFile.addOnDragDrop(new DragEventHandler(this.txtFile_dragDrop));

В операции drag-and-drop чаще всего обрабатывается событие drag-Drop. Но независимо от конкретного события, код обработчика должен делать минимум три вещи. Сначала определить, может ли он принять данные предлагаемого формата, и, если да, то скопировать и отобразить их (или как-то иначе уведомить пользователя о приеме данных). Все данные и информация передаются в событии DragEvent. Среди других полей в нем есть поле данных и поле режима. Поле DragEvent.data содержит объект, поддерживающий интерфейс IDataObject, который предусматривает методы для получения данных и их форматов, а также запроса о наличии определенного формата.

Поэтому обработчик должен сначала вызвать метод-Drag Event, data, getDataPresent, передав ему формат, который он распознает, а потом определить, присутствует ли такой тип данных. Если да, обработчик может вызвать метод DragEvent.data.getData.(передав ему этот тип данных) и получить сами данные. Способ отображения данных определяется элементом управления. Следующий пример демонстрирует обработчик события dragDrop для поля; он отображает текст, перемещенный мышью в это поле.

private void txtFile_dragDrop(Object source, DragEvent e)

{

// если в объекте содержится текст, выводим его в поле

if (e.data.getDataPresent(DataFormats.CF_TEXT))

{

String filename = (String)e.data.getData(DataFormats.CF_TEXT);

txtFile.setText(filename);

} }

Использование Java-потоков с WFC

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

Некоторые классы активно используют преимущества свободных потоков, и в WFC предусмотрен блокирующий код, позволяющий сделать их объекты безопасными для потока. Вот эти классы:

С другой стороны, любой объект, производный от com.ms.wfc.ui.Control, использует модель разделенных потоков (apartment threading model), так как с каждым элементом управления связано Win32-OKHO. Кроме того, большинство других объектов пакета com.ms.wfc.ui не синхронизируется, и их тоже следует считать использующими модель разделенных потоков. По сходным причинам пакеты com.ms.wfc.io, com.ms.wfc.html и com.ms.wfc.util нельзя считать реентерабельными.

Смешение моделей Java- и Win 32-потоков

WFC обращается к Win32-конструкциям (например, окнам) из Java-объектов. Диспетчер Win32-OKOH придерживается модели разделенных потоков, a Windows при необходимости автоматически выполняет мар-шалинг вызовов из одного потока в другой. Когда объект, построенный на модели свободных потоков, вызывает объект, использующий модель разделенных потоков, необходим маршалинг этого вызова в поток (apartment thread) объекта. И пока он не обработает запрос, свободный поток блокируется. Любые другие вызовы его объекта из объектов свободных потоков блокируются, пока не освободится поток, первого. В конечном счете это может привести в тупик — к взаимной блокировке. Каким же образом Java-объекты, построенные на модели свободных потоков, работают с WFC-элементами? Когда дело доходит до межпотоковых операций, WFC перекладывает ответственность за соответствующий запрос на программиста — он должен разработать алгоритм так, чтобы предотвратить возникновение тупиковых ситуаций. Для этого из потока элемента управления можно вызвать делегат, который в свою очередь вызовет указанный в нем метод.

Для вызова нужного делегата из потока, создавшего описатель окна элемента управления и содержащего цикл обработки сообщений, применяйте методы Control, invoke или Control. invokeAsync. Очень важно использовать именно поток элемента управления — особенно на случай, если элементу управления по какой-то причине понадобится заново создать описатель своего окна. Метод invoke заставляет поток обратиться к заданному методу обратного вызова и ждать возврата управления от него, a invokeAsync приводит к тому же, но возврата управления не ждет. Все исключения в вызываемом потоке, в обоих случаях передаются элементу управления, которому принадлежит этот поток. Кроме того, объект Control.createGraphics позволяет выполнять фоновые операции закраски и анимации в объекте ui.Graphics. Хотя объект Graphics использует модель разделенных потоков, createGraphics полностью отвечает модели свободных потоков. А это дает возможность одному потоку создать графический объект для элемента управления из другого потока.

Создание и завершение потоков

Свободные потоки можно создавать по стандартной в Java методике: реализуя интерфейс java.lang.Runnable.

Приведенный ниже пример демонстрирует класс, реализующий Run-nable и принимающий два элемента управления (линейку с ползунком и метку) как параметры своего конструктора. В методе run потока он переходит в поток первого элемента управления, вызывая его метод invokeAsync. Этот метод передает делегат с именем tDelegate (экземпляр com.ms.app.Methodlnvoker), который определяет метод обратного вызова tCallBack. Внутри последнего метода поток элемента управления может безопасно манипулировать свойствами линейки с ползунком — в данном случае он изменяет стиль разметки шкалы. Это приводит к необходимости повторного создания описателя окна элемента. Если пропустить показанный здесь переход в поток элемента, последний будет создан в новом потоке, и уже больше не примет никаких сообщений, а значит, перестанет отвечать.

import com.ms.wfc.app.*;

import com.ms.wfc.core. *;

import com.ms.wfc.ui.*;

/.*

* Runnable - интерфейс, реализуемый при создании нового Java-потока */

public class RunnableClass implements Runnable

{

final int SLEEP = 500; Label 1; TrackBar tb;

/**

* это поток для нашего класса

*/ Thread thread;

/*,

* создаем специальный делегат, чтобы WFC мог вызывать его из потока

* элемента управления

*/ Methodlnvoker tDelegate = new Methodlnvoker(tCallback);

/**

* создаем новый Java-поток и указываем ему стартовать

* через метод start() */

public RunnableClass (TrackBar tb, Label 1)

{

this.l = 1; this.tb = tb;

thread = new Thread(this, "RunnableClass thread");

thread.start();

}

public void run()

{

while (true)

{ /**

* вызываем заданный метод из потока метки

*/

tb.invokeAsync (tCallback);

try

{

Thread.sleep (SLEEP);

}

catch (InterruptedException e)

{ } }

}

int nCount = 0;

int nTickStyles[] = {TickStyle.BOTH,

TickStyle.BOTTOMRIGHT,

TickStyle.NONE,

TickStyle.TOPLEFT};

/**

* этот код выполняется в потоке линейки с ползунком */

private void tCallback() {

int nlndex = nCount % (nTiokStyles.length);

l.setText ("hello from tCallBack: " + nCount);

tb.setTickStyle (nTickStyles [nlndex]);

nCount++;

int nValue = tb.getValue();

if (nValue >= tb.getMaximum())

tb.setValue(0);

else

tb.setValue (nValue + 1);

}

public void stopThread()

{

thread.stop();

} }

Завершение потока в данном случае сводится лишь к вызову его метода stop. Здесь класс Form, создающий объект RunnableClass, вызывает при уничтожении этого объекта его метод stopThread. Сказанное иллюстрирует следующий код.

import RunnableClass;

public class SimpleRunnable extends Form {

/**

* это класс, реализующий интерфейс Runnable */

RunnableClass runnableClass;

public SimpleRunnable()

{

// требуется для поддержки Forms Designer initForm();

runnableClass = new RunnableClass (tb, 1); }

public void dispose()

{

runnableClass.stopThread();

super.dlspose();

components.d±spose(); }

Container components = new Container();

Edit edescription = new Edit();

TrackBar tb = new TrackBar();

Label 1 = new Label();

private void initForm() {

// код, инициализирующий элементы управления, опущен... }

public static void main(String args[])

{

Application. run(new SimpleRunnable());

} }

В качестве альтернативы новый поток приложения можно создать методом Application. createThread и обойтись без реализации Java-интерфейса Runnable или расширения Java.lang.Thread. Этот метод принимает делегат как параметр (в качестве делегата часто используется Met-hodlnvoker, но вместо него допустим любой другой). Тогда всю логику можно свести в один класс — обычно класс приложения, основанный на Form. Следующий пример демонстрирует этот вариант.

import com.ms.wfc.app.*;

import com.ms.wfc.core.*;

import com.ms.wfc.ui.*;

public class SimpleAppThread extends Form

{

final int SLEEP = 700;

Thread thread;

// указываем контекст потока, в котором запускается метод

Methodlnvoker cbDelegate = new Methodlnvoker(cbThrdCallback);

public SimpleAppThread()

{

initForm();

/.„

* Здесь создается новый поток и в нем запускается methodlnvoker.

* Возвращаемый объект потока необходим, чтобы мы могли завершить

* поток при закрытии (уничтожении) формы. Заметьте, что метод

* thread.start() вызывается автоматически.

*/

thread = Application.createThread

(new Methodlnvoker(this.methodlnvoker)); }

private void methodlnvoker() {

while (true) {

// cbThrdCallback вызывается в потоке элемента управления "флажок"

cb.invoke (cbDelegate);

try

{

Thread.sleep (SLEEP); }

catch (InterruptedException e) { } }

}

int nCount = 0; /..

* метод обратного вызова потока, который устанавливает свойство

* alignment флажка; его код должен выполняться в потоке флажка */

private void cbThrdCallback() {

cb.setText ("threadCallback loop: " + nCount++);

if (nCount % 2 == 0)

cb.setTextAlign (LeftRightAlignment.LEFT);

else

cb.setTextAlign (LeftRightAlignment.RIGHT);

}

public void dispose() {

thread.stop();

super. disposeO;

components.dispose(); }

private void cbSuspend_click(Object sender, Event e)

{

if (cbSuspend.getChecked())

{

cbSuspend.setText ("press to resume thread");

thread.suspendO;

}

else {

cbSuspend.setText ("press to suspend thread");

thread.resume();

}

}

Container components = new Container();

CheckBox cbSuspend = new CheckBox();

CheckBox cb = new CheckBox();

private void initForm() {

// код, инициализирующий элементы управления, опущен... }

public static void main(String args[])

{

Application. run(new SimpleAppThread());

} }

Здесь поток останавливается вызовом его метода stop из метода dispose формы. Поток не находится в отдельном классе, поэтому его методы можно вызывать напрямую из класса, основанного на Form. У класса Application есть также метод Application.exitThread, который прекращает цикл обработки сообщений потока и закрывает его окна, но не завершает и не останавливает сам поток. А метод Application, exit прекращает циклы обработки сообщений всех потоков и закрывает все окна.

Использование локальной памяти потока

Поддержку локальной памяти потока (Thread Local Storage, TLS) обеспечивает WFC-класс Application. Каждый поток может выделить TLS-область для хранения только своих данных. Вызов метода Application. allocThreadStorage возвращает индекс этой области. Чтобы записать значение в TLS, вызовите метод Application.setThreadStorage, передав индекс области и записываемое значение. А чтобы получить значение, вызовите метод Application. getThreadStorage. По окончании не забудьте освободить все занятые участки TLS-памяти потока, вызвав Application.freeThreadStorage.

Обработка исключений в потоках

В пакете com.ms.wfc.app содержится класс ThreadExceptionDialog, автоматически отображаемый при возникновении в потоке необрабатываемого исключения. Вы можете взять исключения под свой контроль, вызвав метод Application. addOnThreadException и назначив собственный обработчик исключений. Метод addOnThreadException принимает в качестве параметра делегат ThreadExceptionEventHandler, конструируемый из Вашего метода обработки события и класса ThreadException-Event.

Обычно обработчик исключений потока запрашивает поле исключений переданного ему объекта ThreadExceptionEvent и таким образом определяет, что делать. Из этого обработчика можно запустить ThreadExceptionDialog и получить от него результаты так же, как, и в случае любых других диалоговых окон WFC. Иначе говоря, Вы запускаете диалоговое окно методом Form. showDialog и извлекаете результаты из полей класса com.ms.wfc.ui.DialogResult.

к оглавлению   к 4GL - визуальному программированию

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

НОВОСТИ ФОРУМА

Форум Рыцари теории эфира


Рыцари теории эфира
 10.11.2021 - 12:37: ПЕРСОНАЛИИ - Personalias -> WHO IS WHO - КТО ЕСТЬ КТО - Карим_Хайдаров.
10.11.2021 - 12:36: СОВЕСТЬ - Conscience -> РАСЧЕЛОВЕЧИВАНИЕ ЧЕЛОВЕКА. КОМУ ЭТО НАДО? - Карим_Хайдаров.
10.11.2021 - 12:36: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от д.м.н. Александра Алексеевича Редько - Карим_Хайдаров.
10.11.2021 - 12:35: ЭКОЛОГИЯ - Ecology -> Биологическая безопасность населения - Карим_Хайдаров.
10.11.2021 - 12:34: ВОЙНА, ПОЛИТИКА И НАУКА - War, Politics and Science -> Проблема государственного терроризма - Карим_Хайдаров.
10.11.2021 - 12:34: ВОЙНА, ПОЛИТИКА И НАУКА - War, Politics and Science -> ПРАВОСУДИЯ.НЕТ - Карим_Хайдаров.
10.11.2021 - 12:34: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Вадима Глогера, США - Карим_Хайдаров.
10.11.2021 - 09:18: НОВЫЕ ТЕХНОЛОГИИ - New Technologies -> Волновая генетика Петра Гаряева, 5G-контроль и управление - Карим_Хайдаров.
10.11.2021 - 09:18: ЭКОЛОГИЯ - Ecology -> ЭКОЛОГИЯ ДЛЯ ВСЕХ - Карим_Хайдаров.
10.11.2021 - 09:16: ЭКОЛОГИЯ - Ecology -> ПРОБЛЕМЫ МЕДИЦИНЫ - Карим_Хайдаров.
10.11.2021 - 09:15: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Екатерины Коваленко - Карим_Хайдаров.
10.11.2021 - 09:13: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ - Upbringing, Inlightening, Education -> Просвещение от Вильгельма Варкентина - Карим_Хайдаров.
Bourabai Research - Технологии XXI века Bourabai Research Institution