Визуальный HTM- редактор своими руками
Визуальный 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  | 
Выделить блок  | 
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  | 
Доступные команды
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  | 
Снимает выделение.  | 
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
    

