Свой шедулер на Delphi
Года четыре назад мне пришлось писать «электронный органайзер» для одного
французского перца. Зачем он был ему нужен? Очень просто – каждый буржуй хочет,
чтобы интерфейс любой проги полностью соответствовал его желаниям, и ему не
приходилось напрягаться с мастерами, кастомизацией и делать в пять кликов то,
что можно сделать одним хоткеем. Таким хоткеем, каким ему нужно. Соответствовать
его требованиям было сложновато, т.к. общались мы на английском, которого ни я,
ни он толком не знали :).
Делаем для себя
Потом эта прога пригодилась и мне. Правда, ее исходники посеялись при
очередном формате винта, а попросить их у того дяди было невозможно, поскольку
его мыльник я тоже потерял. Убивать же 15 минут на создание своей утилиты мне
было лень, и я полез в инет. Результат меня разочаровал. Конечно, шедулеров и
ремайндеров там куча, в том числе включающих расчет биоритмов, записные книжки и
даже какие-то лунные календарики. Но того, что мне надо, а именно, простую
прогу, висящую в трее и напоминающую о событиях с точностью до минуты, красивым
окошком и гимном СССР, я не нашел. В итоге пришлось делать все самому.
Жесткие требования
Казалось бы, органайзер – это всего лишь «IF data = data then showmessage
(‘Вам пора!’);». Однако, цены на эти проги могут достигать 30 баксов за
регистрацию. Почему? А все потому, что юзер любит комфорт. Например, вот что
может его порадовать:
- Удобный интерфейс. Это основная форма и popup-menu иконки около часов.
Пункт «быстро добавить задание» просто обязателен.
- Маленький размер. 1,2 Мб в оперативке - это не предел
мечтаний. Когда я впервые увидел такую гигантскую прогу, она не порадовала мой тогдашний 566
МГц/192 Мб. По-хорошему, код шедулера должен быть 100% из WinAPI.
- Гибкость базы событий. Разумеется, это должна быть БД.
Хранить события в ini-файле уже немодно, да и тебе наверняка
придется выводить по желанию пользователя ближайшие задания,
фильтровать их по дню, часу или имени события (день
рождения/праздник/траур и т.п.), поэтому будем беречь нервы. В
своем примере я использовал XML.
- 2 больших подраздела опций: краткий и развернутый. Краткий
необходим для быстрого внесения заданий и элементарного
контроля, развернутый – это «мастера заданий» плюс все то, что
тебе придет в голову. Кстати, есть товарищи, которые используют
только второй способ. Получается вот что: «если событие - не
траур, нажмите , иначе нажмите <далее> и переходите к следующей странице». Лучше
уж тренировать мозги, чем использовать такой органайзер :).
-
Дополнительные возможности. Чего только в них ни
встраивают помимо того, что я сказал. Встречаются и календари
месячных, и какие-то кармическо-астрологические бонусы. Так что попробуй и ты добавить что-нибудь
оригинальное. Например, «расчет даты родов». Вот тебе 2 формулы их определения:
«дата последней менструации – 3 месяца + 7 дней» или «известная дата зачатия – 3
месяца – 7 дней». И какая, по-твоему, женщина устоит перед органайзером,
постоянно напоминающим: «Ваш малыш родится через Х дней»? :)
Кодинг
Для начала добавим в программу автозагрузку. Наш органайзер должен
запускаться вместе с виндами и делать свое дело, а не дожидаться милости
пользователя. Нам сгодится любой способ: через RUN реестра, автозагрузку,
win.ini. Мы воспользуемся последним способом. Для этого в OnCreate формы занесем
следующее:
Добавляем себя в win.ini
var win: TIniFile;
pres: string;
begin
Win:= TIniFile.Create('win.ini'); //Поглядим в win.ini
Win.ReadString ('windows', 'run', pres); //Почитаем, чего там в RUN
IF pres <> application.ExeName then win.WriteString('windows', 'run', application.ExeName);
//Ах, не мы?? Теперь мы :)
Win.Free; //Сохраняем
Эти строчки кода наверняка напомнят тебе старые добрые времена, когда еще
существовал Win3x, да и молодежь была не та, что нынче :). Собственно, из-за
этой самой ностальгии я и не стал использовать реестр. Так веселее – в Win9X
сохранилась возможность автозапуска через win.ini, а WinXP ее вообще без лишних
слов преобразует в реестр. Win.ini же останется девственно чистой. Кстати, для
работы этого кода тебе понадобится подключить inifiles - uses inifiles.
Как я уже говорил, основой нашей программы станет XML таблица. Как с ней
работать, я рассказывал еще в июльском номере («Тест для большого дяди – на все
100»), однако кратко напомню последовательность действий:
- Зарегистрируй midas.dll (Пуск -> выполнить -> regsvr32 midas.dll).
- Положи на форму компоненты DataSource1 и ClientDataSet1,
свяжи их со свойством DataSet компонента DataSource1.
- Свойство FieldDefs (ClientDataSet1) определяет нужные поля. Создавай: key1
(ключевое поле, тип AutoInc), EventName (имя или тип события, потом можно
сделать список из «дней варенья», «дней стакана» и пр., тип String), DateTime –
в нем будет храниться дата и время активизации события; тип – TDateTime, хотя
можно и String, т.к. есть функция StrToDateTime ;)), EventText – текст события.
Например, «Сдавай статью или умри». Тип – Memo. Поле ProgPath будет определять
путь к программе, необходимой для запуска (например, почтовик). Тип – string.
Размер чем больше, тем лучше. Сами пути бывают разные. Url и Sound будут,
соответственно, содержать урл для открытия и звук, который разбудит заснувшего
на клавиатуре пользователя.
Вот что еще необходимо добавить. XML-таблицы ведут подробные логи изменений
(ты всегда сможешь сделать откат), но нам лучше поставить свойство LogChanges в
False и не захламлять диск лишними данными. Иначе на винте у юзера будут
собираться напоминания о делах давно минувших дней.
Сам интерфейс проги я сделал из 4 CheckBox’ов, 3 DBEdit, 1 DBMemo, 1 MaskEdit
(для ввода времени), 1 Tcalendar, 3 кнопок, 2 Tlabel и одного компонента
CoolTrayIcon (о нем чуть позже). Получившийся результат смотри на скриншоте.
Все это хозяйство я снабдил следующими атрибутами:
Label1 – свойство caption – «Что изволите?»
Label2 – свойство caption – «Когда изволите?»
Checkbox1 – свойство caption – «Запустить программу: »
Checkbox2 – свойство caption – «Открыть УРЛ: »
Checkbox3 – свойство caption – «Напомнить о: »
Checkbox4 – свойство caption – «При этом играть: »
Кстати, свойство enabled, привязанное к DBEdit`ам, должно напрямую зависеть
от того, Checked оно или нет. Это очень важно для солидности :). Свойство
MaskEdit1 EditMask должно быть равно ShortTime. Поэтому-то я и предпочел его
стандартному Edit’у.
Button1 – свойство caption – «Зафиксировать».
Button2 – свойство caption – «Новая запись».
Button3 – свойство caption – «Свернуть».
Раз уж мы заколбасили такой интерфейс (он потянет на 400 кило), самое время
заняться кодом. При запуске проги, первым делом надо открыть базу вопросов и
выяснить, не пуста ли она :). От этого зависит дальнейшая тактика: в пустой
таблице нечего перебирать, т.к. ее еще надо заполнить. Поэтому OnShow для формы
у меня выглядит так:
ClientDataSet1.LoadFromFile('events.xml');
IF ClientDataSet1.Eof= false then timer1.Enabled:= true;
Если в базе есть какие-нибудь данные, то активизируется таймер, который
каждую минуту будет перебирать все события и сравнивать их с текущим временем.
Если время совпало, значит, час пробил, и надо делать запланированное. Этим
вещами у нас будет заведовать обычный «Таймер». Его код смотри на врезке
«Событие OnTimer».
В общем, у нас есть такой план: последовательно, с первого значения и до
конца файла, перебираем варианты и сравниваем их с базой. Но заметь, что
сравниваю я не TDateTime, а строки, причем предварительно убрав из них 2
последних символа – секунды. Я это делаю потому, что наш предел точности 1
минута и, если я буду сравнивать время «20.11.03 15:00», заданное пользователем
с «20.11.03 15:00:34» системной даты, то совпадения просто не будет. Если же ты
захочешь сделать сравнение нормально (а не так, как сделал я), то воспользуйся,
например, функцией DecodeDateTime. Она извлекает все значения из TDateTime в
отдельные переменные: Year, Month, Day и т.д. Их ты сможешь сравнивать как
нормальные цифры. Для вывода напоминания я сделал отдельную форму с 1 Memo и 1
Label.
Все. С поиском разобрались. Давай теперь глянем на OnClick для кнопки «Новая
запись»:
ClientDataSet1.Insert;
Timer1.Enabled:=false;
Она переводит таблицу в режим записи. Каждый DBEdit, соответствующий своему
CheckBox’у, связан с определенным полем таблицы. Например, DBEdit1 имеет
свойство DataField ProgPath – его содержимое – путь для пользовательской проги,
но заполнять его можно только после нажатия этой кнопки. После удачной заливки
значений пользователю наверняка захочется нажать «Зафиксировать», поэтому пиши
для нее следующий ОнКлик:
Обработка OnClick
var full, date : string;
begin
date:= inttostr (calendar1.Day)+'.'+inttostr (calendar1.month)+'.'+inttostr (calendar1.year);
full:= date+' '+maskedit1.Text;
ClientDataSet1.FieldByName('DateTime').AsDateTime:= StrToDateTime(full);
ClientDataSet1.Post;
ClientDataSet1.SaveToFile('events.xml');
timer1.Enabled:= true;
Что здесь происходит. В переменную Date я запихиваю показатели «Календаря» и
MaskEdit’a, делая это в том виде, который нужен для TDateTime. Реализую я это
при помощи функции StrToDateTime. Готовую переменную заливаю в соответствующее
поле таблицы и запускаю таймер.
Крутая иконка в трей
Теперь немного о компоненте CoolTrayIcon. Это абсолютно фриварное чудо ты
можешь взять с www3.brinkster.com/troels/delphi.asp или с нашего диска.
CoolTrayIcon позволяет создавать иконки в трее с очень широкими возможностями.
Включая анимацию, смену иконок и свои методы для скрытия/показа формы. Кстати,
этот компонент включает в себя также и собственный таймер, съедающий ресурсов
меньше, чем стандартный. Как им пользоваться, ты можешь прочесть в документации.
Частью этих функций мы воспользуемся в кнопке «свернуть» - это две простые
строчки:
Application.Minimize;
CoolTrayIcon1.HideMainForm;
После нажатия кнопки программа торжественно улетит к часикам и больше не
будет мозолить глаза на панели задач.
Конец задания
То, что мы сейчас написали – это очень сырой вариант шедулера. В принципе,
проги такого типа очень нужны и в нелегком Х-деле – даже отправку паролей по
почте иногда надо запланировать. Насчет иконок я тоже высказался не зря. Меня
иногда спрашивают, как легче всего обойти антивирус или файрвол. Так вот, проще
всего их не обходить, а заражать (об этом я писал в Спеце «ВИРУСЫ», статья «High
Level Code») или убивать. А на место убитой проги ставить свое фейк-творение,
поскольку редкий юзер интересуется их логами. Им обычно достаточно
обнадеживающей иконки в виде паучка или доктора ;).
Событие OnTimer
procedure TForm1.Timer1Timer(Sender: TObject);
var NowDate, BDate: string;
begin
ClientDataSet1.First;
While not ClientDataSet1.Eof do
begin
NowDate:= DateTimeToStr(now);
BDate:= DateTimeToStr(ClientDataSet1.FieldByName('DateTime').AsDateTime);
Delete (NowDate, length(NowDate)-2, 2);
Delete (BDate, length(bdate)-2, 2);
IF NowDate = Bdate then
begin
IF DBEdit3.Text<>'' then PlaySound (PCHar(DBEdit3.Text), SND_ASYNC, SND_NOWAIT);
IF DBEdit1.Text<>'' then WinExec (PCHar(DBEdit1.text),0); // PCHar не используй
form2.Memo1.Lines:= DBMemo1.Lines;
ClientDataSet1.Delete;
Form2.ShowModal;
end;
application.ProcessMessages;
end;
Полезные качества CoolTrayIcon
Icon – путь к иконке, которая будет светиться в трее.
Hint – короткая строка (128 символов), отображающаяся при наведении мышки.
PopupMenu – какое Popup использовать. Для этого ты должен сначала его создать
;).
Leftpopup – какое всплывающее меню будет использовано для левой кнопки.
CycleIcons – циклировать ли иконки из IconList. По умолчанию – FALSE. Если ты
хочешь менять иконки, то поставь IconList и свяжи его с компонентом (свойство
IconList).
MinimizeToTray – сворачивает прогу в трей без предварительной минимизации.
ShowBalloonHint - показывает хинт. Ей необходимо передать: Title – заголовок
хинта, Text – текст, IconType – тип иконки (на моем скрине - bitInfo),
TimeoutSecs – время существования. В общем, постарайся, и твоя зло-прога ни в
чем не будет уступать невинно убиенному ей антивирусу ;). Обратная функция –
CloseBalloonHint.
Автор: Лозовский Александр
|