Урок 5. Декларация окон, классов.
Изучив основные конструкции и основные возможности языка 4Test, самое время перейти к рассмотрению работы с оконными объектами, так как SilkTest предназначен непосредственно для работы именно с окнами и элементами управления.
Что такое окна, классы. Иерархия стандартных классов.
Окно, это объект операционной системы, визуально представляющий из себя некоторую прямоугольную область экрана, обладающий опеределнными свойствами и выполняющий опереденные действия. В нашем случае это окна приложений, диалоговые окна, элементы управления ( кнопки, текстовые поля и т.д. ). SilkTest распознает эти элементы как оконные объекты и работает с ними. Данные объекты определяются как элементы типа WINDOW.
Многие элементы управления содержат в себе ряд общих характеристик, которые не являются специфическими для конкретного элемента. Это позволяет разделить все объекты на определенные классы. В 4Test-е единственным видом классов являются оконные классы (winclass-ы). По аналогии с классами в С++, оконные классы имеют определенный набор свойств и методов, они могут наследоваться от некоторого класса, перенимая у него свойства и методы родительского класса. Соответственно формируется определенная иерархия объектов. В иерархии стандартных классов 4Test-а выделяются 4 класса, которые не имеют базовых классов:
- CursorClass - класс для работы с курсором
- ClipboardClass - класс, обеспечивающий работу с текстовым буфером обмена
- AgentClass - класс для работы с Агентом
- AnyWin - класс, соответствующий любому окно
Первые 3 из перечисленных классов не соответствуют оконным объектам. Их назначение соответствует назначению классов в С++ - группировка некоторых переменных, констант и функций под одним именем. Не более того. Куда более значительный интерес представляет класс AnyWin. Это базовый класс для всех классов, соответствующих окнам. То есть любое окно, элемент управления так или иначе используют класс, наследуемый от AnyWin. От него наследуются такие основные классы как:
- DesktopWin - соответствует классу окна рабочего стола
- Control - класс, от которого наследуются основные классы элементов управления ( за редким исключением ).
- MovableWin - класс, от которого наследуются классы для главных окон (MainWin), дочерних окон многодокументных приложений (ChildWin) и диалоговых окон (DialogBox). Объединяет эти классы галичие методов, позволяющих перемещать/перетаскивать окна, менять их размер и т.п.
- Menu - соответствует пунктам меню
- CustomWin - соответствует некоторому нестандартному окну, класс которого не описывается как стандартный
Эти классы являются базовыми уже для конкретных классов, с которыми непосредственно придется работать.
Описание оконных деклараций
Как же окна описываются? Описания окон имеют вид:
window <class_name> <window_name>
[[multi]tag <tag_definition>]
[parent <parent_window_name>]
[ Properties, Child controls , Methods]
Здесь:
- <class_name> - имя существующего класса
- <window_name> - имя окна. Оно должно быть уникальным
- <tag_definition> - строка или набор строк, по которым данное окно находится Агентом
- <parent_window_name> - имя родительского окна
Поскольку структура окон является иерархической, то внутри объявления окна можно объявлять другие окна и под-окна, при этом не надо указывать ключевое слово window и parent. Данные ключевые слова используются для окон верхнего уровня.
При этом следует обратить внимание на то, что ключевое слово WINDOW соответствует не только объявлению объекта окна, но это также и тип данных. То есть переменная типа WINDOW и объект-окно - это не одно и тоже, хотя бы потому, что значение переменной может быть изменено в ходе выполнения скрипта, чего нельзя сделать для объекта-окна. Поэтому, если за ключевым словом WINDOW следует имя оконного класса и затем имя, то это объявление окна данного класса. Иначе, идентификатор, следующий за данным ключевым словом, является именем переменной типа WINDOW. |
Тэги, их виды и способы использования
В оконных декларациях теги играют одну из ключевых ролей. Поэтому на них можно остановиться поподробнее. Как уже было указано выше - это строки, позволяющие уникально идентифицировать окна. В зависимости от информации, на основе которой формируется тэг, тэги разделяются на виды:
- По Caption - в основе тега лежит текст заголовка окна или текст привязанный к данному объекту ( для элементов управления ). Данный тег представляет собой обычную строку без каких-либо специальных символов. Это наиболее употребимый вид тэга и наиболее рекомендуемый, поскольку он отражает характеристики объекта, которые можно воспринимать визуально.
- По Prior Static - в основе тэга лежит текст Statiс Text объекта ( или приравненного к нему ), ближайшего к данному объекту. Такой тэг применим для элементов управления и его удобно использовать, если тэг по Caption менее соответствует данному объекту, чем тэг по Prior Static, то есть, если 2-й тэг содержит реально значение текста перед элементом управления, в то время как Caption извлекся из более далекого объекта. Тэг по Prior Static начинается с символа ^ .
- По индексу - в основе тега лежит порядковый номер данного элемента на форме. Порядок следования формируется по возрастанию сверху-вниз и слева-направо. Строка данного тега начинается с # и содержит порядковый номер. Данный тип тэгов достаточно эффективен, для работы с динамическим содержимым, когда можно уловить определенный числовой ряд. Тем не менее этот вид тэга также применяется в случае, если по Caption или по Prior Static тэги не получается получить, при этом фрейм не меняется динамически.
- По ID - в основе тега лежит уникальный идентификатор данного объекта. Для разных видов приложений в качестве такого идентификатора выступают разные величины. Для главных окон приложения это зачастую путь к исполняемому файлу, для элементов управления это HWND объекта ( для GUI-приложений ), а для веб-приложений это значение атрибута name данного объекта. В любом случае данный вид тэга достаточно уникален, но наиболее эффективно его можно использовать при декларации окон веб-приложения.
- По Location - в основе тэга лежат координаты левого верхнего угла объекта. Этот тег имеет формат @( x , y ). Этот вид тэгов используется в основном для контекстных меню, когда единственной определяющей характеристикой является именно местоположение объекта. Иногда этот тэг применим и для нестандартных объектов, которые иначе просто и не видятся. В остальном лучше искать другие тэги.
Это основные тэги. При этом тэг может использоваться как один, так и несколько. Для этого есть ключевое слово multitag, которое описывает варианты тэга для некоторого элемента. Но в большинстве случаев, достаточно одного тэга, чтобы достаточно точно описать окно.
Еще одной особенностью тега является то, что данная строка содержит не монолитное значение, которое в точности соответствует значению в окне, а это фактически шаблон, по которому ищется окно. Причиной этому является то, что текст, который может служить для формирования тэгов в общем случае варьируется, но при этом могут оставаться некоторые общие части. Соответственно, в тэгах допустимы специальные символы *(один или более произвольных символа) и ?(один любой символ). Эти специальные символы позволяют маскировать определенные фрагменты тэга, не придавая им значение при определении элемента в окне приложения. Что нам это дает? Допустим, у нас есть тэг "Test * tag? with*". Тогда данным тэгом можно заменить следующие тэги:
"Test my tags with wildcards"
"Test tag with "
В первом тэге маскированные элементы выделены жирным шрифтом, а во втором примере замаскированными элементами оказались пробелы.
Но что делать, если один тэг не описывает нормально объект, а возможны варианты из 2 и более тэгов. В этом случае можно использовать multitag, но зачастую можно обойтись обычным tag. В этом случае можно использовать символ |. То есть, запись:
|
Code
|
[+] multitag "Caption"
"#2"
|
аналогична записи:
|
Code
|
[+] tag "Caption|#2"
|
Таким образом multitag может быть полностью заменен на tag.
Вполне могут возникнуть случаи, когда 2 и более элемента управления, находящихся на одном уровне иерархии, имеют одинаковые тэги. Эта ситуация не является благоприятной, поскольку не позволяет четко определить, какой же именно объект нужно использовать. Для этого можно, конечно, воспользоваться другим типом тэга, но при этом мы потеряем точность описания, ведь тот же тэг по индексу уже не будет соответствовать требуемому объекту, если выше будет добавлен еще один элемент управления того же типа. Это сдвинет индексы. В этом случае можно в конце тэга в квадратных скобках указать номер элемента, который надо искать с таким же тэгом. Иными словами, если у нас есть 2 окна на одном и том же уровне, одного и того же класса у которых тэг, например, "some duplicating tag", то тэг "some duplicating tag[1]" будет соответствовать именно первому окну, а тэг "some duplicating tag[2]" - второму. Эту возможность наиболее часто приходится использовать, если используется маскировка некоторых частей тэга ( в тэге используются символы * или ? ). Специальные символы * и ? фактически расширяют множество допустимых тэгов, соответственно повышается вероятность того, что один и тот же тэг подойдет сразу нескоьким окнам. Вот здесь и нужно использовать данный прием.
Рассмотрим более конкретно применение данной возможности. Откроем тестовое приложение (TestApp), которое идет в поставке с SilkTest и выберем пункт меню File > New. Появится дочернее окно с текстовым полем. Опишем его и подкорректируем имена. В итоге объявление имеет вид:
|
Code
|
[+] window ChildWin wChild
[ ] tag "MDI Child Window #1"
[ ] parent wTestApplication
[ ]
[+] TextField edtText
[ ] tag "#1"
|
Но если мы повторно выберем пункт меню File > New, то появится такое же окно, но с заголовком "MDI Child Window #2" и тэг у него будет соответствующим. И так далее. Этих окон может быть очень много. Соответственно бесконечное количество копий делать бессмысленно. Тогда можно воспользоваться тэгом "MDI Child Window #*". Тогда нам неважно, какой номер у окна. Но при этом такой тэг будет неуникальным. Для того, чтобы описать тэг именно для активного окна. В данном случае, это первое окно, тэг которого удовлетворяет данному шаблону. Соответственно, скорректированная декларация окна имеет вид:
|
Code
|
[+] window ChildWin wChild
[ ] tag "MDI Child Window #*[1]"
[ ] parent wTestApplication
[ ]
[+] TextField edtText
[ ] tag "#1"
|
Также могут возникнуть случаи, когда класс окна в объявлении может несоответствовать реальному классу, который имеется у окна приложения. Это может быть вызвано различными причинами. Наиболее часто данная вещь полезна при тестировании приложений, основанных на диалогах или для приложений, у которых при запуске появляется вначале диалог. В чем проблема в данном случае. Диалоговое окно описывается классом DialogBox, у которого нету никакого метода, который бы стартовал приложение. Встроенные команды для работы с командной строкой не расчитаны на запуск оконных приложений, для подобных целей надо вытаскивать функции из системных dll, что не очень удобно и не всегда доступно. Но мы знаем, что у класса MainWin есть метод Start, который просто запускает командную строку и ждет появления данного окна. Этот метод удобно задействовать. При этом, это окно по-прежнему должно восприниматься как диалог. Допустим, данный диалог записывается так:
|
Code
|
[+] window DialogBox dDialog
[ ] tag "Dialog"
|
Тогда вышеописанное решение можно реализовать так:
|
Code
|
[+] window MainWin dDialog
[ ] tag "[DialogBox]Dialog"
|
Ключевые моменты подсвечены жирным шрифтом. Как видим, мы данное окно объявили как MainWin, но при этом мы поменяли тег, добавили перед строкой тега [DialogBox]. Это означает, что для данного окна будут доступны все свойства и методы класса MainWin, но при этом в приложении будет выискиваться именно диалоговое окно. Сама возможность осуществления подобное приема в очередной раз подтверждает, что описание объекта и реальная структура окна не являются взаимозависимыми вещами и их можно разделять.
В подтверждение этих слов осталось привести еще одну возможность работы с тегами. Как правило, вручную описывать окна - это задача слишком трудоемкая, требующая немалого воображения и усилий. Поэтому куда более предпочтительно запись оконных деклараций делать автоматически. При этом, во многих случаях оконная иерархия бывает очень сложной и вложенной, хотя с точки зрения логической структуры она намного проще. Это достаточно характерно для веб-приложений, когда таблицы используются просто для форматирования элементов на форме, также много схожих случаев в других приложениях. Соответственно, работать с такой сильно вложенной иерархией объектов достаточно утомительно, особенно, если очевидно, что некоторые окна являются просто промежуточными звеньями. Для целей оптимизации структуры оконной декларации можно в теге указывать составные пути к нужному объекту. Например, у нас есть декларация вида:
|
Code
|
[+] window MainWin wMain
[ ] tag "Main"
[ ]
[+] DialogBox dDialog
[ ] tag "Dialog"
[ ]
[+] TextField edtText
[ ] tag "Text"
|
Конструкция кода, которая вставит некоторый текст в текстовое поле в данном случае имеет вид:
|
Code
|
[ ] wMain.dDialog.edtText.sValue = "Some Text"
|
Допустим, из всей этой иерархии нам нужно только текстовое поле edtText, при этом не хочется при работе с данным объектом тянуть промежуточные звенья (тот же dDialog). Тогда иерархию можно упростить так:
|
Code
|
[+] window MainWin wMain
[ ] tag "Main"
[ ]
[+] TextField edtText
[ ] tag "[DialogBox]Dialog/Text"
|
и тогда в коде данному текстовому полю можно ввести текст вот таким образом:
|
Code
|
[ ] wMain.edtText.sValue = "Some Text"
|
Как правило, елементы управнения (типа кнопок, текстовых полей, чекбоксов и т.п.) не выносят на самый верхний уровень при описании. Вышеприведенной оптимизации структуры более чем достаточно, но в принципе вполне возможно и такое объявление:
|
Code
|
[+] window TextField edtText
[ ] tag "[MainWin]Main/[DialogBox]Dialog/Text"
|
и это нам позволит реализовать ввод текста в данное поле вот в такой форме:
|
Code
|
[ ] edtText.sValue = "Some Text"
|
Таким образом, в тегах можно указывать пути в оконной иерархии. При этом каждое звено пути отделяется символом "/", а также для каждого звена желательно явно указать класс.
По иерархии в тегах можно двигаться как в направлении увеличения вложенности, так и наоборот, вверх по иерархии. Для этого в качестве звена в строке пути тега нужно задать "..". В рассматриваемом в данный момент примере вернемся к исходной декларации, но слегка подкорректируем тег для диалога. Это выглядит вот так:
|
Code
|
[+] window MainWin wMain
[ ] tag "Main"
[ ]
[+] DialogBox dDialog
[ ] tag "Dialog/[TextField]Text/.."
[ ]
[+] TextField edtText
[ ] tag "Text"
|
В данном случае мы ничего не поменяли. Мы протянули путь к текстовому полю, а затем перешли на уровень вверх в иерархии объектов. Такую запись тега следует воспринимать как "диалоговое окно с тегом Dialog, у которого есть дочерний элемент - текстовое поле с тегом Text". Когда подобная запись будет полезной? Допустим, у нас есть несколько окон с одинаковыми тегами, но с разным содержимым. Соответственно, и в этом случае нам нужно искать окно с некоторым тегом, содержащее элемент определенного вида. Это опять же позволит логическую структуру окна отделить от физической, что тоже зачастую бывает полезно.
Создание оконных классов.
Когда уже есть достаточное количество описанных оконных деклараций, то вполне может возникнуть ситуация, когда есть несколько различных окон, у которых многие элементы, если не все являются одинаковыми. То есть наблюдается некоторый общий каркас. Подобное явление часто имеет место у веб-приложений, у них зачастую страницы имеют некоторый общий каркас (те же главные меню, шапки и т.п.). В таких случаях имеет смысл группировать общие части этих окон в некоторый оконный класс. Описание класса имеет вид:
winсlass <class_name> [: <parent_class_name>]
[[multi]tag <tag_definition>]
[parent <parent_window_name>]
[ Properties, Child controls , Methods]
Здесь:
- <class_name> - имя создаваемого класса
- <parent_class_name> - имя базового класса, чьи свойства, методы и дочерние объекты переносятся на создаваемый класс
- <tag_definition> - строка или набор строк, по которым данное окно находится Агентом
- <parent_window_name> - имя родительского окна
То есть структура отличается от описания окна только первой строкой.
Важным моментом для понимания является тот факт, что winclass несколько отличается от класса в его привычном понимании по аналогии с C++, Java, Perl и подобных объектно-ориентированных языков программирования. Winclass не является некоторым типом данных, это скорее тип окон, поэтому все инстансы winclass-a являются объектами типа WINDOW. Как следствие вышеуказанных особенностей, у winclass-a нет конструкторов (по-крайней мере в явном виде), деструкторов. |
Итак, как происходит группировка элементов в класс. Например, у нас есть 2 оконные декларации вида:
|
Code
|
[+] window MainWin wMain
[ ] tag "Main"
[ ]
[+] DialogBox dDialog
[ ] tag "Dialog"
[ ]
[+] TextField edtText
[ ] tag "Text"
[ ]
[+] Toolbar tbTools
[ ] tag "Tools"
[ ]
[+] window MainWin wAnotherMain
[ ] tag "Another Main"
[ ]
[+] DialogBox dDialog
[ ] tag "Dialog"
[ ]
[+] TextField edtText
[ ] tag "Text"
[ ]
[+] StatusBar sbStatus
[ ] tag "Status"
[ ]
|
Жирным шрифтом выделены общие составляющие. Как видно, оба окна одинаковых классов, а также содержат несколько общих элементов. На основе этих 2-х окон можно создать класс вида:
|
Code
|
[+] winclass MyMainWin : MainWin
[ ]
[+] DialogBox dDialog
[ ] tag "Dialog"
[ ]
[+] TextField edtText
[ ] tag "Text"
[ ]
|
После этого декларации окон могут быть приведены вот к такому виду:
|
Code
|
[+] window MyMainWin wMain
[ ] tag "Main"
[ ]
[+] Toolbar tbTools
[ ] tag "Tools"
[ ]
[+] window MyMainWin wAnotherMain
[ ] tag "Another Main"
[ ]
[+] StatusBar sbStatus
[ ] tag "Status"
|
Как видно запись самих окон несколько сократилась. Чем более мощный каркас мы вынесем в оконный класс, тем большую оптимизацию описания окон мы получим. Это имеет немаловажное значение для больших приложений, когда небольшие изменения интерфейса могут повлечь значительные корректировки фрейма. Также SilkTest плохо работает с файлами, у которых количество строк кода приближается к отметке 30 тысяч.
Использование переменных внутри класса, окна. Создание и использование методов.
Окна и оконные классы помимо структурированного описания оконных объектов являются еще и областями видимости. Это позволяет в описания окон или классов добавлять объявления переменных и методов. Добраться до них можно через конкретный инстанс класса. Например, переменную внутри класса можно объявить так:
|
Code
|
[-] window MainWin wMain
[ ] tag "Main"
[ ]
[ ] STRING sTest = "Test"
[ ] INTEGER iValue
|
То есть обычное объявление функций, с той разницей, что они располагаются внутри оконного объекта и обращение к ним осуществляется, как к элементу данного окна. В нашем примере это может быть реализовано, например, так:
|
Code
|
[ ] wMain.sTest = "Some line"
[ ] wMain.iValue = 0
[ ] STRING sValue = wMain.sTest
|
Аналогично с созданием методов. Они создаются по тому же принципу, что и функции за исключением того, что методы объявляются внутри окна или класса. Например:
|
Code
|
[-] window MainWin wMain
[ ] tag "Main"
[ ]
[ ] STRING sTest
[ ] INTEGER iValue
[ ]
[+] VOID Init()
[ ] sTest = "Test"
[ ] iValue = 0
|
Метод Init в коде может быть вызван так:
|
Code
|
[ ] wMain.Init()
|
По аналогии с С++ для окон и классов определено ключевое слово this, которое позволит явно указать, что обращаться надо именно к текущему инстансу окна, класса. то есть в предыдущем примере с инициализацией внутренних переменных можно явно указать, что мы инициализируем переменные этого же окна. Вот так это выглядит:
|
Code
|
[-] window MainWin wMain
[ ] tag "Main"
[ ]
[ ] STRING sTest
[ ] INTEGER iValue
[ ]
[+] VOID Init()
[ ] this.sTest = "Test"
[ ] this.iValue = 0
|
Если нужно, чтобы некоторый метод был доступен только в пределах некоторого окна или класса, то этот метод определяется внутри нужного объекта с ключевым словом private. Например:
|
Code
|
[-] window MainWin wMain
[ ] tag "Main"
[ ]
[ ] STRING sTest
[ ] INTEGER iValue
[ ]
[+] private VOID _init()
[ ] this.sTest = "Test"
[ ] this.iValue = 0
|
В случае такого описания конструкция вида:
|
Code
|
[ ] wMain._init()
|
уже должна выдать ошибку, хотя в некоторых версиях этого не происходит. Единственное ,приватный метод не виден в списке автозаполнения.
property
Каждый раз, когда необходимо изменить данные в элементе управления (ввести текст в поле ввода, установить или снять флажок с чекбокса, выбрать элемент из списка и т.д.) имеется выбор между использованием стандартных свойств или методов. Например, для ввода текста в поле ввода можно воспользоваться двумя стандартными средствами этого элемента управления:
- использовать метод SetText ()
- использовать свойство sValue
Например, следующие две строки выполнят одно и то же: занесут заданную строку "Text" в поле ввода Field, которое находится в окне Dialog:
- Dialog.Field.SetText ("Text")
- Dialog.Field.sValue = "Text"
Кроме того, можно воспользоваться общим для всех окон методом TypeKeys:
Dialog.Field.TypeKeys ("Text")
Dialog.Field.TypeKeys ("Text")
Точно также можно работать и с другими элементами управления. Например, для элемента RadioList можно использовать метод Select() или одно из свойств: sValue, iValue.
На самом деле встроенными являются лишь методы, а свойства (iValue, sValue и т.д.) определены средствами SilkTest-а в файле winclass.inc.
Точно так же можно описывать свои свойства, которые могут пригодиться в работе.
У свойств могут быть 2 метода: Set() и Get(). Первый предназначен для установки значения, второй - для его (значения) извлечения.
У свойств могут быть 2 метода: Set() и Get(). Первый предназначен для установки значения, второй - для его (значения) извлечения.
Например, имеем следующее описание объекта в диалоговом окне:
|
Code
|
[-] window DialogBox dDialog
[ ] tag "#1"
[-] TextField edtField
[ ] tag "#1"
|
Если мы хотим ввести какие-либо данные в это поле, мы можем воспользоваться одним из описанных выше способов. А можно немного сократить запись, используя свойства.
В качестве примера напишем свойство sField, при обращении к которому можно изменять и считывать значение из этого поля.
Выглядеть оно будет так:
В качестве примера напишем свойство sField, при обращении к которому можно изменять и считывать значение из этого поля.
Выглядеть оно будет так:
|
Code
|
[-] property sField
[-] STRING Get ()
[ ] return this.edtField.sValue
[-] VOID Set (STRING sValue)
[ ] this.edtField.sValue = sValue
|
Поместить объявление этого свойства необходимо на одном уровне с полем edtField.
Теперь работа с этим полем немного упростилась:
Теперь работа с этим полем немного упростилась:
- чтобы ввести значение, необходимо написать dDialog.sField = "Text" (сравните со стандартным dDialog.edtField.sValue = "Text" или dDialog.edtField.SetText("Text") )
- чтобы извлечь значение, необходимо написать STRING sData = dDialog.sField (сравните со стандартным STRING sData = dDialog.edtField.sValue или STRING sData = dDialog.edtField.GetText() ).
Это очень простой пример, однако могут встречаться и случаи сложнее.
Пример из практики: в приложении было текстовое поле с несколькими символами в нем, каждый из которых обозначал какую-то настройку. Каждый из символов влиял на функциональность всего приложения. Извлечь их было очень просто, стандартными методами SilkTest-а, а вот записать строку в это поле невозможно: строка была доступна только для чтения, а для изменения строки в этом поле служило диалоговое окно с кучей CheckBox'ов в нем. Если CheckBox включен - соответствующий символ появляется в строке. Это дополнительное окно открывалось по нажатию на кнопку рядом с полем ввода. Естественно, что каждый раз писать код для открытия этого окна и изменения значений CheckBox'ов не имеет смысла. Поэтому было описано новое свойство. Метод Get() для него был простой: использовался стандартный метод GetText(); а вот метод Set() был посложнее: он принимал строку символов, нажимал на кнопку, в диалоговом окне заполнял требуемые данные, после чего закрывал окно. Написать этот метод Set() было ненамного сложнее, чем написать эти же действия для какого-то конкретного случая, а экономия времени в дальнейшем велика: вместо кучи действий использовалось всего одно присваивание.
Некоторые особенности наследования оконных классов
Как уже было указано выше, оконные классы немного отличаются по своей сути от классов в С++, Java и т.п. Это накладывает свои особенности. Перегрузить метод оконного класса нельзя, но при этом методы могут быть переопределены в классах-наследниках и даже инстансах класса. Например, у нас есть такие объявления:
|
Code
|
[+] winclass BaseClass
[ ]
[+] VOID Method()
[ ] Print( "Base Class method" )
[ ]
[+] winclass DerivedClass : BaseClass
[ ]
[+] VOID Method()
[ ] Print( "Derived class method" )
[ ]
[+] window BaseClass wBaseWin
[ ]
[+] window DerivedClass wDerivedWin
[ ]
[ ]
[+] window DerivedClass wDerivedWinRedef
[ ]
[+] VOID Method()
[ ] Print( "Redefined Derived class method" )
|
Жирным шрифтом подсвечено 3 переопределения метода Method для классов BaseClass, DerivedClass и окна wDerivedWinRedef. Соответственно ,если мы напишем код вида:
|
Code
|
[ ] wBaseWin.Method()
[ ] wDerivedWin.Method()
[ ] wDerivedWinRedef.Method()
|
то вывод будет иметь вид:
[ ] Base Class method
[ ] Derived class method
[ ] Redefined Derived class method
То есть сработает ближайший переопределенный метод.
Аналогично методам срабатывают и переопределения таких атрибутов как tag и parent. То есть, если у некоторого окна не определен тег и не задано родительское окно, то эти атрибуты берутся у класса, к которому относится данное окно.
Часто могут возникнуть случаи, когда некоторый (даже стандартный) класс не содержит некоторого полезного функционала, применимого ко всем объектам данного класса. Например, у текстовых полей нет стандартной проверки на read-only. Безусловно, можно отнаследовать новый класс от данного стандартного и там определить все, что нужно. Тем не менее, в 4Test-е есть более удобная возможность для этого. Оконные класс можно пронаследовать сам от себя. Рассмотрим такой случай: у окон класса HtmlColumn первый текст в колонке воспринимается как имя колонки. Но есть случаи, когда колонки в таблице идут без заголовков, просто табулированное представление данных. В этом случае, если мы попытаемся извлечь содержимое данной колонки, но первого элемента там не окажется. Поэтому удобно определить некоторое свойство, которое могло бы позволить извлечь текст и из первой строки. Пример реализации этого:
|
Code
|
[-] winclass HtmlColumn : HtmlColumn
[ ]
[+] property lsValueEx
[+] LIST OF STRING Get()
[ ] LIST OF STRING lsData = {this.sCaption}
[ ] STRING sValue
[+] for each sValue in this.GetRowRangeText()
[ ] ListAppend( lsData , sValue)
[ ] return lsData
|
При такой записи свойство lsValueEx класса HtmlColumn будет доступно во всех файлах, которые подключают текущий файл, в котором переопределен данный класс. Данная возмо
Динамическое обращение к окнам
Часто могут возникать случаи, когда некоторые окна не имеют фиксированного тега, но есть некоторый алгоритм, по которому мы можем его получить, или количество таких элементов может быть произвольным. Например, ссылки на странице результатов поиска в Google могут иметь самые различные атрибуты в зависимости от текста страницы, которая была найдена, строки запроса и т.п. Также и количество этих объектов зависит от многих параметров. Такие объекты во фрейм заносить или где-то статически фиксировать бессмысленно. Но обращаться к ним как-то надо.
Решение данной проблемы заключается в следующем: если вызвать функцию или метод, имя которого совпадает с именем некоторого оконного класса и в качестве параметра передать некоторую строку, то эта функция или метод вернет окно-инстанс данного класса с тегом, указанным строкой-параметром. А теперь еще раз и уже на конкретном примере. Допустим, у нас есть оконная декларация вида:
|
Code
|
[+] window MainWin wMain
[ ] tag "Main"
[ ]
[+] DialogBox dDialog
[ ] tag "Dialog"
[ ]
[+] TextField edtText
[ ] tag "Text"
|
и допустим, в окне wMain.dDialog есть еще текстовый элемент, который принимает значение, которое мы ввели в поле wMain.dDialog.edtText. То есть мы знаем, что это некоторое окно класса StaticText, у которого тег примет значение, совпадающее с текстом, хранящимся в wMain.dDialog.edtText. Тогда этот текстовый элемент можно получить вот такой конструкцией:
|
Code
|
[ ] STRING sTag = wMain.dDialog.edtText.sValue
[ ] WINDOW wStatic = wMain.dDialog.StaticText( sTag )
|
или, если одной строкой:
|
Code
|
[ ] WINDOW wStatic = wMain.dDialog.StaticText( wMain.dDialog.edtText.sValue )
|
Другой достаточно распространенный пример: необходимо получить список всех окон некоторого заданного класса. Например, все текстовые поля класса TextField у некоторого окна wSomeWin. Для решения этой задачи стоит вспомнить про теги, которые основаны на индексе объекта (см. выше). По такому тегу можно найти все объекты. Как это делается:
|
Code
|
[ ] LIST OF WINDOW lwFields = {}
[ ] INTEGER i = 1
[ ]
[+] while( wSomeWin.TextField( "#{i}" ).Exists() )
[ ] ListAppend( lwFields , wSomeWin.TextField( "#{i}" ) )
[ ] i++
|
Более того, если вернуться к оконной декларации для предпоследнего примера, то обращение к окну wMain.dDialog.edtText может быть реализовано вот такими способами:
|
Code
|
[ ] WINDOW wWin
[ ]
[ ] wWin = wMain.dDialog.edtText
[ ] wWin = wMain.dDialog.TextField( "Text" )
[ ] wWin = wMain.DialogBox( "Dialog" ).TextField( "Text" )
[ ] wWin = MainWin( "Main" ).DialogBox( "Dialog" ).TextField( "Text" )
|
То есть само описание оконных деклараций - в принципе вещь необязательная, но все-таки рекомендуемая, так как при изменениях в интерфейсе пользователя лучше сделать правку в одном месте в оконной декларации, чем проходиться по всему коду и править все обращения к данному окну.
Маппинг классов

При этом следует обратить внимание на то, что ключевое слово WINDOW соответствует не только объявлению объекта окна, но это также и тип данных. То есть переменная типа WINDOW и объект-окно - это не одно и тоже, хотя бы потому, что значение переменной может быть изменено в ходе выполнения скрипта, чего нельзя сделать для объекта-окна. Поэтому, если за ключевым словом WINDOW следует имя оконного класса и затем имя, то это объявление окна данного класса. Иначе, идентификатор, следующий за данным ключевым словом, является именем переменной типа WINDOW.