Кладовая программиста. Текстовое совершенство. Текстовый редактор "Супер" v2.0
Нелегко быть творцом программ. В прошлый раз мы разобрали, как сделать настоящий текстовый редактор. До уровня Word он не дотягивает, но с " Блокнотом " может конкурировать на равных. Сегодня мы усовершенствуем утилиту и доведем ее до уровня WordPad. Как показывает практика, самая удачная архитектура для любой программы — модульная. Вначале мы создаем некий элементарный каркас, который выполняет основные функции программы, а потом добавляем новые возможности. С текстовым редактором “Супер” (а может, вы его назвали по-другому?) мы поступим так же, проапгрейдив его до версии 2.0. Профессиональные программисты обычно прикладывают к своим программам файл history.txt , в котором описывают, какие новые возможности появились в программе с прошлой версии. В свой history.txt вы сегодня сможете записать следующие возможности: поиск, полноценное форматирование по стилю, шрифту, кеглю, гарнитуре, цвету шрифта, поддержка формата rtf, возможность форматировать текст по краям или по центру, функция автопереноса текста по границе окна и кое-что еще. Если вы по каким-то причинам не читали предыдущий номер “Игромании” и поэтому не воспользовались нашими рекомендациями по созданию первой версии редактора, не спешите отчаиваться, проклинать судьбу и переворачивать страницу журнала. На нашем диске вы найдете исходники не только финальной версии программы, но и промежуточную версию редактора. Открывайте ее и продолжайте творить вместе с нами. Нарисуй мне дом… Начнем с форматирования. Сделаем в меню рабочего окна пункт Форматирование и добавим в него несколько подпунктов. Для этого дважды кликните по иконке MainMenu на форме.
Всего несколько страниц кода — и удобный текстовый редактор к вашим услугам. Если бы вы писали такой же редактор на MSVC++, код бы занял раз в пять больше места.
Появится окошко редактора менюшек. В пункте Форматирование создайте подпункт Выровнять. Он будет отвечать за выравнивание текста. Можно сделать отдельное окошко, где пользователь будет выбирать направление выравнивания. Но мы поступим проще. Щелкните правой кнопкой мыши по пункту Выравнивание и выберите Create submenu. И уже в подменю добавьте пункты По левому краю , По правому краю и По центру. Казалось бы, теперь надо прописать для каждого пункта свой обработчик события. Но давайте создадим один обработчик события для всех трех пунктов меню. Ведь в “Дельфи” и такое возможно! Пропишите обработчик OnClick , например, для первого пункта и назовите его AlignSelectionClick. Название не так важно, но у программистов есть негласные правила, благодаря которым в программный код вносится некоторая упорядоченность. Вопреки известной шутке, надо стараться, чтобы текст ваших программ был понятен и другим людям. Выделите остальные два пункта меню и в разворачивающемся списке около обработчика OnClick выберите AlignSelectionClick. Всем трем пунктам меню мы присвоили один и тот же обработчик событий. А вот и код, который надо ввести в окне обработчика: Left1.Checked := False; Right1.Checked := False; Center1.Checked := False; with Sender as TMenuItem do Checked := True; with RichEdit1.Paragraph do if Left1.Checked then Alignment := taLeftJustify else if Right1.Checked then Alignment := taRightJustify else if Center1.Checked then Alignment := taCenter; Небольшое замечание. Объекты Left1 , Right1 и Center1 — это соответствующие пункты меню. У вас они, скорее всего, будут называться по-другому. Обычно пункты меню обозначаются буквой “N” и какой-то цифрой. Это не очень удобно, поэтому предлагаю переименовать соответствующие объекты так, как показано в этом кусочке программного кода. Внимательно посмотрите на текст кода. За наличие галочки напротив пункта меню отвечает свойство Checked. В первых трех строчках мы снимаем все галочки со всех трех
Создание главного меню — дело ответственное. Вооружаемся редактором меню, инспектором объектов и деревом объектов — и вперед. Впрочем, без дерева объектов можно и обойтись. Но с ним вы сможете легко проследить имена, которые Delphi присваивает каждому пункту меню.
пунктов меню. А следующая строчка помечает галочкой тот пункт меню, по которому пользователь кликнул. Почему все так сложно? Дело в том, что обработчик у нас один на все три пункта меню. В переменной Sender как раз и записывается объект, для которого произошло событие (правильнее было бы сказать — которому пришло сообщение). Основную функцию выполняет следующая громадная строчка (она там всего одна, а не три, как может показаться) с тремя исключающими друг друга условиями. В зависимости от того, какой пункт меню выбрал пользователь, мы форматируем текст с помощью свойства Alignment. И тут за нас постарались работящие дяди, поэтому процедуры форматирования самим писать не придется — все уже готово. Вы видели в “Блокноте” пункт меню Перенос по словам? Очень удобная возможность. Надо брать у конкурентов все самое лучшее, поэтому давайте сделаем и у себя такой пункт. Создайте его в меню и назовите & Перенос по словам. А в обработчике напишите: RichEdit1.WordWrap:= not RichEdit1.WordWrap; WorldWrap1.Checked:=RichEdit1.WordWrap; Вы и без подсказки догадаетесь, что объект соответствующего пункта меню надо назвать WorldWrap1. Внимание! Не перепутайте имя и текст объекта ( Name и Caption соответственно). Они вроде бы похожи, но на самом деле отличаются. Name — это уникальный идентификатор, по которому к объекту можно адресоваться из программного кода. Он не должен содержать русских букв. А Caption — всего лишь надпись, в случае с пунктами меню это название (не имя!) самого пункта. Первая строчка этого кусочка кода может вогнать в ступор новичков, но ничего сложного из себя не представляет. С помощью логического отрицания мы “переключаем” свойство WordWrap , которое отвечает за перенос по словам. Если это свойство имело значение Истина , то станет — Ложью. Вторая строчка обработчика помечает пункт меню галочкой, если WordWrap включен, и снимает галочку, если выключен. Иногда может возникнуть небольшая нестыковка этих двух свойств, потому что WordWrap по умолчанию включен. Надо озаботиться тем, чтобы отключить это свойство. Думаю, у вас уже хватит опыта добавить эту команду самостоятельно. Подскажу только место — событие OnCreate формы.
Редактор в работе. Раскрасим текст во все цвета радуги!
Перейдем к самому интересному — созданию шрифтового оформления текста. Создайте в пункте Форматирование подпункт Шрифт… (по концепции Windows , три точки в конце пункта меню указывают на то, что он развернется в диалоговое окно). В палитре компонентов на вкладке Dialogs найдите невизуальный компонент FontDialog и поместите его на форму. Этот компонент скрывает в себе стандартный виндовый диалог настройки шрифта. В обработчике пункта меню пишите: FontDialog1.Font := RichEdit1.Font; if FontDialog1.Execute then RichEdit1.SelAttributes.Assign(FontDialog1.Font); Давайте разберемся в этих трех строчках поподробнее. В первой строчке мы присваиваем свойству Font диалога настройки шрифта свойство RichEdit. Зачем это нужно? Чтобы в появившемся диалоге используемый шрифт был уже определен. Свойство Font универсально для многих объектов и описывает шрифт целиком, начиная с гарнитуры/кегля и заканчивая цветом. Если окно появилось на экране и текущий шрифт был изменен, присваиваем атрибуты нового шрифта только выделенному участку текста (свойство SelAttributes как раз указывает на атрибуты шрифта выделенного текста). Будни сыщика Нашему редактору не хватает возможности быстро найти требуемую часть текста. Где бы разместить пункт меню Найти…? Разумнее всего создать отдельный пункт главного меню программы, без подпунктов. Так мы и сделаем. А теперь поместите на форму невизуальный компонент FindDialog с вкладки Dialogs. Он скрывает в себе стандартное окно поиска. В обработчике OnClick пункта меню Найти… пишем: with FindDialog1 do begin Options:=Options+[frHideMatchCase]+[frHideWholeWord]+[frHideUpDown]; Execute; end; Этот код выводит диалог поиска на экран и убирает некоторые ненужные его элементы. К сожалению, перед нами всего лишь диалог. Функцию поиска чужой дядя за нас не написал, поэтому придется писать ее самим в обработчике OnFind компонента FindDialog1 (по умолчанию он будет называться именно так, если вы сами не поменяли его имя). В означенном
Благодаря возможностям ActiveX и COM в “Дельфи” можно одним кликом мыши добавить в программу стандартный диалог настройки принтера. Причем для каждого конкретного принтера он будет разным!
обработчике пишем: var FoundAt: LongInt; StartPos, ToEnd: Integer; begin with RichEdit1 do begin if SelLength <> 0 then StartPos := SelStart else StartPos := 0; if SelLength <> 0 then ToEnd := Length(Text) — StartPos else ToEnd := SelLength; FoundAt := FindText(FindDialog1.FindText, StartPos, ToEnd, [stMatchCase]); if FoundAt <> -1 then begin SetFocus; SelStart := FoundAt; SelLength := Length(FindDialog1.FindText); end; end; end; Обратите внимание на одну очень важную деталь. Мы впервые объявили несколько локальных переменных. Обработчик события — это процедура, самостоятельный фрагмент программы. И у него, как и у главной программы, могут быть свои переменные, константы и даже собственные локальные процедуры. Все это помещается в раздел объявлений. Раздел объявлений находится между заголовком процедуры и словом begin , обозначающим начало кода. Блок переменных должен быть озаглавлен ключевым словом var. Мы объявили в этом тексте три переменные: FoundAt , StartPos и ToEnd , все три — целочисленные. В первую будет помещаться номер символа в тексте, с которого начинается найденная строка, а через две других мы передадим функции поиска адрес того места, с которого будем искать, и длину проверяемого куска (это нужно, чтобы можно было искать не только во всем тексте, а и в выделенном фрагменте тоже). Не забудьте, что при создании обработчика слова begin и end появляются автоматически, а поскольку в тексте кода они уже есть, их надо удалить. Код кажется пугающе большим, но на самом деле прост. Расшифруем его строчки. Сначала мы указываем, что все нижеперечисленные действия собираемся выполнять с объектом RichEdit1 , который находится в поле ввода. Потом
Так выглядит скрипты в текстовом редакторе.
определяем условие, что пользователь может задать поиск только по выделенному фрагменту текста. Наконец, запускаем функцию поиска, которая включена в RichEdit1, а результат (абсолютный индекс первого символа найденной подстроки) записываем в переменную FoundAt. Если ничего не найдено, функция вернет 1. В следующем условии как раз проверяется, равна ли переменная минус единице, а если нет — выделяется найденный участок текста, и программа “перескакивает” на него. Не так уж сложно, но чтобы окончательно разобраться, перечитайте этот абзац еще раз, соотнося действия со строчками кода. Последние штрихи Остается добавить поддержку формата RTF , и редактор мечты готов. Неужели надо добавлять на форму еще одни OpenDialog и SaveDialog и прописывать обработчики событий новых пунктов меню? Нет, все значительно проще. Для сохранения в форматы TXT и RTF в компоненте TRichEdit используются одни и те же процедуры. Различие только в расширении файлов и их содержании. Формат RTF помимо собственно текста предусматривает еще и специальные управляющие значки, которыми обозначается форматирование. Чтобы добавить поддержку RTF, достаточно назначить соответствующий фильтр в OpenDialog и SaveDialog. Выделите компонент OpenDialog на форме и в объектном инспекторе кликните на маленькой кнопочке с тремя точками рядом со строчкой Filter. Появится окошко Filter Editor. Первая строчка уже заполнена: там прописан фильтр для расширения TXT. Добавьте в первую колонку второй строчки текст “Документ RTF”, а во вторую колонку — “*.rtf”. После этого кликайте на OK. Тот же самый фокус проделайте с SaveDialog. Теперь запустите программу и попробуйте открыть какой-нибудь файл с расширением RTF. Перед вами появится форматированный текст. Редактор почти готов. Но есть еще несколько мелочей, без которых программа теряет свое лицо: нет системы помощии окошка About. Для них обычно делают отдельный пункт главного меню " ? ". В этот пункт меню поместите два подпункта: Помощь и О программе… Я не буду вас учить писать hlp -файлы. К программированию это не имеет никакого отношения. В Сети полно программ, с помощью которых можно достаточно быстро оформить любой файл помощи. А вот
Еще один фрагмент скриптов.
действительно программистская задачка — как из программы вызвать этот файл.ShellExecute(Application.Handle,‘open’,‘help.hlp’,nil,nil,0); help.hlp — это ваш файл помощи. Создать окошко О программе… еще проще. Оно уже есть в комплекте Delphi. Добавим это окошко к нашему проекту. В меню File выбирайте New… и в появившемся окне на вкладке Forms дважды кликайте по About Box. К проекту добавится новая форма. Внимательно на нее взглянем. Некрасиво. Все по-английски, а кнопка OK не работает. Перевести все надписи на этой форме вы сможете и без моей помощи. В прошлой статье мы уделили немало времени вопросам перевода, данный случай ничем не отличается от уже рассмотренных. Напишите что-нибудь умное в комментариях и копирайте (только не слишком умничайте — народ не любит читать длинные лицензионные соглашения). Теперь дважды кликайте на кнопке OK и в появившемся обработчике событий пишите одну простую строчку: Close; Эта команда закрывает текущее окно. В обработчике события пункта О программе… пишите: AboutBox.show; Если вы сейчас попытаетесь запустить программу, вас ждет небольшой сюрприз. “Дельфи” вежливо сообщит, что вы из модуля одной формы адресуетесь к другой форме, которая не прописана в Uses первого модуля. Это ошибка. И какой-нибудь MSVC++ на том и остановился бы, оставив программиста наедине с тяжкими раздумьями. Но “Дельфи” проявляет чудеса вежливости и догадливости: предлагает свою помощь по решению этой проблемы. От помощи отказываться не будем — поэтому жмите на Yes , и “Дельфи” автоматически внесет изменения в Uses модуля. Теперь можно запустить программу, и все будет работать. Что такое Uses? Прокрутите код программы до
Word XP — самый функциональный из всех текстовых редакторов.
самого начала и вглядитесь в третью строчку. Это блок программы, который отвечает за подключение других модулей. У каждой формы есть свой собственный модуль. И вы не сможете адресоваться из текста первого модуля к какой-то переменной или объекту второго модуля, если он не включен в список Uses. Так как форма тоже объект, ее модуль надо прописать в Uses тех модулей, из которых вы хотите посылать запросы к форме. Кроме ссылок на модули других форм, в Uses вы увидите около десятка разных вспомогательных модулей, без которых программа не сможет нормально работать. Самые важные из них: Windows , Messages и Classes. Если вы поместили на форму какой-то компонент, “Дельфи” самостоятельно пропишет в Uses все необходимые для его работы модули. Вот и готов наш текстовый редактор. Правда, не совсем. “Да что ж такое! Опять “не совсем”?” — воскликнет нетерпеливый читатель. Остались мелочи. В меню Project кликайте на Options. Сейчас мы изменим некоторые настройки программы. Идите на вкладку Application и в нужных полях прописывайте название своей программы (здесь можно употреблять русские буквы) и путь до файла помощи (если он будет). Здесь же с помощью кнопочки Load icon подберите вашему редактору симпатичную иконку. Можно нарисовать новую с помощью входящего в комплект “Дельфи” Image Editor. В меню Version Info поставьте галочку в пункт Include version information in project. Откроется огромное количество опций. Здесь вы сможете заявить версию своей программы (вплоть до “5.027.0314 Release 14 Build 21”) , назначить “родной” язык программы, а также заполнить поля о названии вашей компании, описании файла, многочисленных копирайтах, внутренних именах. Где будет отображаться эта информация? Если вы щелкнете правой кнопкой мыши по готовому ехе-файлу и выберете Свойства , то кроме стандартной увидите еще и вторую вкладку, со всеми вашими регалиями и копирайтами. Пустячок, а приятно. *** Прогресс бесконечен. При желании вы сможете довести текстовый редактор до уровня Word. Если вы всерьез задумали развивать эту идею, прямо сейчас подскажу некоторые детали, которых не хватает в нашей программе. Во-первых, неплохо бы вывести все пункты меню на панель инструментов в виде кнопок. Во-вторых, не ко всем пунктам меню есть контекстная помощь. Дописать ее — дело нескольких минут. Попробуйте сделать диалог, который при попытке выйти из программы предупреждал бы о том, что текст изменился, и предлагал бы его сохранить. Много чего еще можно сделать. А мы продолжаем двигаться по бесконечной спирали эволюции. В следующей статье цикла, которая будет опубликована в одном из ближайших номеров “Мании” , мы сделаем простенькую игру.