Графические часы (исходники, документация)

delphi позволяет рисовать почти где угодно. Можно рисовать на форме, можно рисовать на метке, можно даже рисовать непосредственно на экране! Но лучше использовать всё-таки специальный компонент timage, уже хотя бы потому, что этот компонент сам будет следить затем, что бы при перерисовке окна твоей программы, твои каракули тоже перерисовывались. Если ты нарисуешь что-то, например, на форме, то при сворачивании окна программы твой рисунок сотрётся.

Собственно за рисование отвечает свойство canvas. Если у компонента есть такое свойство, то на нём можно рисовать, если нет - увы. Кстати, это свойство ты не найдёшь в инспекторе объектов. Это свойство не доступно во время разработки программы. Оно станет доступно только во время исполнения. Разумеется, к нему можно обращаться в тексте программы - ведь текст программы начнёт работать только после запуска. Как же определить есть свойство canvas у компонента или нет? Если ты выделишь компонент на форме, свойство или событие в инспекторе объекта или поставишь курсор внутрь какого-либо слова в тексте программы и нажмёшь клавишу f1, то откроется система помощи, причём откроется она на описании выделенного объекта, свойства, события или слова (разумеется, если этот элемент в помощи имеется). Если элементу соответствует несколько записей в системе помощи, то тебя поросят выбрать нужный (в delphi7 очень многим компонентам соответствует по 2 записи - нужно выбирать ту в которой есть аббревиатура vcl). У всех компонентов в помощи имеется ссылка properties, при нажатии на которую откроется окно со списком всех свойств этого объекта. Тебе останется только найти нужное свойство в этом списке или установить факт его отсутствия.

Если на твоей форме места для таких часов нет, предлагаю включить в нашу программу ещё одну форму.

0. С помощью кнопки «new form» или пункта меню «file/new form» создаёшь новую форму. Жмёшь на кнопочку «save». Вводишь какое-нибудь имя для этого нового модуля (конечно можно оставить и unit2, но лучше использовать осмысленные имена).

1. Помещаешь на новую форму компонент image (палитра additional, 6-я кнопка в виде картинки) на форму. Свойство align выставляешь в alclient, в результате image будет растекаться по всей форме.

2. Кладёшь на форму timer. Создаёшь ему обработчик события ontimer.

3. Объявляешь новые переменные:

var xc,yc : integer;
u : double;

Обрати внимание. Объявление переменных может занять несколько строк, но слово var пишешь только один раз, то есть var помечает весь блок переменных, а не отдельную его строку. Тип переменных double - это вещественный тип, в нём можно хранить дробные числа. Переменные xc, yc используем для хранения координат центра часов, а в переменную u будем записывать угол поворота стрелок.

4. Объявляешь типизированные константы:

const lh : integer = 60;
lm : integer = 100;
ls : integer = 80;

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

5. В теле процедуры пишем:

xc := image1.width div 2;
yc := image1.height div 2;
u := pi/2 - time*2*2*pi;
image1.canvas.moveto(xc,yc);
image1.canvas.lineto(xc+round(lh*cos(u)),yc-round(lh*sin(u)));

В первых двух строчках рассчитываем координаты центра image, это и будет центр наших часов.

В третьей строчке считаем угол для часовой стрелки. Символ «/» означает деление, символ «*» умножение, «-» знак минус. Порядок действий стандартный для математических выражений, то есть сначала делим/умножаем, потом складываем/вычитаем. Функция pi возвращает число пи. Объяснять, почему угол считается именно так, надо? Или твоих знаний геометрии хватит? Ах, да, сначала Великая Эроида, затем тяжкое наследие гуманитарного образования… Значит, угол в математике (и в delphi) принято отсчитывать от горизонтали и против часовой стрелки. А на часах - от вертикали и по часовой. Знак «-» перед time меняет направление отсчёта, а «пи пополам» смещает начало отсчёта на четверть оборота против часовой стрелки (то есть на цифру 12 на часах). За сутки time меняет своё значение от 0 до 1, а часовая стрелка должна за это время сделать 2 оборота, то есть «2*2*pi» радиан. Таким образом, мы получаем угол u в радианах. Именно в радианах этот угол и нужно подставлять в функции синуса и косинуса.

Четвёртая строка: помещаем указатель в центр.

Пятая строка: проводим линию от текущего положения указателя до точки с координатами xc+round(lh*cos(u)),yc-round(lh*sin(u)). Координату x получаем, сложив xc и длину стрелки, умноженную на косинус угла. Поскольку косинус угла - величина вещественная, а координаты должны быть целочисленными, нужно использовать функцию round - округление. Вопрос на засыпку: почему нельзя писать lh*round(cos(u))? Минус при вычислении координаты y связан с тем, что ось у направлена не вверх (как обычно принято в математике), а вниз, ну для тебя, как специалиста по Бейсику это не должно удивить.

6. Последние три строчки скопируй два раза и модифицируй для отображения минутной и секундной стрелок. Справишься?

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

form2.show;

Подумай, где можно дать эту команду. Кнопочку поставить или на какое событие у формы повесить (например, у формы тоже есть событие onclick). Важно, что бы это событие вызывалось достаточно редко (один раз или по действию пользователя) - событие ontimer не подойдёт.

Запусти программу. Часы работают. Только стрелки не стираются. Нужно очищать image перед каждой новой отрисовкой. Самый простой способ очистить image - закрасить его весь цветом фона.

1. Объяви новую переменную rect типа trect. Этот тип содержит внутри себя 4 поля - left, top, right, bottom целочисленного типа, и предназначен для определения координат прямоугольника.

2. В самом начале тела процедуры нужно присвоить полям rect значения координат прямоугольника, который мы хотим закрасить и вызвать метод для закраски:

rect.left := 0;
rect.top := 0;
rect.right := image1.width-1;
rect.bottom := image1.height-1;
image1.canvas.fillrect(rect);

Вопросы и ответы

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

О. Конечно, можно. Для минутной стрелки нужно делать угол кратным величине pi/30. Например, так: u := round(u*30/pi)/(30/pi); или так: u := int(u*30/pi)/(30/pi); Функция round, напомню, округляет числа, а int - отбрасывает дробную часть. (Вообще-то между ними есть ещё одно очень серьёзное отличие: round возвращает целочисленное значение, а int - вещественное, но для данной задачи это не важно). Для часовой - думайте сами. :-)

В. На счёт второй формы, можно просто задать ей свойство visible в true, тогда её будет видно при запуске, но она будет типо второстепенной.

О. Можно. Я хотел просто продемонстрировать возможность в любой момент показать форму (метод show), а в обном из следющих уроков - спрятать её (метод hide). Для этих целей можно присваивать значения true и false свойству visible - оба способа практически равнозначны (в действительности и во втором способе вызываются методы show и hide, и в первом изменяются значения visible), но рекомендуют использовать методы, как более корректный с точки зрения объектно-ориентированного программирования подход.

В. Теперь на счёт закрашивания цветом фона и вообще цвета фона=) Как его (цвет фона) задавать? да и цвет линии=) В паскале для этого дела есть функции setcolor - для рисования и setbkcolor - для фона. В Делфи я ниче подобного не нашла=(

О. У canvas есть свойства brush (кисть) и pen (карандаш), так вот, за закрашивание областей отвечает кисть, а за рисование карандаш. У них есть куча свойств, в том числе и цвет. Нажмите f1, выделив компонент image, щёлкните по ссылке properties, найдите canvas, опять properties, и т.д. - почитайте сами что есть у brush и pen. Мы этим тоже обязательно займёмся, но немного позже.

В. И ещё на счёт паскаля, там анимацию можно делать двумя способами или страницами или перерисовывать, так вот, при перерисовывании сначала рисуется что-то цветом, потом закрашивается цветом фона, потом перерисовывается и т.д. Не знаю, можно ли с нашими стрелками сделать нечто подобное=)

О. В Паскале рисование производится непосредственно работой с видеокартой, в delphi тоже возможен подобный вариант с использованием, например, библиотеки directx, но сейчас мы рисуем используя возможности операционной системы. Рисунок у нас простенький, так что всё работает без проблем. В принципе можно эмулировать привычные Вам из Паскаля страницы с помощью двух одинаковых компонентов image, расположенных друг под другом, и меняя у поочерёдно меняя у них свойство visible.

В. И ещё один вопрос на счёт часов, то что графический таймер немножечко отстаёт от текстового - это нормально?

О. Надо уменьшить interval у таймера. :-)


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=3841