Андрей Боровский -
kylixportal@narod.ruВыкладываю статью, опубликованную в журнале RSDN (с любезного разрешения редакции). На форуме RSDN меня поправили, что нужно говорить X-Window (и это правильно :-)). Однако, во-первых термин X-Windows все же встречается в литературе, а во-вторых "из песни слова не выкинешь".
Так что оставляю все как есть.В этой статье речь пойдет о работе с X-Windows средствами Kylix. Мы рассмотрим такие полезные возможности как генерация скриншотов окон и отдельных элементов управления, поиск окна в иерархии окон X-Windows и некоторые другие. Интерфейсы для работы с X-Windows в Borland Kylix предоставляются модулями Xlib и Qt.
“Сейчас я вас щелкну…”
Скриншоты (изображения элементов графического вывода программы) часто используются в качестве иллюстраций в материалах, посвященных описанию приложений и визуальных сред разработки (статья, которую вы читаете, не является исключением :-)). Неудивительно поэтому, что практически все графические оболочки поставляются со средствами получения скриншотов. Однако, такие программы как KSnapshot, подходят
не для всех ситуаций. Приведу несколько примеров. Допустим вы, программист, работающий в среде Borland Kylix, хотите сделать скриншот отдельного элемента управления (control), а не всего окна. Или же вам может понадобиться получить “снимок”, отражающий состояние программы в строго определенный момент ее выполнения. Возможно также, вы захотите получить серию изображений для создания простейшей анимации. Во всех этих случаях было бы желательно иметь в своем распоряжении средства генерации скриншотов “изнутри” самой программы.Получить скриншот элемента управления или окна в Kylix-приложении совсем несложно. CLXDisplay API, являющийся оболочкой Qt library, включает две функции : QPixmap_grabWidget и QPixmap_grabWindow, предназначенные для получения соответственно скриншотов элементов управления Qt library и окон X-Windows. Обе эти функции возвращают графические данные в объекте QPixmap, указатель на который должен быть им передан. Главное различие между функциями QPixmap_grabWidget и QPixmap_grabWindow заключается в способе получения изображения. Функция QPixmap_grabWidget вызывает перерисовку элемента управления (и его дочерних элементов) при помощи метода PaintEvent, перенаправляя данные во внутренний буфер, в то время как QPixmap_grabWindow считывает изображение, созданное системой X-Windows. Обычно QPixmap_grabWindow выполняется быстрее, чем QPixmap_grabWidget, однако при использовании первой функции результирующее изображение получается таким, каким оно представляется на экране, т. е. если отображаемое окно частично скрыто другими окнами, скрытые части скриншоте будут заполнены черным цветом, в то время как функция QPixmap_grabWidget генерирует полное изображение элемента управления, вне зависимости от его положения на экране. Следует отметить, что получить изображения окон X-Windows, не связанных с Qt-объектами, можно только при помощи функции QPixmap_grabWindow. Объект-приемник QPixmap позволяет выполнять различные операции с полученным изображением, например, сохранять его на диске или копировать в буфер обмена.
Используя QPixmap_grabWidget, напишем процедуру GrabControl, позволяющую получить скриншот элемента управления Kylix и сохранить его на диске в заданном формате.
procedure
GrabControl(Control : TWidgetControl;Первым аргументом процедуры должен быть экземпляр одного из потомков класса TWidgetControl, т. е. любой элемент управления Kylix. Второй аргумент – имя файла, в котором следует сохранить полученное изображение. Третьим параметром процедуры является строка, в которой передается формат сохранения изображения. Допустимыми значениями являются графические форматы, поддерживаемые Qt library ('BMP', 'PNG', 'XPM' и т. п.). Рассмотрим подробнее функцию QPixmap_grabWidget. Первый параметр этой функции – ссылка на созданный ранее объект QPixmap, которому передается изображение. Лежащая в основе этой функции статическая функция Qt library QPixmap::grabWidget сама создает новый объект QPixmap, однако в CLXDisplay API этот механизм изменен.
Скриншот элемента управления, полученный с помощью GrabControl
Объект QPixmap является основой компонента TBitmap. Следующий код позволяет отобразить скриншот при помощи компонента TImage (Image1):
QPixmap_grabWidget(Image1.Picture.Bitmap.Handle,
Control.Handle, 0, 0, -1, -1);
Image1.Refresh;
При помощи процедуры GrabControl можно получить скриншот формы приложения со всем ее содержимым (в следующей строке процедура вызывается из метода формы):
GrabControl(Self, 'form.png', 'PNG');
Однако на скриншоте, полученном таким способом, будет отображена только клиентская область формы, т. е. внутренняя часть окна без заголовка и обрамляющих элементов. Объясняется это тем, что элементы обрамления окна не являются частью элемента управления, лежащего в основе компонента TForm. Для отображения окна формы “целиком” нам потребуется другая процедура:
procedure
GrabForm(Form : TCustomForm;Формат вызова процедуры GarbForm такой же, как и у GrabControl, разница в том, что в первом параметре GarbForm передается указатель не на любой элемент управления, а только на форму.
Чтобы понять как работает эта процедура, рассмотрим механизм прорисовки окон в системе X-Windows. Прежде всего следует отметить, что все окна в X-Windows организованны в иерархическую структуру. Список окон представляет собой дерево, корнем которого является окно оболочки (desktop window). Вывод окон осуществляется оконным менеджером (window manager), который и создает все обрамляющие элементы. Получив запрос на создание нового окна, оконный менеджер создает базовое окно-контейнер, которое “отвечает” за прорисовку заголовка и обрамления и внутри которого размещаются клиентское окно приложения и окна кнопок заголовка. Клиентское окно и окна кнопок являются дочерними окнами окна-контейнера, а само окно-контейнер является непосредственным потомком корневого окна.
Для того, чтобы получить идентификатор окна-контейнера, нам необходимо получить сперва идентификатор клиентского окна, а затем подняться вверх по иерархии окон до окна-контейнера. Для этого мы используем функцию XQueryTree, которая позволяет для заданного окна получить идентификатор его родительского окна, корневого окна и список идентификаторов дочерних окон (если они есть). Вызов XQueryT
ree выполняется в цикле, так как в разных оконных менеджерах “родословная” клиентского окна относительно окна-контейнера может различаться. Например в KDE окно-контейнер является “дедушкой” клиентского окна, тогда как в WindowMaker клиентское окно – непосредственный потомок окна-контейнера. Но в любом случае окно-контейнер должно быть потомком окна Desktop, так что мы поднимаемся вверх до тех пор, пока не найдем такое окно. Поскольку теперь мы имеем дело с окном X-Windows, а не с Qt-объектом, нам придется воспользоваться функцией QPixmap_grabWidget. При этом следует учесть особенности этой функции (например, проследить, чтобы окно формы располагалось на экране поверх других окон). Обращение к процедуре GrabForm может происходить следующим образом:Self.BringToFront;
GrabForm(Self, 'form.png', 'PNG');
Изображения формы, полученные в результате выполнения процедур GrabControl и
GrabForm.Вверх и вниз по дереву окон
Мы уже знакомы с функцией XQueryTree, позволяющей получить идентификаторы предков и потомков окна в иерархии окон X-Windows. Эту функцию можно использовать для поиска и анализа окон, не принадлежащих вызывающему ее приложению. В качестве примера использования XQueryTree рассмотрим функцию FindXWindow, находящую окно, имя которого совпадает с заданным, и возвращающую идентификатор этого окна.
function
FindXWindow(Display : PDisplay;Функция FindXWindow осуществляет обход дерева окон в прямом порядке. Первый аргумент функции – указатель на X дисплей. В параметре Root передается идентификатор окна, среди потомков которого следует вести поиск (для поиска по всей системе следует передать идентификатор корневого окна). Третий аргумент функции – имя окна, идентификатор которого нужно получить. Если окно с указанным именем не будет найдено среди потомков окна Root, функция вернет значение 0. Константа StackDepth ограничивает максимальную “глубину погружения” при обходе дерева окон. Следующая строка кода иллюстрирует вызов функции FindXWindow:
KonsoleID := FindXWindow(QtDisplay, QWidget_winID(Application.Desktop), 'Konsole');
Функцию FindXWindow несложно преобразовать в процедуру создания списка всех окон системы, которая может использоваться, например, в отладочных приложениях.
Если возможно получить идентификатор окна, не принадлежащего нашей программе, значит можно и выполнять некоторые операции с “чужими” окнами. Напишем приложение, аналогичное поставляемой с KDE утилите KSnapshot. Создайте новый проект. Добавьте в раздел uses модуля Unit1 модули Qt и Xlib. Поместите в окно формы стандартную кнопку и компонент TImage. Добавьте в объект TForm1 поле Grabbing типа Bool
ean и присвойте этому полю значение False в конструкторе формы. В обработчике события OnClick объекта Button1 задайте следующий код:procedure
TForm1.Button1Click(Sender: TObject);Назначьте следующий обработчик событию OnMouseDown главной формы:
procedure
TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;После того, как кнопка Button1 нажата, следующий щелчок мышью в окне какого-либо приложения вызовет генерацию скриншота этого окна. Поле Grabbing необходимо для того, чтобы скриншот не генерировался каждый раз при щелчке в окне формы приложения. Функция XGrabPointer позволяет приложению отслеживать состояние мыши тогда, когда указатель мыши находится за пределами окна приложения. Эта функция аналогична функции SetCapture Windows API. Функция XQueryPointe
r позволяет определить, в каком окне находится отслеживаемый указатель. В принципе, после получения скриншота мы должны были бы вызвать функцию XUngrabPointer, прекращающую отслеживание мыши, но в данном случае в этом нет необходимости, так как Kylix “любезно” выполнит соответствующую операцию за нас.И еще один, “опасный”, пример. Хотите превратить описанное выше приложение в аналог системной утилиты XKill? Тогда замените код обработчика OnMouseDown следующим:
procedure
TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;Теперь после нажатия на кнопку Button1, щелчок мышью в каком-либо окне приведет к закрытию соответствующего приложения. Завершение приложения-владельца окна X-Windows осуществляется функцией XKillClient, которой передается идентификатор окна. Обратите внимание, что в этой процедуре мы выполняем действия, обратные тому, что мы делали в процедуре GrabWindow – спускаемся вниз по дереву окон. Это необходимо потому, что функция XQueryPointer возвращает идентификатор окна-контейнера, и если мы передадим этот идентификатор функции XKillClient, будет “закрыт” компонент X-Windows, отвечающий за прорисовку обрамляющих элементов окон. Для того, чтобы избежать этого неприятного явления, мы ищем среди потомков первого “ребенка” окна-контейнера дочернее окно, не имеющее дочерних окон. Это окно уж точно принадлежит приложению, а не системному компоненту.
Истина где-то рядом?
Система X-Windows справедливо “славится” сложностью своего интерфейса программирования (в знаменитом Jargon File она удостоена эпитетов “hairy” и “over-complicated”). Однако, такие библиотеки как Qt library (и, соответственно, CLXDisplay API), в большинстве случаев избавляют нас от необходимости “прямого общения” с X-Windows, а использование отдельных функций X-Windo
ws в приложении не вызывает, как мы видели, никаких затруднений. Двойственные чувства возникают в связи с возможностью получения информации и управления “чужими” окнами. С одной стороны, эти возможности позволяют реализовать ряд полезных утилит. С другой стороны, та же функция XKillClient действует “невзирая на звания и лица”, в результате чего становится возможным, например, закрыть приложение с высоким уровнем доступа из приложения с более низким уровнем.Вообще же говоря, обращения к функциям X-Windows напрямую следует по возможности избегать, так как при использовании этих функций ObjectPascal приложение теряет кросс-платформенную переносимость, наконец-то обретенную с появлением Kylix.
Эта статья была впервые опубликована в журнале RSDN, #0, 2002 г. Перепечатка статьи возможна только с разрешения редакции журнала.