Форум программистов, компьютерный форум, киберфорум
cotseec
Войти
Регистрация
Восстановить пароль
Различные приемы программирования на Delphi, применяемые мной на практике.
Рейтинг: 5.00. Голосов: 2.

Использование Text Object Model (TOM) в Delphi.

Запись от cotseec размещена 20.01.2016 в 22:51

TOM, как гласит писание, есть ничто иное, как набор интерфейсов для манипуляций с текстом (разбиение на абзацы, установка отступов, плотность текста, окраска, подчеркивание и т.п., все то, что имеется в Microsoft Word) в объекте класса RichEdit. Весь набор интерфейсов не имплементируется приложением (согласно писания, но почему бы и не поэспериментировать...)

Основной объект (объект верхнего уровня в иерархии модели интерфейсов ТОМ) является интерфейс ITextDocument, он позволяет манипулировать с сущностью всего документа (создать, загрузить из файла, сохранить в файл, установить табуляцию по умолчанию) , а также получить доступ к объектам нижестоящих интерфейсов (интерфейс истории изменений, интерфейс выделения, интерфейс абзаца, интерфейс диапазона и интерфейс шрифта - немного вольный перевод названий интерфейсов, отражающий их назначение). Для получения доступа к объектам нижестоящих интерфейсов используются соответствующие методы ITextDocument.

Интерфейс истории изменений ITextStoryRanges обеспечивает доступ к перечислителю истории изменений (возможно получить общее количество изменений и доступ к конкретному изменению) - в Microsoft Word операции Undo и Redo (точнее, сами операции Undo и Redo выполняет ITextDocument, а ITextStoryRanges позволяет обратиться к списку измений для его просмотра).

Интерфейс диапазона ITextRange позволяет выбрать некоторый произвольный диапазон текста в документе для его изменения (фактически для того, что делает ITextDocument за исключением создания/открытия/сохранения документа) и предоставляет доступ к интерфейсу абзаца.

Интерфейс выделения ITextSelection (если в Microsoft Word выделить какой-либо текст/объект мышкой, то получается именно этот интерфейс), позволяет получить выделенный текст как таковой, перемещать курсор согласно горячим клавишам (влево/вправо, в нчало конец выделенного диапазона и т.п. - эмулировать ввод данных с клавиатуры) и тип выделенного объекта (текст/картинка/графический примитив), как гласит писание - это пользовательско ориентированные методы интерфейса диапазона (ITextRange).

Интерфейс абзаца ITextPara (как я понимаю в MSDN абзац именуют параграфом) обеспечивает доступ к элементам форматирования абзаца и интерфейсу шрифта.

Интерфейс шрифта ITextFont является самым нижним в иерархии TOM и обеспечивает доступ к операциям, касающимся шрифта некоторого текста (размер, плотность, цвет, анимацию, подчеркивание и т.п.).

Чтобы как-то понимать что за чем и откуда можно представить работу в Microsoft Word: сначала необходимо получить доступ к какому-либо документу (создав или загрузив его) - сам документ и методы его создания обеспечивает ITextDocument, все, что можно сделать непосредственно с документом является методами ITextDocument. Для изменения каких-либо аттрибутов текста (или иного объекта) его надо выделить - получаем ITextSelection и ITextRange, далее в выделенном тексте устанавливаем атрибуты абзаца - ITextPara (выделенный текст содержит только один объект абзаца, если визуально это несколько абзацев, то изменения коснуться всех выделенных абзацев) и в абзаце атрибуты шрифта - ITextFont.

Теперь непосредственно получение доступа к указанным интерфейсам TOM и работа с ними.
Изначально, как и при работе с любыми внешними интерфейсами, необходимо получить описание типов (Type LiBrary - Delphi генерирует файл *****_TLB.pas). Как гласит MSDN для работы с интерфейсами требуется библиотека msftedit.dll, вероятно из нее можно и получить требуемое описание, что было бы очень даже великолепно, учитывая, что эта же библиотека требуется интерфейсам оконного RichEdit'a (ITextHost и ITextServices), позволяющим, помимо всего прочего, обеспечить рисование на его поверхности. Но то ли лыжи не едут, то ли еще чего, но мне не удалось получить описание из указанной dll - ошибка обращения к OLE - с которой на тот момент времени и желания разбираться не было. В итоге требуемое описание было получено из библиотеки RICHEDIT20.dll (файл описания типов приложен вместе с примером), т.е. описание версии RichEdit'а 2.0 или 3.0.

С прелюдией закончено, приступим к процессу.
Исходные данные: визуальные - форма, на которой расположен TRichEdit, в нем набран некоторый текст; невизуальные - к проекту подключен файл описания типов (в uses добавлено соответствующее пространство имен, в данном случае tom_TLB) и открыто писание на разделе "About Text Object Model"; для простоты понимания в примере имена визуальных объектов оставлены по умолчанию.
Первое, с чего необходимо начать - это получить доступ к ITextDocument конкретного RichEdit'а. Как гласит писание для этого необходимо запросить IRichEditOle (также заслуживающем некоторого внимания), за неимением описания этого интерфейса и отсутствием текущей необходимости непосредственно с ним работать использую родоначальника всех интерфейсов IUnknown (или IInterface). IRichEditOle получается посылкой RichEdit'у сообщения EM_GETOLEINTERFACE с адресом в lParam области памяти, куда будет записан адрес IRichEditOle, который после использования необходимо самостоятельно освободить. С EM_GETOLEINTERFACE также вышла некоторая засада - в имеющихся у меня хейдерах значение этой константы я не нашел, поэтому пришлось объявить самостоятельно в проекте. Далее запрашиваем у полученного IRichEditOle ITextDocument (GUID которого имеется в файле описания типов) и начинаем работать. Дальнейшее взаимодействие с интерфейсами не представляет особого труда, главное не забывать освобождать (Release) занятые интерфейсы при окончании работы с ними и в порядке, обратном их вызову ("и последние станут первыми" (С)).
Пример кода, позволяющий окрасить фон какой-либо строки и подчеркнуть строку, с некоторыми пояснениями:

Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
const
  LINE_NUMBER_COLOR = 3; // номер строки, которую окрашиваем (начиная с 1)
  LINE_COLOR = $1E8EF7; // цвет строки
  LINE_NUMBER_UNDERLINE = 2; // номер строки, которую подчеркиваем
  LINE_UNDERLINE = tomDotted + $10; // тип подчеркивания: вид подчеркивания + цвет подчеркивания (штрихи синего цвета, цвета чередуются кратно $0F)
  
  EM_GETOLEINTERFACE = $400 + 60; // идентификатор EM_GETOLEINTERFACE
var
  REOle: IUnknown;
  TOM: ITextDocument;
  TOMRange: ITextRange;
  TOMFont: ITextFont;
begin
// заполняем ричэдит неким случайным текстом
  RichEdit1.Lines.Add('nslvnsldnvjklsdnvkljsdfnvkljsdfnvkljsdfnklvnjsdfklvjnsdlfjkvnlsdfjkvnklsdfjvnklsdfjnvkljsdfvnsdjklfvnklsdf' +
                      'sdcsdnsdlkvjnsdjklvnklsdfjvnklsdfjvnjklsdfvjklsdfnvkljsdfnvkljsdfnkljvnjklsdfnvkljnsdfvjklnsdfkljvnsdfjklvnk' +
                      'dsjfklsdajfklasdjfkl'#13'fasdfasdfasdfadsfasdf'#13'3453456345234jknh3kl4j5234kljb5jklb5l2hb5jklkbhkl2345' +
                      '3458902384yu9hj pf02n8990348cun90234u8 990yr90 c3h90rc34j90c8u3'); 
 
// иннициализируем переменные
  REOle := nil;
  TOM := nil;
  TOMRange := nil;
  TOMFont := nil;
  
// получаем IRichEditOle
  if BOOL(SendMessage(RichEdit1.Handle, EM_GETOLEINTERFACE, 0, integer(@REOle))) then 
  begin
    if Succeeded(REOle.QueryInterface(IID_ITextDocument, TOM)) then // запрашиваем интерфейс ITextDocument
    begin
{получаем диапазон (БЕЗ проверки на выход за пределы количества строк), если значение LINE_NUMBER превышает количество строк,
то ничего небудет окрашено}
      TOMRange := TOM.Range(SendMessage(RichEdit1.Handle, EM_LINEINDEX, LINE_NUMBER_COLOR - 1, 0),
                            SendMessage(RichEdit1.Handle, EM_LINEINDEX, LINE_NUMBER_COLOR, 0));
                            
// получаем шрифт диапазона
      if Assigned(TOMRange) then
         TOMFont := TOMRange.Font;
         
// устанавливаем BackColor шрифта (цвет строки)
      if Assigned(TOMFont) then
         TOMFont.BackColor := LINE_COLOR; 
 
      TOMFont := nil;
      TOMRange := nil;
 
      TOMRange := TOM.Range(SendMessage(RichEdit1.Handle, EM_LINEINDEX, LINE_NUMBER_UNDERLINE - 1, 0),
                            SendMessage(RichEdit1.Handle, EM_LINEINDEX, LINE_NUMBER_UNDERLINE, 0));
 
      if Assigned(TOMRange) then
         TOMFont := TOMRange.Font;
         
// подчеркиваем строку
      if Assigned(TOMFont) then
         TOMFont.Underline := LINE_UNDERLINE; 
    end;
  end;
  
// освобождаем интерфейсы в порядке, обратном их получению
  TOMFont := nil;
  TOMRange := nil;
  TOM := nil;
  REOle := nil;
вот, что получается:
Нажмите на изображение для увеличения
Название: View.png
Просмотров: 344
Размер:	28.8 Кб
ID:	3574


Пример, писаный в Delphi7 Example.zip

З.Ы. помимо использования TOM некоторые аналогичные результаты позволяет получить посылка RichEdit'у соответствующих сообщений (EM_CANREDO, EM_REDO, EM_SETCHARFORMAT, EM_SETPARAFORMAT и т.п.)

З.Ы.Ы. для обработки реакции RichEdit'а на действия пользователя, как и для любого контрола Windows, существует событийная модель, однако для ее инициализации необходимо установить маску событий посылкой сообщения EM_SETEVENTMASK, которая по умолчанию равна ENM_NONE (нет трансляции событий)

З.Ы.Ы.Ы. описано при использовании Win7, в Win 8 добавлена TOM Version 2
Размещено в Без категории
Показов 3919 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru