Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Визуальный HTM- редактор своими руками

01.01.2007

Визуальный HTML редактор своими руками

Здравствуйте, дорогие друзья. Понадобилась мне недавно компонента визуального 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 ... class="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
 

Источник: www.samum2000.narod.ru