Как стать автором
Обновить

Delphi: Простая задачка RichEdit которую нужно решить до того как вы решите его использовать

Время на прочтение3 мин
Количество просмотров16K
Потихоньку начал писать собственный редактор для написания программ под ARM на языке ассемблера, и решил начать с самого простого: сделать разбор текста при редактировании.
И тут я нашел небольшие такие грабельки :-)

Итак вопрос:
Есть редактор RichEdit в который мы ввели текст:

Курсор стоит вначале строки перед "9", RichText.SelStart := 12

Как в программе узнать символ на котором стоит курсор?

Если ваш опыт подсказывает конструкцию наподобие:
   with RichEdit do
        textChar:=Text [SelStart]; 

— то ваш опыт не верен!

И если вам интересно — то правильный ответ можно увидеть под катом…


Итак обещанный правильный ответ:
для того чтобы прочитать символ на котором стоит курсор — нужно читать символ в RichEdit.Text с номером 16!
   with RichEdit do
        textChar:=Text [16]; 


Число взятое непонятно откуда? — ну не скажите! все имеет логическое объяснение, просто нужно один раз разобраться!

Первое.
Нумерация символов в свойстве RichEdit.SelStart идет с нуля. То есть символ "0" когда перед ним стоит курсор будет иметь индекс RichEdit.SelStart = 0
Согласитесь — это очень просто!
Одновременно, для того чтобы прочитать этот символ нам нужно читать символ в строке в позиции = 1:
      textChar := RichEdit.Text[1]

А все потому, что свойство RichEdit.Text имеет тип String, а в типе String — нулевой символ хранит длину строки, и только после этого идет собственно текст: таким образом первый символ в строке (у нас это "0") имеет номер "1".

Запоминайте!
Нумерация символов в свойстве RichEdit.SelStart начинается с нуля (первый символ имеет номер =0), а нумерация символов в строке (свойство RichEdit.Text, тип String) идет с единицы (первый символ имеет номер =1)


Второе.
Документация нам указывает, что свойство SelStart указывает на номер символа в тексте. И тут необходимо вспомнить про символ генерируемый при переводе строки (нажатии на клавишу «Enter») — в текст добавляются 2 байта (символа) идущих подряд: 0x0D и 0x0A.
Ниже описание данное этим символам в Википедии:
Возврат каретки (англ. carriage return, CR) — управляющий символ ASCII (0x0D, 1310, '\r'), при выводе которого курсор перемещается к левому краю поля, не переходя на другую строку. Этот управляющий символ вводится клавишей «Enter». Будучи записан в файле, в отдельности рассматривается как перевод строки только в системах Macintosh.

Подача строки (от англ. line feed, LF — «подача [бумаги] на строку») — управляющий символ ASCII (0x0A, 10 в десятичной системе счисления, '\n'), при выводе которого курсор перемещается на следующую строку. В случае принтера это означает сдвиг бумаги вверх, в случае дисплея — сдвиг курсора вниз, если ещё осталось место, и прокрутку текста вверх, если курсор находился на нижней строке. Возвращается ли при этом курсор к левому краю или нет, зависит от реализации.


Так вот SelStart считает эти два байта (возврат каретки — 0х0D, перевод строки — 0x0A ) — одним символом!

Таким образом, символ "4" в нашем редакторе будет иметь номер SelStart = 5!


А вот в свойстве RichEdit.Text у него будет номер 7!!!

Для того чтобы все стало совсем понятным я нарисовал табличку (первый столбец — индекс, второй SelStart — соответствие индекса SelStart символу, третий CharPos — соответствие Text[индекс] символу):


Такие вот особенности адресации символов у редактора RichEdit.
Возможно именно из-за этого, многие, начиная делать подсветку кода или разбор строк в RichEdit — вскоре забрасывают это занятие и смотрят на различные сторонние компоненты…

С другой стороны, нам, разобрав то, как происходит адресация символов в RichEdit, было бы глупо не перевести эти знания в практическую плоскость!

Буквально за 5 минут я написал две функции которые преобразуют SelStart в CharPos и обратно:
// Определим номер символа в (.Text) по позиции курсора (SelStart)
function SelStartToCPOS(worktext:string; SelStart:integer):integer;
var
  i:integer;
begin
   result:=1;
   i:=SelStart;
   while i>0 do
      begin
         if (worktext[result]>=' ') or (worktext[result]=#$09) then result:=result+1
            else  result:=result+2;
         i:=i-1;
      end;
end;

// Определим позицию символа (SelStart) по номеру символа в строке (.Text)
function CPOSToSelStart(worktext:string; cpos:integer):integer;
var
  i:integer;
begin
   result:=0;
   i:=1;
   while i<>cpos do
      begin
         if (i<length(worktext)) and ((worktext[i]<>#$09) and (worktext[i]<' ')) then i:=i+1;
         i:=i+1;
         result:=result+1;
      end;
end;


Теперь, при помощи этих двух процедур, вы сможете узнать на каком символе в тексте редактора RichEdit стоит курсор:
    with RichEdit do 
    Ch:= Text [ SelStartToCPOS (Text, SelStart) ];


И наоборот, зная позицию символа в тексте RichEdit.Text сможете произвести изменение его атрибутов:
   // в переменной cpos - индекс символа в строке редактора
    with RichEdit do 
         begin
              SelStart := CPOSToSelStart(Text, cpos);
              SelLength := 1;
              SelAttributes.Color:=clBlue;
         end;


PostScriptum's:
  1. Процедуры которые я привел — несомненно нуждаются в оптимизации, но я уверен — с этим вы справитесь :-)
  2. Если вы считаете, что лучше использовать другой компонент вместо RichEdit — то я не буду с вами спорить, я всего лишь люблю разобраться до конца в том, что работает не так, как я ожидаю

Теги:
Хабы:
Всего голосов 34: ↑20 и ↓14+6
Комментарии25

Публикации