Здравствуйте, дорогие друзья. Понадобилась мне недавно компонента визуального
html редактора. Сколько в internete не искал я информации по этому поводу - не
нашел. В смысле, не нашел приемлемого решения, ведь платить $19.99 за одну
компоненточку жалко. Поэтому сейчас я отмниму хлеб у некоторых
компоненто-писателей и расскажу вам, как можно сделать полноценный html редактор
своими руками, тем более, что для этого практически ничего не нежно.
Нам понадобится самая малость. В первую очередь - delphi 5-7 (у меня стоит
7-я версия, и весь код тестировался именно в этой версии). Такое ограничение
версий вызвано тем, что компонент twebbrowser впервые "прописался" на вкладке
internet именно в 5-й версии (в 4-й его надо было устанавливать как компонент
activex). Еще необходимо, чтобы в системе был установлен internet explorer 4 и
выше, по тем причинам, что именно его части являются основой webbrowser'a.
Сначала нам надо перевести webbrowser в режим редактирования. Для этого у
каждого документа (согласно объектной модели это document) существует свойство
designmode. Если установить его в 'on', то наша компонента автоматически
переключается в режим редактирования, а если установить его в 'off', то
компонент вернется в режим просмотра.
Проверим это! Создадим новую форму, разместим на ней компоненту twebbrowser и
несколько компонент tspeedbutton. Затем напишем такой код:
unit main;
interface
...
var
form1: tform1;
disp: idispatch;
editor: ihtmldocument2;
implementation
{$r *.dfm}
procedure tform1.webbrowser1documentcomplete(sender: tobject;
const pdisp: idispatch; var url: olevariant);
var
currentwb: iwebbrowser;
editor: ihtmldocument2;
begin
disp:=pdisp;
end;
procedure tform1.speedbutton1click(sender: tobject);
var
currentwb: iwebbrowser;
begin
currentwb := disp as iwebbrowser;
editor:=(currentwb.document as ihtmldocument2);
editor.designmode := 'on';
end;
procedure tform1.formcreate(sender: tobject);
begin
webbrowser1.navigate('about:<html><body></body></html>');
end;
Теперь по порядку о том, что мы написали. В событии oncreate формы мы
загружаем в браузер простую страницу (напомню, что протокол about: позволяет
загружать в браузер html строку). Это необходимо для того, чтобы в последующем
мы могли обращаться к документу. Сразу после этого будет вызван обработчик
события ondocumentcomplete. Но пока еще ничего не произошло. Внимательный
читатель мог обратить внимание, что для перевода браузера в режим редактирования
надо нажать кнопку 1. editor - это экземпляр нашего документа (document). Его
свойство designmode устанавливается в 'on'. Теперь наш редактор практически
готов. Он уже умеет править текст, копировать/вырезать/вставлять текст и
картинки, делать текст жирным/подчеркнутым/наклонным. Для этого есть
соответствующие комбинации клавиш.
Стандартые сочетания клавиш
ctrl + c |
Копировать |
ctrl + x |
Вырезать |
ctrl + v |
Вставить |
ctrl + b |
Жирный текст |
ctrl + i |
Наклонный текст |
ctrl + u |
Подчеркнутый текст |
ctrl + z |
Отменить |
ctrl + y |
Повторить |
ctrl + k |
Гиперссылка |
ctrl + f |
Найти |
ctrl + a |
Выделить всё |
ctrl + left-click |
Выделить блок |
"Это, конечно, хорошо, что есть горячие клавиши, но мне не хотелось бы все их
запоминать" - можете сказать вы. Хорошо. Тогда давайте разберем, как из delphi
заставить webbrowser выполнять все эти действия. Для этого есть метод
execcommand интерфейса ihtmltxtrange (он описан в модуле mshtml_tlb). Рассмотрим
простой пример.
procedure tform1.speedbutton2click(sender: tobject);
var
range: ihtmltxtrange;
begin
range:=(editor.selection.createrange as ihtmltxtrange);
range.execcommand('bold',false,emptyparam)
end;
Сначала в этой процедуре создается объект range. После этого вызывается метод
execcommand:
function execcommand(cmdid: widestring; showui: wordbool; value:
olevariant): wordbool;
cmdid – это строка идентификатор команды (в нашем примере 'bold'
заставляет редактор переключаться между жирным и обычным начертанием текста);
полный список команд смотри в приложении.
showui – show user interface - показывать интерфейс пользователя (если
таковой имеется, как правило это различные диалоговые окна). Если параметр равен
false, то команда выполняется без предупреждения.
value – содержит дополнительную информацию в зависимости от
команды.
Несколько слов об объекте range. Помимо уже знакомого нам execcommand этот
объект обладает еще рядом свойств и методов, некоторые из которых сейчас
рассмотрим.
text |
widestring |
Содержит текст выделения (без тегов html) |
htmltext |
widestring |
Полный текст выделения |
movestart(const unit_:widestring;
count:integer) |
procedure |
Перемещает начальную позицию выделения на count символов вправо
(если count<0, то влево), unit_-единицы измерения смещения (чаще
всего используется 'character': 1 символ). При этом конечная позиция
не смещается. |
movestart(const unit_:widestring;count:integer) |
procedure |
То же самое, только для конечной позиции выделения. |
pastehtml(const html: widestring); |
procedure |
Вставляет html-строку |
execcommandshowhelp(cmdid: widestring); |
function,
wordbool |
Отображает помощь по команде, указанной в cmdid |
Пожалуй, на сегодня всё. Об остальных объектах (картинки, таблицы, элементы
управления) поговорим в другой раз. Будут вопросы - пишите:
samum2000@mail15.com.
Приложение.
Доступные команды
backcolor |
Устанавливает или получает цвет фона текущего выделения.
value должно содержать имя цвета или его шеснадцитиричный rgb
эквивалент (например, #ffcc00). |
bold |
Переключает начертание текста текущего выделения между
полужирным и нормальным. |
copy |
Копирует выделение в буфер обмена |
createbookmark |
Получает имя якоря или создает его для текущего выделения.
value - строка, содержащая имя якоря. |
createlink |
Получает url ссылки или создает новую ссылку. Параметр value
должен содержать url. |
cut |
Вырезает текущее выделение в буфер обмена. |
delete |
Очищает текущее выделение (удаляет всё его содержимое). |
find |
Находит текст, заданный в параметре value в текущем
выделении. |
fontname |
Устанавливает шрифт для текущего выделения. value
содержит описание этого шрифта (как в теге font). |
fontsize |
Устанавливает размер шрифта. value - число от 1 до 7
включительно. |
forecolor |
Устанавливает цвет текста. value должно содержать имя
цвета или его шеснадцитиричный rgb эквивалент (например, #ffcc00) |
formatblock |
Устанавливает или получает форматирование текущего блока.
value может содержать теги-описатели. |
indent |
Увеличивает отступ выделенного текста на одну единицу приращения |
insertbutton |
Записывает идентификатор кнопки вместо текущего выделения.
value - строка, содержащая идентификатор кнопки. |
insertfieldset |
То же для поля ввода. |
inserthorizontalrule |
То же для горизонтальной полосы. |
insertiframe |
То же для встроеных фреймов (iframe). |
insertimage |
То же для изображений. |
insertinputbutton |
То же для кнопки. |
insertinputcheckbox |
То же для чекбоксов (checkbox). |
insertinputfileupload |
То же для элемента выбора файла. |
insertinputhidden |
То же для скрытого поля (hidden) |
insertinputimage |
То же для изображения. |
insertinputpassword |
То же для поля ввода пароля. |
insertinputradio |
То же для радио-кнопок (radio) |
insertinputreset |
То же для кнопки reset. |
insertinputsubmit |
То же для кнопки submit. |
insertinputtext |
То же для поля ввода текста. |
insertparagraph |
Вставляет новый раздел (абзац). |
insertorderedlist |
Переключает стиль текущего выделения между списком и простым
текстом. |
insertunorderedlist |
То же самое. |
insertselectdropdown |
Записывает элемент drop-down вместо текущего выделения. value
должно содержать идентификатор элемента. |
inserttextarea |
То же для элемента textarea. |
italic |
Переключает начертание текста текущего выделения между наклонным
и обычным. |
justifycenter |
Устанавливает выравнивание по центру для всего блока, в котором
расположено текущее выделение. |
justifyleft |
Устанавливает выравнивание по левому краю для всего блока, в
котором расположено текущее выделение. |
justifyright |
Устанавливает выравнивание по правому краю для всего блока, в
котором расположено текущее выделение. |
outdent |
Уменьшает отступ для всего блока, в котором расположено
выделение, на одну единицу. |
overwrite |
Переключается между режимами вставки текста и замены текста при
вводе. value: true - замена, false - вставка. |
paste |
Вставляет текст из буфера обмена вместо текущего выделения. |
refresh |
Обновляет текущий документ. |
removeformat |
Удаляет из текущего фрагмента все теги форматирования |
selectall |
Выделяет все содержимое документа. |
unbookmark |
Удаляет все закладки из текущего выделения. |
underline |
Переключает начертание текста текущего выделения между
подчеркнутым и обычным. |
unlink |
Удаляет все гиперссылки из текущего выделенного фрагмента. |
unselect |
Снимает выделение. |
Часть ii.
В прошлый раз речь шла о том, как работать с текстом в html
редакторе. В этот раз мы поговорим о том, как работать с другими объектами html
страниц - контролами. К ним относятся всевозможные элементы управления,
изображения, фреймы, таблицы.
Рассмотрим общий принцип работы с этими элементами. Как и в случае с текстом,
прежде всего надо создать объект-выделение (назовем его range):
range: ihtmlcontrolrange;
Интерфейс ihtmlcontrolrange предназначен специально для
выполнения различных операций с выделенными объектами страницы, однако,
его совершенно невозможно применять для работы с текстовым выделением - вы
получите исключительную ситуацию eintfcasterror с сообщением о том, что выбраннй
интерфейс не поддерживается (тоже самое будет, если использовать ihtmltxtrange для
работы с контролами). Чтобы избегать подобных ситуаций, в интерфейсе ihtmlselectionobject
введено поле type_: widestring. В зависимости от типа выделения оно будет
содержать 'control' или 'text' (если ничего не выделено, то это поле будет
содежать 'none'). Вот простой пример того, как можно вставить картинку в
определенное место документа (как открыть документ в режиме редактирования было
описано в первой статейке):
procedure tform1.speedbutton13click(sender: tobject);
var
ctrlrange: ihtmlcontrolrange;
textrange: ihtmltxtrange;
begin
if editor.selection.type_='control' then
begin
ctrlrange:=(editor.selection.createrange as ihtmlcontrolrange);
if not ctrlrange.querycommandenabled('insertimage') then
application.messagebox('not supported!','');
else
ctrlrange.execcommand('insertimage',false,'c:\my files\porshe1.jpg') end
else
begin
textrange:=(editor.selection.createrange as ihtmltxtrange);
textrange.execcommand('insertimage',false,'c:\my files\porshe1.jpg')
end;
end;
Обратите внимание на то, что когда веделен объект, мы
используем метод querycommandenabled чтобы убедиться, что данную комманду можно
выполнить над выделенным контролом. Это связано с тем, что, например, встроенный
фрейм нельзя заменить на картинку. На самом деле это проверка необязательная, но
я все же рекомендую её проводить во избежание неприятных последствий. Еще один
метод - querycommandsupported(cmdid: widestring): boolean позволяет выянить,
поддерживается ли данная комманда данным типом выделения. Такие же методы есть и
у интерфейса ihtmltxtrange, но в данном случае в них нет необходимости.
С таблицами дело обстоит гораздо сложнеее. Контролы типа
htmltable, htmlrow и htmlcell, согласно документации от microsoft, предназначены
для создания таблиц при формировании страницы на стороне сервера. Соответсвенно,
в нашем случае возникают некоторые трудности: в частности, как добавить
полученную таблицу в документ (во всяком случае, у меня ничего не вышло). Как
вариант я предлагаю следующее: создавать таблицу типа htmltable, работать с ней
так, как будто мы формируем документ на сервере, а затем, использовать свойство
outerhtml. Это поле содержит текстовое представление таблицы в формате html.
Рассмотрим подробнее этот способ на примере:
procedure tform1.speedbutton14click(sender: tobject);
var
table: htmltable;
textrange: ihtmltxtrange;
row: htmltablerow;
col: htmltablecol;
i: integer;
begin
if editor.selection.type_<>'control' then
begin
table:=(editor.createelement('table') as htmltable);
for i:=0 to 3 do
begin
row:=(table.insertrow(i) as disphtmltablerow);
col:=(row.insertcell(0) as disphtmltablecol);
col.width:='200';
col.style.bordercolor:='#ff0000';
col.innertext:='Ячейка #'+inttostr(i);
end;
table.style.bordercolor:='#00ff00';
textrange:=(editor.selection.createrange as ihtmltxtrange);
textrange.pastehtml(table.outerhtml);
end;
end;
На мой взгляд, этот пример достаточно информативен. Очевидное преимущество
использования объекта htmltable и сопутствующих ему объектов состоит в том, что
программисту не надо беспокоится о том, как описать на html то или иное свойство
таблицы, нет необходимости работать со строками, писать парсеры и т.п. - таблица
сама себя опишет. Однако, очевидным недостатком такого метода является то, что в
последствии невозможно будет обратиться к созданной таблице как к объекту, и
изменить её програмным методом (использование парсеров и интерпритаторов кода в
расчет брать не будем).
[Дополнение от 27.07.2004:
Я еще несколько раз прочитал msdn и нашел таки способ нормально работать с
таблицами и ячейками. Вот простой пример того, как можно заменить текст в уже
созданной таблице:
var
i, j: integer;
ovtable: olevariant;
t: htmltable;
begin
// В документе должна быть таблица, описанная примерно так:
//<table ... id="mytable">
ovtable := webbrowser1.oleobject.document.getelementsbyname('mytable').item(0);
//webbrowser1.oleobject.document.getelementsbyname('mytable') -
//это коллекция элементов (ведь несколько элементов могут иметь
//id равный "mytable"
for i := 0 to (ovtable.rows.length - 1) do
for j := 0 to (ovtable.rows.item(i).cells.length - 1) do
ovtable.rows.item(i).cells.item(j).innertext:='new text!';
end;
То есть теперь у нас есть возможность как получать данные из таблицы, так и
заносить их туда в любой момент времени. Все свойства соответствуют свойствам
dom. Остается только сказать, что таким образом можно работать и с формами, и с
изображениями, в общем со всем, что поддерживается в Объектной модели докумета (dom).]
Если вы знаете более изящный способ работы с таблицами, или можете чем-то
дополнить изложенное выше, очень прошу вас написать мне на e-mail :
samum2000@mail15.com
|