HwGUI 2.20: справочное руководство  
  Александр С. Кресин, Октябрь 2016 вперед


1. Введение

1.1. Что такое HwGUI

HwGUI - это библиотека, или, если угодно, GUI фреймворк для Harbour and xHarbour, предназначенная для того, чтобы писать GUI приложения на Харборе. Название, собственно, так и расшифровывается: Harbour Windows GUI (когда библиотека создавалась, у меня еще не было мыслей о ее реализации на других ОС).

Сейчас у нас есть две версии HwGUI:

Большинство GUI фреймворков, не только для Harbour, но и для других языков, основанных на сторонних библиотеках, используют эти библиотеки на всех поддерживаемых ОС. Т.е., если какой-нибудь фреймворк основан, скажем, на QT, он использует QT и для Windows, и для Linux. Ключевой особенностью HwGUI является то, что для Windows используется родной WinAPI - не нужно дополнительных многотонных dll для каждого, даже самого скромного приложения, все работает быстрее, так как нет дополнительных прослоек между вашим кодом и WinAPI. В то же время для других ОС, в первую очередь, Linux, используется GTK. Но при этом ваш код на Harbour остается тем же самым.

Одна из главных целей HwGUI - скрыть от конечного пользователя, программиста, пишущего на Harbour, технические детали низкоуровневого API и создать набор классов, команд и функций, который позволит легко манипулировать GUI элементами, причем, что очень важно, этот набор - общий для обеих версий. Т.е., вы можете использовать один и тот же Harbour код для программ, собираемых с Windows или GTK версией HwGUI - детали реализации скрыты от вас, вы можете и не знать, во что преобразуются потом ваши строки, в вызовы WinAPI или вызовы GTK функций. Конечно, идеала достичь трудно, на практике дело обстоит не совсем так. Некоторые функции Windows версии не реализованы пока в GTK версии, некоторые имеют небольшие ограничения и это надо иметь ввиду.

HwGUI может использоваться в UNICODE режиме, подробнее об этом - в разделе 3.10. Работаем с UNICODE.


1.2. История HwGUI

Я начал работать над HwGUI в августе 2001, первая версия появилась на свет 21 августа. Безусловно, источником вдохновения для меня служила Fivewin Антонио Линареса - единственная на тот момент GUI библиотека для Harbour.

Моей первоначальной целью было создание маленькой и быстрой GUI библиотеки преимущественно для моих собственных нужд. Хотелось продолжать работать с Clipper/Harbour, но ограничиваться консольными программами было уже невозможно. Надо было максимально быстро сделать что-нибудь простое, чтобы реализовать на Harbour назревшие GUI проекты. И уже в октябре 2001 первая маленькая GUI программа для предприятия, где я работал, была готова. Она использовала базы данных, созданные работающей бухгалтерской системой, генерировала некоторые документы и отправляла их по факсу.

Первоначально, до релиза 1.3, HwGUI не использовал классы, больше того, я даже декларировал это, как одну из особенностей библиотеки - все хранилось в массивах. Главным причиной было стремление к лучшей производительности и к стабильности. Реализация классов в Harbour тогда еще хромала, оставались каки-то ошибки и я не хотел лишней головной боли. Ну и, конечно, доступ к элементам массива осуществляется быстрее, чем к данным объекта...

Но позже я пришел к решению переписать HwGUI под использование ООП - чтобы сделать конечный код, код приложения более понятным и структуированным. К тому времени и c реализацией классов в Harbour дела поправились. Поэтому, начиная с релиза 2.0 HwGUI основан на ООП.

С осени 2003 года исходный код HwGUI выложен на SourceForge, где и обитает по сей день. К разработке подключилась группа программистов. Я благодарен всем за вклад и участие.

Другая важная веха - декабрь 2005, когда стартовала работа над GTK версией и HwGUI стал кросс-платформенным инструментом.


1.3. Как изучить HwGUI

Для начала, конечно, надо прочитать это руководство - по крайней мере первые 3 раздела. При этом подразделы Внутри HwGUI и Работаем с UNICODE можно пока пропустить. Для дальнейшего изучения я настойчиво рекомендую утилиту Tutorial, находящуюся в hwgui/utils/tutorial/. Эта утилита содержит упорядоченный набор примеров кода на HwGUI с комментариями, каждый из которых можно запустить на исполнение прямо из утилиты. Больше того, программный код помещен в окно встроенного редактора с подсветкой синтаксиса, его можно отредактировать и сразу же исполнить, т.е., вы можете самым быстрым способом проверять работу тех или иных конструкций HwGUI, стилей и т.п. Соберите примеры из hwgui/samples/, утилиты из hwgui/utils/, просмотрите их код. Ну и, конечно, пишите сами, пишите побольше - это лучший способ чему-то научиться. Успехов!


2. Установка HwGUI

2.1. Состав дистрибутива

Дистрибутив HwGUI представляет собой архивный файл, без какой-либо установочной программы. Это может быть zip - архив, если он предназначен для Windows, или tar.gz, tar.bz2, если для Linux. Вам надо просто распаковать его в любое место, которое вы сочтете подходящим - например, в каталог Harbour. В общем случае архив включает следующие каталоги и файлы:

     Changelog        - собственно, Changelog - журнал изменений
     *.hbp,*.hbc      - файлы для сборки с помощью Hbmk
     make_b32.bat     - командный файл для сборки библиотек HwGUI с Borland C
     makefile.bc
     make_vc.bat      - командный файл для сборки библиотек HwGUI с MSVC
     makefile.vc
     make_pc.bat      - командный файл для сборки библиотек HwGUI с Pelles C
     makefile.pc
     make_w32.bat     - командный файл для сборки библиотек HwGUI с Open Watcom C
     makefile.wc
     makemngw.bat     - командный файл для сборки библиотек HwGUI с Mingw
     makefile.gcc
     makedll.bat      - командный файл для сборки HwGUI dll с Borland C
     makedll.bc
     license.txt
     install.txt      - инструкции по установке
     whatsnew.txt
     contrib\         - каталог с дополнительными модулями, которые по тем
                        или иным причинам не вошли в основной набор исходников
         activex\     - исходники классов HActiveX И HHtml - реализация ActiveX технологии
         ext_controls\- несколько дополнительных классов виджетов
         hwmake\
         misc\
         qhtm\        - исходники класса HQhtm
     doc\             - каталог с документацией
     image\           - каталог с файлами изображений для примеров
     include\         - каталог с заголовочными файлами HwGUI
     lib\             - каталог с готовыми библиотеками HwGUI
     samples\         - каталог с примерами HwGUI
         gtk_samples\ - каталог с примерами для GTK версии
     source\          - каталог с исходниками Hwgui
         common\      - общие исходники для обеих версий
             debug\   - исходники библиотеки hwgdebug
             editor\  - исходники классов Hilight и HcEdit - редактора с подсветкой синтаксиса
             procmisc\- набор разных функций
             xml\     - исходники классов HXMLDoc, HXMLNode - чтение и запись xml файлов
         gtk\         - исходники GTK версии
         winapi\      - исходники Windows версии
     utils\           - каталог с утилитами
         bincnt\      - Утилита для создания бинарного контейнера
         dbc\         - Data Base Control, утилита для просмотра/редактирования dbf файлов
         debugger\    - GUI отладчик для Harbour
         designer\    - Дизайнер, утилита для создания экранных форм и отчетов
         editor\      - Текстовый редактор с возможностями форматирования уровня Wordpad
         tutorial\    - Интерактивное руководство по HwGUI
     

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


2.2. Сборка Windows версии

Для бинарного дистрибутива этот вопрос, сами понимаете, не стоит: библиотеки собраны и находятся в каталоге lib/. Так что мы здесь говорим о том, как собрать библиотеки из дистрибутива, содержащего исходники. Процедура эта не сложнее, чем сборка любого приложения под Harbour.

Пользователи Harbour могут воспользоваться утилитой Hbmk (в xHarbour эта утилита отсутствует). Ее надо запустить с каждым из четырех перечисленных ниже hbp файлов:

       hbmk2 hwgui.hbp hbxml.hbp hwgdebug.hbp procmisc.hbp
    

В результате в каталоге lib/ у вас должны появиться библиотеки hwgui, hbxml, hwgdebug и procmisc.

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

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

       SET HB_PATH=c:\harbour
    

После этого запускаете на исполнение этот командный файл и, собственно, все: в lib/ должны появиться все библиотеки.


2.3. Сборка GTK версии

Исходники GTK версии находятся в каталоге hwgui/source/gtk/. Там же находится и bash скрипт для сборки библиотек - build.sh. Перед его запуском надо разобраться, как у вас установлен Harbour. Если он ставился из бинарных deb или rpm пакетов, то файлы Harbour обычно бывают раскиданы по каталогам /usr/local/bin, /usr/local/include/harbour, /usr/local/lib/harbour. В этом случае вам ничего дополнительно прописывать не надо. Если же у вас, как у меня, есть где-то отдельный каталог Harbour, распакованный из архива, в котором находятся все его файлы - и сам компилятор, и заголовочные файлы, и библиотеки, то вам надо указать путь к этому каталогу в переменной HB_ROOT, вставив соответствующую строчку в build.sh, например:

       export HB_ROOT=../../..
    

Ну а теперь можно запускать build.sh на исполнение, собранные библиотеки должны появиться в каталоге lib/.


2.4. Сборка приложений с HwGUI

Теперь поговорим о том, как собирать (компилировать и линковать) GUI программы на Harbour. HwGUI предоставляет для этого несколько заголовочных файлов (в include/) и 4 основные библиотеки:

Hwgdebug, hbxml и procmisc можно, кстати, использовать и без HwGUI, как самостоятельные модули. Кроме того, есть 3 дополнительные библиотеки из contrib/, которые можно подключить при необходимости:

Чтобы собрать GUI программу, надо подключить нужные библиотеки и добавить кое-какие опции для C линковщика - эти опции разные для разных С систем. Чтобы не писать все это руками, в дистрибутив HwGUI входят скрипты, решающие эту задачу. Для тех, кто пользуется Hbmk, имеется hwgui.hbc. Вот так будет выглядеть команда для сборки вашего файла myprog.prg, исходя из предположения, что каталог hwgui\ находится в c:\harbour\:

      hbmk2 c:\harbour\hwgui\hwgui.hbc myprog.prg
    
Для Windows версии рекомендую всегда подключать файл samples\hwgui_xp.rc, чтобы использовать Windows стили:
      hbmk2 c:\harbour\hwgui\hwgui.hbc hwgui_xp.rc myprog.prg
    

Для тех, кто, как я, не пользуется Hbmk, а также для пользователей xHarbour, где Hbmk просто нет, в каталоге samples\ предусмотрены командные bat файлы для разных С компиляторов. Вы можете использовать их для сборки примеров из samples\, а также как образцы для сборки своих программ.

Для Linux версии можно использовать Hbmk или samples/gtk_samples/build.sh - перед этим вам, возможно, потребуется изменить в этом скрипте значение переменной HB_ROOT, как описано в разделе 2.3. Сборка GTK версии.


3. Как использовать HwGUI

3.1. Терминология

Всегда важно сначала разобраться с терминологией, чтобы потом не возникало проблем с взаимопониманием. Давайте сначала определимся, как нам называть элементы окна: метки, комбобоксы, радиокнопки и пр. В Windows в английском принят общий термин "control". Его часто так и переводят: "контрол", что, как мне кажется, звучит по-русски как-то странно. Поэтому я в дальнейшем буду использовать термин "виджет", что является транслитерацией "widget" - термина, принятого, например, в GTK. Он не похож ни на одно уже существующее русское слово и, поэтому, как мне кажется, не так режет слух.

Окнами я буду называть то, что обычно называют окнами пользователи компьютеров - главное окно приложения, MDI окна и диалоги. Я специально отмечаю это обстоятельство, так как, строго говоря, в Windows API виджеты тоже являются окнами.

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

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

Для операций печати и рисования важнейшим объектом, на который, собственно, и производится вывод, является так называемый device context. Мы будем иметь дело с "дескриптор" этого объекта - handle of device context, сокращенно - hDC. Именно так: hDC я его и буду в дальнейшем называть.

И еще один термин, перевод которого каждый раз вызывает у меня проблемы. Это "clause" - имеется ввиду clause команды. Как известно, отдельные элементы команд в Clipper/Harbour называются "clause". Так, например, у хорошо нам известной команды @ ... GET ... есть такие clause, как PICTURE, VALID и др. Я долго думал, что с этим делать, и в конце концов решил так и называть это дело: clause, без всякого перевода и транслитерации.

Итак, договорились: виджеты, окна, GUI элементы, clause, дескриптор, hDC. И еще: HwGUI можно использовать и с Harbour, и с xHarbour, но для простоты я буду употреблять слово "Harbour" - тем более, что сам работаю только с ним. Если что-то с xHarbour работает не так, это будет специально отмечено.


3.2. Классы, команды и функции

Для каждого вида GUI элементов в HwGUI определен свой класс. Для главного окна, например, HMainWindow, для диалога - HDialog, для обычной кнопки - HButton, и т.д. Метод New() каждого из этих классов создает объект соответствующего GUI элемента. Переменные объекта определяют свойста элемента, методы - действия, производимые с ними.

Существуют также несколько базовых классов: HObject, HScrollArea, HCustomWindow, HWindow, HControl, которые являются "родителями" для других классов. Их не следует использовать в программах напрямую - только для наследования от них, если вы создаете новый класс. Эти классы содержат набор переменных и методов, наследуемых остальными классами, представляющими реальные GUI элементы. Так, например, HCustomWindow - базовый класс для всех GUI элементов, включает в себя oParent - объект элемента-контейнера, включающего в себя данный элемент ( окна, панели, ... ), nLeft, nTop, nWidth, nHeight - координаты и размеры элемента и другие, необходимые для любого GUI элемента переменные. Из методов HCustomWindow отмечу, например, Hide() и Show() - спрятать и показать элемент на экране, Move( x,y,width,height ) - переместить элемент на другое место и/или изменить его размер, SetColor( tcolor,bColor,lRepaint ) - изменить цвет текста и/или фона GUI элемента.

Есть также классы, который нельзя отнести к вышеперечисленным. Это, например, HPrinter - класс, предназначенный для печати, HTimer - класс таймера, HBinC - класс для работы с бинарным контейнером, HStyle - класс, описывающий стиль отрисовывания элемента, и некоторые другие. Подробнее все классы HwGUI рассмотрены в соответствующем разделе документации.

Команды HwGUI определены в заголовочном файле include/guilib.ch. Большинство из них предназначены для создания каких-либо GUI элементов, окон или виджетов, и преобразуются препроцессором в вызов метода New() соответствующего класса. Можно, конечно, вызывать New() напрямую, но использование команды обычно гораздо удобнее, так как в этом случае нет необходимости помнить порядок и предназначение многочисленных параметров этого метода. Сравните, например, команду создания диалога с соответсвующим вызовом метода:

    INIT DIALOG oDlg TITLE "Images" AT 100, 100 SIZE 300, 400

        или

    oDlg := HDialog():New( 11,, 100, 100, 300, 400, "Images",,,,,,,,, ;
          .F.,,, .F.,,, .F.,, .F. )
    

Подробнее все команды рассмотрены в соответствующем разделе документации.

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


3.3. Первая программа на HwGUI

       #include "hwgui.ch"

       Function Main
       Local oMainWnd, oFont
       Local aCombo := {"First","Second" }

          PREPARE FONT oFont NAME "MS Sans Serif" WIDTH 0 HEIGHT -13

          INIT WINDOW oMainWnd TITLE "Example" ;
             FONT oFont ;
             ON EXIT {||hwg_MsgYesNo("Really want to quit ?")}

          @ 20,10 EDITBOX "Hello, World!" SIZE 200,30

          @ 270,10 COMBOBOX aCombo SIZE 100, 150 TOOLTIP "Combobox"

          @ 120,60 BUTTON "Close" SIZE 150,30 ;
             ON CLICK {||oMainWnd:Close()}

          MENU OF oMainWnd
             MENUITEM "About" ACTION hwg_MsgInfo("First HwGUI Application")
          ENDMENU

          ACTIVATE WINDOW oMainWnd

          hwg_writelog( "Program terminated " + Dtoc(Date()) + " at " + Time() )

      Return
    

Первое, что вам, наверное, понадобится сделать - это создать главное окно. Наилучший способ это сделать - использовать команду INIT WINDOW. В этой команде вы можете указать начальную позицию, размер окна, иконку, стиль, шрифт, цвет, ... Можно также указать обработчики событий: ON INIT, ON EXIT, ON PAINT, ON SIZE, ON GETFOCUS, ON LOSTFOCUS и, возможно, другие.

Далее, добавляем несколько виджетов и главное меню (MENU ... ENDMENU), и, наконец, активизируем окно (ACTIVATE WINDOW), чтобы показать его на экране. Это обычная последовательность действий при формировании любого окна или диалога: создаем окно, создаем его виджеты и, если надо, меню, и затем активизируем его. При необходимости, уже после активизации окна можно удалять какие-то виджеты или создавать новые.

Теперь пройдемся по вышеприведенному примеру.

       #include "hwgui.ch"
    

Подключаем основной заголовочный файл HwGUI - это обязательно.

      PREPARE FONT oFont NAME "MS Sans Serif" WIDTH 0 HEIGHT -13
    

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

      INIT WINDOW oMainWnd MAIN TITLE "Example" ;
         FONT oFont ;
         ON EXIT {||hwg_MsgYesNo("Really want to quit ?")}
    

Эта команда создает главное окно с заголовком "Example" и с подготовленным шрифтом oFont. Кодоблок, указанный со служебным словом ON EXIT, выведет при закрытии окна стандартный диалог с вопросом, на который пользователь должен будет ответить положительно или отрицательно. Функция hwg_MsgYesNo() возвращает логическое значение, которое используется в данном случае для того, чтобы определить, закрывать окно или нет.

      @ 20,10 EDITBOX "Hello, World!" ;
         SIZE 200,30
      @ 270,10 COMBOBOX aCombo ;
         SIZE 100, 150 TOOLTIP "Combobox"
      @ 120,60 BUTTON "Close" ;
         SIZE 150,30 ;
         ON CLICK {||hwg_EndWindow()}
    

Эти команды создают соответствующие виджеты - Edit (виджет для ввода/редактирования текста), комбобокс и кнопку (думаю, вы представляете, что это такое). Комбобокс инициализируется предварительно определенным массивом aCombo. Кнопка при нажатии (ON CLICK) вызывает функцию hwg_EndWindow(), закрывающую главное окно и, соответственно, приложение.

      MENU OF oMainWnd
         MENUITEM "About" ACTION hwg_MsgInfo("First HwGUI Application")
      ENDMENU
    

Эти команды создают главное меню, состоящего только из одного элемента "About"

      ACTIVATE WINDOW oMainWnd
    

И, наконец, команда активизации главного окна, именно она приводит к его появлению на экране. Здесь надо сразу отметить такое важное обстоятельство. Главное окно - модальное, т.е. его вывод на экран приостанавливает выполнение программы. Это значит, что строки текущей функции, в нашем случае - функции Main, находящиеся ниже команды ACTIVATE WINDOW, будут выполнены только после закрытия окна. В нашем примере это строчка, записывающая дату и время окончания работы в файл a.log:

      hwg_writelog( "Program terminated " + Dtoc(Date()) + " at " + Time() )
    

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


3.4. Окна в HwGUI. Модальные и немодальные.

HwGUI поддерживает следующие виды окон:

Есть, вообще-то, еще и просто дочернее окно (HChildWindow, INIT WINDOW ... CHILD), я его реализовал просто потому что оно есть в WinAPI, но варианты его использования мне как-то не приходили в голову, я им не занимался, поэтому упоминать его дальше не буду. Если найдете для него какое-нибудь полезное применение, то, что удобно сделать именно с ним, сообщите мне.

Главное окно, в т.ч. MDI - модальное. Дочернее (Child) MDI окно - немодальное. Диалог же может быть как модальным, так и немодальным (это определяется в команде ACTIVATE DIALOG, для немодального добавляется clause NOMODAL).

Тема модальных/немодальных окон уже заграгивалась в разделе 3.3. Первая программа на HwGUI, сейчас мы рассмотрим ее немного подробнее. Итак, при использовании модального окна активация (команда ACTIVATE или вызов метода oWnd:Activate()) окна приводит к приостановке выполнения программы, т.е. строки программы, расположенные после ACTIVATE, выполняются только после закрытия окна. При использовании же немодального программа продолжает выполняться своим чередом и после активации окна.

Вот типичный пример с модальным диалогом:

    Function Test
       Local oDlg, cVar := "InitValue", xResult

       INIT DIALOG oDlg TITLE "Test" AT 100, 100 SIZE 400, 150

       @ 20,20 SAY "Input: " SIZE 60, 24
       @ 80,20 GET cVar SIZE 200,26
       ...

       @ 50,100 BUTTON "Ok" ID IDOK SIZE 100,30
       @ 250,100 BUTTON "Cancel" ID IDCANCEL SIZE 100,30

       ACTIVATE DIALOG oDlg

       IF oDlg:lResult
          ...
          xResult := "SomeResultValue"
       ENDIF
      
    Return xResult
    

Пользователь вводит в диалоговом окне какие-то данные, закрывает диалог. Функция Test() после закрытия диалога, если была нажата кнопка Ok (или какая другая), выполняет какие-то действия, вычисляет и возвращает результат. Т.е., мы можем сделать в программе вызов этой функции: x := Test() и быть уверенными, что полученный результат, присвоенный переменной x, как-то связан с тем, что ввел пользователь и что связанные с этим действия уже произведены.

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

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


3.5. Обзор виджетов

Виджеты в HwGUI можно условно разделить на две части:

Есть, правда, случаи, когда виджет, стандартный в одной версии, является нестандартным в другой. Так Tree - вполне себе стандартный в Windows версии, но в GTK я реализовал его сам, потому что тот, что был там, меня не устраивал. Ниже - список стандартных виджетов:

Первые в этом списке: Label, Edit, Group, Button, Checkbox, Radiobutton, Combobox, Updown - всем известные стандартные виджеты, используемые в диалоговых окнах для ввода информации. Label - для вывода на экран какого-либо текста, Edit - виджет для ввода текста, Group - прямоугольная рамка дл группировки виджетов, Button - обыкновенная кнопка, Checkbox и Radiobutton позволяют отметить нужный вариант, Combobox - выбор из выпадающего списка, Updown - виджет, позволяющий нажатием кнопок вверх/вниз увеличивать/уменьшать значение вводимого числа. К этой группе можно отнести Datepicker - виджет для выбора даты из выпадающего календарика и, пожалуй, Richedit - виджет для ввода текста с возможностями форматирования; помните только, что эти два последних не реализованы в GTK версии.

Tab - вы знаете, такой мультистраничный контейнер для других виджетов. Tree и Progress bar - тоже, думаю, ясно, что это такое. Status - панелька в нижней части окна, рекомендую вместо нее новый виджет Status panel (см.ниже). MonthCalendar - календарик, как в DatePicker, только не выпадающий, а постоянно отображающийся в окне. TrackBar - виджет типа регулятора звука в музыкальном или видео проигрывателе, отсутствует на данный момент в GTK версии. Toolbar - стандартная панель с кнопками, рекомендую вместо него использовать Panel - более функциональный и более поддерживаемый виджет (я Toolbar'ом не занимаюсь и не планирую это делать).

Далее идут несколько виджетов, о которых мне почти нечего сказать. Они реализованы просто потому что WinAPI их предоставляет. Grid и Listbox - я для этих целей реклмендую использовать Browse. Pager, Rebar, Animation - не очень представляю, где и зачем я мог бы их использовать...

А вот список нестандартных виджетов:

Panel - универсальный виджет, может использоваться и для размещения на нем других виджетов (я его использую, в частности, вместо ToolBar'а), и как холст, на котором можно что-нибудь нарисовать. Команда ADD TOP PANEL создает PANEL, "привязанную" к верней часть окна, изменяющую ширину синхронно с изменением размера окна.

Status Panel - подвид PANEL (класс HPanelSts - субкласс, наследник HPanel), "привязан" к нижней части окна и предназначен для замены стандартного виджета Status. Его преимуществом является то, что его можно нарисовать по-своему (ON PAINT), разместить на нем другие виджеты, задать цвет и стиль (HStyle). Кроме того, GTK версия стандартного STATUS не позволяет разбивать его на части, а у STATUS PANEL с этим проблем нет.

Browse - виджет, предназначеннный для вывода табличной информации. Это может быть и таблица базы данных, и массив.

Owner button - кнопка, нарисованная средствами HwGUI, на ней можно разместить и текст, и картинку, можно сделать ее плоской, можно определить для нее стиль (HStyle) и сделать с его помощью градиентный фон. Можно, если установить переменную объекта HWonBtn :lCheck в истину, заставить ее работать наподобие CheckBox. Ну и, наконец, можно нарисовать ее полностью самому, реализовав обработчик (кодоблок) ON PAINT.

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

Link - это Label (субкласс HStatic ), которая ведет себя как ссылка в броузере.

Bitmap, Icon - это такие графические Label, просто отображают в заданном месте bmp или ico - файл. Image - то же самое, но отобразить можно графический файл любого формата, при этом требуется наличие dll от FreeImage.

Graph реализует простенькие графики.


3.6. Общие сведения по использованию команд

Если вы уже немного знакомы с командами HwGUI, то вы обратили внимание, что многие из них имеют почти идентичный набор элементов (clause). Вот о некоторых таких типичных clause мы и поведем здесь речь.

       OF oWnd ;
       ID nId  ;
       SIZE nWidth, nHeight ;
       COLOR color ;
       BACKCOLOR bcolor ;
       STYLE nStyle ;
       FONT oFont ;
       TOOLTIP cToolTip

OF указывает на родительское окно или виджет (родительским виджетом может быть Panel или Tab), т.е. на то окно или виджет, которое является контейнером для данного виджета. Координаты виджета при этом всегда указываются относительно левого верхнего угла родителя/контейнера. В большинстве случаев OF указывать не требуется: при этом родительским окном по умолчанию принимается последнее объявленное. Т.е., если виджет создается в промежутке между INIT DIALOG oDlg и ACTIVATE DIALOG oDlg, его родителем по умолчанию становится oDlg. То же правило действует и для Tab'а. А вот если вы укладываете виджеты на Panel, то OF oPanel надо обязательно указывать.

ID - численный идентификатор виджета, который требуется WinAPI при его создании. Указывать его не обязательно: HwGUI при необходимости создаст его автоматически. Можно применять для поиска объекта этого виджета: object := oDlg:FindControl( nId ).

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

COLOR и BACKCOLOR - цвета, соответственно, текста и фона виджета. Для окна цвет текста не указывается, COLOR там оставлен пока для совместимости и на деле определяет то же, что и BACKCOLOR.

STYLE - стиль окна или виджета, не путать (!) с объектом класса HStyle. Этот стиль - характеристика GUI элемента для WinAPI, он передается WipAPI при создании элемента, HwGUI его для своих нужд за редкими исключениями не использует. GTK версия в большинстве случаев просто игнорирует этот параметр, только некоторые стили HwGUI использует для установки соответствующих свойств GUI элементов: SS_LEFT, SS_RIGHT, SS_CENTER - выравнивание Label (@ ... SAY), ES_PASSWORD, ES_MULTILINE, ES_READONLY - для Edit, WS_VSCROLL и WS_HSCROLL - для Browse и Panel. Для GUI элемента может быть определено несколько стилей - в этом случае их желательно объединить при помощи hwg_bitor() или hb_bitor(). Список стилей для каждого GUI элемента и их описание вы можете найти в документации по Windows API - например, в MSDN.

FONT - шрифт окна или виджета, объект класса HFont. Если он указывается для окна, то все виджеты этого окна унаследуют этот шрифт.

TOOLTIP - текстовая подсказка, которая появляется, если вы задержите на пару секунд указатель мыши на этом виджете.

       ON INIT bInit   ;
       ON SIZE bSize   ;
       ON PAINT bDraw  ;
       ON GETFOCUS bGfocus  ;
       ON LOSTFOCUS bLfocus ;
       ON OTHER MESSAGES bOther ;
       ON EXIT bDestroy ;

Эта группа clause определяет кодоблоки - обработчики тех или иных событий (в терминологии WinAPI это сообщения, в GTK принято говорить о событиях и сигналах).

ON INIT, ON EXIT - кодоблоки, выполняемые, соответственно, при начальной инициализации и при ликвидации GUI элемента. В качестве параметра им передается объект соответствующего GUI элемента. bDestroy должен возвращать логическое значение - если .F., то ликвидация элемента, обычно это закрытие окна, отменяется.

ON SIZE - этот кодоблок выполняется при изменении размеров окна. Для окна кодоблок получает в качестве параметров объект окна и новые размеры, ширину и высоту. Для виджета - объект виджета и новые размеры родительского контейнера. Типичный обработчик для виджета изменяет его размер и, может быть, положение, и будет выглядеть примерно так:

       ON SIZE {|o,w,h| o:Move( ... ) }
    
Вместо кодоблока в этом clause можно указать anchor - специальный цифровой код, подробнее см. здесь.

ON PAINT - кодоблок, выполняемый, когда система рисует на экране GUI элемент и предназначенный для того, чтобы нарисовать что-то отличное от стандартного представления. В полной мере он работает для тех виджетов, которые HwGUI отрисовывает самостоятельно: Panel, Splitter, Owner button. В Windows версии он работает и для окон. В качестве параметра он получает объект окна/виджета и выглядит примерно так:

       ...
       ON PAINT {|o| onPaint(o) }
       ...
    Function onPaint( oCtrl )
    Local pps, hDC

       // Сначала получим hDC поверхности для рисования
       pps := hwg_Definepaintstru()
       hDC := hwg_Beginpaint( o:handle, pps )
       ...
       // Здесь мы что-то рисуем 

       // Заканчиваем рисовать
       hwg_Endpaint( o:handle, pps )

    Return Nil
    

ON GETFOCUS, ON LOSTFOCUS - кодоблоки, выполняющиеся при, соответственно, получении и потере фокуса GUI элементом, в качестве параметра получают объект элемента.

ON OTHER MESSAGES - обработчик, вызываемый при наступлении любого сообщения/события/сигнала, получает в качестве параметров объект, идентификатор сообщения, и два дополнительных параметра. Выглядит этот обработчик примерно так:

       ...
       ON OTHER MESSAGES {|o,m,w,l| onOtherMess(o,m,w,l) }
       ...
    Function onOtherMess( oCtrl, msg, wParam, lParam )

       IF msg == WM_BUTTONDOWN
          ...
       ELSEIF msg == WM_KEYDOWN
          ...
       ENDIF
    Return -1
    

3.7. Общие сведения по использованию классов

Для каждого GUI элемента в HwGUI имеется соответствующий класс (это не значит, однако, что только GUI элементы имеют свои классы). Каждый класс имеет свои собственные переменные и методы и наследует переменные и методы родительского класса. Так, все GUI элементы наследуют переменные и методы HCustomWindow, а окна еще и HWindow, виджеты - HControl. Иерархию классов вы можете увидеть в разделе 6.2.

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


handle - дескриптор GUI элемента, его надо указывать при вызове некоторых функций;
id - идентификатор виджета для WinAPI (clause ID);
oParent - объект родительского окна или виджета (clause OF);
nTop, nLeft - координаты верхнего левого угла GUI элемента в пикселах;
nWidth, nHeight - ширина и высота GUI элемента в пикселах (clause SIZE);
tcolor, bcolor - цвет, соответственно, текста и фона GUI элемента (clauses COLOR, BACKCOLOR);
oFont - шрифт GUI элемента, объект класса HFont (clause FONT);
tooltip - текстовая подсказка, которая появляется, если вы задержите на пару секунд указатель мыши на этом виджете (clause TOOLTIP);
anchor - код, указывающий, как должны изменяться размер и координаты виджета при изменении размера содержащего его окна/виджета. Ниже приведен список возможных кодов (определены в guilib.ch):
     ANCHOR_TOPLEFT          Привязывает виджет к левой и верхней границе контейнера
                             (не изменяет расстояние до левой и верхней границы).
     ANCHOR_TOPABS           Привязывает виджет к верхней границе контейнера.
     ANCHOR_LEFTABS          Привязывает виджет к левой границе контейнера.
     ANCHOR_BOTTOMABS        Привязывает виджет к нижней границе контейнера.
     ANCHOR_RIGHTABS         Привязывает виджет к правой границе контейнера.
     ANCHOR_TOPREL           Сохраняет относительное расстояние до верхней границы.
     ANCHOR_LEFTREL          Сохраняет относительное расстояние до левой границы.
     ANCHOR_BOTTOMREL        Сохраняет относительное расстояние до нижней границы.
     ANCHOR_RIGHTREL         Сохраняет относительное расстояние до правой границы.
     ANCHOR_HORFIX           Центрирует виджет по горизонтали и сохраняет фиксированный размер.
     ANCHOR_VERTFIX          Центрирует виджет по вертикали и сохраняет фиксированный размер.
    
Эти коды можно комбинировать, например: oCtrl:anchor := ANCHOR_TOPABS + ANCHOR_LEFTABS + ANCHOR_RIGHTABS.

aControls - массив виджетов, расположенных в этом окне/виджете(Panel,Tab), т.е., тех, для которых это окно/виджет является родительским;

bInit - кодоблок, исполняемый при инициализации GUI элемента (clause ON INIT);
bDestroy - кодоблок, исполняемый при ликвидации GUI элемента;
bSize - кодоблок, исполняемый при изменении размера родительского окна/виджета (clause ON SIZE);
bPaint - кодоблок, отрисовывающий этот виджет (clause ON PAINT);
bGetFocus - кодоблок, исполняемый при получении фокуса (clause ON GETFOCUS);
bLostFocus - кодоблок, исполняемый при потере фокуса (clause ON LOSTFOCUS);
bOther - кодоблок, исполняемый при получении любого сообщения (подробнее см. clause ON OTHER MESSAGES).

И еще одна важная переменная класса HControl: objname. Напрямую в программах она обычно не используется, но о ней нужно знать. Любая команда создания виджета содержит вызов метода New() соответствующего класса и вызов функции hwg_SetCtrlName(). Так, например, команда @ ... EDITBOX oEdit ... транслируется препроцессором в:

       oEdit := HEdit():New( ...
       hwg_SetCtrlName( oEdit, "oEdit" )

hwg_SetCtrlName( oEdit, "oEdit" ) проверяет, передано ли ей имя объекта ("oEdit") в данном случае, не является ли это имя элементом массива или переменной объекта (вы ведь можете написать: @ ... EDITBOX arr[1] ... и, если все нормально, присваивает его значение переменной объекта objname, в данном случае:<.p>

       oEdit:objname := "OEDIT"

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

Теперь о методах.


Hide() - прячет GUI элемент, делает его невидимым и неактивным;
Show() - показывает GUI элемент (применяется для спрятанного методом Hide());
FindControl( nId, nHandle ) - возвращает объект дочернего виджета по его идентификатору или дескриптору;
Move( x1, y1, width, height ) - Перемещает и/или изменяет размеры GUI элемента, при этом переменным объекта nLeft, nTop, nWidth, nHeight присваиваются новые значения. Любые из параметров метода могут быть опущены - тогда соответствующие переменные не изменяются. Так, например, oCtrl:Move( ,, 100 ) изменяет только ширину oCtrl, устанавливает ее равной 100 пикселов.
SetColor( tcolor, bColor, lRepaint ) изменяет цвета текста и фона GUI элемента, при этом переменным объекта tColor, bColor присваиваются новые значения. Если lRepaint установлен в .T., виджет сразу перерисовывается. Любой из параметров метода могжет быть опущен - тогда соответствующая переменная не изменяется. Так, например, oCtrl:SetColor( 255 ) изменяет только цвет текста oCtrl.
Disable() - делает виджет неактивным;
Enable() - делает виджет активным;


3.8. GET-система


3.9. Внутри HwGUI

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

С точки зрения обработки сообщений все GUI элементы HwGUI - окна и виджеты - могут быть разделены на две группы:

1) Элементы, сообщения для которых обрабатываются HwGUI.
2) Элементы, сообщения для которых не доходят до HwGUI и обрабатываются только средствами winapi или GTK.

В первую группу входят все окна и диалоги, а также некоторые виджеты, в т.ч., HBrowse, HEdit, HStaticLink, HOwnButton, HPanel, HRichEdit, HSplitter, HTab, HTrackBar (я использую имена соответствующих классов). Во вторую - остальные виджеты, например, HStatic, HStatus, HButton, HGroup, HCheckButton и др. Вы можете определить, к какой группе относится тот или иной виджет, заглянув в исходники соответствующего класса. Если там есть метод onEvent - значит, этот виджет относится к первой группе, т.к. обработка сообщений для него осуществляется именно в onEvent.

Реализовано это следующим образом. Есть в HwGUI такая функция: hwg_SetWindowObject( handle, object ). Она связывает дескриптор handle GUI элемента и соответствующий объект object. В данном случае имеется ввиду объект одного из HwGUI классов, созданный для этого элемента (например, объект класса HMainWindow). Поскольку дескриптор фактически является некой структурой данных, передаваемый объект сохраняется в этой структуре: и WinAPI, и GTK обеспечивают средства для этого.

Для того, чтобы обработка сообщений для GUI элемента не прошла мимо HwGUI, целиком осуществляясь на нижестоящем уровне, необходимо проделать определенные действия. В WinAPI надо написать С функцию для обработки сообщений для этого элемента и "привязать" ее к нему. Посмотрите, например, как это реализовано для HEdit. В source/winapi/control.c есть функция обработки сообщения для HEdit: EditSubclassProc() и есть функция hwg_InitEditProc(handle), "привязывающая" EditSubclassProc() к дескриптору виджета класса HEdit. Hwg_InitEditProc(handle) вызывается при инициализации объекта HEdit() (см. source/winapi/hedit.prg), обеспечивая таким образом прохождение процесса обработки сообщений через HwGUI. В GTK версии это реализовано немного по другому, но суть остается та же.

При появлении сообщения для GUI элемента функция, обрабатывающая это сообщение (в случае с HEdit это функция EditSubclassProc()), извлекает из дескриптора сохраненный там объект элемента и, если у этого объекта есть метод onEvent, вызывает этот метод.

onEvent может обработать полученное сообщение сам, может передать его для обработки методу onEvent родительского класса, или просто вернуть код -1 - в этом случае сообщение возвращается для обработки по умолчанию средствами WinAPI или GTK.

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

1) Создайте новый класс, наследник HStatus, и сделайте его виджетом первой группы: привяжите к дескриптору STATUS функцию обработки сообщений, используйте hwg_SetWindowObject() для привязки объекта к дескриптору STATUS и напишите для этого нового субкласса метод onEvent.
2) Используйте вместо STATUS другой виджет. Я рекомендую для этого STATUS PANEL (субкласс PANEL).

3.10. Работаем с UNICODE


  table of contents next
    commands


Комментариев:       ()       пред.    след.       Добавить комментарий
Длина комментария - не больше 4000 символов.
Ваше имя:

Адрес электронной почты:
(не предназначено к показу)
 
Введите текст с картинки: