Тайны DELPHIйского оракула

Опубликовано в журнале "Hard'n'Soft" №10 2003 г.

Давно уже канули в Лету те времена, когда программирование было уделом избранных, а от компьютеров можно было ожидать любой выходки в самый неподходящий момент. Сейчас каждый нормальный пользователь в состоянии без напряжения справиться с подавляющим большинством проблем, которые могут возникнуть. (Хотя всё ещё встречаются лаборантки-секретарши, ждущие "полумифического программиста", который вывел бы им ярлычок для Ворда на рабочий стол.) И пользователям всё чаще требуются такие программы, каких либо нет под рукой, либо вообще в природе. А даже если подходящая программа уже существует, то, как часто оказывается, что автор за неё просит $xx ? Поэтому, самое логичное решение - написать требуемое самому. А с появлением системы визуального программирования Delphi, наследницы языка Pascal, программирование стало если не совсем лёгким занятием, то уж необременительным - точно!

Прошло уже больше года, как вышла седьмая версия Delphi, а это говорит о том, что система не стоит на месте, непрерывно развивается и совершенствуется. Хотя (да простят мне это приверженцы VB и VC) с момента своего появления превосходила по возможностям, простоте применения и удобству Visual Basic и ни в чём не уступала Visual C. Delphi - это, как я уже сказал, среда визуального программирования. Как правило, значительную часть программы строят на основе компонент. В состав Delphi 7 Enterprise входит более 300 (!) стандартных компонент, но это ничто в сравнении с тем, сколько их разработано сторонними компаниями и программистами-одиночками. А при желании или необходимости можно самостоятельно построить такой компонент, какой нужен именно Вам - они разрабатываются средствами самой Delphi. Если Вы решили перейти на Delphi с VB Вам не придётся отказываться от привычных нестандартных компонент, которые Вы использовали раньше: Delphi умеет работать с компонентами VBX и OCX. Система программирования Delphi столь универсальна, что подойдёт для всего, что бы Вы ни решили написать. Есть книги, посвящённые разработке баз данных с помощью Delphi размером примерно с толковый словарь С.И. Ожегова. И это ещё не самые подробные! А простую программу для просмотра произвольной базы данных можно изготовить, не написав ни одной (!) строчки кода, только лишь соединяя компоненты Delphi в нужном порядке. В тоже время можно писать и игры.

Я встречал шахматы, написанные на Delphi 4, а сам создал квэст. Если Вы собираетесь написать web-приложение - Delphi отлично в этом поможет. В комплекте поставки есть даже пример несложного браузера! Так что Delphi подходит абсолютно для любых задач.

Ещё одним доводом в пользу программирования является море рутинных задач, с которыми мы регулярно сталкиваемся и которые слишком часто решаются нерационально. Например: требуется Вам регулярно создавать список файлов, находящихся в неизвестной заранее папке. Можно смотреть на список и ручками его перепечатывать. Особенное удовольствие это доставляет, когда файлов 2-3 тысячи. :) Можно написать .bat файл с командой dir /A-D/O/B >FileList.txt и копировать его каждый раз в нужную папку, запускать и получать список в файле FileList.txt. А можно потратить минуты три с половиной на то, чтобы написать программу, которая будет записывать в файл список файлов находящихся в папке, выбранной стандартным для Windows способом. Ну не красота ли? Итак вместо недели нудной работы получили десять минут работы (из них 6 на украшательство :) и готовый результат вместо красных глаз и замотанных в узел нервов.

Надеюсь, теперь Вы со мной безоговорочно согласитесь: зная Delphi жить проще и интересней. А раз так, то Вы будете программировать. И неизбежно рано или поздно встретитесь с новым для себя вопросом. Окажется, что вроде бы ясно, ЧТО надо сделать, но не ясно, КАК это сделать. В таком случае хорошо бывает покопаться в хэлпе или задать вопрос на конференции или почитать рассылку... Чтобы избавить кого-то от мучительных поисков ответа я приведу решения некоторых, показавшихся мне интересными, задач. Приведу их в наиболее удобной для восприятия форме: вопрос (Q) - ответ (A).

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

Q: Как показать часы?
A: Используйте компонент Timer. Свойство Interval установите равным 1000, а в обработчике OnTimer напишите Form1.Label1.Caption:=TimeToStr(Now); На форме должна быть метка Label1. Если выбрать красивый шрифт получатся вполне симпатичные часики.

Q: Как узнать системную дату?
A: Дату можно получить аналогично времени. Команда DateToStr(Now) возвращает строку, содержащую текущую дату в формате ДД.ММ.ГГ. Если есть необходимость получить численные значения даты или времени, лучше использовать WinApi функцию GetLocalTime.

	procedure TForm1.Button1Click(Sender: TObject);
	var ST:SystemTime;
	begin
	 GetLocalTime(ST);
	 Label1.Caption:='Год: '+inttostr(TST.wYear);
	 Label2.Caption:='Месяц: '+inttostr(TST.wMonth);
	 Label4.Caption:='День: '+inttostr(TST.wDay);
	 Label3.Caption:='День недели: '+inttostr(TST.wDayOfWeek);
	 Label5.Caption:='Часы: '+inttostr(TST.wHour);
	 Label6.Caption:='Минуты: '+inttostr(TST.wMinute);
	 Label7.Caption:='Секунды: '+inttostr(TST.wSecond);
	 Label8.Caption:='Миллисекунды: '+inttostr(TST.wMilliseconds);
	end;

Q: Как получить название месяца текстом?
A: В процедуре, приведённой в предыдущем ответе, измените строчку
Label2.Caption:='Месяц: '+LongMonthNames[TST.wMonth];

Q: Как использовать таймер, пользуясь только WinApi?
A: Используйте функции SetTimer и KillTimer. Например:

 procedure TimerProc;
 begin
  Form1.Label13.Caption:=inttostr(strToInt(Form1.Label13.Caption)+1);
  if not Form1.CheckBox1.Checked then KillTimer(Form1.Handle,1);
 end;

 procedure TForm1.Button1Click(Sender: TObject);
 begin
  SetTimer(Form1.Handle,1,500,@TimerProc);
 end;

Здесь раз в пол секунды (третий параметр SetTimer) увеличивается значение, записанное в Label13, если установлена галочка в CheckBox1.

Можно использовать свои программы и для организации защиты компьютера. Только при этом стоит быть очень внимательным. Например, стандартными средствами можно убрать пункт "Завершение работы" из меню "Пуск". Но чтобы самому не выключать компьютер нажатием кнопки Power (а ведь и её можно отключить!) можно использовать следующие команды.

Q: Как программно выключить/перезагрузить компьютер?
A: Можно использовать функцию ExitWindowsEx:
Выключение - ExitWindowsEx(EWX_FORCE OR EWX_SHUTDOWN,0);
Перезагрузка - ExitWindowsEx(EWX_FORCE OR EWX_REBOOT,0);
А можно выполнить команду
"rundll32 shell32,SHExitWindowsEx 1" для выключения или
"rundll32 shell32,SHExitWindowsEx 2" для перезагрузки.

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

Q: Как выполнить внешнюю команду или запустить другую программу из своей?
A: Для этого хорошо подходит функция ShellExecute из модуля ShellApi. Пример:
ShellExecute(handle,'open','C:\Windows\Explorer.exe',nil,'',SW_Normal);
С помощью такой команды можно открыть любой файл зарегистрированного в системе типа. А вот с помощью WinExec можно запускать только программы.
WinExec('notepad.exe C:\Msdos.sys',SW_Normal);
Хотя в строке запуска можно указать параметры (например - имя файла). Для желающих есть и более мощная, но и значительно более сложная функция CreateProcess.

Часто программам требуется получить от системы те или иные данные, зачастую хранящиеся в реестре Windows.

Q: Как получить адреса системных папок?
A: Есть функции GetWindowsDirectory и GetSystemDirectory, возвращающие адреса каталога Windows и System32 соответственно.

	procedure TForm1.Button6Click(Sender: TObject);
	var A:array [0..200] of char;
	begin
	 GetWindowsDirectory(a,200);
	 label14.Caption:=strpas(a);
	end;

Другой способ заключается в чтении интересующих адресов из ключей реестра
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders и HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion.

Q: Как читать ключи реестра и записывать свои параметры в реестр Windows?
A: Для этого можно использовать
1) функции модуля windows
2) средства модуля Registry или
3) возможности модуля RegIniFiles.
Первый вариант чуть сложней, но зато позволяет сэкономить пару десятков килобайт создаваемой программы. Это существенно, только если вы пишете программу без форм. Третий вариант стоит использовать только если вам одновременно необходим доступ и к реестру и к *.ini файлам. В остальных случаях я рекомендую второй вариант. Для демонстрации чтения из реестра - извлечём имя пользователя, на которого была зарегистрирована Windows при установке.

	procedure TForm1.Button8Click(Sender: TObject);
	var R:TRegistry;
	    s:string;
	begin
	  R:=TRegistry.Create;
	  R.RootKey:=HKEY_LOCAL_MACHINE;
	  R.OpenKey('Software\Microsoft\Windows\CurrentVersion',false);
	  s:=R.ReadString('RegisteredOwner');
	  ShowMessage('Имя пользователя: '+s);
	  R.CloseKey;
	  R.Destroy;
	end;
И запишем на это место любое другое имя, введённое в Edit1 в качестве демонстрации метода записи.
	procedure TForm1.Button9Click(Sender: TObject);
	var R:TRegistry;
	begin
	  R:=TRegistry.Create;
	  R.RootKey:=HKEY_LOCAL_MACHINE;
	  R.OpenKey('Software\Microsoft\Windows\CurrentVersion',false);
	  R.WriteString('RegisteredOwner',edit1.text);
	  R.CloseKey;
	  R.Destroy;
	end;
Ввод и чтение данных не строкового типа ничем не отличается от рассмотренных примеров.

Если Вы пишете какую-то системную программу, то может иметь смысл позаботиться, чтобы программа запускалась при включении компьютера. Те же методы можно использовать и тогда, когда Вы готовите "сюрприз" ближнему своему. ;)

Q: Как "научить" программу запускаться при загрузке компьютера?
A: Во-первых, можно вручную положить программу в папку Автозагрузка, но это не очень удобно и не так интересно. Во-вторых, можно написать программу так, чтобы она сама вписывала себя на автозапуск в реестр (или Win.ini в Windows9x).

	procedure AddToStartUp;
	var r:Tregistry;
	    path:string;
	begin
	  path:=ParamStr(0);
	  r:=TRegistry.Create;
	  r.RootKey:=HKEY_LOCAL_MACHINE;
	  r.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run',true);
	  r.WriteString('DemoAutorun',path);
	  r.CloseKey;
	  r.Destroy;
	end;
А если программа при этом ещё и копирует себя куда-нибудь, то получается уже что-то подозрительное. :) Скопировать себя программа может, например, следующим образом.
	procedure Self_Copy;
	var path1,path2:array[0..150]of char;
	       windir:array[0..100] of char;
	       NewFile:string;
	begin
	 GetWindowsDirectory(windir,255);
	 NewFile:=strpas(windir)+'\Demo.exe';
	 strPCopy(path1,paramstr(0));
	 strPCopy(path2,NewFile);
	 copyfile(path1,path2,false);
	end;

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

Q: Как просто преобразовать текст из DOS-кодировки в Windows-кодировку?
A: Для этого существуют функции AnsiToOEM и OEMToAnsi. Первая из них преобразует PChar строку из DOS-кодировки в Windows-кодировку, а вторая - в обратную сторону.

procedure TForm1.Button13Click(Sender: TObject); var Source,Dest:array[0..200] of char; begin StrPCopy(Source,Edit2.text); AnsiToOEM(Source,Dest); Edit3.Text:=StrPas(Dest); end; procedure TForm1.Button14Click(Sender: TObject); var Source,Dest:array[0..200] of char; begin StrPCopy(Source,Edit2.text); OEMToAnsi(Source,Dest); Edit3.Text:=StrPas(Dest); end;

Как видите, вызываются эти функции абсолютно идентично, что и следовало ожидать.

Q: Как вывести текст под углом?
A: Чтобы вывести текст под углом есть две функции CreateFont и CreateFontIndirect. В принципе, они очень похожи, но с моей точки зрения, CreateFontIndirect удобней. При её использовании надо только заполнить поля "логического шрифта" пользуясь подсказкой Delphi, а не указывать 14 параметров вручную. Прежде чем выводить текст надо создать т.н. логический шрифт, которым и будет осуществляться написание. Шрифт характеризуется набором параметров: название, набор символов (русский, английский,...), ширина, высота, толщина, угол наклона, подчёркивание, зачёркивание, курсив... На практике это выглядит так:

	procedure TForm1.Button12Click(Sender: TObject);
	var lf:TlogFont;
	begin
	 Randomize;
	 FillChar(lf, SizeOf(lf), 0);
	 lf.lfFaceName:='Arial';
	 lf.lfCharSet := RUSSIAN_CharSet;
	    with lf do begin
	      lfWidth := 20 + random(50);
	      lfHeight := 20 + random(50);
	      lfEscapement := random(3600);
	      lfWeight := random(1000);
	      lfItalic := random(2);
	      lfUnderline := random(2);
	      lfStrikeOut := random(2);
	    end;
	 form1.canvas.Font.Handle := CreateFontIndirect(lf);
	 form1.canvas.TextOut(100,100,'Buenos noches');
	end;

За угол наклона отвечает параметр lfEscapement. Его значение - угол поворота отсчитываемый от горизонтали против часовой стрелки заданный в десятых долях градуса. Кстати, если выбранный вами шрифт выводит квадратики вместо русских букв при выборе RUSSIAN_CHARSET, просто не указывайте этот параметр - вдруг поможет. ;)

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

Q: Как создать не прямоугольное окно?
A: Используйте какую-либо из следующих функций (или их сочетание):
CreateEllipticRgn - эллиптическая область
CreateRectRgn - прямоугольная область
CreatePolygonRgn - произвольная область, строится по точкам
С помощью этих функций можно не только изменить форму формы, простите за тавтологию, но заняться "фигурным вырезанием" на форме. Следующие две процедуры демонстрируют обе эти возможности.

	procedure TForm1.Button17Click(Sender: TObject);
	var R1: HRgn;
	begin
	  R1:=CreateEllipticRgn(0,0,width,height);
	  SetWindowRgn(Handle, R1, True);
	end;
После выполнения этой процедуры форма приобретает вид эллипса. А следующая процедура прорезает звёздочку в форме.
	procedure TForm1.Button15Click(Sender: TObject);
	const xc=450; yc=100; r=50; a=pi*72/180;
	var  P : array [0..4] of TPoint;
	     R1,R2: HRgn;
	     i:integer;
	begin
	  for i:=1 to 5 do
	   P[i-1] := Point(xc+round(r*cos(a*i*2-pi/2)),
	                   yc+round(r*sin(a*i*2-pi/2)));
	  R2 := CreatePolygonRgn(P, 5, WINDING);
	  R1:=CreateRectRgn(0,0,width,height);
	  CombineRgn(R2,R1,R2,RGN_DIFF);{}
	  SetWindowRgn(Handle, R2, True);
	end;

Кстати, эти функции можно применять не только к форме, но и ко всему, что имеет handle, например, к кнопкам.

Q: Как воспроизвести звук, не пользуясь MediaPlayer'ом?
A: В модуле MMsystem есть подходящая функция: sndPlaySound. Она позволяет проигрывать wav файлы без обращения к чему бы то ни было ещё.
sndPlaySound('C:\WINDOWS\Media\chimes.wav',SND_SYNC);
Второй параметр отвечает за то, как будет воспроизводиться звук. Выбранное в примере значение SND_SYNC означает, что звук будет воспроизводиться синхронно с остальной программой, т.е. пока он не отзвучит, всё остальное блокируется. А если выбрать вариант SND_ASYNC, то функция начнёт воспроизведение, а вызвавшая её программа продолжит свою работу без задержек. Это - асинхронный вариант. Можно воспроизводить звук в цикле: SND_LOOP. И, разумеется, все эти варианты можно сочетать, например SND_SYNC+SND_LOOP будет в цикле проигрывать файл и напрочь заблокирует всё остальное. :)

Q: Как можно эмулировать нажатие кнопок на клавиатуре?
A: Для этого достаточно вызывать функцию keybd_event. Она генерирует нажатие/отпускание клавиши клавиатуры, заданной её виртуальным кодом. Для использования удобно написать небольшую процедуру подобную этой

	procedure Press(Key:byte);
	begin
	 Keybd_event(key,0,0,0);
	 Keybd_event(key,0,KEYEVENTF_KEYUP,0);
	 sleep(500);
	end;

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

Q: Как в своей программе использовать ресурсы?
A: Ресурсы - это данные, встраиваемые в исполняемый файл программы (dll-библиотеки), которые можно вызывать во время выполнения программы и редактировать внешними средствами без перекомпиляции программы, т.е. даже не обязательно иметь исходные коды для редактирования ресурсов! Я расскажу об использовании строковых ресурсов, ресурсов - картинок, ресурсов - видео и ресурсах иконках/курсорах. Чтобы добавить в свою программу ресурс нужно сначала подготовить файл с этим ресурсом (*.res). Это можно сделать либо с помощью программы типа Restorator, Resource WorkShop и им подобных, либо с помощью компилятора ресурсов от Borland, поставляемого вместе с Delphi (brcc32.exe).
Для подключения файла с ресурсами к программе можно в любом модуле или в проекте в заголовке ввести такую строчку {$R FileName.RES}, где FileName - имя вашего файла ресурсов. Единственный вид ресурсов, который можно не компилировать - строки. Они объявляются аналогично константам, но вместо служебного слова const используется resourcestring.
Теперь приступим непосредственно к использованию ресурсов. Строковые ресурсы вызываются как обычные строковые константы: ShowMessage(ResString1); Видео удобно выводить в компонент Animate, тем более, что он сам умеет открывать видео из ресурсов по указанному имени.
Animate1.ResName := 'ARes';
Animate1.Active := True;
С курсорами и иконками дело обстоит не сложней. Достаточно воспользоваться функциями LoadCursor или LoadIcon соответственно.

	procedure TForm1.Button21Click(Sender: TObject);
	const crMyCursor=1;
	begin
	 Screen.Cursors[crMyCursor] := LoadCursor(HInstance, 'GreenCursor');
	 Cursor := crMyCursor;
	end;
Здесь для формы устанавливается зелёный курсор, сохранённый в ресурсах под именем GreenCursor. Картинки-ресурсы также в использовании не доставляют проблем.
	procedure TForm1.Button23Click(Sender: TObject);
	var Bmp:TBitMap;
	begin
	  Bmp:=Tbitmap.Create;
	  bmp.Handle:=LoadBitmap(hInstance,'Sign');
	  Image1.Picture.Bitmap:=bmp;
	end;

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

Ни одна программа не должна обходиться без About! Как же, написать программу и "забыть" похвастаться, что это именно ТЫ и ни кто другой написал сей мировой шедевр? На такой случай и предусмотрен пункт About, который можно встретить в 99 программах из 100.

Q: Как в своей программе использовать стандартное Windows-окно About?
A: Нет ничего проще! В модуле ShellApi есть функция ShellAbout. Она и выводит окно About стандартного вида.
ShellAbout(Handle,'Демонстрационная программа к статье Delphi FAQ','Макаров Владимир',application.Icon.Handle);
Что приятно в использовании этой функции, так это то, что в любой Windows: 9x/Me/NT/2000/XP это окошко будет таким, какое оно есть в этой системе.

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

Макаров Владимир


Обратно на главную

Hosted by uCoz