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

== Часть I ==
Здравствуйте, дорогие друзья. Понадобилась мне недавно компонента визуального 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
: Полный текст выделения
procedure moveStart(const unit_: WideString; count: Integer);
: Перемещает начальную позицию выделения на count символов вправо (если count<0, то влево), unit_-единицы измерения смещения (чаще всего используется 'character': 1 символ). При этом конечная позиция не смещается.
procedure moveStart(const unit_: WideString; count: Integer);
: То же самое, только для конечной позиции выделения.
procedure PasteHTML(const html: WideString);
: Вставляет HTML-строку
function execCommandShowHelp(cmdID: WideString): 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
 // В документе должна быть таблица, описанная примерно так:
 // 
 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
Source: www.samum2000.narod.ru
ID: 03541
Category:Связь
Category:Web