Уроки по Delphi

Написать автору Уроки по Delphi

Тема 1. Введение
  1. Что это такое?
  2. Программы и события
  3. Что такое RAD?
  4. Объекты и классы
  5. Среда Borland Delphi
Тема 2. Консольные программы
  1. Что это такое?
  2. Первая программа
  3. Отладка
  4. Списки строк
  5. Алфавитно-частотный словарь
Тема 3. Форма и компоненты
  1. Форма
  2. Компоненты
  3. Практикум: «Обмен валюты»
  4. Практикум: «Калькулятор»
Тема 4. Графика и управление
  1. Рисование на холсте
  2. Сохранение рисунка
  3. Броуновское движение
  4. Клавиатура
  5. Мышь
Тема 5. Графический редактор
  1. Карандаш
  2. Прокрутка
  3. Панель инструментов
  4. Отрезок
  5. Прямоугольник
  6. Выбор цвета
  7. Заливка
  8. Работа с файлами
 Тема 6. Текстовый редактор
  1. Меню
  2. Работа с файлами
  3. Шаблоны и действия
  4. Картинки в меню
  5. Панель инструментов
  6. Окно «О программе»
  7. Окно «Настройка»

Дополнительные файлы

Архив с файлами, которые используются в практических работах (тексты, картинки, иконки), можно загрузить прямо из этого учебника.

Готовые программы, рассмотренные здесь, можно скачать с сайта автора kpolyakov.narod.ru.

Условные обозначения

В такую рамку заключаются практические задания, которые надо выполнять на компьютере.
Так обозначаются задания повышенной сложности. При их выполнении «в лоб» вы должны столкнуться с какой-то неожиданностью и преодолеть возникшую проблему.
Этим знаком обозначаются замечания и важные практические приемы, которые осваиваются как бы между прочим, в ходе выполнения практической работы по другой теме.

Автор

ПОЛЯКОВ Константин Юрьевич, доктор технических наук, профессор СПбГМТУ, учитель информатики ГОУ СОШ № 163 г. Санкт-Петербурга

Электронная почта: kpolyakov@mail.ru

Сайт автора: kpolyakov.narod.ru

© 2009 К. Поляков


Уроки по Delphi: Введение

Тема 1. Введение

  1. Что это такое?
  2. Программы и события
  3. Что такое RAD?
  4. Объекты и классы
  5. Среда Borland Delphi

Вы узнаете:

  • как работают программы, использующие обмен сообщениями;
  • что такое среды RAD;
  • что такое объекты и классы;
  • как выглядит среда Delphi.

1. Что это такое?

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

Для программирования мы будем использовать среду Delphi 2006 (или ее бесплатный аналог — Turbo Delphi Explorer).

Конечно, в пределах нескольких уроков невозможно сколько-нибудь детально изучить программирование в Delphi. В такой ситуации всегда приходится что-то выбирать. Особенности этого курса можно сформулировать, на взгляд автора, так:

  • это специально подобранный набор примеров; хотя они могут показаться слишком «разношерстными», автор надеется, что в результате у читателя сформируется целостное представление об основных возможностях среды Delphi;
  • каждый из примеров — осмысленная задача, мы не будет писать программы типа «давайте погоняем кнопку по экрану»;
  • мы не рассматриваем основы программирования: понятия цикла, процедуры, функции, массива и т.п., алгоритмы работы с данными; внимание сосредоточено на принципах создании программ, обрабатывающих события, и на особенностях среды Delphi;
  • при исследовании каждого примера правильное решение не приводится сразу, а вместо этого рассматривается естественный процесс разработки программы, включающий в том числе и ошибочные ходы;
  • работа с базами данных не рассматривается вообще (умышленно), потому что это отдельная область, требующая тщательного и неспешного изучения.

2. Программы и события

Скорее всего, вы уже умеете писать программы в какой-нибудь среде, например, АЛГО или Паскаль-ABC. Но все эти программы работали по классической схеме:

Главный принцип такого (процедурного) программирования — основная программа выполняется строчка за строчкой, вызывая процедуры и функции, все ветвления выполняются с помощью условных операторов.

Этот подход хорошо подходит для простых задач, где последовательность операций известна заранее. Однако, в в некоторых задачах порядок действий определяется пользователем, другими программами или поступлением новых данных из другого источника (например, из сети), поэтому классическая схема не работает.

Так при работе в текстовом редакторе пользователь может щелкать по любым кнопкам и выбирать любые пункты меню в произвольном порядке. Программа-сервер, передающая данные с Web-сайта на компьютер пользователя, начинает действовать только при поступлении очередного запроса. При программировании сетевых игр нужно учитывать взаимодействие многих объектов, информация о которых передается по сети в случайные моменты времени.

Во всех этих примерах программа должна «включаться в работу» только тогда, когда получит условный сигнал, то есть произойдет некоторое событие (изменение состояния). При этом программа должна «знать», как реагировать на нужное ей событие, то есть должна иметь обработчик этого события — процедуру специального типа.

На следющем рисунке показана схема работы программы, основанной на событиях:

Основная программа обычно содержит всего несколько строк, ее задача — сделать все начальные установки и сообщить операционной системе адрес процедуры обработки сообщений (или оконной процедуры). В эту процедуру будут направляться все сообщения, предназначенные данной программе: информация о действиях пользователя, а также сообщения операционной системы (например, сигналы таймера).

Получив сообщение от операционной системы, процедура обработки сообщений «разбирается» с ним: вызывает обработчик этого сообщения, если он существует. Если такого обработчика нет, программа никак не реагирует на полученное сообщение.

Обратите внимание, что здесь основная программа напрямую не связана с процедурами-обработчиками, она сама их не вызывает. Их вызывает процедура обработки сообщений, получив сообщение от операционной системы.

к началу К началу страницы

3. Что такое RAD?

Написание программ для операционной системы Windows в первые годы ее существования было довольно сложным и утомительным делом. Самое главное — очень много усилий уходило на то, чтобы написать команды для создания интерфейса с пользователем: разместить элементы в окне программы, написать и правильно оформить обработчики сообщений. То есть, значительную часть своего времени программист занимался совершенно «тупой» работой, которая почти никак не связана с решением главной задачи. Поэтому возникла естественная мысль — поручить всю рутинную работу компьтеру так, чтобы интерфейс можно было построить с помощью мыши, а человек думал главным образом о сути задачи, то есть об алгоритмах обработки информации. Такие системы получили название RAD (Rapid Application Development — быстрая разработка приложений).

Одной из первых сред быстрой разработки стала Delphi, разработанная фирмой Borland. В 2006 году была выпущена бесплатная версия Delphi 2006 с ограниченными возможностями, которая называлась Turbo Delphi Explorer, ее можно скачать на сайте www.turbomirror.com.

Другая популярная система быстрой разработки — Microsoft Visual Studio для программирования на языках Visual Basic .NET, Visual C++ и Visual C#.

Разработка программы в среде RAD состоит из следующих этапов:

  1. создание формы (окна); при этом минимальный код строится автоматически и сразу получается работоспособная программа;
  2. расстановка на форме элементов интерфейса (поля ввода, кнопки, списки) с помощью мыши;
  3. создание обработчиков событий двойным щелчком мыши, минимальный код также строится автоматически;
  4. написание кода обработчиков, который реализует нужные алгоритмы обработки данных.

Среды RAD позволили существенно сократить время разработки программ. Однако нужно помнить, что любой инструмент — это только инструмент, который можно использовать грамотно и безграмотно. Использование среды RAD не гарантирует, что у вас автоматически получится хорошая программа с хорошим пользовательским интерфейсом.

к началу К началу страницы

4. Объекты и классы

Что такое объект?

Постановка серьезной задачи практически никогда не содержит описания переменных и массивов, которые надо использовать. Напротив, большинство реальных задач формулируется как описание поведения объектов, например при компьютерном моделировании сложных систем.

В конце 80-х годов XX века программирование оказалось в достаточно серьезном кризисе. Главная проблема состояла в том, что сложность программ стала «превышать возможности человеческого интеллекта» (Г. Буч). Одному человеку стало не под силу охватить разумом все аспекты сложной программы.

Выходом из кризисной ситуации стал подход, основанный на понятии объекта. Новая идея заключалась в том, чтобы разбить сложную систему на части-объекты и попытаться понять и совершенствовать каждую часть в отдельности, независимо от других. При этом разработку каждого объекта можно поручить отдельному человеку. Такой подход называют объектно-ориентированным программированием.

Объект — это нечто, имеющее свойства и методы.

Свойства — это какие-то характеристики объекта, которые мы может наблюдать и, возможно, изменять. Васю Пупкина можно рассматривать как объект. Его свойства — это рост, вес, цвет глаз, характер, прическа и т.п. Свойства объекта-автомобиля — это его марка, цвет, объем двигателя, год выпуска.

Методы — это команды, которые объект понимает и может выполнять, причем в документации на объект сказано, как именно он реагирует на эти команды. Например, автомобиль реагирует на нажатие педалей, поворот руля, переключение скоростей.

Мы ничего не знаем о том, как устроен объект внутри. Для нас это, как говорят кибернетики, «черный ящик». Но самое главное — для того, чтобы работать с объектом, нам и не нужно знать его внутреннее устройство! Достаточно, что мы можем узнать и изменить его свойства, а также применять доступные методы управления.

Свойства и методы представляют собой интерфейс объекта, то есть способ его общения с внешним миром. Мы можем сколько угодно переделывать «внутренности» объекта, сохраняя интерфейс, и при этом оставшуюся часть программы переделывать не придется, потому что с «внешней» стороны (для наблюдателя) объект никак не изменился.

Объединение свойств и методов в едином понятии объекта называют инкапсуляцией.

Классы и наследование

В любой науке очень важна классификация объектов, с которым она имеет дело. Это означает, что все объекты разбиваются на группы, обладающие общими свойствами. Например, в биологии эти группы называются царство, тип, класс, отряд, семейство, род и вид.

В программировании класс — это описание общих свойств и методов группы объектов. Каждый объект (экземпляр) класса обладает всеми свойствами и поддерживает все методы этого класса.

Например, класс Собака может описывать все общие свойства, присущие собакам (подумайте, какие?) А Жучка, бегающая во дворе, — это экземпляр или объект класса Собака.

Человек часто использует многоуровневую (иерархическую) классификацию. Например, попугай какаду относится к царству Животные, типу Хордовые, подтипу Позвоночные, классу Птицы и отряду Попугаеобразные (рисунок справа). Это значит, что Хордовые обладают всеми признаками Животных (и могут иметь свои собственные!), все Позвоночные имеют все свойства Хордовых и т.д. Можно сказать, что каждый следующий уровень наследует все признаки предыдущего (более высокого) уровня.

В программировании нет царств, отрядов, подтипов и пр., вместо них используется одно понятие класс. Но класс B может наследовать свойства другого (базового) класса A, при этом все экземпляры класса B будут иметь все свойства и поддерживать все методы базового класса A. Кроме того, объекты класса B могут иметь новые свойства и методы, которых не было в классе A.

Классы в Delphi

Программы в среде Delphi чаще всего строятся на принципах объектно-ориентированного программирования. Например, большинство программ, работающих в системе Windows, имеют окно. В Delphi окно (оно называется формой) — это объект, у которого есть свойства (заголовок, цвет, размеры и т.п.) и методы (в том числе обработчики сообщений).

На форме расположены элементы управления — кнопки, переключатели, поля ввода и др. Они также являются объектами со своими свойствами и методами.

Мы будем постепенно знакомиться с объектами Delphi на примерах, углубляясь только в те детали, которые нужны в данной задаче.

к началу К началу страницы

5. Среда Borland Delphi

На следующем рисунке показано окно среды Delphi:

В центре находится окно, в котором редактируется код программы. В этой же области мы будем мышкой передвигать элементы на форме. В верхней части этого окна видно несколько закладок, это значит, что можно одновременно открыть несколько файлов и переключаться между ними. Закладка Welcome Page — это страница, которая автоматически загружается при запуске Delphi. На ней вы можете щелчком мыши создать новый проект, открыть существующий проект, зайти в справочную систему. К сожалению, вся документация к Delphi написана на английском языке.

Слева вверху в окне Structure вы видите структуру программы: список всех процедур и функций (Procedures), список переменных и констант (Variables/Constants) и список используемых стандартных модулей (Uses) — в них хранятся встроенные функции.

Под окном Structure размещено окно инспектора объектов (Object Inspector). Здесь мы будем изменять свойства элементов формы и задавать обработчики нужных событий.

Справа вверху — окно проекта Project Manager. Программа на Delphi (проект) может состоять из множества файлов, здесь вы увидите их полный список и сможете перейти к любому из них, просто щелкнув мышкой по его имени.

Справа внизу размещено окно с палитрой инструментов (Tool Palette). Сейчас в нем перечислены типы программ, которые можно создать. Когда мы будем работать с окнами, здесь будут перечислены все элементы, которые можно добавить на форму (кнопки, списки, поля ввода и др.)

В самом верху расположено меню (к сожалению, тоже на английском языке) и панель инструментов (кнопок). Мы будем изучать их постепенно, по мере надобности. Сейчас только скажем, что кнопка [image] запускает программу на выполнение.

к началу К началу страницы

Оглавление В начало Вперед Консольные программы


Уроки по Delphi: Консольные программы

Тема 2. Консольные программы

  1. Что это такое?
  2. Первая программа
  3. Отладка
  4. Списки строк
  5. Алфавитно-частотный словарь

Вы узнаете:

  • как работать в среде Delphi с проектами;
  • как написать простейшую программу с операторами ввода и вывода;
  • как сменить кодировку русского текста;
  • как отлаживать программу;
  • как работать со списками строк TStringList.

1. Что это такое?

Раньше вы чаще всего писали программы, которые вводили информацию в текстовом режиме с помощью операторов Read и Readln, а выводили — с помощью Write и Writeln, причем ввод и вывод выполняется в одном и том же окне. Такие программы называют консольными (раньше в распоряжении оператора была только консоль — монитор и клавиатура).

Консольные программы — лучший выбор, когда нужно сосредоточиться на сути проблемы, отвлекаясь от оформления. Поэтому начинать учиться программировать лучше именно на таких программах. Кроме того, профессиональные программисты часто пишут консольные программы для своего личного использования, потому что это экономит время (не нужно разрабатывать интерфейс).

Итак, в ближайшее время мы будем писать консольные программы. Вы увидите, что это лишь чуть-чуть отличается от программирования на Паскале, которое вы изучали раньше.

к началу К началу страницы

2. Первая программа

Быстрый старт

После запуска Delphi вы увидите стартовую страницу (Welcome Page). Сверху расположены кнопки

— создать новый проект

— открыть существующий проект

— открыть файл

— вызвать справочную систему

Проект — это группа файлов, которые «собираются» в единую программу (exe-файл). В частности, проект может состоять и из одного файла. Когда мы загружаем новый проект, мы файтически переходим к работе с другой программой. Напротив, операция Open File (Открыть файл) позволяет открыть на редактирование любой файл, даже никак не связанный с проектом. При этом проект не выгружается из оболочки, то есть, мы продолжаем работать с той же программой.

Если вы уже работали в Delphi на этом компьютере, под кнопками вы увидите список последних открывавшихся проектов Recent Projects. Щелчок мышью на названии проекта загружает его в Delphi.

Запустите среду Delphi. Щелкните по кнопке [image] и выберите в появившемся окне тип программы Console Application (консольное приложение).

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

Есть и другой способ создания консольного приложения. В правом нижнем углу окна расположена палитра инструментов (Tool Palette), где в самом начале перечислены все возможные типы приложений. Для создания консольного приложения достаточно сделать двойной щелчок по строчке Console Application.

Вы увидите такой текст программы, который очень напоминает обычную программу на Паскале:

program Project1;
{$APPTYPE CONSOLE}
uses
  SysUtils;
begin
  { TODO -oUser -cConsole Main : Insert code here }
end.

Как вы помните, в фигурных скобках записываются комментарии. Второй комментарий заканчивается словами Insert code here (Вставьте код сюда) — тут все понятно. Первый комментарий {$APPTYPE CONSOLE} — особый, потому что он начинается с первой позиции строки, и его второй символ — знак $. Это говорит о том, что далее следует некоторая команда (директива) транслятору, который будет строить исполняемый файл. В данном случае сказано, что нам нужно консольное приложение. Это значит, что необходимо создать текстовое (консольное) окно для ввода и вывода, все это будет сделано автоматически.

Блок uses есть и в классическом Паскале: он перечисляет дополнительные модули, которые используются в программе. В данном случае автоматически подключен модуль SysUtils, в котором находятся некоторые часто используемые стандартные функции.

Далее мы можем использовать старые знания. Код программы записывается между begin и end, константы и переменные объявляются выше основной программы, процедуры и функции размещают между объявлением переменных и основной программой.

Добавьте в тело программы строчку Writeln('Hello!'); и запустите ее на выполнение, щелкнув по кнопке [image] или нажав клавишу F9.

Скорее всего, вы увидели, что мелькнуло черное окно, но даже не успели прочитать, что там было написано. Дело в том, что как только выполнение программы заканчивается, консольное окно уничтожается и программа выгружается из памяти. Чтобы этого не происходило, нужно как-то задержать программу, чтобы она ожидала нажатие какой-нибудь клавиши. Простейший метод — добавить оператор Readln, который будет ждать нажатия клавиши Enter.

Добавьте в конец программы строчку Readln; и запустите ее на выполнение.

Сохранение файлов

Теперь сохраним файлы программы на диске. При сохранении вам будет предложено выбрать папку и изменить (если нужно) имена файлов. Лучше сохранять каждую программу в отдельной папке, поэтому нужно будет создать папку Консоль. По умолчанию файл проекта сохраняется под именем Project1.bdsproj, но имя (но не расширение!) можно изменить, например, на Console.bdsproj. Это важно, потому что исполняемый файл будет иметь точно такое же имя и расширение .exe, то есть Console.exe.

Сохраните проект в отдельной папке Консоль под именем Console.bdsproj, выбрав пункт меню File-Save All или щелкнув по кнопке [image] на панели инструментов (или нажав клавиши Shift+Ctrl+S).
Запустите программу Проводник и зайдите в папку Console.

Скорее всего, вы увидели там не один файл, а четыре. Кроме файла Console.bdsproj (файл проекта с описанием всех настроек), были созданы еще

Console.dpr — основная программа

Console.cfg — опции (настройки, режимы работы) транслятора

Console.bdsproj.local — вспомогательный файл проекта

Заметьте, что имена этих файлов совпадают с именем файла-проекта, поэтому при сохранении они не запрашиваются.

Наиболее интересен для нас файл Console.dpr — это и есть та основная программа, которую мы редактируем. Кстати, при открытии проекта вы можете выбрать как файл Console.bdsproj, так и файл Console.dpr, результат будет одинаковый.

Кроме того, была создана скрытая папка __history, где хранятся несколько предыдущих верcий программы (это обеспечивает возможность «отката» назад при неудачных изменениях).

Русский текст

Давайте попробуем теперь вывести на экран русский текст.

Замените команду вывода на Writeln('Привет!'); и запустите ее на выполнение.

Наверняка, вместо приветствия вы увидели «кракозябры». Можно догадаться, что не совпала используемая кодировка, так как русские буквы не входят в таблицу ASCII.

Когда мы набираем текст в редакторе, используется кодовая страница Windows-1251, в то время как консоль выводит символы в кодировке CP-866 или OEM-кодировке (она использовалась раньше в программах для операционной системы MS DOS).

Чтобы русский текст можно было прочитать, нужно его перекодировать. Конечно, можно написать свою функцию перекодировки, взяв таблицы символов двух кодовых страниц. Но, к счастью, такая функция уже есть, она называется CharToOEM. Чтобы перекодировать символьную строку s из кодировки Windows-1251 в кодировку CP-866 используется команда

  CharToOEM ( PСhar(s), PChar(s) );

Здесь PChar(s) обозначает адрес символа, с которого начинается строка s. Первый параметр функции — адрес исходной строки, а второй — адрес строки-результата. В данном случае оба адреса совпадают.

Для получения подробной справки о любой функции можно ввести ее имя в редакторе, установить на него текстовый курсор и нажать клавишу F1. Справка будет на английском языке.
Объявите символьную переменную s и измените код основной программы так:
    s := 'Привет!';
    CharToOEM ( PChar(s), PChar(s) );
    Writeln ( s );

Наверное, вы заметили, что имя функции подчеркнуто красной линией. Это означает, что среда Delphi обнаружила ошибку прямо во время набора текста программы, хотя вы еще не запускали ее!

Теперь посмотрите на окно Structure (Структура):

Здесь четко указано, какую ошибку мы допустили (Undeclared identifier — неизвестное имя) и где (строка 13, позиция 3). Щелкнув дважды по сообщению об ошибке, мы сразу переходим на нужную строчку кода, это особенно удобно в больших программах.

Итак, что же делать? В даном случае, программе неизвестно имя CharToOEM. Дело в том, функция CharToOEM описана в модуле Windows, который мы не подключали (его нет в списке uses). Строго говоря, это даже не функция Delphi, а встроенная функция операционной системы, которая вызывается из Delphi.

Если вы не знаете, в каком модуле описана функция, можно просто навести мышь на ее имя, и краткая справка появится во всплывающей подсказке. Для функции CharToOEM читаем Declared in Windows, то есть «объявлена в модуле Windows».
Добавьте модуль Windows в список uses:
  uses
    Windows, SysUtils;
и запустите программу.
В окне Structure раскройте списки переменных и констант (Variables/Constants) и список используемых модулей (Uses). Сравните с тем, что фактически используется в программе.
Сохраните программу и закройте проект с помощью меню (File-Close All).
к началу К началу страницы

3. Отладка

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

Попробуйте определить, в каком модуле описаны процедуры Readln и Writeln.

Модуль System, в котором объявлены Readln и Writeln, мы не подключали, однако он подключается к любой программе на Delphi автоматически.

Для получения подробной информации можно перевести на имя функции текстовый курсор (каретку) и нажать клавишу F1.

Получите подробную информацию о процедуре Readln в справочной системе. При нажатии клавиши F1 вы увидите список, показывающий, что есть несколько статей по функции Readln. Вам нужно выбрать ту, где в столбце Location написано Delphi for Win32 Reference (справка по Delphi для 32-разрядной операционной системы).

Посмотрите на окно редактора:

Строки программы нумеруются через 10 (10, 20, 30 и т.д.). Кроме того, показан номер текущей строки (в которой стоит курсор), эта строка выделяется светло-желтым фоном.

Зеленая линия на полях обозначает, что эти строки изменялись с момента открытия программы, но эти изменения уже были сохранены. Желтая линия обозначает строки, изменения в которых еще не сохранены.

Значки [image] позволяют сворачивать блоки кода (в данном случае — тело основной программы и всю программу).

Сверните основную программу, щелкнув по значку [image] слева от слова begin. Снова разверните блок, щелкнув по значку [image].

При нажатии на клавишу F4 программа выполняется «до курсора», то есть, до той строчки, где стоит текстовый курсор.

Выполните программу до строчки, где вызывается функция CharToOEM.

Выполнение программы было остановлено, красным цветом выделена строка, которая будет выполняться следующей (еще не выполнялась). Дополнительно эта строка выделяется синей стрелкой на полях.

Синие точки у левого края показывают, в каких строчках программу можно остановить. Заметьте, что блоки uses и var не отмечены, это значит, что они используются только на этапе трансляции (но не при выполнении программы).

Если навести мышку на имя любой переменной, мы увидим ее значение во всплывающей подсказке (см. рисунок).

Для того, чтобы в этом режиме выполнить очередную строчку, нужно нажать клавишу F8. Для того, чтобы войти внутрь вызываемой процедуры, вместо F8 нужно нажать клавишу F7. Клавиша F9 запускает программу дальше (без остановки).

Выполните одну строчку программы, где вызывается функция CharToOEM. Снова посмотрите на значение переменной s, объясните результат. Затем выполните программу до конца.

В программе можно установить сколько угодно точек останова (breakpoints). Так называются точки, в которых программа должна остановиться. Чтобы задать точку останова, надо щелкнуть мышкой на синем кружке слева на полях. Вместо синего кружка появится красный.

Установите точки останова в строках, где вызываются функция CharToOEM и процедура Readln.

Запустите программу. Посмотрите, где она остановилась. Еще раз нажмите на клавишу F9. И еще раз, чтобы выполнить программу до конца.

Чтобы «снять» точку останова, надо щелкнуть на красном кружке в соответствующей строчке.

При нажатии на клавиши Ctrl+F7 открывается окно Evaluate/Modify, в котором можно ввести имя переменной или выражение (поле Expression), увидеть его значение в поле Result и даже изменить значение переменной в памяти (в поле New Value).

Конечно, существуют и другие, более сложные методы отладки, но на первых порах вам этого хватит. А когда не хватит, вы уже сможете разобраться самостоятельно (верхнее меню Run).

к началу К началу страницы

4. Списки строк

Что это такое?

Когда нужно работать с большим количеством символьных строк, можно выделить в памяти массив строк, например, так:

const N = 100;
var A: array[1..N] of string;
Однако при этом нужно знать заранее их количество (или хотя бы максимально возможное количество, чтобы выделить память с запасом).

А что если число строк совсем неизвестно? На этот случай в Delphi введен новый тип данных, точнее — новый класс объектов — список TStringList (сокращение от Type String List — тип «список строк»). Можно добавлять в список неограниченное число строк, вставлять новые строки в определенное место списка, удалять и переставлять строки, сортировать их.

Для работы со списком нужно объявить переменную типа TStringList:

var L: TStringList;

А вот теперь будьте внимательны! Переменная L — это еще не список, не объект. Его нельзя использовать. При попытке добавить строку в этот список вы получите ошибку Access violation (нарушение прав доступа). Это означает, что программа неверно работает с памятью.

Создание и удаление списка

Оказывается, пока мы объявили только указатель на список (занимающий всего 4 байта!). Указатель — это переменная, в которой хранится адрес объекта. И все. То есть, самого объекта-списка в памяти еще нет, есть только ячейка, куда можно записать адрес списка.

Чтобы фактически создать список, нужно написать (уже в тексте программы) такой код:

L := TStringList.Create;

Это означает «создать объект класса TStringList и записать его адрес в переменную-указатель L». Теперь список можно использовать. Метод Create называется конструктором класса, с его помощью создаются новые объекты.

Для того, чтобы работать со списками, нужно подключить модуль Classes (добавить его в список uses). Именно в этом модуле описан класс TStringList, все его свойства и методы.

Когда список больше не нужен, его надо удалить из памяти с помощью специального метода-деструктора («разрушителя») Free:

L.Free;

После этого переменная L снова становится «недействительной»: она не связана ни с каким списком.

Для самопроверки попробуйте сами понять, что произойдет при выполнении такого кода:

L := TStringList.Create;
L := TStringList.Create;
L.Free;
L.Free;

С первого взгляда может показаться, что сначала создаются два списка, а затем память корректно освобождается. Однако, последняя строчка приведет к ошибке.

Попробуем разобраться. Сначала создается новый список, его адрес записывается в переменную L. Затем (во второй строке) создается второй список, и его адрес записывается в ту же переменную L. Это значит, что адрес первого списка стерт, потерян, мы уже не можем к нему обратиться.

Далее, третья команда удаляет из памяти список, адрес которого хранится в переменной L — это второй список. Четвертая команда попытается удалить тот же список, потому что адрес в переменной L не поменялся. Тут то и «вылезет» ошибка, так как список уже был удален.

Заметьте, что первый из двух созданных списков остался в памяти до завершения работы программы. Его невозможно удалить, потому что потерян его адрес.

Работа со списком

Когда список создан, можно обращаться к нему через переменную L. Например, метод Add добавляет новую строку в конец списка:

L.Add ( 'Здравствуй!' );
s := 'И тебе не кашлять'; // s - символьная строка
L.Add ( s + ', Вася!' );

Этот код добавляет в список две строчки, «Привет» и «И тебе не кашлять, Вася!». Обратите внимание, что при вызове метода объекта имя метода отделяется точкой от имени объекта (точнее — указателя).

Здесь показан еще один вид комментария, который начинается знаками //. Все, что стоит в этой строчке справа от пары «слэшей» — это комментарий.

Строки в списке нумеруются с нуля, к самой первой строке можно обратиться как L[0], то есть так же, как и к элементу массива. Только, в отличие от массива, нам не нужно заранее знать, сколько строк будет в списке — необходимая память выделяется автоматически при добавлении очередной строки (точнее, блоками сразу по несколько строк).

Чтобы определить количество строк в списке, нужно запросить его свойство Count. Поскольку строки нумеруются с нуля, последняя строка имеет номер L.Count-1. Следующий код выводит на экран все строки списка L:

for i:=0 to L.Count-1 do // i - целая переменная
  Writeln ( L[i] );

С помощью метода Delete можно удалить строку с известным номером:

L.Delete ( i );

Чтобы очистить список (удалить все строки), используют метод Clear:

L.Clear;

При этом (в отличие от метода Free) объект-список не уничтожается, его можно использовать дальше (например, добавлять новые строки).

Чтобы найти какую-то строку в списке, применяется метод IndexOf. Эта функция возвращает номер найденной строки-образца или -1, если ничего не найдено:

n := L.IndexOf ( 'Вася'  ); // n - целая переменная
if n < 0 then
     Writeln ( 'Нет строки "Вася"!' )
else Writeln ( 'Это строка номер ', n );

Метод Sort сортирует строки в списке по алфавиту:

L.Sort;

Метод SaveToFile позволяет очень просто записать список на диск в виде текстового файла. В этом примере будет записан файл example.txt, каждый элемент списка на отдельной строчке:

L.SaveToFile ( 'example.txt' );

Метод LoadFromFile загружает список из файла в память.

L.LoadFromFile ( 'example.txt' );

При этом старые строки в списке стираются. Каждая строка файла становится отдельным элементом списка.

Объекты

Вместе с каждым элементом списка строк можно хранить еще и адрес объекта, связанного с этой строкой. Все объекты в Delphi являются наследниками класса TObject, поэтому можно использовать любые объекты, которые созданы с помощью метода-конструктора Create.

Чтобы добавить к списку строку вместе со связанным объектом, вместо метода Add используют метод AddObject:

L.AddObject ( 'Вася', адрес объекта );

Здесь второй параметр — адрес объекта (или указатель на объект, например, переменная типа TStringList).

Если мы используем метод Add, вместе со строкой автоматически добавляется нулевой адрес объекта (nil).

Объект — это наследник класса TObject, поэтому при вызове метода AddObject второй параметр должен принадлежать к типу TObject или к одному из его наследников.

В следующей программе нам будет необходимо хранить для каждой строки не объект, а целое число. Тут нужно применить хитрость — преобразовать целое число к типу TObject. Это можно сделать, потому что в памяти переменная-указатель и целое число занимают одинаковое место (4 байта). Например:

L.AddObject ( 'Вася', TObject(3) );
n := 4;
L.AddObject ( 'Петя', TObject(n) );

«Добраться» до объектов можно с помощью свойства-массива Objects. Запись L.Objects[i] означает «объект, связанный со строкой L[i]». Если с каждой строкой хранится целое число, вывести эти числа на экран можно так:

for i:=0 to L.Count-1 do // i - целая переменная
  Writeln ( integer(L.Objects[i]) );

Запись integer() обозначает «преобразовать величину к типу integer». Если этого не сделать, при вызове Writeln будет ошибка, так как эта процедура не умеет выводить адреса объектов.

к началу К началу страницы

5. Алфавитно-частотный словарь

Задача

Каждый человек имеет свой стиль письма. Исследователи литературы нередко используют частотный анализ, то есть, определяют, какие слова писатель использует чаще, а какие — реже. Для этого анализируются все имеющиеся тексты, и по ним составляется алфавитно-частотный словарь, в котором для каждого слова указано, сколько раз оно встретилось:

435 а
20  абориген
5   самосвал

В следующем примере мы составим алфавитно-частотный словарь для заданного файла. Для этого будем использовать класс TStringList и его методы. Такая программа не должна активно «общаться» с пользователем, поэтому лучше сделать ее в виде консольного приложения.

Алгоритм

Сначала попробуем описать алгоритм решения задачи на русском языке. Очевидно, что нужно обрабатывать файл с первой строки до последней и хранить в памяти список всех найденных слов. Для каждого из этих слов нужно ввести счетчик — целую переменную, которая показывает, сколько раз это слово встретилось в тексте. Если очередное слово есть в списке, нужно просто увеличить счетчик на 1, а если нет — добавить его в список и установить счетчик в 1. Вот что получается в итоге:

while в тексте есть слова do begin
  прочитать очередное слово
  искать слово в списке слов
  if слово найдено then
    увеличить счетчик для этого слова
  else begin
    добавить слово в список
    установить счетчик слова в 1
  end;
end;
отсортировать список

Реализация этого алгоритма будет зависеть от того, какие структуры данных мы будем использовать. Во-первых, количество слов может быть любое, оно заранее неизвестно. Во-вторых, с каждым словом нужно хранить целое число — счетчик. Все это наводит на мысль, что для хранения слов лучше всего использовать список TStringList, о котором шла речь чуть выше в этой теме.

Чтение данных из файла можно сделать по-разному. Мы будем и здесь использовать список, в который сначала прочитаем весь файл с помощью метода LoadFromFile. Тогда для того, чтобы выделить очередное слово, нужно будет «разобрать» строку. Таким образом, нужно немного поменять алгоритм:

прочитать файл в список Source
for i:=0 to Source.Count-1 do begin
  s := Source[i];
  while s <> '' do begin
    выделить слово из строки s
    искать слово в списке слов
    if слово найдено then
      увеличить счетчик для этого слова
    else begin
      добавить слово в список
      установить счетчик слова в 1
    end;
  end;
end;
отсортировать список

Основная программа

Создайте новое консольное приложение. Сохраните проект в папке Словарь, при сохранении имя файла-проекта Project1.bdsproj замените на Dictionary.bdsproj.
Добавьте в список используемых модулей модуль Classes. Объявите символьную переменную s и два списка:
  var Source, Words: TStringList;
В самом начале основной программы создайте оба списка, а в конце программы удалите их из памяти.

Теперь прочитаем исходный файл onegin.txt и выведем на экран число строк в нем. Чтобы вывести сообщение по-русски, приходится перекодировать его с помощью функции CharToOEM. Для того, чтобы преобразовать число строк Source.Count в символьную строку, мы используем стандартную функцию IntToStr.

Добавьте в программу код:
  Source := TStringList.Create;
  Source.LoadFromFile ( 'onegin.txt' );
  s := 'Прочитано '+ IntToStr(Source.Count) + ' строк';
  CharToOEM ( PChar(s), PChar(s) );
  Writeln ( s );
  Readln;
Скопируйте в папку Словарь файл onegin.txt. Запустите программу и проверьте ее работу.

Для того, чтобы написать программу на Паскале, нужно научиться разбивать символьную строку на отдельные слова. Договоримся, что словами будем считать группы символов, разделенных пробелами или переходом на новую строку. Для того, чтобы выделить первое слово из строки, нужно найти пробел, выделить часть перед пробелом (очередное слово) и удалить ее из строки. Эти шаги повторяются, пока все слова не будут обработаны (пока строка не станет пустой).

Добавьте в программу основной цикл обработки строк:
  1 for i:=0 to Source.Count-1 do begin
  2   s := Trim(Source[i]);
  3   while s <> '' do begin
  4     p := Pos(' ', s);
  5     if p = 0 then p := Length(s)+1;
  6     AddWord(Copy(s,1,p-1));
  7     s := Trim(Copy(s,p+1));
  8   end;
  9 end;

Для удобства здесь добавлены номера строк. В строке 2 использована стандартная функция Trim, которая удаляет пробелы в начале и в конце строки. В строке 4 функция Pos ищет номер первого пробела в строке, если он не найден, то в строке 5 в переменную p записывается номер символа за границами строки, то есть, считается, что слово продолжается до конца строки.

В строке 6 вызывается процедура AddWord (мы напишем ее позже), которая добавляет слово в список или увеличивает счетчик для этого слова, если оно уже встречалось. Функция Copy вырезает из строки s слово до первого пробела. В строке 7 мы удаляем обработанное слово из строки. Третий параметр функции Copy не указан, это означает «взять все до конца строки».

Процедура AddWord

Процедура AddWord может выглядеть, например, так:
 1 procedure AddWord ( word: string );
 2 var n, count: integer;
 3 begin
 4   if word = '' then Exit;
 5   word := AnsiLowerCase ( word );
 6   n := Words.IndexOf(word);
 7   if n < 0 then
 8      Words.AddObject(word, TObject(1))
 9   else begin
10      count := integer(Words.Objects[n]);
11      Words.Objects[n] := TObject(count+1);
12   end;
13 end;

Параметр word — это очередное обрабатываемое слово. Переменные n и count обозначают соответственно номер этого слова в списке Words и счетчик слов.

В строке 4 обрабатывается ошибочная ситуация, когда процедуре передана пустая строка — в этом случае нужно просто выйти из процедуры с помощью оператора Exit и ничего больше не делать.

Чтобы не различать заглавные и строчные буквы, в строке 5 все заглавные буквы (в том числе и русские!) переводятся в строчные, это делает стандартная функция AnsiLowerCase.

В строке 6 в переменную n записывается номер найденного слова в списке. Если он равен -1, такого слова нет, поэтому далее в строке 8 в список добавляется слово вместе с объектом-счетчиком, равным 1.

Если слово найдено, в строке 10 мы получаем прошлое значение счетчика, и в строке 11 увеличиваем его на 1, записывая новое значение в свойство-массив Objects.

Добавьте процедуру AddWord перед основной программой.

Вывод результата

После окончания основного цикла список составлен, счетчики слов имеют правильные значения, но список не отсортирован. Это можно легко исправить, используя метод Sort:
Words.Sort;

Далее перед каждым словом добавим значение соответствующего счетчика, отделив его от слова символом табуляции, который имеет код 9 (на Паскале этот символ записывается как #9):

for i:=0 to Words.Count-1 do
  Words[i] := IntToStr(integer(Words.Objects[i]))
              + #9 + Words[i];

Остается сохранить готовый список слов в файле:

Words.SaveToFile ( 'words.txt' );
Добавьте приведенный выше код для вывода отсортированного списка в файл. Запустите программу и откройте файл words.txt в программе Блокнот. Все ли вам понравилось?

Замена в символьных строках

Наверное, вы заметили, что в тексте остались знаки препинания, скобки и другие символы, которые нужно удалить для получения правильного результата. Это можно просто сделать с помощью функции StringReplace. Например, код

s := StringReplace ( s, ':', '', [rfReplaceAll] );

удаляет из символьной строки s все двоеточия (заменяя их на пустую строку). Легко догадаться, что первый параметр этой функции — исходная строка, второй — образец для замены, третий — строка, на которую мы меняем образец. Четвертый параметр — это множество режимов работы функции (как и любое множество, оно записывается в квадратных скобках). В данном случае мы задали один специальный режим rfReplaceAll — заменить все двоеточия, а не только первое.

Добавьте код, который удаляет из очередной строки символы .,:;!?"-(). Запустите программу и посмотрите, как изменился результат в файле words.txt.
Сделайте так, чтобы после выполнения основного цикла программа выводила количество найденных слов, например: Найдено 425 слов.
В качестве более серьезного упражнения сделайте так, чтобы слова были отсортированы по убыванию их количества в тексте, а затем (при одинаковом количестве) — по алфавиту.
к началу К началу страницы

 |  | Оглавление |
Введение | Назад | В начало | Вперед | Форма и компоненты


Уроки по Delphi: Форма и компоненты

Тема 3. Форма и компоненты

  1. Форма
  2. Компоненты
  3. Практикум: «Обмен валюты»
  4. Практикум: «Калькулятор»

Вы узнаете:

  • что такое форма и как с ней работать;
  • как выводить сообщения и вопросы пользователю;
  • как работать с компонентами;
  • как задержать выполнение программы;
  • как работать с таймером;
  • как обрабатывать исключительные ситуации.

1. Форма

Новая программа с формой

Теперь мы будем создавать «настоящие» программы для Windows, которые работают в окне. Окно программы в Delphi называется формой.

Форму в средах быстрой разработки приложений (RAD) обычно строят с помощью мыши, визуально (наглядно). Формы и другие элементы интерфейса входят в библиотеку визуальных компонентов VCL = Visual Component Library. Программа, имеющая форму, относится к типу VCL Forms Application.

Создайте новый проект, выбрав тип VCL Forms Application.

В центральной области вы видите форму, ее заголовок по умолчанию — Form1. Здесь же мы будем редактировать код программы, связанный с этой формой.

Для переключения между формой и кодом нужно нажать клавишу F12.

Слева вверху в окне Structure вы видите структуру формы: в данном случае здесь только сама форма, при добавлении элементов интерфейса они будут появляться в этом списке.

Под окном Structure размещено окно инспектора объектов (Object Inspector). На странице Properties (свойства) мы будем изменять свойства формы и ее компонентов. В левом столбце вы видите названия свойств, а в правом — их значения. Для многих свойств значения выбираются из готового списка, некоторые (например, высота и ширина) вводятся вручную. Высоту и ширину формы можно менять мышкой в центральной области, перетаскивая границы формы за маркеры.

На странице Events (события) инспектора объектов мы будем задавать обработчики событий.

Справа вверху находится окно проекта Project Manager. Сейчас мы видим, что проект называется Project1, в результате будет создан исполняемый файл Project1.exe.

В проект пока входит только один модуль Unit1.pas, который связан с формой, эта форма (ее свойства и расположение элементов) хранится в файле Unit1.dfm. Все эти имена присвоены автоматически во время создания формы (по умолчанию), при сохранении проекта их можно изменить.

Программа на Delphi (проект) может состоять из множества файлов. В этом случае в окне Project Manager вы увидите полный список файлов и сможете перейти к любому из них, щелкнув мышкой по его имени.

Справа внизу размещено окно с палитрой инструментов (Tool Palette). В нем вы видите список компонентов, которые можно перетащит на форму (меню, метки, кнопки, списки, поля ввода и др.)

Сохраните все файлы проекта в папке Форма. При сохранении имя Unit1.pas оставьте без изменений, а имя файла-проекта Project1.bdsproj замените на Form.bdsproj. Посмотрите структуру проекта в окне Project Manager.

Свойства формы

Теперь нужно научиться менять свойства формы на странице Properties в окне Object Inspector. Чтобы изменять свойства любого объекта, нужно его сначала выделить. Во-первых, можно щелкнуть по нему мышкой. Во-вторых, можно щелкнуть на имени этого объекта в окне Structure.

В-третьих, иногда бывает удобнее выбрать нужный объект из списка объектов в верхней части окна Object Inspector: Сейчас в этом списке одна только форма с именем Form1. Надпись серым шрифтом TForm1 справа от имени говорит о том, что форма относится к классу TForm1.

По умолчанию свойства разбиты на группы (категории), но удобнее расположить их в алфавитном порядке. Для этого нужно щелкнуть правой кнопкой мыши на названии страницы Properties и выбрать из контекстного меню пункт Arrange - By Name (расположить по имени).

Отсортируйте свойства в Object Inspector по имени. Найдите свойства Name и Caption, сравните их значения.

Скорее всего, два значения оказались одинаковые, равные Form1, однако роль этих свойств совершенно разная. Свойство Name — это имя формы-объекта в программе, имя переменной. По этому имени мы будем обращаться к форме в тексте программы. Имя может состоять только из латинских букв, цифр и знаков подчеркивания (без пробелов), заглавные и строчные буквы не различаются, как и в «обычном» Паскале. Пользователь никогда не видит это имя.

Свойство Caption — это заголовок формы, он выводится в самой верхней части формы — в заголовке окна (по умолчанию — на синем фоне). В нем могут быть любые символы, в том числе русские буквы и пробелы.

Измените свойство Name на MyForm, а свойство Caption на «Моя первая форма». Посмотрите, что изменилось в окне Structure, на самой форме (в центральной части окна) и в списке объектов в окне Object Inspector.

Теперь изменим некоторые другие свойства. Например, свойство Position определяет, как будет показана форма на экране при запуске программы. Наиболее интересный режим — poScreenCenter, что значит «в центре экрана».

Измените свойство Position на poScreenCenter. Установите курсор на значение свойства и вызовите справочную систему, нажав на клавишу F1 — вы увидите описание остальных вариантов на английском языке.
Измените цвет формы на зеленый (свойство Color, значение clLime). Запустите программу и проверьте ее в работе.
Для того, чтобы выбрать цвет в полной палитре (а не только из предлагаемого списка), можно сделать двойной щелчок мышкой на значении цвета. Если выбранный цвет не имеет своего названия, вставляется его шестнадцатеричный код.

Для формы (и для любого ее элемента) можно задать всплывающую подсказку, которая появляется, если навести мышь на объект и немного задержать ее. Для этого нужно записать текст подсказки в свойство Hint и установить свойство ShowHint=True, чтобы разрешить всплывающую подсказку.

Логические свойства, принимающие значения True или False, можно менять двойным щелчком мыши .
Установите для формы всплывающую подсказку с любым текстом. Для изменения формы курсора задайте свойство Cursor=crHandPoint. Запустите программу, обратите внимание на вид курсора мыши, посмотрите работу всплывающей подсказки.

Вы знаете, что каждая программа имеет иконку — значок, который показывается в левом верхнем углу окна. Кроме того, такой же значок вы видите в Проводнике слева от имени программы. Наша программа сейчас имеет стандартную иконку [image], которую Delphi устанавливает по умолчанию. Однако иконку легко сменить, для этого нужен файл в формате .ico, где хранится рисунок размером 32 на 32 пикселя (многие графические редакторы, например, бесплдатная программа IcoFX, умеют работать с таким форматом). Для изменения иконки нужно использовать окно свойств приложения.

Выберите пункт меню Project - Options (или нажмите на клавиши Shift+Ctrl+F11). В левой части появившегося окна выберите раздел свойств Application (приложение), в правой части щелкните по кнопке Load Icon (загрузить иконку) и выберите файл ball.ico. Закройте окно свойств и запустите программу, посмотрите, какая теперь у нее иконка.
Каждая форма может иметь свою собственную иконку, она устанавливается через свойство Icon. Если это свойство не установлено, форма имеет ту же иконку, что и приложение.

Обратите внимание, что мы еще не написали ни одной строчки кода, а у нас уже есть полностью работающая программа со своим окном. В папке Form уже создан исполняемый файл Form.exe, который можно запускать на любом компьютере, даже если на нем не установлена среда Delphi.

Редактируем код

Нажмите на клавишу F12, чтобы увидеть код, связанный с формой.
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes,
  Graphics, Controls, Forms, Dialogs;
type
  TMyForm = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  MyForm: TMyForm;
implementation
{$R *.dfm}
end.

Весь этот код построен автоматически. Слово unit в первой строчке говорит о том, что это не основная программа, а модуль — подключаемый файл, содержащий вспомогательные процедуры и функции. Имя модуля — Unit1, он заканчивается оператором end с точкой.

Далее следуют две секции: interface (интерфейс) и implementation (реализация). Как следует из названия, секция interface служит для «общения» этого модуля с другими. Все, что в ней написано, может быть доступно другому модулю, если модуль Unit1 будет к нему подключен (включен в список uses).

В блоке uses перечислены подключенные стандартные модули. Далее расположены еще два блока: type (объявление новых типов данных) и var (объявление переменных). Кстати, кроме них в блоке const можно описывать и общедоступные константы.

Здесь объявлен один новый тип данных, точнее — класс TMyForm. Запись class(TForm) означает, что он является наследником встроенного класса TForm среды Delphi, который определяет общие свойства и методы форм. После слова private (личный) описываются все закрытые переменные, процедуры и функции формы, к которым можно обращаться только из методов самой формы. Все, что объявлено после слова public — общедоступно. Оператор end заканчивает описание класса.

Вы не должны ничего менять в объявлении класса выше блока private, эта часть кода обрабатывается автоматически.

В данном случае в описании формы нет никаких дополнительных объявлений, это значит, что наша форма не имеет никаких новых свойств и методов в сравнении со встроенным классом TForm.

Далее объявлена одна переменная MyForm (это имя, которое мы вводили в значение свойства Name!) класса TMyForm.

В секции implementation (реализация) тоже можно подключать модули (uses), объявлять новые типы данных (type), а также объявлять константы (const) и переменные (var), но из остальных модулей все это «не видно».

У внимательного читателя должны были возникнуть три вопроса:

  1. где хранятся значения свойств формы, которые мы изменяли?
  2. где находится основная программа?
  3. как и в случае со списком строк, переменная MyForm — это не сама форма, а указатель на нее (адрес); где же создается форма?

Сначала мы ответим на первый вопрос, два оставшиеся будут рассмотрены в следующем пункте.

В секции implementation вставлен комментарий {$R *.dfm}. В теме 2 мы уже упоминали, что если комментарий начинается в первой позиции строки и его второй символ — $, это уже не совсем комментарий, а директива (команда) транслятору. В данном случае она говорит о том, что нужно добавить к программе так называемый ресурсный файл с тем же именем и расширением .dfm, то есть, Unit1.dfm. Этот текстовый файл создан автоматически и лежит рядом с Unit1.pas. Если вы откроете его в Блокноте, то увидите примерно вот это:

object MyForm: TMyForm
  Left = 0
  Top = 0
  Hint = #1069#1090#1086' '#1092#1086#1088#1084#1072'!'
  Caption = #1052#1086#1103' '#1092#1086#1088#1084#1072
  ClientHeight = 166
  ClientWidth = 270
  Color = clLime
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  ShowHint = True
  PixelsPerInch = 96
  TextHeight = 13
end

Здесь в специальном формате перечислены все свойства формы, которые отличаются от заданных по умолчанию. Обратите внимание, что русские буквы в свойствах Hint и Caption записаны в виде двухбайтной кодировки UNICODE.

Чтобы просмотреть DFM-файл, не обязательно запускать Блокнот, это можно сделать из самой среды Delphi.

Щелкните правой кнопкой мыши на форме и выберите команду View as Text из контекстного меню. Сравните DFM-файл с приведенным выше вариантом.
Чтобы снова вернуться к форме, нажмите клавиши Alt+F12. Можно также щелкнуть правой кнопкой мыши на ярлычке Unit1 в окне редактора и выбрать команду View as Form.

В принципе, DFM-файл можно редактировать вручную, но делать это новичкам не рекомендуется, поскольку при ошибочных изменениях форма может вообще не загрузиться.

Основная программа

Вы уже знаете, что у программы на Delphi есть два файла-проекта с расширениями bdsproj и dpr. Когда мы работали с консольными приложениями, мы редактировали именно DPR-файл, это и была основная программа. Заглянув в папку Form в Проводнике вы можете увидеть файл Form.dpr и открыть его в Блокноте. Но есть возможность сделать это прямо из среды Delphi.

Выберите пункт меню Project-View Source.
program Form;
uses
  Forms,
  Unit1 in 'Unit1.pas' {MyForm};
{$R *.res}
begin
  Application.Initialize;
  Application.CreateForm(TMyForm, MyForm);
  Application.Run;
end.

Это и есть основная программа. В начале сказано, что используется стандартный модуль Forms и наш модуль Unit1, сохраненный под именем Unit1.pas. В нем (в секции interface!) определяется форма MyForm.

В основной программе используется объект Application (приложение), он уже объявлен в модуле Forms, один на всю программу. В первой строчке программы выполняется инициализация (установка начальных значений), во второй — создается объект-форма MyForm класса TMyForm, в третьей — программа запускается на выполнение. Это значит, что она передает операционной системе адрес своей оконной процедуры, в которую будут направляться все сообщения, предназначенный нашей программе. И все. Никаких вызовов процедур и функций. Эта программа построена по принципу обработки событий.

Комментарий {$R *.res} говорит о том, что при сборке программы нужно подключить файл ресурсов с тем же именем и расширением .res, то есть Form.res. В этом файле хранятся свойства приложения, например, иконка, которую мы выбрали. Она будет одинаковой для всех форм проекта.

Основная программа редко редактируется вручную, но иногда приходится это делать, когда программисту нужно «что-то особенное».

Закройте окно основной программы, нажав на кнопку с крестиком [image] в правом верхнем углу области редактора (или выберите пункт меню File - Close).

Обработчики сообщений

Обработчики сообщений для формы и других объектов определяются на вкладке Events в окне Object Inspector.

Выделите форму и откройте вкладку Events в окне Object Inspector.

Названия большинства обработчиков начинаются с предлога On (реакция НА событие), например, обработчик OnClick — реакция на щелчок мышкой по форме. Сейчас во втором столбце пусто, потому что ни одного обработчика мы еще не создали.

Сделайте двойной щелчок мышкой по пустой ячейке справа от названия обработчика OnClick.

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

procedure TMyForm.FormClick(Sender: TObject);
begin
|
end;

Заметьте, что процедура называется TMyForm.FormClick, а не просто FormClick. Это говорит о том, что она является методом класса TMyForm, а не независимой процедурой.

У процедуры есть единственный параметр Sender (отправитель), который обозначает объект, отправивший сообщение. В данном случае это будет сама форма.

Одновременно описание (строка-заголовок) этого обработчика добавлено в описание класса TMyForm.

Прокрутите файл в редакторе немного вверх и посмотрите, куда вставлено описание нового обработчика события.
Добавьте в тело обработчика код, изменяющий цвет формы на синий:
  Color := clBlue;
Запустите программу и проверьте ее работу, выполнив щелчок мышкой.
Измените обработчик события OnClick, сделав переключение цвета по щелчку:
if Color = clLime then
       Color := clBlue
  else Color := clLime;
Проверьте работу программы.
Постройте обработчик события OnClose, который вызывается при закрытии формы (если закрывается главная форма программы, вся программа заканчивает работу и выгружается из памяти):
ShowMessage ( 'Конец работы!' );
Проверьте, что будет происходить при закрытии окна.

Процедура ShowMessage выводит сообщение на экран. Обратите внимание, что перекодировать сообщение (как это мы делали для консольных программ) не нужно!

Наведите мышку на имя процедуры ShowMessage и посмотрите, в каком модуле она объявлена. Есть ли этот модуль в списке используемых модулей в начале файла?

Диалог с пользователем

Обработчик события OnCloseQuery тоже вызывается при закрытии формы, перед обработчиком OnClose. Слово query означает «запрос», то есть здесь мы можем разрешить или запретить закрытие формы. Сейчас мы добавим код обработчика, который будет спрашивать пользователя, в самом ли деле он хочет завершить работу программы.

Добавьте обработчик события onCloseQuery:
  procedure TMyForm.FormCloseQuery(Sender: TObject;
            var CanClose: Boolean);
  begin
  if mrYes = MessageDlg('Разрешить выход ?',
               mtConfirmation, [mbYes, mbNo], 0)
    then CanClose := True
    else CanClose := False;
  end;
Запустите программу и посмотрите, что будет происходить при закрытии окна.

Теперь давайте разбираться. Этот обработчик содержит два параметра, Sender и CanClose, причем перед вторым из них стоит ключевое слово var, говорящее о том, что это параметр-результат, его можно менять в процедуре. CanClose — это логическая переменная, причем из названия (can close — можно закрыть) следует, что если в нее записать значение True, форму можно закрывать, а если False — нельзя.

В теле процедуры стоит условный оператор, если условие верно, то он записывает в CanClose значение True, иначе — False. Итак, дело в условии.

Условие непростое, оно содержит вызов встроенной функции MessageDlg (сокращение от message dialog — сообщение с диалогом). Фактически, пользователю предлагается ответить на вопрос, который задается первым параметром.

Второй параметр — тип диалога, он определяет рисунок слева от вопроса. Чаще всего используют типы

  • mtWarning — предупреждение;
  • mtError — сообщение об ошибке;
  • mtConfirmation — подтверждение действия.

Третий параметр функции — это множество кнопок. В данном случае используются кнопки «Да» и «Нет» с английскими надписями «Yes» и «No», их условные обозначения — mbYes и mbNo. К сожалению, сделать русские надписи на кнопках не так просто, но есть несколько решений. Одно из них можно посмотреть на сайте compfaq.ru.

Четвертый параметр — номер раздела справочной системы для этого диалога. У нашей программы справочной системы нет, поэтому ставим 0.

Функция MessageDlg возвращает результат ответа пользователя — код нажатой кнопки. Поскольку мы включили в диалог две кнопки mbYes и mbNo, возможные ответы — это mrYes и mrNo (сочетание mb меняется на mr!).

Итак, смысл обработчика следующий: спросить у пользователя, можно ли закрыть форму. Если он ответил «Нет», записать в переменную CanClose значение True, иначе записать False.

Закройте проект, выбрав команду меню File - Close All.
к началу К началу страницы

2. Компоненты

Палитра компонентов

Теперь будем расставлять на форме компоненты и управлять ими. Компонентами называют блоки кода, которые выполняют некоторые функции. Для пользователя компонент — это «черный ящик», его внутреннее устройство неизвестно; программист работает с компонентом через его свойства и методы.

В Delphi есть видимые компоненты, которые видны на форме во время выполнения (элементы интерфейса с пользователем), и невидимые, которые работают «за кадром». Мы познакомимся и с теми, и с другими.

Палитра компонентов расположена в правом нижнем углу окна (см. рисунок справа). Компоненты разбиты на группы: Standard (стандартные), Additional (дополнительные), Win32 (32-разрядная Windows), System (системные), Dialogs (диалоги) и другие.

Компонентов много, для того, чтобы найти нужный компонент по имени, используют фильтр. Сначала надо щелкнуть по кнопке [image] в верхней строке, затем начать набирать имя компонента. Все имена начинаются с буквы T, ее можно не набирать. По мере ввода новых символов фильтр будет оставлять только те компоненты, которые начинаются с введенной цепочки.

Определите, сколько есть компонентов, имена которых начинаются с «ma».

Скорее всего, вы нашли три компонента: TMainMenu (главное меню) из группы Standard; TMaskEdit (редактор с маской) из группы Additional и TMaster из группы Servers.

Размещение компонентов

В этом примере мы напишем программу, которая моделирует работу светофора на пешеходном переходе с кнопкой вызова (чтобы перейти дорогу, человек должен нажать кнопку).

Создайте новый проект типа VCL Forms Application. Cохраните все файлы проекта в папке Светофор. При сохранении имя файла-проекта Project1.bdsproj замените на Light.bdsproj. Измените заголовок формы на Светофор.

В этой программе нам понадобится 4 компонента: кнопка (типа TButton), прямоугольник и три круга (компоненты TShape — фигуры).

Для того, чтобы добавить компонент, можно просто перетащить его из палитры на форму в нужное место. Вместо этого можно просто сделать двойной щелчок по названию компонента.

Свойства компонента устанавливаются так же, как и для формы. Предварительно компонент нужно выделить одним из трех способов: щелчком мышки на форме, в окне Structure или выбрав из списка объектов в верхней части окна Object Inspector. После этого мышкой можно перемещать компоненты по форме и изменять их размеры за маркеры на рамке.

Любой компонент имеет имя — свойство Name, по которому мы будем в программе обращаться к свойствам и методам этого компонента.

Добавьте на форму кнопку TButton (группа Standard), дайте ей имя btnGo (свойство Name) и измените текст на «Перейти дорогу» (свойство Caption). Разместите кнопку в нужном месте и отрегулируйте ее размеры.

Теперь будем добавлять фигуры. Чтобы изменить фон, используем сложное свойство Brush. Если раскрыть его щелчком по значку [image] (или двойным щелчком), мы увидим, что оно состоит из двух подсвойств: Color (цвет) и Style (стиль). Для обращения к этим свойствам используют запись Brush.Color и Brush.Style.

Добавьте на форму фигуру TShape (группа Additional) в виде высокого прямоугольника, установите для него черный цвет фона (Brush.Color=clBlack). Посмотрите, какие бывают стили заливки.

Остальные фигуры — круги. Для того, чтобы изменить тип фигуры, используют свойство Shape.

Добавьте на форму еще одну фигуру, сделайте ее кругом (Shape=stCircle) и присвойте имя shRed. Установите красный цвет заливки и попытайтесь разместить ее на месте красного сигнала.

Наверное, вы уже заметили, что при перетаскивании фигура «прилипает» к узлам сетки. Это специальный режим, позволяющий быстро и просто выравнивать объекты. Однако сейчас он только мешает, поскольку не позволяет правильно разместить красный круг. Для точного расположения выделенной фигуры можно перемещать ее клавишами-стрелками при нажатой клавише Ctrl.

Чтобы отключить выравнивание по сетке совсем, нужно войти в меню Tools - Options (Инструменты - Настройки), выбрать в левой части группу настроек Environment Options - VCL Designer (Среда - Визуальное проектирование) и в правой части снять флажок Snap to grid (Привязать к сетке).
Добавьте на форму еще одну фигуру, установите для нее серый цвет заливки (clGray) и имя shYellow. Разместите этот круг на месте желтого сигнала. Проверьте, чтобы у двух кругов были одинаковые размеры (свойства Width — ширина, и Height — высота).

Третий круг мы построим с помощью копирования.

Выделите второй круг и скопируйте его в буфер обмена, нажав клавиши Ctrl+C (или выбрав пункт верхнего меню Edit - Copy). Затем вставьте копию из буфера обмена (клавиши Ctrl+V или меню Edit - Paste). Дайте новой фигуре имя shGreen.

Для того, чтобы ровно разместить фигуры относительно друг друга, нужно выделить их, последовательно щелкая на каждой при нажатой клавише Shift, нажать на одной из фигур правую кнопку мыши и выбрать пункт Position - Align из контекстного меню. Вы увидите такое окно:

Здесь в левой части устанавливается тип горизонтального выравнивания, а в правой части — выравнивание по вертикали. Для тех, кто слаб в английском, переведем:

No change — нет изменений

Left sides — по левой границе

Centers — по центру

Right sides — по правой границе

Tops — по верхней границе

Bottoms — по нижней границе

Space equally — распределить равномерно

Center in window — по центру окна

Выделите все круги и выровняйте их, установив такие режимы, как на рисунке выше. Если нужно, отрегулируйте размер прямоугольника.
Измените размеры окна, чтобы слева и снизу не оставалось много пустого места. Чтобы запретить изменение размеров мышкой, установите для формы свойство BorderStyle=bsSingle. Запустите программу и убедитесь, что размеры окна не меняются (однако, можно развернуть его на весь экран).

Обработчики сообщений

Теперь пришло время заняться алгоритмом переключения сигналов светофора. После щелчка по кнопке сначала должен загореться желтый свет (точнее, красный с желтым одновременно), затем — зеленый, потом опять красный.

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

shRed.Brush.Color := clGray;

Это значит (запись с несколькими точками «читаем» с конца) «цвет кисти фигуры shRed будет серый».

Предположим, что желтый загорается на 1 секунду, а зеленый горит 4 секунды. Чтобы организовать задержку на заданное время, используют процедуру sleep, параметром которой является время в миллисекундах. Команда

sleep ( 1000 );

останавливает программу ровно на 1 секунду. В это время программа не реагирует на действия пользователя и не обрабатывает сообщения (то есть, «висит»).

Выделите кнопку и определите для нее обработчик события OnClick:
  shYellow.Brush.Color := clYellow;
  sleep(1000);
  shRed.Brush.Color := clGray;
  shYellow.Brush.Color := clGray;
  shGreen.Brush.Color := clLime;
  sleep(4000);
  shGreen.Brush.Color := clGray;
  shRed.Brush.Color := clRed;
Каждый компонент имеет обработчик по умолчанию, который открывается при двойном щелчке на этом компоненте. Для кнопки это обработчик OnClick, поэтому для создания обработчика можно было просто дважды щелкнуть по кнопке на форме.
Попробуйте сделать двойной щелчок по кнопке.

Когда светофор работает, нужно заблокировать кнопку от повторного нажатия. Это можно сделать с помощью свойства Enabled. Если оно имеет значение True, кнопка работает, если False — кнопка неактивна.

Добавьте в начало и в конец обработчика соответственно команды
  btnGo.Enabled := False;
  ...
  btnGo.Enabled := True;
Запустите программу и посмотрите, как она работает.

Кажется, что команды изменения цвета кругов не выполняются, хотя кнопка блокируется на 5 секунд (становится серой и неактивной). Кроме того, в течение этих 5 секунд программа вообще не реагирует на действия пользователя (например, ее окно не закрыть). Что же происходит?

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

Таким образом, после команды на изменение цвета кругов (перед sleep!) нужно дать программе «вздохнуть», то есть обработать поступившие к этому моменту сообщения. Для этого у глобального объекта Application есть метод ProcessMessages.

Добавьте перед каждой командой sleep строчку
  Application.ProcessMessages;
Запустите программу и оцените результат.

Таймер

Хотя вроде бы светофор теперь работает как надо, вы могли заметить, что при выполнении команды sleep программа все равно не реагирует на действия пользователя. Как этого избежать? Хотелось бы, чтобы программа включалась в работу только тогда, когда надо менять состояние светофора, а все остальное время находилась в режиме ожидания (а не в «спящем» состоянии!).

Светофор — это автомат, который (в нашей задаче) может находиться в трех состояниях:

  1. горит только красный;
  2. горят красный и желтый;
  3. горит зеленый.

Переход из состояния 1 в состояние 2 происходит при нажатии кнопки; переход из состояния 2 в состояние 3 — автоматически через 1 секунду, и переход из состояния 3 обратно в состояния 1 также автоматически через 4 секунды. Эти переходы показаны на рисунке (время дано в миллисекундах):

С переходом 1—2 вроде бы все ясно, его нужно сделать в обработчике события OnClick для кнопки. Для того, чтобы выполнить переходы по времени, нам нужно, чтобы через заданное время кто-то прислал нашей программе соответствующее сообщение. Это может сделать только операционная система, которая 1 раз примерно в 55 мс посылает сообщение WM_TIMER всем программам.

К счастью, в Delphi уже есть компонент, который делает работу с таймером простой и удобной. Он называется TTimer (группа System). У него два основных свойства:

  • Enabled — разрешить/запретить (True/False);
  • Interval — время срабатывания с миллисекундах.

и единственное событие OnTimer, которое срабатывает тогда, когда заданный интервал истек. Во время выполнения программы компонент TTimer на форме не виден.

Добавьте на форму компонент TTimer и установите его свойство Enabled=True.
Объявите целую переменную state, которая будет хранить состояние светофора в данный момент, и массив times, куда мы запишем время ожидания для второго и третьего состояния:
  state: integer;
  times: array[2..3] of integer = (1000, 4000);
Оставьте в обработчике OnClick кнопки только код перехода 1-2 и запуск таймера с интервалом для перехода 2-3:
  btnGo.Enabled := False;
  shYellow.Brush.Color := clYellow;
  state := 2;
  Timer1.Interval := times[2];
  Timer1.Enabled := True;
Сделав двойной щелчок на компоненте-таймере на форме, создайте обработчик события OnTimer и добавьте в него код переключения сигналов светофора:
  case state of
    2: begin
         shRed.Brush.Color := clGray;
         shYellow.Brush.Color := clGray;
         shGreen.Brush.Color := clLime;
       end;
    3: begin
         shGreen.Brush.Color := clGray;
         shRed.Brush.Color := clRed;
         Timer1.Enabled := False;
         btnGo.Enabled := True;
       end;
  end;

Обратите внимание, что после перехода из третьего состояния в первое таймер выключается и кнопка становится активной.

В конец того же обработчика добавьте код для перехода к следующему состоянию и установки нового интервала ожидания:
  if state < 3 then begin
    state := state + 1;
    Timer1.Interval := times[state];
  end;
Запустите программу и проверьте ее работу. Можно ли сейчас закрыть окно программы во время ожидания?
Посмотрите на объявление класса формы в самом начале кода программы. Куда система поместила список добавленных компонентов?

Еще раз напомним, что эту часть кода (выше блока private) менять нельзя, она обрабатывается автоматически.

В качестве упражнения измените программу так, чтобы переход от зеленого к красному выполнялся через мигающий зеленый.
к началу К началу страницы

3. Практикум: «Обмен валюты»

Размещение компонентов

Теперь мы познакомимся с компонентами для ввода и вывода текстовой информации. В результате у нас получится программа, которая умеет переводить суммы из рублей в доллары и евро.

Создайте новый проект и сохраните все файлы в папке Обмен. При сохранении имя файла-проекта замените на Exchange.bdsproj. Измените заголовок формы на «Обмен валюты».

Далее мы изменим размер шрифта. Шрифт переводится на английский как Font. Посмотрев в список свойств в Object Inspector, мы найдем сложное свойство Font, которое раскрывается при щелчке по значку [image]. Сразу возникает вопрос: «Зачем форме шрифт, ведь на ней нет текста»? Дело в том, что все компоненты, которые мы разместим на форме, наследуют те характеристики шрифта, которые мы установим для формы. Конечно, шрифт можно задать и индивидуально для каждого из компонентов.

Сделаем шрифт покрупнее — зададим ему размер (свойство Font.Size) 10 пт. Кроме того, изменим свойство Font.Charset (набор символов) на RUSSIAN_CHARSET. Значение, установленное по умолчанию (DEFAULT_CHARSET) означает, что набор символов будет зависеть от версии Windows: в английской версии будет установлена английская таблица и русские буквы могут выводиться неправильно.

Измените свойства шрифта для формы:
Font.Size = 10,
Font.Charset = RUSSIAN_CHARSET.

Самый простой элемент интерфейса — текстовая метка TLabel (группа Standard). Пользователь не может изменять ее, но из программы это можно сделать с помощью свойства Caption.

Добавьте на форму метку TLabel, установите ее свойства
Name = RateLabel,
Caption = «Текущий курс: 1 доллар = 33,573 рубля».
Добавьте еще одну метку, измените ее текст на «Сумма: ».

Для ввода данных удобно использовать однострочный текстовый редактор TEdit (группа Standard). Текст в этом поле доступен через свойство Text, его можно изменять из программы (устанавливая новое значение) и читать (то, что ввел пользователь).

Добавьте поле ввода TEdit, измените его имя на RubEdit, а текст — на 100. Выровняйте поле так, чтобы нижняя граница текста у поля и метки слева совпадали (при совпадении двух уровней вы увидите соответствующую фиолетовую линию).
Добавьте еще одну метку с текстом «руб. = », а затем справа — еще одну метку (с именем CurLabel и текстом «?») и третью метку с текстом «долларов».
Для метки CurLabel установите синий цвет текста и жирный шрифт (раскройте сложное свойство Font.Style и установите значение fsBold=True).
Свойство Font.Style — это множество, в которое могут входить fsBold (жирный), fsItalic (курсив), fsUnderline (подчеркивание) и fsStrikeOut (вычеркивание) в любых сочетаниях.

Теперь мы добавим кнопку, которая будет «запускать» процесс перевода валюты (чуть позднее вы увидите, что эта кнопка не очень-то и нужна).

Кнопка TBitBtn (группа Additional) может (в отличие от обычной кнопки TButton) содержать не только текст, но и картинку, которую можно загрузить из файла (изменив свойство Glyph). Кроме того, есть несколько стандартных кнопок с готовыми картинками («ОК», «Отмена», «Да», «Нет» и другие). Для использования одной из них нужно выбрать соответствующий вариант в свойстве Kind (вид, режим), и ее рисунок автоматически загрузится в свойство Glyph.

Добавьте справа от всех меток кнопку TBitBtn и установите ее свойства:
Name = btnGo
Kind = bkOK
Caption = «Перевести».

Преобразование данных

Когда пользователь щелкнет по кнопке, нужно прочитать введенное значение суммы из поля RubEdit, разделить это число на курс доллара и вывести результат на метку CurLabel.

Дважды щелкните по кнопке и введите код обработчика события OnClick:
  procedure TForm1.btnGoClick(Sender: TObject);
  var rub, cur: double;
  begin
     rub := RubEdit.Text;
     cur := rub / 33.573;
     CurLabel.Caption := cur;
  end;

Здесь вещественные переменные rub и cur с двойной точность (тип double) обозначают суммы в рублях и в долларах.

Попытка запустить эту программу приведет к ошибке в строке

rub := RubEdit.Text;

Обратите внимание, что эта ошибка не была обнаружена до запуска программы. Сообщение об ошибке можно прочитать в окне Messages (Сообщения) в нижней части окна Delphi:

Выражение Incompatible types переводится как Несовместимые типы данных. Это значит, что мы хотим записать в какую-то переменную значение, которое отличается по типу. Обратите внимание, что таких ошибок две — вторая произошла двумя строчками ниже.

Дело в том, что rub и cur — вещественные переменные, в них можно записывать числа. В то же время RubEdit.Text и CurLabel.Caption — символьные строки, поэтому мы получили то, что и должны были получить.

Ясно, что нам нужно как-то преобразовать строку в вещественное число (сумма пользователя в рублях) и обратно (вывод результата). К счастью, в Delphi уже есть готовые функции для этого, они называются StrToFloat и FloatToStr.

Для целых чисел есть аналогичные функции StrToInt и IntToStr.
Замените ошибочные строки:
  rub := StrToFloat(RubEdit.Text);
  ...
  CurLabel.Caption := FloatToStr(cur);
Запустите программу и проверьте ее работу. Что вам не понравилось?

Обычно нужно знать только два знака в дробной части результата (доллары и центы), поэтому хочется как-то округлить число до двух цифр после запятой. Это можно сделать с помощью функции Format:

CurLabel.Caption := Format('%0.2f', [cur]);

Первый параметр — это специальная форматная строка, второй — множество выводимых значений. Для каждого из этих значений в первой строке должен быть определен формат вывода, начинающийся знаком %. У нас одно число, формат %0.2f означает, что в этом месте нужно вывести вещественное число, оставив только два знака в дробной части и использовав минимально возможное число позиций для вывода целой части.

Если вы знакомы с языком Си, функция Format явно напомнит вам функцию printf языка Си.

Исправьте строчку, где выводится результат. Проверьте работу программы.

По умолчанию свойство метки AutoSize (автоматическое изменение размеров) имеет значение True, то есть метка сужается или расширяется в зависимости от текста. При этом она может залезать на следующую метку.

Установите для свойства AutoSize метки CurLabel значение False. Увеличьте ширину метки так, чтобы она заканчивалась немного не доходя надписи «долларов».

Хотелось бы, чтобы полученная сумма в пределах поля метки была выровнена вправо. Для этого используем свойство метки Alignment, которое может принимать одно из трех значения:

  • taCenter — по центру
  • taLeftJustify — влево
  • taRightJustify — вправо
Установите для свойства Alignment метки CurLabel значение taRightJustify. Проверьте работу программы.

Теперь попробуем усовершенствовать программу. Во-первых, в ней два раза встречается курс доллара (в тексте метки RateLabel и в обработчике OnClick). Мы сделаем так, чтобы курс задавался переменной Rate.

Объявите новую переменную:
  Rate: double = 33.573;
и измените строчку, где вычисляется результат:
cur := rub / Rate;
Запустите программу и убедитесь, что результат не изменился.

Осталось понять, как связать это значение с меткой. Логика подсказывает, что нужно изменять текст метки прямо в программе, используя значение переменной Rate:

RateLabel.Caption := 'Текущий курс: 1 доллар = '
                     + FloatToStr(Rate) + ' руб.';

Где поставить эту строчку? Естественно, метку нужно подготовить до того, как форма появится на экране. У формы есть обработчик OnCreate, который вызывается тогда, когда форма уже создана, но еще не показана на экране. Туда и нужно поставить приведенную выше строчку.

Выделите форму и создайте обработчик OnCreate так, как описано в предыдущих абзацах. Измените курс доллара в программе и проверьте ее работу. Изменился ли курс на метке?

«Защита от дурака»

Для серьезной программы недостаточно, что она правильно работает на правильных исходных данных. Важно, чтобы при вводе всякой чуши программа не завершалась аварийно. Кроме того, желательно обработать ошибку и, если ее нельзя игнорировать, выдать пользователю внятное сообщение на родном языке. Это называется «защита от дурака» (foolproof).

Запустите программу, попробуйте ввести буквы вместо цифр и щелкните по кнопке.

Вы должны увидеть окно с сообщением об ошибке: «... is not a valid floating point value», где вместо многоточия будет стоять введенный вами текст. В переводе это значит «некорректное вещественное число». Понятно, что ошибка возникла при вызове функции StrToFloat. Как ее перехватить?

В Delphi есть так называемый механизм исключений, который позволяет обрабатывать практически все возможные ошибки. Для этого все «опасные» участки кода (на которых может возникнуть ошибка) нужно взять в блок try - except:

try
  «опасные» команды
except
  обработка ошибки
end;

Слово try означает попытаться, except — исключение (исключительная или ошибочная, непредвиденная ситуация). Программа попадает в блок except - end только тогда, когда между try и except произошла ошибка. В случае ошибки мы запишем вместо результата знак вопроса.

Измените код обработчика:
try    rub := StrToFloat(RubEdit.Text);
    cur := rub / Rate;
    CurLabel.Caption := Format('%0.2f', [cur]);
  except
    CurLabel.Caption := '?';
  end;
Запустите программу и проверьте ее работу при вводе нецифровых символов.

Есть и еще один метода защиты — разрешить только ввод цифр, а все остальные символы игнорировать. Оказывается, это совсем несложно сделать. Когда пользователь нажмет клавишу в поле ввода, возникает событие OnKeyPress, которое можно перехватить, установив соответствующий обработчик. В нем все «ненужные» символы заменяются символом с кодом 0, который не изменяет содержимое поля.

Создайте для поля ввода RubEdit обработчик сообщения OnKeyPress:
  procedure TForm1.RubEditKeyPress(Sender: TObject;
                                   var Key: Char);
  begin
    if not (Key in ['0'..'9',#8]) then Key := #0;
  end;
Запустите программу и попробуйте вводить буквы.

Разберемся, как это работает. Обратите внимание, что второй параметр обработчика объявлен как var Key, то есть, его можно менять в процедуре. В квадратных скобках записывают множество значений, причем '0'..'9' означает интервал: все символы от '0' до '9'. Символ с кодом 8, обозначаемый как #8 — это возврат каретки (Backspace, удаление символа слева от курсора). Если Key — не цифра и не возврат каретки, в эту переменную записывается 0 — нажатие клавиши игнорируется.

Оптимизация

Теперь подумаем, а нужна ли нам кнопка? Мы заставляем пользователя каждый раз при вводе суммы выполнять лишнее действие — щелчок мышью по кнопке. Было бы неплохо, если бы пересчет выполнялся автоматически при каждом изменении содержимого поля ввода.

Соответствующее событие называется OnChange, осталось его перехватить и написать обработчик. Но что будет делать этот обработчик? Очевидно, что то же самое, что и обработчик OnClick для кнопки. Поэтому можно назначить двум обработчикам одинаковый адрес!

Выделите поле ввода и перейдите на страницу Events в окне Object Inspector. Щелкните мышью в пустом поле для значения, откройте выпадающий список (в нем перечислены все подходящие процедуры-обработчики) и выберите btnGoClick. Проверьте работу программы.

Осталась одна проблема — при запуске программы значение не пересчитывается, в метке появляется знак вопроса. Выход — вызвать обработчик btnGoClick при создании формы, в ее обработчике OnCreate. Этот метод имеет один параметр типа TObject — объект, посылающий сообщение. В данном случае сообщение посылает форма, поэтому можно написать

btnGoClick ( Self );

Здесь Self означает «тот объект, в методе которого мы работаем». Все процедуры-обработчики являются методами формы, поэтому Self — это форма.

Поскольку параметр Sender вообще не используется в методе btnGoClick, туда можно теоретически подставить любой адрес, например, нулевой:

btnGoClick ( nil );

При этом результат не изменится.

Дополните обработчик OnCreate, чтобы сумма в долларах пересчитывалась в начале работы программы.

А теперь подумайте, зачем нам нужна кнопка?

Удалите кнопку с формы и проверьте работу программы.

Список

Попробуем сделать так, чтобы программа переводила в две валюты: доллары и евро, причем вид валюты должен выбрать пользователь.

Удалите метку с надписью «долларов» и на ее место поместите список TComboBox (группа Standard) и дайте ему имя cbCurrency.
Щелкните дважды по значению свойства Items (элементы). В появившемся окне введите в первую строчку «долларов», а во вторую — «евро». Установите значения свойств:
ItemIndex = 0
Style = csDropDownList

Свойство ItemIndex обозначает номер выбранного элемента. Элементы списка нумеруются с нуля, поэтому по умолчанию будет выбран вариант перевода в доллары. Стиль csDropDownList означает, что пользователь не может сам добавлять элементы в список, он может только выбрать один из заранее заданных вариантов.

Понятно, что при смене валюты нужно поменять курс и текст метки CurLabel. Для этого мы напишем отдельную процедуру, которую удобно описать прямо в классе TForm1.

Добавьте после слова public в описании класса TForm1 объявление процедуры
  public
    procedure ChangeCurrency;

Поскольку эта процедура описана в классе, ее полное имя должно быть TForm1.ChangeCurrency. Будучи методом класса, она может свободно обращаться ко всем элементам формы.

В секции implementation добавьте код процедуры
  procedure TForm1.ChangeCurrency;
  var curName: string;
  begin
     if cbCurrency.ItemIndex = 0 then begin
        Rate := 33.573;
        curName := 'доллар';
     end
     else begin
        Rate := 45.236;
        curName := 'евро';
     end;
     RateLabel.Caption := 'Текущий курс: 1 '
           +  curName + ' = '
           + FloatToStr(Rate) + ' руб.';
     btnGoClick(nil);
  end;

Текст этой процедуры достаточно понятен. Локальная переменная curName (она известна только внутри этой процедуры) обозначает название валюты (символьная строка). Если в списке валют выбран элемент 0 (cbCurrency.ItemIndex=0), нужно установить перевод в доллары, иначе — в евро.

Процедуру ChangeCurrency нужно вызывать в двух ситуациях: при создании формы (начальный выбор валюты) и при выборе нового элемента в списке (событие OnClick).

Из обработчика OnCreate удалите все, что там было, и добавьте вызов процедуры ChangeCurrency. Такой же вызов добавьте в обработчик OnClick для выпадающего списка. Отрегулируйте размеры формы до минимально необходимых и проверьте работу программы. После этого закройте все файлы проекта.
Добавьте в программу возможность перевода в какую-нибудь третью валюту.
В качестве более сложного упражнения добавьте возможность обратного перевода (из долларов в рубли и т.п.).

к началу К началу страницы

4. Практикум: «Калькулятор»

Размещение компонентов

В этом разделе мы построим простейший калькулятор. Он не будет самым мощным из существующих, но на его примере мы разберем несколько новых приемов практического программирования.

Создайте новый проект с формой, сохраните его в новой папке Калькулятор под именем Calc.bdsproj. Установите для формы заголовок «Calc» и размер шрифта 10 пт.
Разместите на форме одно поле ввода, дайте ему имя Disp и очистите свойство Text.

Для калькулятора мы будем использовать еще один вид кнопок — TSpeedButton (группа Additional). Их отличие от уже рассмотренных ранее классов TButton и TBitBtn состоит в том, что они не получают фокуса ввода клавиатуры. Это значит, что их нельзя сделать активными, переходя от элемента к элементу с помощью клавиши Tab, и вообще их можно привести в действие только мышкой. Таким образом, на форме будет всего один компонент, принимающий сигналы с клавиатуры — это поле ввода.

Для того, чтобы настраивать свойства сразу нескольких компонентов, нужно выделить их все при нажатой клавише Shift и после этого изменять свойства в окне Object Inspector.
Добавьте на форму 12 кнопок TSpeedButton для цифр, арифметических действий, знака «равно» и операции «сброс». Установите для всех кнопок размеры 25 на 25 пикселей и разместите их так, как на образце.
Дайте кнопкам-действиям имена btnPlus, btnMinus, btnMul, btnDiv (сложение, вычитание, умножение и деление), а кнопке «равно» — имя btnCalc.
Для действий и кнопки «С» установите жирный шрифт, а для кнопки «С» дополнительно — красный цвет шрифта.

Ввод данных

Кнопка «С» должна просто стирать содержимое поля ввода Disp. То есть, нужно вызвать метод Disp.Clear.

Добавьте обработчик события OnClick для кнопки «С».

Понятно, что когда пользователь щелкнул по кнопке-цифре, нужно добавить эту цифру в конец текста поля ввода. Конечно, можно для каждой кнопки написать обработчик типа

Disp.Text := Disp.Text + '1';

Однако можно сделать и более грамотно, определив для всех кнопок общий обработчик. Заметим, что цифра, которую нужно добавить, это и есть текст кнопки, ее свойство Caption. Таким образом, когда мы назначим всем кнопкам один единственный обработчик, в нем нужно как-то определить, какую именно кнопку мы нажали. А такая возможность есть, ведь обработчик имеет параметр Sender — это адрес компонента, от которого пришло сообщение!

Выделите все кнопки-цифры и определите для них общий обработчик события onClick:
Disp.Text := Disp.Text + Sender.Caption;
Попытайтесь запустить программу.

Скорее всего, вы получили сообщение об ошибке:

Фраза Indecrared identifier означает Необъявленное имя, то есть, по мнению Delphi объект Sender не имеет свойства Caption. Как же так?

Обратим внимание на то, что параметр Sender объявлен в заголовке процедуры как переменная типа TObject, а объект TObject действительно не имеет свойства Caption (это можно проверить по справочной системе).

Но мы знаем, что в нашей программе все компоненты, которые могут вызвать этот обработчик, относятся к типу TSpeedButton, у которого свойство Caption есть. Поэтому можно сказать, что программа должна воспринимать параметр Sender не как просто объект, а как TSpeedButton:

Disp.Text := Disp.Text + TSpeedButton(Sender).Caption;
Исправьте код и запустите программу. Проверьте, как она работает при вводе текста и букв с клавиатуры и с помощью мыши. Что вам не понравилось?

Во-первых, вы увидели, что с клавиатуры можно ввести буквы, которые нам совсем не нужны. Вы уже знаете, как решить эту проблему — нужно поставить обработчик события OnKeyPress для поля ввода, как в предыдущей программе.

Заблокируйте ввод неверных символов с клавиатуры. Проверьте работу программы.

Второй недостаток, который вы могли заметить — при щелчке по кнопке курсор сразу перемещается в начало числа, а хотелось бы, чтобы он оставался справа.

В принципе, можно совсем отключить курсор, но это не так легко. Профессиональные решения этой задачи можно найти в Интернете.

Мы сделаем так, чтобы при вводе очередной цифры с помощью кнопки курсор автоматически переводился в конец числа. У компонента TEdit есть свойства SelStart и SelLength, которые определяют начало и длину выделенной части (она обычно обозначается синим фоном). Если SelLength=0 (ничего не выделено), тогда SelStart указывает на позицию курсора в поле ввода. Если установить это свойство равным длине текста, курсор встанет сразу за последним символом.

Добавьте в конец обработчика события OnClick код
Disp.SelStart := Length(Disp.Text);
Проверьте работу программы.

Вычисления

Теперь нужно организовать вычисления. Нам нужны две переменных для хранения чисел и одна символьная переменная, в которую будем записывать тип операции. Поскольку при расчетах могут получиться числа с дробной частью, для хранения чисел будем использовать вещественные (а не целые!) переменные.

В простейшем случае для хранения операции можно использовать переменную типа char (один символ), но мы объявим ее как символьную строку, так как при доработке программы могут понадобиться и многосимвольные названия операций, например, sin.

Объявите в начале программы две вещественных переменные x1 и x2 типа double и одну символьную строку oper.

Когда мы нажимаем на одну из кнопок-операций, нужно запомнить введенное число в переменной x1 и тип операции в переменной oper. Тип операции (надпись на кнопке) легко узнать, обратившись к свойству Caption. Поэтому можно установить для всех кнопок-операций один обработчик.

Хочется написать так:

x1 := StrToFloat(Disp.Text);
oper := Sender.Caption;

Однако вторая строчка вызовет ошибку, ее причины мы только что выяснили.

Выделите все кнопки-операции и создайте для них один обработчик события OnClick, исправив ошибку в приведенном выше коде.

При нажатии на кнопку «равно» нужно прочитать из поля ввода второе число и выполнить операцию. При этом первое число и тип операции уже должны находиться в переменных x1 и oper. Поскольку в результате деления может получиться число с дробной частью, переменная для хранения результата (назовем ее res) тоже должна быть вещественной.

Введите обработчик события OnClick для кнопки «равно»:
  var res: double;
  begin
    x2 := StrToFloat(Disp.Text);
    if oper = '+' then res := x1 + x2;
    if oper = '-' then res := x1 - x2;
    if oper = '*' then res := x1 * x2;
    if oper = '/' then res := x1 / x2;
    Disp.Text := FloatToStr(res);
  end;
Запустите программу и проверьте ее работу. Учтите, что для ввода второго числа нужно сначала очистить экран кнопкой «С» (позже мы исправим это неудобство).

Совершенствование

Теперь проверим «защиту от дурака».

Закройте окно программы и затем запустите ее заново. Введите какое-нибудь число, а затем сразу щелкните по кнопке «равно». Что получилось? Попытайтесь объяснить этот эффект, учитывая, что при создании глобальной символьной переменной oper в нее записана пустая строка.

Наверняка вы догадались, что проблема вызвана тем, что мы еще не задали операцию, а уже попытались что-то вычислить. При этом ни один условный оператор не сработал, и значение переменной res не изменялось. А что же в ней было? Откуда взялось это число?

Переменная res объявлена в процедуре, то есть, она локальная. Память для локальных переменных выделяется в стеке при каждом новом вызове процедуры, причем эти ячейки не обнуляются. Поэтому в переменной res в нашем последнем эксперименте остался «мусор» — постороннее значение, которое программа и вывела на экран.

Если вы были очень внимательны, можно было заметить, что при трансляции программы в окне Message (в нижнем левом углу) было выдано предупреждение:

Это значит «Переменной res, возможно, не будет присвоено никакого значения». Формально это не ошибка, и программа может запуститься, однако к предупреждениям нужно относиться внимательно, потому что они могут указать на скрытые логические ошибки (ошибки в алгоритме), как в нашем случае.

Какой же выход? Проще всего сделать так: если в переменной oper записана пустая строка, мы просто выйдем из процедуры с помощью оператора Exit.

Добавьте в начало обработчика события OnClick для кнопки «равно» строчку
  if oper = '' then Exit;
Проверьте работу программы. Что вам не нравится?

Конечно, очень неудобно, что перед вводом второго числа нужно очищать поле ввода, нажимая на кнопку «С». Хотелось бы делать это автоматически.

Запустите стандартную программу Калькулятор и посмотрите, в какой момент стирается первое число.

Наверное, вы увидели, что число из поля ввода автоматически стирается, когда после нажатия на кнопки-действия или кнопку «равно» пользователь набирает новое число. Мы введем логическую переменную newNumber, которой будем присваивать значение True в том случае, если нужно начинать вводить новое число.

Объявите глобальную логическую переменную newNumber. В конце обработчиков события OnClick для кнопок-действий и кнопки «равно» добавьте строку
newNumber := True;

Очистку экрана будем делать перед вводом нового числа. Во-первых, дополним обработчики кнопок-цифр.

В начало обработчика события OnClick для кнопок-цифр добавьте код
  if nextNumber then begin
     Disp.Clear;
     nextNumber := False;
  end;

Кроме того, нужно учесть, что пользователь может набирать число на клавиатуре. Поэтому нужно дополнить аналогичным кодом обработчик события OnKeyPress для поля ввода. Теперь он должен выглядеть так:

if not (Key in ['0'..'9',#8]) then Key := #0
else
  if nextNumber then begin
    Disp.Clear;
    nextNumber := False;
  end;

Измените обработчик события OnKeyPress так, как показано выше. Запустите программу и проверьте ее. Можно ли ввести знаки сложения, вычитания, умножения и деления с клавиатуры? Выполняется ли действие при нажатии на клавишу Enter?

Наверное, вы увидели, что пока программа позволяет вводить с клавиатуры только цифры, но не коды действий. Чтобы исправить этот недостаток, мы дополним обработчик OnKeyPress.

Что нужно сделать, когда нажали символ + на клавиатуре? Проще всего имитировать щелчок по кнопке, то есть, вызвать процедуру-обработчик события OnClick для кнопок-действий. Предположим, что этот обработчик называется btnDivClick (у вас он может называться по-другому). Но этой функции нужно передать параметр — адрес объекта, пославшего сообщение. Этот объект— кнопка btnPlus, поэтому при нажатии клавиши + нужно выполнить команду

btnDivClick ( btnPlus );

Обработка нажатия других клавишей строится аналогично. При нажатии клавиши Enter, имеющей код 13 (он обозначается в программе как #13), нужно вызвать обработчик события OnClick для кнопки «равно» (она должна называться btnCalc).

Измените код обработчика OnKeyPress следующим образом:
  if not (Key in ['0'..'9',#8]) then begin
    case Key of
     '+': btnDivClick(btnPlus);
     '-': btnDivClick(btnMinus);
     '*': btnDivClick(btnMul);
     '/': btnDivClick(btnDiv);
     #13: btnCalcClick(btnCalc);
    end;
    Key := #0;
  end
  else
    if nextNumber then begin
      Disp.Clear;
      nextNumber := False;
    end;
Запустите программу и проверьте ее работу. После этого закройте проект.

Совершенствовать даже такую простую программу можно бесконечно. Например, вы можете добавить возможность ввода дробных чисел, новые операции и т.д.

к началу К началу страницы

 |  | Оглавление |
Консольные программы | Назад | В начало | Вперед | Графика и управление


Уроки по Delphi: Графика и управление

Тема 4. Графика и управление

  1. Рисование на холсте
  2. Сохранение рисунка
  3. Броуновское движение
  4. Клавиатура
  5. Мышь

Вы узнаете:

  • что такое холст и как на нем рисовать;
  • как работать с отдельными пикселями;
  • как рисовать отрезки, прямоугольники и др.;
  • как работать с рисунком в памяти (TBitmap);
  • как организовать ввод данных пользователем;
  • как обрабатывать сообщения клавиатуры;
  • как перетаскивать объекты мышью;
  • как менять размеры объекта мышью.

1. Рисование на холсте

Пиксели и цвет

У вас наверняка возник вопрос: «А как в программах для Windows выполняется рисование»?

Каждый компонент, на котором можно нарисовать, имеет свойство Canvas (холст), с помощью его свойств и методов можно нарисовать все, что угодно.

Создайте новый проект с формой (VCL Forms Application) и сохраните его в папке Пиксели под именем Pixels.bdsproj.

Определить или изменить цвет отдельных пикселей можно с помощью свойства-массива Pixels. У каждого пикселя две стандартных координаты: x — расстояние от левой границы холста, y — расстояние от верхней границы. Точка (0,0) — это левый верхний угол.

Чтобы узнать или поменять цвет пикселя с координатами (x,y), используют обращение Canvas.Pixels[x,y].

Некоторые элементы формы (например, заголовок и рамка) недоступны для рисования («обычными» методами). Область, с которой можно работать, называется клиентской, ее ширина и высота определяются через свойства формы ClientWidth и ClientHeight. Поскольку отсчет координат начинается с нуля, нижний правый пиксель клиентской области имеет координаты (ClentWidth-1,ClientHeight-1).

Добавьте обработчик события OnClick для формы:
  var x, y: integer;
  begin
    for x:=0 to ClientWidth-1 do
      for y:=0 to ClientHeight-1 do
         Canvas.Pixels[x,y] := clRed;
  end;
Запустите программу и щелкните мышью по форме.

Легко понять, что в этой программе в двойном цикле перебираются все пиксели, и для каждого устанавливается красный цвет, который обозначается как clRed.

Поскольку этот обработчик является методом формы, здесь запись Canvas обозначает то же самое, что Form1.Canvas, то есть, мы рисуем прямо на форме.

Даже на быстродействующем компьютере работа с отдельными пикселями выполняется медленно, нужно этого избегать: рисовать с помощью линий, прямоугольников и др.

Цвет можно задать и по-другому, а формате RGB (red — красный, green — зеленый, blue —синий). Каждая из этих составляющих цвета — целое число в интервале от 0 до 255.

Измените строчку, где записывается новый цвет:
Canvas.Pixels[x,y] := RGB(0,255,255);
Запустите программу и проверьте ее.

Цвет в Delphi — это целое число, занимающее 4 байта. В принципе, можно прямо записывать это число в массив Pixels, при этом удобнее всего использовать шестнадцатеричную запись, где каждая составляющая кодируется двумя шестнадцатеричными цифрами (и занимает 1 байт). Перед шестнадцатеричными числами в Паскале ставится знак $.

Измените строчку, где записывается новый цвет:
Canvas.Pixels[x,y] := $00FFFF;
Запустите программу и проверьте ее.

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

Функции GetRValue, GetGValue и GetBValue позволяют выделить из цвета красную, зеленую и синюю составляющие. Все они возвращают байтовое значение.

В начале обработчика OnClick добавьте объявление переменных
  var R, G, B: byte;
      color: TColor;
В конец процедуры добавьте код, который определяет цвет пикселя (10,10), раскладывает его на составляющие и выводит их значения в окне сообщения:
  color := Canvas.Pixels[10,10];
  R := GetRValue(color);
  G := GetGValue(color);
  B := GetBValue(color);
  ShowMessage('Цвет (' + IntToStr(R) + ',' + IntToStr(G)
                 + ',' + IntToStr(B) + ')');
Проверьте работу программы.

Переменная color, в которую сначала записывается цвет пикселя, объявлена как TColor — специальный тип данных для кодирования цвета. По сути, это целое число, но именно такое объявление более грамотно, потому что явно показывает назначение переменной.

Цвет пикселей можно получать в результате вычислений, при этом могут получиться весьма интересные узоры.

Измените строчку, где записывается новый цвет:
Canvas.Pixels[x,y] := x*y;
Запустите программу, разверните окно на полный экран и щелкните по форме. После этого закройте проект.

Геометрические фигуры

В Delphi, так же, как и в других системах программирования с графическими возможностями, существуют так называемые графические примитивы — простейшие объекты, из которых составляется рисунок.

Цвет и стиль линий определяется свойством Canvas.Pen (перо), которое содержит несколько подсвойств. Из них наиболее важны три следующих:

Color — цвет линий;

Width — толщина линий

Style — стиль линии: psClear — нет линий, psSolid — сплошная, psDash — штриховая, psDot — точечная и другие.

По умолчанию линия сплошная, имеет черный цвет и толщину 1 пиксель. Вот эта команда устанавливает красный цвет для линий:

Canvas.Pen.Color := clRed;

Замкнутые фигуры можно залить сплошным цветом или узором прямо при рисовании. Тип заливки определяется кистью — свойством Canvas.Brush, которое имеет два основных подсвойства:

Color — цвет кисти;

Style — стиль кисти: bsClear — нет заливки, bsSolid — сплошная, bsCross — в клеточку и другие.

По умолчанию устанавливается сплошная заливка белым цветом.

При рисовании линий используется понятие текущей позиции рисования (гарфического курсора). Это невидимая точка, из которой начинается рисование очередного отрезка. Метод MoveTo(x,y) перемещает курсор (текущую позицию) в точку с координатами (x,y), а команда LineTo(x,y) рисует отрезок из текущей позиции в точку (x,y). Например, эти команды рисуют отрезок из точки (10,10) в точку (100,100):

Canvas.MoveTo ( 10, 10 );
Canvas.LineTo ( 100, 100 );

После выполнения метода LineTo(x,y) текущая позиция смещается в точку (x,y).

Создайте новый проект и сохраните его в папке Графика под именем Graph.bdsproj.
Добавьте кнопку TButton с надписью «Нарисовать».
Добавьте в описание класса формы процедуру Draw:
  public
    procedure Draw;
Добавьте в секцию кода implementation реализацию процедуры:
  procedure TForm1.Draw;
  begin
    Canvas.Pen.Color := clBlue;
    Canvas.MoveTo(0,0);
    Canvas.LineTo(100,100);
  end;
Создайте обработчик события OnClick, который вызывает процедуру Draw. Запустите программу и проверьте ее работу.

Хотелось бы избавиться от утомительного написания символов «Canvas.» перед каждой командой рисования. Для этого есть простой способ: использовать оператор with:

with Canvas do begin
...
end;

Программа будет считать, что все обращения внутри этого блока — это обращения к свойствам и методам объекта Canvas.

Измените код процедуры таким образом:
  with Canvas do begin
    Pen.Color := clBlue;
    MoveTo(0,0);
    LineTo(100,100);
  end;

Для рисования прямоугольника используется метод

Canvas.Rectangle ( x1, y1, x2, y2 );

Два противоположных угла этого прямоугольника находятся в точках (x1,y1) и (x2,y2), для контура используются настройки пера (Canvas.Pen), для заливки внутренней части — кисть (Canvas.Brush). Следующий код рисует красный прямоугольник с синей заливкой:

with Canvas do begin
  Pen.Color := clRed;
  Brush.Color := clBlue;
end;

Чтобы отключит заливку, достаточно установить прозрачную кисть, то есть

Canvas.Brush.Style := bsClear;

Эллипсы (и круги) рисуются с помощью метода Canvas.Ellipse:

Canvas.Ellipse( x1, y1, x2, y2 );

Точки (x1,y1) и (x2,y2) — это противоположные углы прямоугольника, в который вписан этот эллипс.

Заливка

Чтобы залить прямоугольник каким-то цветом или узором (не рисуя рамку), используют метод Canvas.FillRect. Его параметр — объект типа TRect, который можно построить с помощью функции Rect прямо при вызове метода:

Canvas.FillRect( Rect(x1, y1, x2, y2) );

Здесь x1, y1, x2 и y2 — числа или арифметические выражения. Как и в методе Rectangle, точки (x1,y1) и (x2,y2) — это противоположные углы прямоугольника.

Иногда нужно залить область со сложной границей. В этом случае применяют метод FloodFill. Например,

Canvas.FloodFill ( 50, 100, clBlack, fsBorder );

Цвет и стиль заливки определяется текущими установками кисти.

Здесь первые два параметра — координаты точки, откуда начинается заливка. Третий параметр — цвет границы или внутренней области, в зависимости от значения четвертого параметра. Если он равен fsBorder, мы заливаем все, начиная от исходной точки до границы заданного цвета (в данном примере — черного, clBlack). Черная граница должна быть сплошной, если она будет разорвана, зальется весь холст.

Возможен второй вариант, когда нужно залить область одного цвета, и эта область не имеет одноцветной границы. Так работает, например, заливка в редакторе Paint. В этом случае заливка, начинающаяся с точки (50,100), выполняется так:

Canvas.FloodFill ( 50, 100, Pixels[50,100], fsSurface );

Обратите внимание, что для надежности мы определили цвет точки (50,100) с помощью массива Pixels.

Вывод текста

Для того, чтобы вывести текст на холст, используется метод Canvas.TextOut. Например,
Canvas.TextOut ( 50, 100, 'Дом, который построил Джек' );

Первые два параметра определяют левый верхний угол текста (здесь — точка (50,100)), третий — сам текст (символьная строка).

Используемый шрифт задается в свойстве Canvas.Font, которое имеет несколько подсвойств (название шрифта, цвет, размер, стиль и др.). В этом примере текст набран шрифтом Verdana, размером 10 пт, жирным, курсивным:

with Canvas do begin
  Font.Name := 'Verdana';  // гарнитура
  Font.Color := clRed;     // цвет
  Font.Size := 10;         // размер в пунктах
  Font.Style := [fsBold,fsItalic]; // стиль
  TextOut ( 50, 100, 'Дом, который построил Джек' );
end;

Напомним, что свойство Font.Style — это множество, которое может включать элементы fsBold (жирный), fsItalic (курсив), fsUnderline (подчеркивание) и fsStrikeOut (вычеркивание) в любых комбинациях.

У объекта Canvas есть и другие методы, про которые можно прочитать в справочной системе или в Интернете. Некоторые из них мы изучим далее.

Практикум

Измените программу так, чтобы при нажатии на кнопку на форме появлялся домик с подписью, примерно такой, как на рисунке:

Для закраски двери используйте коричневый цвет (R=140, G=70, B=20).
к началу К началу страницы

2. Сохранение рисунка

Еще раз запустите программу, и выведите рисунок на форму. После этого сверните окно программы (кнопка [image] в заголовке окна программы) и снова раскройте его. Сохранился ли рисунок?

А рисунок не сохранился. Почему?

Дело в том, что мы рисовали домик прямо на экране, холст Canvas нигде не хранит это изображение, поэтому при сворачивании окна оно было потеряно. Где же выход?

Чтобы ответить на этот вопрос, нужно понять, как строится изображение в окне программы. Дело в том, что Windows не хранит изображения окон программ. Каждая программа обязана уметь перерисовать свое окно, когда получит от операционной системы специальной сообщение WM_PAINT. Например, это случается при разворачивании окна из свернутого состояния. Перехватить это сообщение можно с помощью события OnPaint.

Создайте обработчик события OnPaint для формы, вызвав в нем процедуру Draw. Проверьте, решило ли это проблему.

Теперь рисунок на форме появляется сразу, хотя и сохраняется при любых действиях с окном. Для того, чтобы мы видели рисунок только после нажатия на кнопку, введем логическую переменную IsReady, которой будем устанавливать значение True в обработчике OnClick кнопки. Напомним, что во все глобальные переменные при создании записываются нулевые значения, для логической переменной это соответствует значению False.

Объявите глобальную логическую переменную IsReady. В обработчике OnClick присвойте ей значение True. Измените обработчик OnPaint чтобы процедура Draw вызывалась только при истинном значении переменной IsReady. Проверьте работу программы.

Показанный прием (перерисовка в обработчике OnPaint) хорошо работает, когда рисунок несложный и перерисовать его — дело сотых долей секунды. Теперь представьте, что рисунок строится очень долго (минуты или даже часы), например, в ходе сложных расчетов или эксперимента. Перерисовать его быстро невозможно, значит, изображение нужно где-то хранить, конкретнее — в памяти.

Для хранения изображений в памяти служит класс TBitmap (bitmap — битовая карта). При запуске программы рисунок в памяти нужно создать (вызвав конструктор TBitmap.Create), а при завершении работы — удалить из памяти с помощью метода-деструктора Free.

Объявите глобальную переменную bmp типа TBitmap:
var bmp: TBitmap;
В обработчике OnCreate формы создайте рисунок в памяти:
  bmp := TBitmap.Create;
  bmp.Width := 300;
  bmp.Height := 300;
Свойства Width и Height задают ширину и высоту рисунка в пикселях.
В обработчике OnDestroy, который вызывается при уничтожении формы, освободите память:
bmp.Free;

Теперь в процедуре Draw нам нужно рисовать не на форме, а на холсте объекта bmp, поэтому она должна выглядеть так:

with bmp.Canvas do begin
...
end;

Измените процедуру Draw, чтобы рисование выполнялось в памяти на холсте объекта bmp. Запустите программу и проверьте ее работу, попытайтесь понять, почему рисунка не видно.

Теперь процедура Draw рисует только в памяти, поэтому мы не видим картинки. Чтобы это исправить, в обработчике OnPaint формы нужно выводить на холст формы битовую карту, что можно сделать м помощью метода Canvas.Draw:

Canvas.Draw ( 0, 0, bmp );

Первые два параметра — это координаты верхнего левого угла рисунка, третий — адрес рисунка в памяти.

Еще один метод, Canvas.StretchDraw, позволяет изменять размеры выводимого рисунка (растягивать или сжимать его). Его первый параметр — прямоугольник, в котором нужно вывести рисунок. Это объект типа TRect, его можно построить с помощью функции Rect (мы уже делали это при вызове метода FillRect).

Измените обработчик события OnPaint так, чтобы вывести на форму сам рисунок и его уменьшенную копию (справа):
  if IsReady then begin
    Canvas.Draw ( 0, 0, bmp );
    Canvas.StretchDraw(Rect(300, 150, 390, 240), bmp);
  end;
Запустите программу и щелкните по кнопке. Теперь сверните окно программы и разверните его заново. Попробуйте объяснить результат.

Итак, щелчок по кнопке не привел к появлению рисунка. Это наводит на мысль (и это действительно так!), что обработчик OnPaint не был вызван. Чтобы его вызвать, в обработчик OnClick кнопки нужно добавить вызов метода Repaint (перерисовать) для формы.

Добавьте в обработчик OnClick вызов метода
Repaint;
и проверьте работу программы. После этого можно закрыть проект.
к началу К началу страницы

3. Броуновское движение

Строим интерфейс

Сейчас мы напишем программу, которая моделирует броуновское (хаотическое) движение молекулы.

Создайте новый проект с формой и сохраните его Броун под именем Brown.bdsproj. Измените заголовок формы на «Броуновское движение».

В этой программе мы будем использовать новые компоненты:

  • TCheckBox — переключатель (включено/выключено);
  • TRadioGroup — группа радиокнопок (выбор одного из нескольких вариантов);
  • TGroupBox — компонент с рамкой для группировки других компонентов, например, переключателей;
  • TBevel — просто декоративная рамка;
  • TImage — рисунок, изображение.
Добавьте в левый верхний угол компонент TGroupBox из группы Standard, задайте ему заголовок (свойство Caption) «Шаг».

В этой группе будут два элемента: поле для ввода величины шага и переключатель, позволяющий включить режим случайного изменения шага.

В эту группу (сверху на только что добавленный компонент TGroupBox) добавьте поле ввода TEdit (имя StepEdit, начальное значение — свойство Text — 5) и флажок-переключатель TCheckBox (имя RandStepCB, заголовок «изменять случайно»).

Компонент TCheckBox — это переключатель (флажок), который может быть включен и выключен. Если рядом расположены несколько таких переключателей, каждый из них может включаться и выключаться независимо от других.

Чтобы узнать, включил ли пользователь переключатель, надо проверить его свойство Checked. Оно может принимать логическое значение True (включен) или False (выключен). Свойство Checked можно изменять из программы.

Если попробовать перетащить компонент TGroupBox, то с ним вместе переместятся все находящиеся на нем компоненты. Он становится родительским компонентом (Parent), это хорошо видно, если посмотреть на окно Structure в левой верхней части экрана (cм. рисунок справа). Здесь форма является родителем компонента GroupBox1, а он, в свою очередь, родителем компонентов RandStepCB и StepEdit.

Ниже уже добавленной группы создадим еще одну группу для выбора начальной точки для случайного блуждания частицы. Это может быть центр поля или случайная точка на поле. Важно, что эти варианты исключают друг друга, то есть, нужно разрешить пользователю выбирать только один их них. Для этого служат так называемые радиокнопки. Из них всегда включена только одна, при включении какой-то кнопки все остальные выключаются (как на радиоприемнике при выборе волнового диапазона: УКВ, КВ, СВ, ДВ).

Удобнее всего для этой цели использовать особую группу — группу радиокнопок TRadioGroup. Ее свойство Items — это список строк, которые содержат текст подписей всех кнопок. Свойство ItemIndex — это номер выбранной кнопки. Нумерация начинается с нуля, поэтому ItemIndex=0 говорит о том, что выбрана самая первая кнопка.

Добавьте компонент TRadioGroup, задайте ему имя PositionRG и заголовок «Начальная точка». В окне Object Inspector щелкните дважды на значении свойства Items и введите в первой строчке «центр поля» (текст для первой кнопки), а во второй — «случайная». Чтобы включить первую кнопку, измените свойство ItemIndex на 0.

Иногда хочется объединить в одной группе разные элементы, в том числе радиокнопки и переключатели. Сделать это с помощью компонента TRadioGroup нельзя. Однако можно использовать простую группу, TGroupBox, и разместить на ней (как угодно!) несколько компонентов TRadioButton (радиокнопка). Все они будут работать «в одной упряжке», то есть включена может быть только одна из них (в этом случае ее свойство Checked будет равно True).

Ниже двух групп добавьте метку «Время» и поле ввода TEdit с именем TimeEdit и начальным значением 100. Запретите вводить в оба поля любые символы, кроме цифр (установите один обработчик OnKeyPress для обоих полей ввода).
Добавьте ниже всех компонентов кнопку TBitBtn, установите для нее свойства
Name = btnGo
Kind = bkOK
Caption = Старт.

Обратите внимание, что свойство Default у кнопки типа bkOK установлено в True. Это значит, что при нажатии на клавишу Enter на клавиатуре кнопка срабатывает автоматически.

Осталось разместить поле, где мы будем рисовать траекторию движения точки. Его можно сделать разными способами, мы сейчас покажем самый простой. На рисунке выше вы видели, что поле имеет белый фон и рамку с имитацией объемного вида. Эта область строится из двух компонентов: рамки TBevel и изображения TImage (оба они находятся в группе Additional).

В правой части разместите компонент TBevel размером 304 на 304 пикселя, внутри него по центру положите компонент TImage размером 300 на 300 пикселей. Для точного выравнивание двух компонентов выделите их (при нажатой клавише Shift) и используйте пункт контекстного меню Position - Align. Подправьте размеры формы, чтобы на нее помещались все компоненты.

Обработчики событий

Вся основная работа этой программы будет происходить при нажатии кнопки, то есть, в обработчике OnClick. Мы будем использовать следующие переменные:

x, y — координаты точки (частицы);

alpha — случайный угол (направление);

h0 и h — начальный и текущий шаг; начальный шаг h0 читается из поля StepEdit; если установлен флажок RandStepCB, текущий шаг h определяется случайно, но не превышает h0; если этот флажок сброшен, h0 и h равны;

N — максимальное число шагов, читается из поля TimeEdit;

i — номер текущего шага.

Создайте обработчик события onClick для кнопки. В нем объявите локальные переменные
var i, N, h0: integer;
      x, y, h, alpha: double;
В начале обработчика OnCLick запретите повторное нажатие кнопки
btnGo.Enabled := False;
а в конце — сделайте кнопку снова активной:
btnGo.Enabled := True;

Теперь нужно определить начальное положение частицы. Оно зависит от варианта, выбранного в радиогруппе PositionRG. Если выбран вариант «центр поля» (ItemIndex=0), то координаты x и y определяются как половины от ширины и высоты рисунка. Если выбран вариант «случайная точка», то x и y рассчитываются случайным образом в пределах поля.

Добавьте в обработчик события onClick код для определения начального положения частицы:
  if PositionRG.ItemIndex = 0 then begin
    x := Image1.Width div 2;
    y := Image1.Height div 2;
  end
  else begin
    x := random*Image1.Width;
    y := random*Image1.Height;
  end;

Теперь прочитаем параметры моделирования из полей ввода. Вместо уже знакомой функции StrToInt, преобразующей строку в целое число, мы применим StrToIntDef, которая умеет обрабатывать ошибки преобразования (если введено неверное число). В этом случае результатом функции будет число, указанное во втором параметре.

Добавьте в обработчик события onClick код для получения исходных данных
  h0 := StrToIntDef(StepEdit.Text, 5);
  N := StrToIntDef(TimeEdit.Text, 100);
  h := h0;

Осталось запрограммировать само случайное движение частицы и вывод траектории на экран. У объекта TImage есть свойство Canvas, с помощью которого мы будем рисовать на нем так же, как раньше на форме. Более того, этот объект уже содержит битовую карту в памяти, то есть изображение будет храниться и перерисовываться, когда нужно, вообще без нашего участия.

Перед началом вычислений нужно подготовиться: очистить поле (залить его белым цветом):

with Image1.Canvas do begin
  Brush.Color := clWhite;
  FillRect( Rect(0,0,Image1.Width,Image1.Height) );
end;

и установить текущую позицию рисования в начальную точку:

Image1.Canvas.MoveTo( round(x), round(y) );

Напомним, что функция round округляет вещественные значения до ближайшего целого числа.

Затем в цикле повторяем N раз следующие действия:

  1. вычисляем случайный угол в интервале от 0 до 2π радиан — направление движения:
    alpha := random*2*pi;
  2. если включен флажок RandStepCB, вычисляем случайную величину шага от 0 до h0:
    if RandStepCB.Checked then h := h0*random;
    заметим, что если флажок RandStepCB не включен, в переменной h остается записанное туда ранее значение h0;
  3. находим координаты следующей точки траектории:
    x := x + h*cos(alpha);
y := y - h*sin(alpha);
  4. и рисуем отрезок в эту точку (при этом сюда же смещается текущая позиция рисования):
    Image1.Canvas.LineTo( round(x), round(y) );

Поскольку здесь много обращений к методам объекта Image1.Canvas, удобно взять все операторы в блок

with Image1.Canvas do begin
...
end;
Добавьте в обработчик события onClick код, выполняющий моделирование и вывод траектории на экран:
  with Image1.Canvas do begin
    Brush.Color := clWhite;
    FillRect(Rect(0,0,Image1.Width,Image1.Height));
    MoveTo(round(x), round(y));
    for i:=1 to N do begin
      alpha := random*2*pi;
      if RandStepCB.Checked then h := h0*random;
      x := x + h*cos(alpha);
      y := y - h*sin(alpha);
      LineTo(round(x), round(y));
    end;
  end;
Запустите программу и проверьте ее работу при различных исходных данных. После этого можно закрыть проект.
к началу К началу страницы

4. Клавиатура

События клавиатуры

В этом разделе мы познакомимся с обработкой событий клавиатуры. Кое-что мы уже знаем, например, то, что при нажатии клавиши происходит событие OnKeyPress, которое можно обрабатывать. Возможно, вы сумели заметить, что обработчик OnKeyPress не вызывается для некоторых клавиш, например, в полях ввода работала клавиша Delete, которую мы вроде бы не разрешали.

В самом деле, при нажатии любой клавиши происходит событие OnKeyDown, а при отпускании — OnKeyUp. Эти события возникают при получении сообщений Windows WM_KEYDOWN и WM_KEYUP. При нажатии клавиши (или комбинации клавиш), имеющей соответствующий код в таблице символов, Windows посылает программе сообщение WM_CHAR, и при этом вызывается обработчик сообщения OnKeyPress. Клавиша Delete (как и клавиши-стрелки, например) не соответствует никакому символу в таблице символов, поэтому обработчик OnKeyPress ее не «ловит».

В следующем примере мы будем управлять рисунком с помощью клавиш-стрелок.

Создайте новый проект и сохраните его в папке Клавиатура под именем Keyboard.bdsproj. Измените заголовок формы на «Работа с клавиатурой».

Загрузка рисунка

Добавьте на форму компонент TImage и установите его свойство AutoSize равным True.

Так как AutoSize=True, размер компонента будет автоматически «подгоняться» под фактический размер загруженного в него рисунка.

Найдите (на диске или в Интернете) небольшой рисунок в формате JPEG и сохраните его в папке Клавиатура.

Для того, чтобы компонент TImage мог работать с рисунками в формате JPEG, нужно подключить модуль с таким же именем.

Добавьте в список uses модуль Jpeg.

Компонент TImage умеет загружать рисунки с диска, для этого служит метод LoadFromFile (вспомните, такой же метод был у списка!). Мы вызовем этот метод в обработчике OnCreate, то есть, в самом начале работы программы.

Создайте обработчик события onCreate для формы и в нем загрузите изображение из файла (вместо dog.jpg нужно подставить имя вашего файла):
Image1.Picture.LoadFromFile( 'dog.jpg' );
Запустите программу. Проверьте, чтобы рисунок действительно загрузился.

Для загрузки рисунка необходимо, чтобы программа его нашла на диске. Если путь не указан, программа ищет рисунок в той же папке, в которой находится исполняемый файл.

Можно было загрузить рисунок в компонент сразу, во время разработки. В этом случае он хранится в DFM-файле формы, держать его на диске не нужно, но пользователь уже не сможет заменить его.

Для сохранения рисунка, содержащегося в TImage, служит метод SaveToFile, например,
 Image1.SaveToFile ( 'dog2.jpg' );

Обработка OnKeyUp

Сделаем так, чтобы при нажатии на клавиши-стрелки рисунок перемещался по форме в указанном направлении. Поскольку клавиши-стрелки не имеют своих кодов в таблице символов, обработчик OnKeyPress их не «поймает», и нужно использовать событие OnKeyUp (отпустили клавишу).

Выделите компонент TImage и посмотрите на страницу Events в окне Object Inspector. Есть ли у него клавиатурные события?

Скорее всего, вы выяснили, что рисунок не может обрабатывать события клавиатуры, поскольку не получает фокус ввода. Вместо этого такие сообщения обрабатывает его «родитель» — форма, поэтому событие OnKeyUp нужно искать у нее.

Добавьте обработчик события OnKeyUp для формы и посмотрите на его параметры.

Кроме уже привычного параметра Sender (отправитель сообщения), есть еще Key — код клавиши и Shift — состояние клавиш Shift, Ctrl и Alt, а также кнопок мыши.

Обратим внимание на то, что Key — это не код символа, а (виртуальный) код клавиши, каждая клавиша на клавиатуре имеет свой уникальный код. Важно, что параметр Key описан как целое положительное число (тип word), а не символ.

Для наших целей нужно знать, что клавиши стрелки имеют коды, обозначенные константами VK_LEFT (влево), VK_RIGHT (вправо), VK_UP (вверх) и VK_DOWN (вниз). С параметром Shift мы разберемся немного позже.

Положение рисунка на форме определяется его свойствами Left и Top, если их изменить, то рисунок передвинется соответственно по горизонтали или по вертикали.

Добавьте обработчик события onKeyUp для формы:
  if Key = VK_LEFT then
    Image1.Left := Image1.Left - 5;
  if Key = VK_RIGHT then
    Image1.Left := Image1.Left + 5;
Самостоятельно добавьте реакцию на клавиши-стрелки «вверх» (VK_UP) и «вниз» (VK_DOWN).

Параметр Shift

Теперь обработаем ситуацию, когда стрелки нажимают вместе с клавишей Ctrl. В этом случае будем менять размеры рисунка: увеличивать ширину при нажатии Ctrl+«вправо» и уменьшать при нажатии Ctrl+«влево».

Параметр Shift, переданный в обработчик OnKeyUp, — это множество, которое может включать следующие элементы:

ssShift — нажата клавиша Shift;

ssCtrl — нажата клавиша Ctrl;

ssAlt — нажата клавиша Alt;

ssLeft — нажата левая кнопка мыши;

ssRight — нажата правая кнопка мыши.

Полный список возможных элементов можно посмотреть в справочной системе. Определить вхождение нужного элемента во множество можно с помощью оператора in:

if ssCtrl in Shift then
  клавиша Ctrl нажата
else
  клавиша Ctrl не нажата
Добавьте обработку сочетаний Ctrl+стрелки. Например, для клавиши «влево» код обработки будет выглядеть так:
  if Key = VK_LEFT then begin
    if ssCtrl in Shift then
         Image1.Width := Image1.Width - 5
    else Image1.Left := Image1.Left - 5;
  end;
Запустите программу и проверьте, удается ли изменить размеры рисунка. Попробуйте объяснить увиденное.

Настройка TImage

У нас ничего не получилось, потому что у компонента TImage свойство AutoSize установлено в True, то есть при попытке изменить размеры рисунка он сам снова восстанавливает их в исходное состояние.

Установите для компонента Image1 свойство Autosize=False и проверьте работу программы.

Теперь другая беда — при загрузке рисунка размеры автоматически не определились. Это можно поправить вручную: после чтения рисунка установить размеры компонента Image1 равными размерам загруженного в память рисунка Image1.Picture.

Добавьте в обработчик onCreate строчки
  Image1.Width := Image1.Picture.Width;
  Image1.Height := Image1.Picture.Height;
Уменьшите размер компонента Image1 на форме и проверьте работу программы.

Вы должны были увидеть, что размеры компонента действительно изменяются, однако рисунок не меняется, то есть, не растягивается по размерам компонента. Это объясняется тем, что его свойство Stretch (растяжение) по умолчанию установлено в False.

Установите для компонента Image1 свойство Stretch=True и проверьте работу программы. Что изменилось?

Теперь сделаем так, чтобы при нажатии клавиши Z рисунок перемещался в левый верхний угол формы и его размеры возвращались к исходным. Для этого будем использовать обработчик OnKeyPress.

Добавьте обработчик события onKeyPress для формы:
  if Key = 'Z' then begin
    Image1.Left := 0;
    Image1.Top := 0;
    Image1.Width := Image1.Picture.Width;
    Image1.Height := Image1.Picture.Height;
  end;
Запустите программу и проверьте, будет ли она реагировать на строчную букву z?

Регистр символов

Конечно, можно написать так:

if (Key = 'Z') or (Key = 'z') then ...

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

if UpCase(Key) = 'Z' then ...
Измените программу, чтобы она реагировала как на заглавную, так и на строчную буквы Z. Проверьте ее работу.
Форма может перехватывать и обрабатывать все сообщения клавиатуры, передаваемые ее дочерним объектам. Для этого надо установить ее свойство KeyPreview=True.
к началу К началу страницы

5. Мышь

Перетаскивание объекта

Теперь мы научимся перетаскивать рисунок мышкой. У компонентов Delphi существует несколько обработчиков, связанных с мышью. Наиболее полезные из них:

OnMouseDown — нажата какая-то кнопка мыши;

OnMouseUp — отпущена какая-то кнопка мыши;

OnMouseMove — мышь переместили;

OnClick — щелчок мышью;.

OnDblClick — двойной щелчок мышью.

Ранее мы работали только с событием OnClick, в этом разделе рассмотрим все остальные. В частности, для перетаскивания объекта используются OnMouseDown, OnMouseUp и OnMouseMove.

Иногда бывает полезно использовать еще два «мышиных» события:

OnMouseEnter — мышь навели на кнопку;

OnMouseLeave — мышь «ушла» с кнопки.

Например, с помощью этих событий можно сделать кнопку, которая «всплывает« при наведении мыши.

Вы уже знаете, что для того, чтобы переместить компонент на форме, нужно изменить его свойства Left и Top. Как именно? А вот это определяется тем, как мы переместили мышку. В целом перетаскивание состоит из трех операций:

  1. при нажатии кнопки мыши запомнить, что началось перетаскивание, (например, присвоить логической переменной IsDragging значение True) и запомнить координаты щелчка в переменных x0 и y0;
  2. при перемещении мыши сдвинуть объект настолько, насколько текущие координаты мыши (x,y) отличаются от начальной точки, то есть изменить свойство Left на x-x0, а свойство Top — на y-y0; все эти действия выполняются только тогда, когда идет перетаскивание, то есть IsDragging=True;
  3. когда кнопка мыши отпущена, присвоить переменной IsDragging значение False (нет перетаскивания).
Объявите глобальные целые переменные x0 и y0, а также логическую переменную IsDragging. Для компонента Image1 добавьте обработчики событий OnMouseDown:
  x0 := x;
  y0 := y;
  IsDragging := True;
OnMouseUp:
  IsDragging := False;
и OnMouseMove:
  if IsDragging then begin
    Image1.Left := Image1.Left + x - x0;
    Image1.Top := Image1.Top + y - y0;
  end;
Запустите программу и проверьте ее работу.

Обратите внимание, что x и y, поступающие в обработчики OnMouseDown и OnMouseUp — это координаты мыши относительно левого верхнего угла компонента Image1, а не экрана.

Чтобы получить экранные координаты мыши, используют метод компонента ClientToScreen, обратное преобразование выполняет метод ScreenToClient. Подробнее их описание можно посмотреть в справочной системе или в Интернете.

Изменение размеров

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

Будем считать, что курсор находится на границе рисунка, если расстояние от курсора до края рисунка не более 5 пикселей. Создадим новую функцию (метод формы), которая будет возвращать логическое значение True, если курсор находится над правой границей, и False в противном случае.

Объявите в секции public описания формы новую функцию
  function RightBorder(x, y: integer): Boolean;
В секции implementation добавьте саму функцию:
  function TForm1.RightBorder(x, y: integer): Boolean;
  begin
    Result := x > (Image1.Width - 5);
  end;

Возможно, вы заметили, что для работы этой функции хватило бы одной координаты x, но мы специально вводим в параметры как x, так и y для того, чтобы не запутаться при вызове этой и других похожих функций.

Теперь мы введем логическую переменную ChangingRight, которой будем присваивать значение True, если началось изменение правой границы, то есть, пользователь нажал кнопку мыши в «приграничной полосе».

Введите глобальную логическую переменную ChangingRight и измените последнюю строчку обработчика OnMouseDown так:
  if RightBorder(x,y) then
       ChangingRight := True
  else IsDragging := True;

Таким образом, мы будем различать ситуации «перетаскивание объекта» и «перетаскивание границы» — в первом случае устанавливается переменная-флаг IsDragging (ей присваивается значение True), а во втором — ChangingRight.

Когда мышь наводится на изменяемую границу, указатель (свойство Cursor формы) должен менять вид, показывая, какой именно размер меняется. Типы курсоров, связанные с изменением размеров, легко запомнить по английским названиям сторон света (см. рисунок справа, где изображена компасная роза). Здесь North — север, South — юг, West — запад и East — восток. Ниже перечислены все интересующие нас курсоры:

— crSizeNS

— crSizeWE

— crSizeNWSE

— crSizeNESW

Таким образом, если курсор находится над правой границей (то есть, функция RightBorder вернула значение True) вид курсора нужно изменить на crSizeWE.

Добавьте в начало обработчика OnMouseMove код для изменения курсора:
  if RightBorder(x,y) then
       Cursor := crSizeWE
  else Cursor := crHandPoint;

Если идет перетаскивание границы, в том же обработчике OnMouseMove нужно изменить ширину рисунка на разность между текущим и начальным значением x-координаты. Кроме этого, нужно установить новую начальную точку, то есть, записать в переменную x0 текущее значение x.

Добавьте в обработчик OnMouseMove код
  if ChangingRight then begin
    Image1.Width := Image1.Width + x - x0;
    x0 := x;  // запомнить новую начальную точку
  end;

При отпускании мыши нужно сбросить флаг ChangingRight, то есть, присвоить этой переменной значение False.

В обработчик onMouseUp добавьте строчку
ChangingRight := False;
Запустите программу и проверьте, как меняется правая граница рисунка. Посмотрите, что происходит с курсором, если вывести его за правую границу рисунка.

Когда курсор мыши выходит за границы кнопки, возникает событие OnMouseLeave. Его нужно перехватить и устанавливать стандартный курсор-стрелку, который имеет условное обозначение crArrow.

Добавьте обработчик события OnMouseLeave для формы
Cursor := crArrow;
Запустите программу и проверьте изменение курсора.
Аналогично добавьте в программу возможность изменять высоту рисунка, перетаскивая нижнюю границу.

Теперь сделаем так, чтобы при двойном щелчке на рисунке (событие OnDblClick) восстанавливались его исходные размеры и рисунок перемещался в левый верхний угол формы. Для этого эти размеры нужно запомнить сразу после загрузки рисунка в отдельных переменных.

Добавьте в программу целые переменные w0 и h0. При загрузке рисунка запомните в них его начальную ширину и высоту:
  w0 := Image1.Width;
  h0 := Image1.Height;
Создайте обработчик события onDblClick для рисунка, в нем восстановите исходные размеры и поместите рисунок в левый верхний угол формы.
Добавьте в программу возможность изменять размеры рисунка, перетаскивая верхнюю и левую границы.
Добавьте в программу возможность изменять размеры рисунка, перетаскивая его углы.
к началу К началу страницы

 |  | Оглавление |
Форма и компоненты | Назад | В начало | Вперед | Графический редактор


Уроки по Delphi: Графический редактор

Тема 5. Графический редактор

  1. Карандаш
  2. Прокрутка
  3. Панель инструментов
  4. Отрезок
  5. Прямоугольник
  6. Выбор цвета
  7. Заливка
  8. Работа с файлами

Вы узнаете:

  • как работать с полем для рисования TPaintBox;
  • как сделать область с прокруткой;
  • как рисовать «временные» фигуры и не портить рисунок;
  • как сделать панель инструментов;
  • как организовать выбор цвета;
  • как использовать диалоги открытия и сохранения файлов.

1. Карандаш

В этой главе мы создадим несложный графический редактор. Смысл этой работы не только в этом редакторе, а, прежде всего, в том, чтобы изучить новые компоненты и приемы работы с ними. Программа получится достаточно большая, но мы будем писать ее постепенно.

Создайте новый проект с формой и сохраните его в папке Графический редактор под именем Paint.bdsproj. Измените заголовок формы на «Графический редактор».

Для рисования мы будем использовать компонент TPaintBox (поле для рисования) из группы System. Сначала мы сделаем так, чтобы он занимал всю рабочую (или клиентскую) область формы. При изменении размеров формы размеры поля также будут меняться.

Выравнивание компонента в пределах «родительского» окна можно установить с помощью свойства Align. Оно имеет несколько возможных значений:

alNone — нет выравнивания, размеры и положение не меняются;

alClient — занимает все свободное место (всю клиентскую область «родителя»);

alTop — прижат к верхней границе, ширина — максимально возможная;

alBottom — прижат к нижней границе, ширина — максимально возможная;

alLeft — прижат к левой границе, высота — максимально возможная;

alRight — прижат к правой границе, высота — максимально возможная;

Добавьте на форму компонент TPaintBox. Дайте ему имя Pic и установите его свойство Align=alClient.
Так же, как в предыдущем уроке, для сохранения рисунка в памяти объявите переменную с именем bmp (типа TBitmap). Рисунок должен создаваться в начале работы программы и удаляться из памяти при ее завершении. Установите размеры поля 800 на 600 пикселей.

Битовая карта будет хранить рисунок пользователя в памяти, его нужно вывести на холст компонента TPaintBox при получении сообщения на перерисовку, то есть, в обработчике OnPaint компонента Pic.

В обработчике события onPaint компонента Pic выведите на экран рисунок из памяти:
Pic.Canvas.Draw ( 0, 0, bmp );

Теперь научимся рисовать «от руки»так же, как в редакторе Paint (инструмент Карандаш). Для этого можно применить подход, который уже использовался ранее: будем обрабатывать события OnMouseDown, OnMouseUp и OnMouseMove.

Логическая переменная IsDragging показывает, началось ли рисование, она равна True, если кнопка мыши нажата.

Установка начальной точки выполняется с помощью метода MoveTo. Если мышь сдвинута в режиме рисования (при нажатой кнопке), будем рисовать отрезок в новую точку с помощью LineTo.

После того, как линия нарисована (на холсте объекта bmp — в памяти!), нужно вывести получившийся рисунок на экран. Для этого просто вызываем обработчик OnPaint поля рисования. Если компонент TPaintBox в программе называется Pic, имя обработчика будет PicPaint. Параметр Sender можно задать любой, потому что в обработчике он все равно не используется.

Объявите логическую переменную IsDragging. Для компонента Pic добавьте обработчики «мышиных» событий:
onMouseDown:
  IsDragging := True;
  bmp.Canvas.MoveTo(x,y);
onMouseUp:
  IsDragging := False;
onMouseMove:
  if IsDragging then begin
     bmp.Canvas.LineTo(x, y);
     PicPaint(Sender);
  end;
Запустите программу и порисуйте на поле. Попробуйте уменьшить размер окна. Весь ли рисунок доступен для рисования?
к началу К началу страницы

2. Прокрутка

Вы увидели, что часть рисунка, которая не помещается на форму, не видна, и ее никак не «вытащить», поскольку нет привычных линеек прокрутки. Чтобы решить эту проблему, используем компонент TScrollBox (панель с прокруткой, группа Additional), на котором можно расположить любые другие компоненты, в том числе и наше поле для рисования.

Нам нужно расположить компонент TScrollBox в самый низ, а TPaintBox — на него сверху. Для этого нужно как-то «подсунуть» панель с прокруткой под компонент Pic. Здесь на помощь приходит панель Structure, где компоненты можно перетаскивать мышкой, меняя их «родителя».

Добавьте на форму компонент TScrollBox и установите для него выравнивание alClient. В окне Structure схватите мышкой имя компонента Pic и бросьте его сверху на ScrollBox1. У вас должна получиться такая же структура, как на рисунке. Попробуйте запустить программу. Появляются ли линейки прокрутки при уменьшении размера формы? Почему?

Вспомните, что мы устанавливали для компонента Pic выравнивание alClient, то есть, его размеры автоматически подгоняются под размеры родительского окна (это ScrollBox1). С другой стороны, линейки прокрутки появляются тогда, когда компоненты, находящиеся на панели прокрутки, вылезают за границы ее видимой части. Поэтому нужно отменить всякое выравнивание для поля рисования, расположить его в левом верхнем углу и установить размеры, равные размерам рисунка в памяти.

Для компонента Pic установите выравнивание alNone (нет выравнивания). В обработчик OnCreate формы добавьте код
  with Pic do begin
    Width := bmp.Width;
    Height := bmp.Height;
    Left := 0;
    Top := 0;
  end;
Запустите программу и проверьте ее работу.

Напомним, что запись

with Pic do begin
...
end;

обозначает, что внутри этого блока мы обращаемся к свойствам компонента Pic.

к началу К началу страницы

3. Панель инструментов

Теперь мы будем добавлять новые инструменты, для каждого из них создадим кнопку на специальной панели в левой части окна, как в редакторе Paint.

Добавьте на форму панель TPanel (группа Standard), установите для нее пустое свойство Caption (сотрите старое значение), свойство Align=alLeft и ширину 40 пикселей.

Легко проверить, что панель оказалась на поле с прокруткой, а нам нужно, чтобы она была рядом с ним. Снова выручает окно Structure.

В окне Structure перетащите Panel1 на имя формы (см. рисунок справа).

Теперь будем добавлять кнопки типа TSpeedButton для инструментов. Поскольку в любой момент может быть включен только один инструмент, они должны работать как радиокнопки. Для этого нужно установить для них одинаковое ненулевое свойство GroupIndex (номер группы), остальное будет сделано автоматически. Нажата та кнопка, у которой установлено свойство Down=True. Это свойство можно изменять как в режиме разработки (в окне Object Inspector) так и во время работы программы.

В верхнюю часть панели добавьте кнопку TSpeedButton, дайте ей имя PenBtn. Установите для кнопки размеры 24 на 24 пикселя, в качестве рисунка (свойство Glyph) выберите файл penbtn.bmp. Установите свойства кнопки:
GroupIndex = 1
Down = True
Hint = Карандаш
ShowHint = True

Напомним, что последние два свойства — это текст всплывающей подсказки и флаг, разрешающий ее показывать.

к началу К началу страницы

4. Отрезок

Следующий инструмент — отрезок. Вторую кнопку мы построим с помощью копирования.

Скопируйте кнопку (Ctrl+C, затем Ctrl+V), разместите копию ниже первой кнопки, дайте ей имя LineBtn, замените рисунок на linebtn.bmp и всплывающую подсказку на Отрезок. Запустите программу и проверьте работу кнопок.

Конечно, от добавления кнопок в программу не добавилась возможность рисовать отрезки. Нужно изменить код. Вспомните, как рисуется отрезок в редакторе Paint. Когда нажата кнопка мыши, запоминается начальная точка, и при перемещении мыши появляется «временный» отрезок, второй конец которого следует за мышью. При отпускании мыши окончательный вариант вносится в рисунок.

Объявите целые переменные x0 и y0 для хранения координат начальной точки. Добавьте в обработчик события onMouseDown для объекта Pic строчки
  x0 := x;
  y0 := y;

Важно, что «временный» отрезок не портит изображение на экране. Чтобы этого добиться, можно запоминать «старый» рисунок и каждый раз его перерисовывать. Но проще использовать специальный режим работы пера, такой, что при повторном рисовании того же самого отрезка восстанавливалось исходное изображение. Этих режимов несколько, один из них называется pmNot. Чтобы его установить, нужно изменить свойство Pen.Mode холста, на котором мы рисуем:

Canvas.Pen.Mode := pmNot;

Фактически это означает применение логической операции not («не», отрицание, инверсия) к пикселям существующего рисунка. Операция not обратима, то есть, повторное отрицание вернет обратно исходное значение.

Свойство Pen.Mode по умолчанию имеет значение pmCopy, то есть линия рисуется установленным цветом и стирает рисунок. Есть и другие режимы, вы можете прочитать о них в справочной системе.

Для удобства мы напишем процедуру, которая стирает последний нарисованный отрезок и рисует новый в указанном режиме (pmNot, если отрезок «временный», и pmCopy, если «постоянный»). Эта процедура будет вызываться в обработчиках OnMouseMove (режим pmNot) и OnMouseUp (режим pmCopy).

В нашем распоряжении есть координаты x0 и y0 — координаты начальной точки, а также полученные в обработчике x и y — координаты точки, где находится мышь в данный момент. Как получить координаты последнего нарисованного отрезка? Для этого у объект Canvas есть свойство PenPos (pen position — позиция пера). Это структура типа TPont (точка), которая имеет два поля, x и y.

Добавьте процедуру для рисования линий выше всех существующих процедур (но в секции implementation):
  procedure DrawLine(x0, y0, x, y: integer;
                     Mode: TPenMode);
  var pt: TPoint;  // структура «точка»
  begin
    with bmp.Canvas do begin
      pt := PenPos;      // определяем текущую позицию
      Pen.Mode := pmNot; // стираем временный отрезок
      MoveTo(x0, y0);
      LineTo(pt.x, pt.y);
      Pen.Mode := Mode;  // рисуем новый отрезок
      MoveTo(x0, y0);
      LineTo(x, y);
    end;
  end;

У этой процедуры 5 параметров: координаты начальной точки, текущие координаты мыши и режим рисования. Процедура не является методом класса формы, она работает только с глобальным объектом-рисунком bmp, а к свойствам формы не обращается.

Теперь нужно изменить обработчики OnMouseMove и OnMouseUp так, чтобы учитывать включенный в данный момент инструмент. Заметьте, что при включенном инструменте Отрезок в обработчике OnMouseMove рисуется «временный» отрезок (Mode=pmNot), а в обработчике OnMouseUp — постоянный (Mode=pmCopy).

Измените обработчик события onMouseMove
  if not IsDragging then Exit; // выход, если не рисуем
  if PenBtn.Down then          // Карандаш
     bmp.Canvas.LineTo(x, y);
  if LineBtn.Down then         // Отрезок
     DrawLine(x0, y0, x, y, pmNot);
  PicPaint(Sender);            // обновляем рисунок
Измените обработчик события onMouseUp:
  if not IsDragging then Exit;
  IsDragging := False;
  if LineBtn.Down then
    DrawLine(x0, y0, x, y, pmCopy);
  PicPaint(Sender);
Запустите программу и попробуйте нарисовать отрезки.
к началу К началу страницы

5. Прямоугольник

Прямоугольник — это более сложная фигура, потому что она имеет внутреннюю область, которая заливается автоматически в соответствии с параметрами кисти (свойство Canvas.Brush).

Создайте еще одну кнопку путем копирования (чтобы сохранить все свойства, в том числе GroupIndex), дайте ей имя RectBtn, загрузите картинку rectbtn.bmp и измените всплывающую подсказку на «Прямоугольник»..

Вспомним, как рисуется прямоугольник в редакторе Paint. При нажатии кнопки мыши запоминается начальная точка (угол прямоугольника), затем при перемещении мыши на месте предполагаемого прямоугольника рисуется штриховая рамка, которая становится прямоугольником, когда пользователь отпустил кнопку.

Здесь самый сложный момент — рисование и стирание временного прямоугольника. К счастью, у объекта Canvas есть специальный метод DrawFocusRect, который использует для рисования вторую (кроме not) обратимую логическую операцию — исключающее «ИЛИ» (xor). Поэтому если дважды вызвать этот метод с теми же самыми координатами, на экране восстановится исходное изображение, какое бы оно не было.

Мы построим для рисования прямоугольника процедуру DrawRect, которая очень похода на процедуру рисования линий DrawLine.

procedure DrawRect(x0, y0, x, y: integer; Mode: TPenMode);
var pt: TPoint;
    rct: TRect;
begin
  with bmp.Canvas do begin
    pt := PenPos;
    rct := Rect(x0, y0, pt.x, pt.y);
    DrawFocusRect ( rct );      // стираем временный
    rct := Rect(x0, y0, x, y);  // границы нового
    if Mode = pmCopy then
         Rectangle ( rct )      // рисуем постоянный
    else DrawFocusRect ( rct ); // снова рисуем временный
    MoveTo(x,y);  // новая текущая позиция рисования
  end;
end;

При передаче ей параметра Mode=pmNot будем рисовать временный прямоугольник, вызывая метод DrawFocusRect, а при Mode=pmCopy — постоянный (метод Rectangle).

Поскольку при рисовании прямоугольника текущая позиция рисования не изменяется, нам приходится вручную перемещать ее с помощью метода MoveTo.

Обратите внимание, что объект Canvas имеет два метода Rectangle. В одном (который мы рассматривали ранее) используются 4 параметра, а во втором (в процедуре DrawRect) — только один, объект TRect, составленный их тех же значений. Два метода с одинаковым названием, но с разными параметрами называются перегруженными (overloaded).
Добавьте процедуру для рисования прямоугольников в самое начало секции implementation. Добавьте в обработчик события onMouseMove объекта Pic код
  if RectBtn.Down then
    DrawRect(x0, y0, x, y, pmNot);
Добавьте в обработчик события onMouseUp код
  if RectBtn.Down then
    DrawRect(x0, y0, x, y, pmCopy);
Проверьте, как работает программа, если начинать рисовать прямоугольник не с левого верхнего угла, а с другого. Попробуйте исправить код.

Вы заметили, что при рисовании прямоугольника его внутренняя часть заливается белым цветом (так как по умолчанию установлен белый цвет кисти Canvas.Brush). Чтобы заливки не было, достаточно установить свойство Brush.Style=bsClear, сделав кисть прозрачной.

Измените код в процедуре DrawRect:
  if Mode = pmCopy then begin
    Brush.Style := bsClear;
    Rectangle ( rct );
    Brush.Style := bsSolid;
  end
  ...
Запустите программу и проверьте ее работу.
к началу К началу страницы

6. Выбор цвета

До этого момента все линии на рисунке были черного цвета, потому что мы не меняли установки по умолчанию. Теперь добавим в программу возможность выбора цвета линий и заливки. Для этого используем еще один компонент TPaintBoх, который разместим в нижней части панели Panel1, на которой находятся кнопки. На нем будем показывать два квадрата, залитых соответственно цветом линий и фоновым цветом.

На панель Panel1 добавьте компонент TPaintBox, дайте ему имя ColBox, установите выравнивание alBottom (прижать к нижней границе родительского окна) и высоту 40 пикселей. Добавьте для этого компонента обработчик события onPaint:
  with ColBox.Canvas do begin
    Brush.Color := bmp.Canvas.Brush.Color;
    Rectangle(15, 15, 35, 35);
    Brush.Color := bmp.Canvas.Pen.Color;
    Rectangle(5, 5, 25, 25);
  end;
Запустите программу и посмотрите, что получилось.

Но этого мало — мы видим установленные цвета, но по-прежнему не можем изменить их. В редакторе Paint для выбора цветов используется специальное диалоговое окно; оказывается, его можно вызвать и из программы на Delphi. Для этого используется компонент TColorDialog из группы Dialogs.

Добавьте на форму компонент TColorDialog из группы Dialogs.

Когда мы щелкнем на квадратиках с цветом, нужно вызвать этот диалог с помощью метода Execute. Это функция, которая возвращает логическое значение True, если пользователь выбрал цвет (этот цвет можно получить с помощью свойства Color), и False, если он щелкнул по кнопке Отмена.

Мы сделаем так, что при щелчке левой кнопкой мыши меняется основной цвет, а при щелчке правой кнопкой — фоновый цвет. Событие OnClick возникает тогда, когда пользователь щелкнул левой кнопкой, то есть, «правый щелчок» мы не поймаем. Поэтому будем использовать событие OnMouseUp: у этого обработчика есть параметр Button — кнопка мыши (нас интересуют варианты mbLeft — левая кнопка и mbRight — правая кнопка). Логика выбора может быть описана так:

if новый цвет выбран then begin
  if нажата левая кнопка then
       установить новый цвет линий
  else установить новый цвет заливки
  перерисовать ColBox
end;
Для компонента ColBox создайте обработчик события onMouseUp:
  if ColorDialog1.Execute then begin
    if Button = mbLeft then
         bmp.Canvas.Pen.Color := ColorDialog1.Color
    else bmp.Canvas.Brush.Color := ColorDialog1.Color;
    ColBoxPaint(Sender);
  end;
Уберите (закомментируйте) строчки Brush.Style:=... в процедуре DrawRect. Проверьте работу программы при рисовании прямоугольников.
к началу К началу страницы

7. Заливка

Добавим еще одну кнопку, которая будет включать режим заливки. Как и в редакторе Paint, сделаем заливку одноцветной области, то есть, при вызове метода FloodFill последний параметр будет равен fsSurface.

Вспомним, что цвета линий и фона хранятся прямо в свойствах Pen.Color и Brush.Color объекта bmp.Canvas. Для простоты мы будем выполнять заливку фоновым цветом, поэтому менять параметры кисти не потребуется.

Создайте еще одну кнопку путем копирования, дайте ей имя FillBtn, загрузите картинку fillbtn.bmp и измените всплывающую подсказку на «Заливка».
В обработчике события onMouseUp поля рисования Pic добавьте блок для выполнения заливки:
  if FillBtn.Down then
    bmp.Canvas.FloodFill(x, y, Pixels[x,y], fsSurface);
Запустите программу и проверьте ее работу.
Доработайте программу следующим образом: если пользователь щелкнет левой кнопкой мыши, выполняется заливка основным цветом (цветом линий), а при «правом щелчке» — заливка цветом фона. Для сохранения цвета можно использовать вспомогательную локальную переменную типа TColor.
к началу К началу страницы

8. Работа с файлами

Напоследок мы научимся сохранять и загружать файлы. Для сохранения и открытия файлов любого типа в Delphi существуют стандартные диалоговые окна TSaveDialog и TOpenDialog, однако для рисунков удобнее использовать специальные диалоги TSavePictureDialog и TOpenPictureDialog, которые позволяют сразу посмотреть уменьшенное изображение картинки в правой части окна.

Добавьте на форму компонент TSavePictureDialog из группы Dialogs.

Обычно у этого компонента настраиваются следующие свойства

Title — заголовок диалога (появляется в заголовке диалогового окна);

DefaultExt — расширение по умолчанию (такое расширение получит файл, если пользователь ввел только имя без расширения);

Filter — набор фильтров для выбора файлов.

Наиболее интересно свойство Filter. При его изменении вы увидите таблицу с двумя столбцами.

Левый столбец — это название фильтра, которое увидит пользователь в списке Тип файла, а правый — маска для выбора файлов, где звездочка означает любое количество любых символов, так что фильтр *.bmp отбирает все файлы с расширением bmp, а фильтр *.* показывает вообще все файлы.

В этом примере пользователь может выбрать один из двух фильтров. Свойство FilterIndex показывает номер выбранного фильтра (считая с единицы!).

Чтобы работать с рисунками в формате JPEG, нужно подключить модуль Jpeg (добавить его в список uses).
Добавьте на форму компонент TSavePictureDialog из группы Dialogs. Введите заголовок (свойство Title) «Сохранение рисунка». Установите расширение по умолчанию bmp. Задайте такой же фильтр, как на рисунке выше.

Сделаем так, чтобы файл сохранялся при нажатии клавиш Ctrl+S. Эта комбинация не имеет кода в таблице символов, поэтому ее можно «поймать» только в обработчиках OnKeyDown или OnKeyUp.

Вспомним, что в этих обработчиках параметр Key — это код клавиши. Для буквенных клавиш он совпадает с кодом соответствующей заглавной буквы, который можно определить с помощью функции Ord. Параметр Shift — это множество, которое содержит признаки нажатия клавиш Ctrl, Shift и Alt, а также кнопок мыши. Поэтому условие «нажали клавиши Ctrl+S» запишется в виде:

if (Key = Ord('S')) and (ssCtrl in Shift) then ...

Чтобы заставить диалог работать, нужно вызвать его метод Execute, который вернет логическое значение True, если пользователь удачно выбрал (или ввел вручную) имя файла. Это имя можно определить по значению свойства SavePictureDialog1.FileName. Для сохранения файла используется метод SaveToFile объекта-рисунка bmp.

Выберите форму Form1 в списке Object Inspector и добавьте обработчик события onKeyDown:
  if (Key = Ord('S')) and (ssCtrl in Shift) then
    if SavePictureDialog1.Execute then
      bmp.SaveToFile ( SavePictureDialog1.FileName );
Проверьте работу программы при нажатии клавиш Ctrl+S.
Аналогично добавьте на форму компонент TOpenPictureDialog и сделайте так, чтобы при нажатии клавиш Ctrl+O можно было загрузить новый файл (метод bmp.LoadFromFile(...)). Не забудьте, что после загрузки файла нужно перерисовать поле рисунка.
Сделайте так, чтобы программа предупреждала пользователя о том, что он не сохранил рисунок и собирается загрузить новый (или выйти из программы).
Сделайте так, чтобы после загрузки нового рисунка его имя выводилось в заголовке окна (свойство Caption формы).
к началу К началу страницы

 |  | Оглавление |
Форма и управление | Назад | В начало | Вперед | Текстовый редактор


Уроки по Delphi: Текстовый редактор

Тема 6. Текстовый редактор

  1. Меню
  2. Работа с файлами
  3. Шаблоны и действия
  4. Картинки в меню
  5. Панель инструментов
  6. Окно «О программе»
  7. Окно «Настройка»

Вы узнаете:

  • как работать с редактором текста TRichEdit;
  • как грамотно организовать открытие и сохранение файлов;
  • как построить и настроить меню программы;
  • как использовать контекстные меню;
  • что такое действия (Actions);
  • как сделать панель инструментов (еще один способ);
  • как сделать программу с несколькими формами;
  • как формы обмениваются данными.

1. Меню

Сейчас мы напишем несложный текстовый редактор, причем будем стараться максимально использовать готовые возможности Delphi и писать минимум кода.

Создайте новый проект с формой и сохраните его в папке Редактор под именем Editor.bdsproj. Измените заголовок формы на «Текстовый редактор».

В качестве поля для ввода текста применим компонент TRichEdit из группы Win32, который позволяет редактировать и сохранять документы в формате RTF (Rich Text Format, расширенный текстовый формат). В таком тексте можно, в отличие от простого текста (plain text), использовать выделение шрифтами и цветом, форматирование абзацев и т.п.

Добавьте на форму компонент TRichEdit, присвойте ему имя Editor и установите выравнивание alClient.

В редакторе TRichEdit можно обращаться к каждой строке текста через список строк Lines. Например, Editor.Lines.Count — это число строк, Editor.Lines[0] — самая верхняя строка, в Editor.Lines[Editor.Lines.Count-1] — самая последняя.

Чтобы у окна редактора были полосы прокрутки, нужно изменить свойство ScrollBars. По умолчанию оно равно ssNone (нет полос прокрутки), кроме этого возможны значения ssVertical (только вертикальная), ssHorizontal (только горизонтальная) и ssBoth (обе).

Очистите поле ввода, удалив все строки из свойства Lines. Установите свойство ScrollBars равным ssBoth.
Если вам нужен простейший текстовый редактор (как Блокнот), который не позволяет менять шрифт и форматировать абзацы, можно использовать компонент TMemo. Основные приемы работы с ним такие же, как и с TRichEdit.

В этой программе мы создадим верхнее меню. Для этого нужно поместить на форму компонент TMainMenu из группы Standard. Сейчас всю рабочую область занимает поле редактора, на него перетащить меню не удастся. Есть два выхода из такой ситуации: перетащить меню на заголовок формы или сделать двойной щелчок по названию TMainMenu в палитре инструментов.

Двойной щелчок по этому компоненту открывает редактор меню. В это окне сразу выделен первый элемент меню, его свойства показаны в окне Object Inspector.

Добавьте на форму компонент TMainMenu. Для первого пункта введите имя FileMenu (свойство Name)' и текст Файл (свойство Caption).

Если теперь щелкнуть на окне редактора меню, вы увидите новый пустой пункт верхнего меню и пустой пункт подменю. Их свойства настраиваются точно также. Если вместо текста написать знак «минус», в меню будет линия-разделитель.

Свойство ShortCut — это сочетание клавиш, которое воспринимается программой как выбор этого элемента меню (при этом в меню вообще не нужно заходить!). Например, сочетание Ctrl+N обычно используется для создания нового документа. Для этого нужно записать в значение свойства ShortCut текст «Ctrl+N».

Добавьте подпункты выпадающего меню Файл:
Создать (имя NewMI, клавиши Ctrl+N)
Открыть (имя OpenMI, клавиши Ctrl+O)
Сохранить (имя SaveMI, клавиши Ctrl+S)
линия-разделитель
Выход (имя ExitMI, клавиши Ctrl+Q).

При выборе элемента меню (любым способом) возникает событие OnClick, для которого можно назначить обработчик. Например, при выборе пункта Выход нужно закрыть форму (и завершить работу программы):

Close;

В данном случае вызывается метод Close формы. Поскольку этот вызов стоит в методе самой формы, имя формы указывать не нужно, хотя, в принципе, можно было бы написать Form1.Close.

При создании нового файла нужно очистить все строки редактора:

Editor.Lines.Clear;
Назначьте обработчики события OnClick для пунктов Выход и Создать. Запустите программу и проверьте работу меню.
к началу К началу страницы

2. Работа с файлами

Открытие и сохранение файла сделаем с помощью стандартных диалогов TOpenDialog и TSaveDialog (вспомните аналогичные диалоговые окна TOpenPictureDialog и TSavePictureDialog, которые мы использовали в графическом редакторе).

Добавьте на форму компоненты TOpenDialog и TSaveDialog. В обработчиках события onClick соответствующих пунктов меню организуйте загрузку и сохранение файлов с расширением RTF так же, как и для рисунков (не забудьте задать свойства DefaultExt, Filter и Title у диалогов).
Открыть:
  if OpenDialog1.Execute then
    Editor.Lines.LoadFromFile(OpenDialog1.FileName);
Сохранить
  if SaveDialog1.Execute then
    Editor.Lines.SaveToFile(SaveDialog1.FileName);
Запустите программу и проверьте работу пунктов меню. Что получилось не очень удачно?

Прежде всего, мы не видим, какой файл мы редактируем — его имя нигде не показывается. В профессиональных текстовых редакторах имя файла можно найти в заголовке окна.

Будем хранить имя файла в символьной переменной fName. Как вы помните, при запуске программы все глобальные переменные обнуляются, так что сначала в fName будет пустая строка.

При загрузке файла с диска (а также при сохранении!) нужно записать его имя в переменную fName, а при создании нового файла — записывать в эту переменную пустую строку. Тогда при сохранении документа можно не запрашивать его имя, если оно уже было введено.

Объявите символьную переменную fName для хранения имени загруженного файла. Сделайте так, чтобы при сохранении файла имя не запрашивалось, если оно уже известно (при создании нового файла записывайте в переменную fName пустую строку).
Сделайте так, чтобы имя файла отображалось в заголовке формы (свойство Caption) так: «Vasya.rtf - Текстовый редактор». Удобнее всего ввести новую процедуру SetName и вызывать ее каждый раз, когда нужно изменить имя файла. В этой процедуре заодно меняется и заголовок формы. Поскольку процедура обращается к форме, лучше сделать ее методом класса TForm1.

Имя файла, записанное в переменной fName, определяется при открытии или сохранении файла как свойство FileName диалогового окна. Это полное имя, включающее путь к файлу. Обычно в заголовке окна путь не выводят, поэтому нужно выделить из полного имени собственно имя файла с расширением, отбросив весь путь. Это делает функция ExtractFileName:

Caption := ExtractFileName(fName) + ' - Текстовый редактор';
Добавьте новый пункт меню Файл - Сохранить как. При выборе этого пункта имя файла должно запрашиваться всегда.

Осталась еще одна проблема — при загрузке нового файла и при завершении работы программа не предупреждает пользователя о том, что файл не сохранен. Из-за этого в реальной ситуации могут быть потеряны важные данные.

Введем логическую переменную NotSaved, которая будет равна True, если файл не был сохранен.

При изменении текста в редакторе возникает событие OnChange. В этот момент нужно записать в переменную NotSaved значение True:

NotSaved := True;

Наоборот, при создании, открытии и сохранении файла в эту переменную нужно записывать значение False.

Введите логическую переменную NotSaved и добавьте нужный обработчик события onChange для компонента Editor, а также дополните обработчики пунктов меню Создать, Сохранить и Открыть командой
  NotSaved := False;

Теперь разберемся, как проверить, можно ли загружать новый документ. Если документ был сохранен (NotSaved=False), все в порядке. Если нет, то нужно выдать на экран запрос с тремя кнопками (вариантами ответа): Yes — сохранить, No — не сохранять, Cancel — отменить загрузку нового файла и продолжить редактирование.

Такой запрос выдает встроенная функция MessageDlg, которую мы уже использовали. Она возвращает значение типа TModalResult (результат модального вызова, то есть, все остальные элементы блокируются, пока мы не ответим на вопрос). В данном случае вызов и запись ответа пользователя в переменную answer выглядит так:

var answer: TModalResult;
...
answer := MessageDlg ( 'Файл не сохранен. Сохранить?',
                       mtWarning, mbYesNoCancel, 0);

Напомним, что здесь первый параметр — текст сообщения, второй — тип диалога (mtWarning — предупреждение), третий — множество кнопок (mbYesNoCancel — это то же самое, что и [mbYes,mbNo,mbCancel]), а четвертый (равный нулю) говорит о том, что в справочной системе нет пункта для этого диалога (впрочем, у нас нет справочной системы вообще).

Дальнейшие действия зависят от того, как ответил пользователь на заданный вопрос. Результат answer может быть равен одному из трех значений:

mrYes (нажата кнопка Да), нужно вызвать обработчик OnClick для пункта меню Файл - Сохранить;

mrNo (нажата кнопка Нет), нужно записать в переменную NotSaved значение False, будто бы файл уже сохранен;

mrCancel (нажата кнопка Отмена), ничего не меняем, новый файл не загружается.

В описание формы (в блок public) добавьте функцию, в которой будет выполняться проверка при загрузке нового документа:
  function CanLoadNewFile: boolean;
В раздел implementation добавьте текст этой функции
  function TForm1.CanLoadNewFile: boolean;
  var answer: TModalResult;
  begin
    if NotSaved then begin
      answer := MessageDlg( 'Файл не сохранен. Сохранить?',
                             mtWarning, mbYesNoCancel, 0);
      if answer = mrYes then SaveMIClick(nil);
      if answer = mrNo then NotSaved := False;
    end;
    Result := not NotSaved;
  end;
Добавьте строчку
  if not CanLoadNewFile then Exit;
в самое начало обработчиков события OnClick для команд меню Создать и Открыть.
к началу К началу страницы

3. Шаблоны и действия

Если нажать правую кнопку мыши в редакторе меню, мы увидим список всех команд в контекстном меню:

Простейшие операции — вставка нового пункта (клавиша Insert) и удаление текущего (Delete). Если нажать Ctrl+«вправо», добавляется подменю (сейчас эта возможность неприменима). Пункт Select menu позволяет выбрать нужное меню, если такой компонент на форме не один.

Несколько элементов контекстного меню включают слово Template — шаблон, заготовка. В Delphi есть готовые шаблоны, которые можно использовать и свободно настраивать для каждого случая.

Вызовите редактор меню и выберите пункт Insert from Template из контекстного меню.

Вы увидите список всех встроенных шаблонов:

Можно догадаться, что для меню Файл (File) и Правка (Edit) уже есть готовые шаблоны, правда, на английском языке. Что касается меню Файл, оно у нас уже готово и настроено, а вот меню Правка мы добавим из шаблона.

Добавьте из шаблона меню Правка (Edit menu). Если оно оказалось слева от меню Файл, перетащите его мышкой в нужное место. Удалите все лишние элементы кроме Undo (Отмена), Cut (Вырезать), Copy (Копировать) и Paste (Вставить). Измените название Edit на русский вариант Правка.

Теперь можно заменить все английские названия на русские и построить обработчики для каждого пункта меню, как мы уже делали это раньше. Но на этот раз мы снова используем готовые средства.

В Delphi существуют так называемые действия (Actions) — невидимые компоненты специального типа, которые выполняют стандартные операции, например, работают с буфером обмена. Для работы с такими действиями мы будем использовать компонент TActionList (список действий) из группы Standard.

Добавьте на форму компонент TActionList. Вызовите его редактор двойным щелчком мыши.

Вы увидите, что список действий, который используются в программе, пуст — ведь мы ничего не добавляли.

Щелкните по стрелке справа от единственной активной кнопки. Вы увидите два пункта меню: New Action (создание нового своего действия) и New Standard Action (добавить стандартное действие, именно это нам и нужно).

Выберите пункт меню New Standard Action. В появившемся окне выделите (при нажатой клавише Ctrl) 4 нужных действия из группы Edit (Правка): TEditCut (Вырезать), TEditCopy (Копировать), TEditPaste (Вставить), TEditUndo (Отмена).

Нажмите кнопку ОК, чтобы добавить их в программу. Посмотрите, что изменилось в редакторе компонента TActionList и в окне Structure.

В окне Structure хорошо видно, что новые действия называются EditCut1, EditCopy1, EditPaste1 и EditUndo1.

Выбирая добавленные действия в окне Structure, в их свойствах Caption и Hint замените все английские надписи на русские.

Теперь нужно назначить действия пунктам меню, выбрав нужное действие в списке свойства Actions.

Назначьте нужные действия пунктам выпадающего меню Правка. Обратите внимание, что их названия автоматически поменялись на русские. Почему?
Запустите программу и проверьте, как она работает с буфером обмена.

Остается не совсем ясно, как программа определяет, к какому элементу относятся все эти действия? В самом деле, действия работают с тем элементом, который имеет в данный момент фокус текстового ввода (то есть, получает сообщения с клавиатуры). В нашей программе есть только один такой элемент — это редактор Editor.

Теперь добавим в программу контекстное меню, которое будет появляться при нажатии на правую кнопку мыши над полем редактора.

Поместите на форму компонент TPopupMenu и добавьте в него команды для работы с буфером обмена. Названия очередного элемента меню вводить не требуется, просто выберите нужное действие в свойстве Action.

Меню готово, но пока не «привязано» к какому-то объекту. Для такой привязки используется свойство PopupMenu того компонента, для которого контекстное меню должно работать.

Для компонента Editor установите свойство PopupMenu=PopupMenu1. Проверьте работу контекстного меню.
к началу К началу страницы

4. Картинки в меню

Теперь добавим в меню рисунки (слева от названий). Для этого используется компонент TImageList (список рисунков), куда все эти рисунки нужно предварительно загрузить.

Добавьте на форму компонент TImageList из группы Win32.

Обратите внимание, что по умолчанию его свойства Height и Width (высота и ширина одной картинки) равны 16. Мы не будем менять эти размеры.

Есть два варианта: загружать картинки по одной или сразу все, в виде отдельного файла. Если размер загружаемого рисунка превышает заданный свойствами Height и Width, программа предложит «порезать» его на картинки стандартного размера. Мы загрузим сразу все рисунки, так проще.

В Проводнике (или в любой программе для просмотра рисунков) посмотрите картинку icons.bmp.
Щелкните дважды по компоненту TImageList1, чтобы вызвать его редактор. В появившемся окне щелкните по кнопке Add (добавить) и выберите на диске файл icons.bmp. Когда программа предложит разбить весь рисунок на отдельные картинки размером 16 на 16 пикселей, ответьте «Yes» («Да»).

Обратите внимание, что фоновый фиолетовый цвет (clFuchsia) стал прозрачным (Transparent Color). По умолчанию прозрачным будет тот цвет, который имеет пиксель в левом нижнем углу рисунка.

Теперь под каждой картинкой вы видите ее номер в списке рисунков. Эти номера будут нужны нам при настройке меню.

Запомните или запишите номера рисунков, соответствующих действиям из меню Правка. После этого закройте окно редактора.

Чтобы назначить рисунки действиям, нужно установить для действий свойство ImageIndex равным номеру рисунка в списке ImageList1. Кроме того, надо связать компонент MainMenu1 со списком рисунков.

Выделите компонент MainMenu1 и установите его свойство Images=ImageList1. Сделайте то же самое для компонента PopupMenu1.
Посмотрите, какие номера имеют картинки Вырезать, Копировать, Вставить, Отмена и присвойте эти номера свойству ImageIndex действий EditCut1, EditCopy1, EditPaste1 и EditUndo1.
Посмотрите, какие номера имеют картинки Создать, Открыть, Сохранить, и присвойте эти номера свойству ImageIndex пунктов меню NewMI, OpenMI и SaveMI. Запустите программу и проверьте, появились ли картинки в пунктах верхнего и контекстного меню.
к началу К началу страницы

5. Панель инструментов

В предыдущем проекте мы создавали панель инструментов с помощью универсального компонента TPanel и кнопок TSpeedButton. Однако в Delphi для этой цели существует специальный компонент TToolBar (группа Win32).

Добавьте на форму компонент TToolbar (двойным щелчком или перетащив его на заголовок формы. Установите для него высоту Height=30, а также свойства
BorderWidth = 2 (отступ кнопок от границ панели)
Indent = 5 (дополнительный отступ с левого края)
Images = ImageList1 (список картинок).

Пока на панели нет ни одной кнопки. Если нажать на ней правую кнопку мыши, в верхней части контекстного меню появляются команды New Button (новая кнопка) и New Separator (новая линия-разделитель, граница между группами кнопок).

Сначала мы добавим три кнопки для работы с файлами.

Добавьте на панель инструментов три кнопки, установите для них обработчики события onClick, выбрав в списке готовые функции NewMIClick, OpenMiClick и SaveMIClick. Подумайте, почему на кнопках автоматически появились нужные картинки. Запустите программу и проверьте работу кнопок.

Теперь добавим еще 4 кнопки, связав их с действиями Вырезать, Копировать, Вставить и Отмена.

Добавьте на панель инструментов разделитель (New Separator) и еще четыре кнопки, установите для них свойства Action, выбрав в списке действия EditCut1, EditCopy1, EditPaste1 и EditUndo1. Проверьте работу кнопок.

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

Добавьте к списку действий ActionList1 стандартные действия TRichEditBold (жирный), TRichEditItalic (курсив) и TRichEditUnderline (подчеркивание) из группы Format. Исправьте для этих действий английские надписи на русские и установите правильные номера соответствующих картинок в списке ImageList1.
Добавьте на панель инструментов разделитель (New Separator) и еще три кнопки, назначьте им только что введенные стандартные действия. Запустите программу и проверьте работу новых кнопок.
к началу К началу страницы

6. Окно «О программе»

Обычно серьезные программы на Delphi содержат несколько форм (окон). Одна из них — основная, а другие используются для установки параметров, вывода справочной информации и т.п. В этом разделе мы создадим вторую форму, которая будет показывать информацию о программе.

Добавьте в горизонтальное меню новый пункт Справка.

Для добавления формы к проекту нужно выбрать пункт меню File - New - Form.

Добавьте в проект новую форму, дайте ей имя AboutBox и заголовок «О программе». Установите положение окна на экране Position=poOwnerFormCenter (по центру главного окна). Сохраните форму, изменив имя модуля по умолчанию Unit2.pas на About.pas.
На новую форму добавьте компонент TImage, установите его свойство AutoSize=True и загрузите в свойство Picture рисунок editor.bmp.

Вы увидели, что у рисунка есть зеленый фон, который совершенно не нужен. Убрать его достаточно просто. Вспомните, что при загрузке рисунков в список TImageList цвет пикселя в левом нижнем углу можно было сделать прозрачным. То же самое работает и для компонента TImage, если у него свойство Transparent=True.

Установите для рисунка свойство Transparent=True.

Теперь добавим метки с надписями.

Добавьте на форму метки с названием программы (придумайте его), именем автора и текстом «Распространяется свободно». Сделайте так, чтобы название было набрано крупным и жирным шрифтом.

Осталось добавить кнопку для закрытия окна.

Добавьте на форму кнопку TBitBtn (из группы Additional), установите ее свойство Kind=bkOK. Уменьшите размеры формы до минимально необходимых.

Форма готова. Ее нужно вызывать из обработчика OnClick для соответствующего пункта меню в главной форме.

Скорее всего, главная форма сейчас открыта, и одна из закладок редактора называется Unit1 (если вы не меняли название модуля при сохранении). Чтобы перейти к главной форме нужно щелкнуть мышкой по этой закладке.

Нажав клавиши Shift+F12 можно увидеть список всех форм проекта (даже закрытых!) и выбрать нужную. При нажатии Ctrl+F12 появляется список всех модулей (могут быть модули, не связанные с формой и содержащие только функции и процедуры).

Для того, чтобы в программе вызвать форму (показать ее на экране), можно использовать два метода: Show и ShowModal. Разница между ними в том, что метод ShowModal блокирует работу с программой до тех пор, пока показанное окно не будет закрыто. Такой режим называют модальным, его используют все диалоговые окна, в том числе функция MessageDlg и диалоги для выбора файла на диске. Таким образом, для вызова окна AboutBox нужно написать код

AboutBox.ShowModal;

Тогда возникает естественный вопрос: откуда главная форма узнает о том, что есть еще одна форма AboutBox? Для это в списке uses нужно указать модуль About, где находится эта форма.

Здесь есть одна тонкость. Дело в том, что в модуле может быть два списка uses, в секции interface и в секции implementation. Напомним, что секция interface — это то, что могут «видеть» другие модули, а секция implementation закрыта от «чужих».

В какой список uses нужно добавить второй модуль? Чаще всего — в секцию implementation. Значительно реже бывают случаи, когда данные из другого модуля нужны уже в секции interface, например, если используются объявленные там типы данных.

Недопустима ситуация, когда два модуля подключают друг друга в секции interface. Дело в том, что все модули, подключенные в секции interface, должны быть полностью обработаны до перехода к секции implementation. Получается, что начав обрабатывать первый модуль, программа встречает ссылку на второй и сразу переключается на его обработку. Там она встречает ссылку обратно на первый модуль и т.д., получается «сказка про белого бычка» (по-научному — бесконечная рекурсия).

Перейдите к главной форме и создайте для пункта меню Справка обработчик события onClick:
  AboutBox.ShowModal;
В начало секции implementation добавьте строчку
  uses About;
Запустите программу и проверьте работу нового пункта меню.
В любом модуле можно использовать все типы данных, константы, переменные, процедуры и функции, которые находятся в секции interface всех подключенных модулей.

Обычно в окне с информацией о программе написано, кто является ее владельцем (кому принадлежит лицензия на использование программы). Мы разместим эту информацию в модуле Unit1 (где описана главная форма), а использовать ее будет в окне AboutBox.

Объявим строковую константу, в которой запишем фамилию владельца лицензии:

const LicenceOwner = 'В.А. Хрюндиков';

Так как нам нужно «добраться» до этого значения из другого модуля, константу нужно объявить в секции interface.

Объявите константу LicenceOwner, как показано выше.

Теперь добавим на форму AboutBox еще одну метку, содержимое которой будем определять при открытии этой формы.

Перейдите к форме AboutBox и добавьте еще одну метку ниже существующих. Дайте ей имя LicLabel и измените текст на «Владелец».

При открытии формы происходит событие OnShow, в этот момент мы и будем обновлять метку:

LicLabel.Caption := 'Владелец: ' + LicenceOwner;

Здесь мы хотим использовать константу LicenceOwner, которая описана в модуле Unit1, поэтому этот модуль нужно добавить в список uses в секции implementation.

Добавьте код для вывода имени пользователя на форму AboutBox (см. выше). Запустите программу и проверьте ее работу.
Заметьте, что событие OnCreate для формы происходит только один раз, при ее создании. Напротив, обработчик OnShow вызывается каждый раз, когда форма появляется на экране. При скрытии формы происходит событие OnHide.
к началу К началу страницы

7. Окно «Настройка»

Добавим в программу еще одну форму, с помощью которой пользователь сможет настраивать интерфейс: удалять ненужные кнопки с панели инструментов, а также менять цвет фона и текста в поле редактора.

Заметим, что все кнопки объединены в три группы, разделенные вертикальной черточкой-разделителем. Эти группы можно условно назвать «Файлы», «Правка» и «Шрифт».

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

Эта форма будет появляться при выборе пункта меню Настройка, который мы сейчас добавим.

Добавьте в главное меню пункт Настройка и дайте ему имя ToolsMI.

Теперь создаем новую форму и расставляем на ней компоненты.

Создайте новую форму, измените ее заголовок на текст «Настройка», а имя — на ToolsForm. Установите свойство Position=poScreenCenter, чтобы разместить диалоговое окно в центре экрана.
Поместите на форму два компонента TGroupBox с заголовками «Панели инструментов» и «Цвет фона». В первую группу добавьте три выключателя (TCheckBox), дайте им имена FilesCB, EditCB и FontCB, в свойства Caption введине нужные надписи (см. рисунок). Включите все флажки, установив для них свойство Checked=True.

Для разнообразия мы сейчас не будем использовать группу радиокнопок TRadiGroup, а вместо этого посмотрим, как работать с отдельными кнопками TRadioButton.

Добавьте во вторую группу две радиокнопки TRadioButton, присвойте им имена WhiteRB и BlueRB, введите соответствующий текст в свойство Caption.
Добавьте на форму две кнопки TBitBtn, установите их свойство Kind соответственно в bkOK и bkCancel (Отмена). Для второй кнопки исправьте надпись на русскую. Отрегулируйте размеры формы до минимально необходимых.

Пользователь может нажать кнопку ОК или кнопку Отмена, в последнем случае не нужно делать никаких изменений. Как определить, какая именно кнопка была нажата? Для этой цели у кнопок есть свойство ModalResult — результат модального вызова формы (напомним, что при таком вызове пользователь должен закрыть диалоговое окно, прежде чем продолжать работу с программой).

Посмотрите в окне Object Inspector, чему равно свойство ModalResult у каждой из кнопок.

Если вы сделали все правильно, у кнопки ОК вы увидели ModalResult=mrOK, а у кнопки Отмена — mrCancel. Когда вызывается метод ShowModal формы, одно из этих значений будет возвращено в качестве результата, в зависимости от того, какую кнопку нажал пользователь. Таким образом, получается такой код:

if mrOK = ToolsForm.ShowModal then
  пользователь нажал кнопку ОК
else
  пользователь нажал кнопку Отмена

При нажатии кнопки Отмена ничего делать не нужно, поэтому части else в реальной программе не будет.

Как убрать кнопку с панели инструментов? Для этой цели мы используем свойство Visible, если оно равно True, то кнопка видима, а если False, то скрыта. Поэтому для одной кнопки с именем ToolButton1 из первой группы (файлы) в коде можно написать:

if mrOK = ToolsForm.ShowModal then begin
  if ToolsForm.FilesCB.Checked then
       ToolButton1.Visible := True
  else ToolButton1.Visible := False;
  ...
end;

или еще более красиво:

if mrOK = ToolsForm.ShowModal then begin
  ToolButton1.Visible := ToolsForm.FilesCB.Checked;
  ...
end;

Обратите внимание, что при обращении к флажку FilesCB формы ToolsForm нужно указать название формы, потому что мы обращаемся к ней из метода другой (главной) формы. При этом в модуле главной формы нужно подключить модуль Tools, добавив его в список uses в секции implementation.

Поскольку внутри условного оператора нам часто придется обращаться к элементам и методам формы ToolsForm, имеет смысл использовать оператор with:

with ToolsForm do
  if mrOK = ShowModal then begin
    ToolButton1.Visible := FilesCB.Checked;
    ...
  end;
Создайте обработчик события OnClick для пункта меню Настройка и добавьте в него код для скрытия ненужных кнопок (см. пример выше).

Цвет фона устанавливается с помощью свойства Color редактора Editor. После установки синего фона (тёмно-синий цвет обозначается как clNavy) нужно одновременно поменять цвет шрифта (Editor.Font.Color) на белый. Если пользователь выбрал стандартный вариант, нужно установить белый фон и черный цвет символов.

Чтобы определить цветовой режим, выбранный пользователем, нужно проверить свойство Checked радиокнопки WhiteRB: если она включена, выбран белый фон, иначе — синий.

Добавьте в новый обработчик события OnClick код для изменения цветов:
  if ToolsForm.WhiteRB.Checked then begin
    Editor.Color := clWhite;
    Editor.Font.Color := clBlack;
  end
  else begin
    Editor.Color := clNavy;
    Editor.Font.Color := clWhite;
  end;
Запустите программу и проверьте ее работу. Попробуйте поменять состояние флажков и потом нажать кнопку Отмена. Что вам не понравилось, когда вы снова зашли в окно Настройка?

Вы увидели, что при открытии формы Настройка после выхода по кнопке Отмена состояние флажков и переключателей не всегда отражает реальную ситуацию. Чтобы исправить эту ошибку, используем обработчик OnShow формы ToolsForm (напомним, что он вызывается каждый раз при открытии формы). В этом обработчике нужно установить флажки, проверив видимость (свойство Visible) кнопок (по одной из каждой группы) и выбрать включенную радиокнопку в зависимости от цвета поля редактора.

Поскольку мы будем обращаться из модуля Tools к нескольким компонентам другой формы (Form1), снова удобно использовать оператор with:

with Form1 do begin
  FilesCB.Checked := ToolButton1.Visible;
  EditCB.Checked := ToolButton4.Visible;
  FontCB.Checked := ToolButton9.Visible;
  if Editor.Color = clWhite then
       WhiteRB.Checked := True
  else BlueRB.Checked := True;
end;

Нужно не забыть подключить модуль Unit1 к модулю Tools, добавив его в список uses.

Создайте обработчик сообщения OnShow для формы ToolsForm и добавьте в него приведенный выше код. Запустите программу и проверьте ее работу. Обратите внимание на радиокнопки при первом открытии окна Настройка.

Наверняка вы обнаружили, что включена радиокнопка, соответствующего синему цвету фона. Это значит, что не сработал условный оператор

 if Editor.Color = clWhite then ...

Значит, цвет фона у редактора — не белый?

В окне Object Inspector посмотрите, какой цвет фона и текста установлены у поля Editor.

По умолчанию цвета фона и текста редактора определяются системными настройками, их условные обозначения — clWindow (цвет фона окна) и clWindowText (цвет текста в окне). Изменив настройки экрана, пользователь может выбрать любые цвета, и это сразу отразится на всех окнах.

Для корректной работы нужно везде вместо clWhite использовать clWindow, а вместо clBlack — clWindowText.

Исправьте программу, как описано выше (оба модуля). Проверьте ее работу, вызвав окно Настройка.

Мы уже использовали контекстное меню (компонент TPopupMenu) для работы с буфером обмена, это меню было «привязано» к редактору Editor. Каждый компонент может иметь свое контекстное меню, которое появляется после щелчка на нем правой кнопкой мыши. Теперь мы добавим контекстное меню для панели инструментов, и при выборе единственного пункта Настройка будем вызывать соответствующий обработчик.

Разместите на главной форме компонент TPopupMenu, добавьте в него один пункт — Настройка, и выберите для него тот же обработчик события OnClick, что и для соответствующего пункта верхнего меню.
Свяжите новое меню с панелью инструментов, установив ее свойство PopupMenu. Проверьте работу нового контекстного меню.
к началу К началу страницы

Графический редактор Назад В начало Оглавление