Поиск по блогу

четверг, 3 сентября 2009 г.

Работа с формами в TWebBrowser

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

Основные вопросы, которые будут освещены:

Получение количества форм на странице
Обращение к форме по порядковому номеру
Получение имени формы
Получение доступа к форме по имени
Получение списка имен всех элементов формы
Получение value элемента формы (по name элемента)
Установка value элемента формы (по name элемента)
Сабмит формы


В листинге примеров, приведенных в этой статье, компонент TWebBrowser, в который загружаются страницы, на форме называется WebBrowser. Для работы с интерфейсами, приведенными в листингах, подключите библиотеку MSHTML.


Получение количества форм на странице

function NumberOfForms(document: IHTMLDocument2): integer;
var
forms: IHTMLElementCollection;
begin
forms := document.Forms as IHTMLElementCollection;
result := forms.Length;
end;


Пример вызова функции:

procedure TMyForm.Button1Click(Sender: TObject);
var
nForms: integer;
begin
nForms := NumberOfForms(WebBrowser.Document as IHTMLDocument2);
ShowMessage('Form count: ' + IntToStr(nForms));
end;

_____

Обращение к форме по порядковому номеру

На странице может быть расположено более, чем одна форма. Эта функция возвращает указатель на форму (элемент типа IHTMLFormElement). Нумерация начинается с 0, как обычно :)

function GetFormByNumber(document: IHTMLDocument2;
formNumber: integer): IHTMLFormElement;
var
forms: IHTMLElementCollection;
begin
forms := document.Forms as IHTMLElementCollection;
if formNumber < forms.Length then
result := forms.Item(formNumber,'') as IHTMLFormElement
else
result := nil;
end;


Пример вызова смотрите ниже.

_____

Получение имени формы

Получить имя формы можно так:

var
firstForm: IHTMLFormElement;
document: IHTMLDocument2;
begin
document := WebBrowser.Document as IHTMLDocument2;
firstForm := GetFormByNumber(document,0);
if Assigned(firstForm) then
ShowMessage('Name первой формы ' + firstForm.Name)
else
ShowMessage('На этой странице нет форм');

_____

Получение доступа к форме по имени

Получить доступ к форме можно не только по порядковому номеру, но и по name-у, если у этой формы есть имя.

function GetFormByName(document: IHTMLDocument2;
const formName: string): IHTMLFormElement;
var
forms: IHTMLElementCollection;
begin
forms := document.Forms as IHTMLElementCollection;
result := forms.Item(formName,'') as IHTMLFormElement
end;


Функция вернет nil, если на странице нет формы с таким именем.

_____

Получение списка имен всех элементов формы

function GetFormFieldNames(fromForm: IHTMLFormElement): TStringList;
var
index: integer;
field: IHTMLElement;
input: IHTMLInputElement;
select: IHTMLSelectElement;
text: IHTMLTextAreaElement;
begin
result := TStringList.Create;
for index := 0 to fromForm.length do
begin
field := fromForm.Item(index,'') as IHTMLElement;
if Assigned(field) then
begin
if field.tagName = 'INPUT' then
begin
// поля Input
input := field as IHTMLInputElement;
result.Add(input.name);
end
else if field.tagName = 'SELECT' then
begin
// поля Select
select := field as IHTMLSelectElement;
result.Add(select.name);
end
else if field.tagName = 'TEXTAREA' then
begin
// поля TextArea
text := field as IHTMLTextAreaElement;
result.Add(text.name);
end;
end;
end;
end;


Пример вызова:

procedure TMyForm.Button1Click(Sender: TObject);
var
document: IHTMLDocument2;
theForm: IHTMLFormElement;
index: integer;
begin
document := TWebBrowser.Document as IHTMLDocument2;
theForm := GetFormByNumber(WebBrowser.Document as IHTMLDocument2,0);
fields := GetFormFieldNames(theForm);

for index := 0 to fields.count-1 do
ShowMessage('Поле ' + IntToStr(index) + ' называется ' + fields[index]);
end;

_____

Получение value элемента формы (по name элемента)

Получить значение поля формы, если знаешь его name, можно следующим образом:

function GetFieldValue(fromForm: IHTMLFormElement;
const fieldName: string): string;
var
field: IHTMLElement;
inputField: IHTMLInputElement;
selectField: IHTMLSelectElement;
textField: IHTMLTextAreaElement;
begin
field := fromForm.Item(fieldName,'') as IHTMLElement;
if not Assigned(field) then
result := ''
else if field.tagName = 'INPUT' then
begin
inputField := field as IHTMLInputElement;
if (inputField.type_ <> 'radio') and
(inputField.type_ <> 'checkbox')
then
result := inputField.value
else if inputField.checked then
result := 'checked'
else
result := 'unchecked';
end
else if field.tagName = 'SELECT' then
begin
selectField := field as IHTMLSelectElement;
result := selectField.value
end
else if field.tagName = 'TEXTAREA' then
begin
textField := field as IHTMLTextAreaElement;
result := textField.value;
end;
end;


Пример вызова:

procedure TMyForm.Button1Click(Sender: TObject);
var
document: IHTMLDocument2;
theForm: IHTMLFormElement;
index: integer;
begin
document := TWebBrowser.Document as IHTMLDocument2;
theForm := GetFormByNumber(WebBrowser.Document as IHTMLDocument2,0);
ShowMessage('Поле "name" имеет значение ' + GetFieldValue(theForm,'name'));


Функция "GetFieldValue" такая объемная, так как проверяет принадлежность поля к определенному типу перед тем как узнать его значение. Если вы точно знаете тип поля (radio button, check box, text box и т.д.), вы можете ее существенно сократить для конкретного случая применения.

Например, вы стопроцентно уверены, что поле, значение которого вам надо получить, типа input (то есть в коде HTML оно пределено как "input"), тогда вы с легкостью можете использовать сокращенный вариант функции:

function GetInputField(fromForm: IHTMLFormElement;
const inputName: string;
const instance: integer=0): HTMLInputElement;
var
field: IHTMLElement;
begin
field := fromForm.Item(inputName,instance) as IHTMLElement;
if Assigned(field) then
begin
if field.tagName = 'INPUT' then
begin
result := field as HTMLInputElement;
exit;
end;
end;
result := nil;
end;

_____

Установка value элемента формы (по name элемента)

Установка значения поля по имени:

procedure SetFieldValue(theForm: IHTMLFormElement;
const fieldName: string; const newValue: string;
const instance: integer=0);
var
field: IHTMLElement;
inputField: IHTMLInputElement;
selectField: IHTMLSelectElement;
textField: IHTMLTextAreaElement;
begin
field := theForm.Item(fieldName,instance) as IHTMLElement;
if Assigned(field) then
begin
if field.tagName = 'INPUT' then
begin
inputField := field as IHTMLInputElement;
if (inputField.type_ <> 'radio') and
(inputField.type_ <> 'checkbox')
then
inputField.value := newValue
else
inputField.checked := (newValue = 'checked');
end
else if field.tagName = 'SELECT' then
begin
selectField := field as IHTMLSelectElement;
selectField.value := newValue;
end
else if field.tagName = 'TEXTAREA' then
begin
textField := field as IHTMLTextAreaElement;
textField.value := newValue;
end;
end;
end;


Пример вызова:

procedure TMyForm.Button1Click(Sender: TObject);
var
document: IHTMLDocument2;
theForm: IHTMLFormElement;
index: integer;
begin
document := TWebBrowser.Document as IHTMLDocument2;
theForm := GetFormByNumber(WebBrowser.Document as IHTMLDocument2,0);
SetFieldValue(theForm,'name','Brian Cryer');


_____

Сабмит формы

Вышеописанные функции демонстрируют возможности работы с элементами формы, установку и чтение их значений. После подобных "манипуляций" необходимо отправить данные формы (submit). Сделать это просто:

theForm.submit;


Пример:

procedure TMyForm.Button1Click(Sender: TObject);
var
document: IHTMLDocument2;
theForm: IHTMLFormElement;
index: integer;
begin
document := TWebBrowser.Document as IHTMLDocument2;
theForm := GetFormByNumber(document,0);
SetFieldValue(theForm,'name','Brian Cryer');
theForm.submit;



Чтобы быть в курсе обновлений блога, можно подписаться на RSS.

Статьи схожей тематики:



14 комментариев:

  1. Подскажите, пожалуйста, какой модуль надо подключить, чтобы использовать IHTMLDocument2?

    ОтветитьУдалить
  2. Здравствуйте.
    А нет того же но в сях :)
    Или ссылки, где в сях.
    хотя и не проблема с pascal на c

    ОтветитьУдалить
  3. Владимир, у меня нет материала. Но думаю, что сложностей с переводом возникнуть не должно :)

    ОтветитьУдалить
  4. Подскажите, что за "fields" у вас в коде? у меня делфи 6 его не определяет :)

    ОтветитьУдалить
  5. Спасибо за полезную информацию.
    Как раз взялся за программирование, чтобы избавиться от рутинных операций в интернете.
    Может вопрос не совсем по теме статьи, но по теме заголовка точно.
    Есть какая-то информация, примеры решений, какие-то компоненты, которые позволяют не автоматически, а полуавтоматически заполнять формы?
    Хотелось бы, чтобы как и в большеносые готовых, платных программ при открытии страницы с формой возле каждой появлялся выпадающий список, в списке было названия значений, при выборе значения, например «заголовок» в поле рядом вводился бы заголовок, который хранится не важно где, к примеру в текстовом файле.
    Как понимаю нужно использовать скрипты, отлавливать значение передаваемое скриптом, чтобы считать текст из файла, и так далее.

    ОтветитьУдалить
  6. Вадим, таких готовых компонентов нет :)

    ОтветитьУдалить
  7. Так и думал, что нет, потому что задача специфична.
    Возник еще один вопрос, не знаю, в тему ли.
    На этой странице описывается работа с TwebBrowser, а какое-то значительное в плане получения числового результата есть, от использования idHTTP и обработке содержимого строковыми функциями. Ведь по идее можно так же в тексте страницы подсчитать число форм, число полей и так далее. Понятно что статья про работу с TwebBrowser, но очень интересно понять, имеет ли этот способ какое-то преимущество перед остальными, какое, и когда это проявляется? По идее ведь, если Ajax не вынесен в отдельный скрипт из тела страницы, то строковыми функциями можно многое узнать не исполняя скрипта. Ведь Ajax в большеносые, если это не JQuery лишь управляет видимостью CSS некоторых контейнеров. И строковыми функциями можно видеть то, что не видно.

    ОтветитьУдалить
  8. По-моему, веббраузер логичнее применять для страницы с аджаксом, чем просто пытаться его как-то проанализировать.
    В общем, применение будет зависеть от задачи... Например, я использовала веббраузер для парсинга интернет-магазина, в котором надо было делать каскадный выбор в 4 комбобоксах: пока в одном ничего не выбрано, следующий был неактивен. После выбора запускался скрипт, который "заполнял" второй комбобокс и т.д. (скрин есть в статье про вызов JavaScript).

    ОтветитьУдалить
  9. Спасибо оч помогло!
    Подскажите пожалуйста, нужно нажать 3 кнопки подряд при помощи submit, как дождаться загрузку страницы после отправки первого submit. Проверка ReadyState ничего не дает.

    ОтветитьУдалить
  10. Даже не знаю, как это сделать по-правильному, универсально... Передо мной как-то вставала похожая задача, но что-то я уже не помню, на чем в итоге остановилась.

    ОтветитьУдалить
  11. Опыта работы с TWebBrowser немного, но я использую такую конструкцию для ожидания окончания работы:

    while WebBrowser.Busy do
    begin
    Application.ProcessMessages;
    sleep(200);
    end;

    ОтветитьУдалить
  12. Большое спасибо Вам за статью, она оказалась целиком в тему и очень помогла.

    ОтветитьУдалить

Комментарии модерируются, вопросы не по теме удаляются, троллинг тоже.

К сожалению, у меня нет столько свободного времени, чтобы отвечать на все частные вопросы, так что, может, свой вопрос лучше задать на каком-нибудь форуме?

Поделиться