понедельник, 28 июля 2008 г.

Программный поиск файлов

В этом уроке мы с вами ознакомимся с основными принципами программной организации поиска файлов. Для начала определимся, зачем нам это может быть нужно. Например, вам нужно при запуске программы на выполнение просканировать определенный каталог на присутствие DOC файлов, и при наличии таковых открыть их на редактирование или напечатать. А как вам такая идея: фоновый поиск EXE файла в сети, и при обнаружении новой версии, автоматическое обновление.


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

Вспомним правила поиска файлов. Вы можете задать как имя искомого файла, так и его маску, если название неизвестно или необходимо найти несколько. Т.е. применяя специальный шаблон поиска, вы можете организовать условия выборки найденных файлов. Сразу оговорюсь, что поиск можно применять как к файлам, так и к каталогам. Будем их называть элементами файловой системы. В шаблон маски искомых элементов может входить:
1. Буквы и цифры в названии и расширении.
2. Символ * (звездочка, математический знак "умножить"), заменяющий любое количество всевозможных букв и цифр в названии или расширении.
3. Символ ? (знак вопроса), заменяющий одну букву или цифру в названии или расширении искомого элемента.
Например, вы ищите все текстовые файлы с расширением TXT. В поле имени искомого файла вам нужно ввести "*.TXT" (пишется без кавычек) и система найдет все такие файлы в указанном диске или каталоге. Если вам надо найти все файлы с названием semen, то в поле поиска файла нужно ввести "semen.*". Если вам нужно найти элементы с третьей буквой k и с первой буквой t в расширении, то вводите "??k*.t*". Здесь знак вопроса указывает на любой символ, третьим символом по порядку идет буква k, далее название файла (каталога) может состоять из любого количества букв и цифр, указываем звездочку. В расширении первая буква t, дальше следует любое расширение.
Примечание: файлы и каталоги в операционной системе windows ищутся без учета регистра, т.е. строчние и прописные буквы не различаются.

Теперь рассмотрим программный поиск файлов с помощью языка программирования object pascal.

Вся организация цикла поиска, а именно это и есть цикл с продолжением поиска, сводится к:
1. Задание условий поиска. Это каталог и маска искомого элемента или элементов, атрибуты элемента(ов). При задании условий поиска сразу происходит поиск первого подходящего под условие. Это функция FindFirst.
2. Продолжение поиска следующего элемента по заданным в первом пункте условиям. Это функция FindNext и она может вызываться сколько угодно раз, пока все файлы и каталоги, удовлетворяющие условию, не будут найдены.
3. Закрытие поиска и освобождение памяти, выделяемую системой под поиск. Команда FindClose.

Функция FindFirst.

Синтаксис:

FindFirst (КАТАЛОГ_ПОИСКА_И_МАСКА_ФАЙЛА , АТРИБУТЫ_ИСКОМОГО_ФАЙЛА , ПОИСКОВОЯ_ПЕРЕМЕННАЯ);

где: Каталог для поиска и маска искомого элемента - строковая величина, имеющая тип String, может, например, содержать 'c:\*.*' - все элементы в корне диска С. Обратите внимание, что указывается полный путь для поиска.

Атрибуты искомого элемента это пользовательские или системные атрибуты, которые может иметь файл (каталог, метка диска). Вот их перечень:
faReadOnly - Файлы "только чтение". Такой атрибут устанавливается на файлы, которые не рекомендовано изменять, удалять. Такой атрибут имеют файлы, например, записанные на компакт-дисках.
faHidden - Скрытые файлы. При обычных установках браузера и командира эти файлы невидимы.
faSysFile - Системные файлы.
faVolumeID - Файл метки диска. Такой элемент в своем имени имеет название диска (максимум 11 символов).
faDirectory - Атрибут признака каталога.
faArchive - Обычный файл. По умолчанию устанавливается на заново создаваемых файлах.
faAnyFile - Если установить в качестве атрибута искомых элементов, то будет произведен поиск по всем вышесказанным атрибутам.
Эти вам нужно искать только элементы, имеющие атрибут "каталог" и "скрытый", то можно применить знак математического сложения, например faDirectory + faHidden.

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

Поскольку FindFirst является функцией, то она должна сама возвращать некоторое значение. Это значение имеет тип Integer и означает результат поиска файла (код ошибки поиска). Если файл найден, то принимает нулевое значение.

Функция FindNext.

FindNext ( ПОИСКОВАЯ_ПЕРЕМЕННАЯ );

Эта функция продолжает поиск, заданный в функции FindNext. Возвращает значение результата поиска (нулевое в случае успешного поиска).

Процедура FindClose.

FindClose ( ПОИСКОВАЯ_ПЕРЕМЕННАЯ );

Закрывает поиск и освобождает память, выделенную системой под поиск.

Теперь рассмотрим пример. Допустим, нам надо найти все файлы и каталоги в каталоге DELPHI, находящийся на диске C:. В дальнейшем, вы можете самостоятельно, изменяя маску, менять условия поиска. Для формы с компонентом ListBox1 и кнопкой Button1 реакция на OnClick по кнопке:

procedure TForm1.Button1Click(Sender: TObject);
Var SR:TSearchRec;
// поисковая переменная
FindRes:Integer;
// переменная для записи результата поиска
begin
ListBox1.Clear;
// очистка компонента ListBox1 перед занесением в него списка файлов

FindRes:=FindFirst('c:\delphi\*.*',faAnyFile,SR);
// задание условий поиска и начало поиска

While FindRes=0 do // пока мы находим файлы (каталоги), то выполнять цикл
begin
ListBox1.Items.Add(SR.Name);
// добавление в список название найденного элемента
FindRes:=FindNext(SR);
// продолжение поиска по заданным условиям
end;
FindClose(SR);
// закрываем поиск
end;


Представленный пример кода, в принципе, является основой для организации более углубленного поиска, поиска файлов по времени создания, по содержащимся словам. Если вы запустите эту программу на выполнение, то при нажатии на кнопку Button1 вы увидите в списке в первой и второй строке элементы "." и "..". Это элементы, имеющие атрибут "каталог". Первый содержит связь с корневым каталогом диска, второй содержит связь к каталогом верхнего уровня. Со вторым вы встречаетесь в дисковых командных оболочках, например нортон, когда выбираете каталог ".." и нажимаете на "ввод". Тем самым вы попадаете в каталог на уровень выше. Естественно, в нашей поисковой программе такие элементы не надо вносить в список, поэтому мы игнорируем их нахождение. Исправляем процедуру нажатия на кнопку Button1:

procedure TForm1.Button1Click(Sender: TObject);
Var SR:TSearchRec;
FindRes:Integer;
begin
ListBox1.Clear;

FindRes:=FindFirst('c:\delphi\*.*',faAnyFile,SR);
While FindRes=0 do
begin
if ((SR.Attr and faDirectory)=faDirectory) an
d // если найденный элемент каталог и
((SR.Name='.')or(SR.Name='..')) then
// он имеет название "." или "..", тогда:
begin
FindRes:=FindNext(SR);
// продолжить поиск
Continue;
// продолжить цикл
end;

ListBox1.Items.Add(SR.Name);
FindRes:=FindNext(SR);
end;
FindClose(SR);
end;


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

Теперь рассмотрим тип TSearchRec. Он имеет в себе несколько полезных свойств:
Name - название найденного каталога (файла);
Size - размер файла в байтах;
Attr - атрибуты каталога (файла);
Time - упакованное значение времени и даты создания каталога (файла).

Все вышеперечисленные свойства мы уже рассмотрели или они понятны сразу, за исключением свойства Time. Оно имеет тип Integer и содержит в себе упакованное значение даты и времени создания файла. Распаковка производится с помощью функции FileDateToDateTime, которая в результате возвращает значение даты и времени.
Теперь добавим в нашу форму компонент DateTimePicher1 (страница Win32) и допишем несколько строк.

procedure TForm1.Button1Click(Sender: TObject);
Var SR:TSearchRec;
FindRes:Integer;
begin
ListBox1.Clear;

FindRes:=FindFirst('c:\delphi\*.*',faAnyFile,SR);
While FindRes=0 do
begin
if ((SR.Attr and faDirectory)=faDirectory) and
((SR.Name='.')or(SR.Name='..')) then
begin
FindRes:=FindNext(SR);
Continue;
end;
if FileDateToDateTime(SR.Time)// если у файла (каталога) дата создания меньше, чем установлено в DateTimePicker1, то
begin
FindRes:=FindNext(SR);
// продолжить поиск
Continue;
// продолжить цикл
end;

ListBox1.Items.Add(SR.Name);
FindRes:=FindNext(SR);
end;
FindClose(SR);
end;


Как вы уже заметили, мы отбираем файлы и каталоги по дате создания, начиная с указанной в компоненте DateTimePicker1.

Теперь попробуем организовать поиск файлов во всех вложенных каталогах. Это не так просто, как может показаться на первый взгляд. Нам придется вручную организовывать весь цикл входа-выхода из каталога, перебор файлов. Немного сложноватый материал, но возможно те из вас, кто уже работал с языком программирования pascal или другим, знакомы с технологией многократности и многовложенности использования одного и того же программного кода. Коротко объясню алгоритм работы такой программы.
1. Задание начальных условий поиска, поиск первого элемента.
2. Если найден файл, то выводим его и соответственно обрабатываем (выводим в список, открываем, удаляем и т.п.).
3. Если найден каталог, то начинаем новую процедуру поиска. Но программный код остается прежним. Мы просто заново вызываем и входим в эту же процедуру поиска.
4. Обрабатываем таким же образом все вложенные в этот каталог файлы и каталоги (начинаем новый поиск в обнаруженном каталоге).
5. Если элементов во вложенном каталоге больше нет, то обработка процедуры поиска в нем завершается, и мы выходим из нее. При этом мы оказываемся в том же месте, откуда и вызвали эту процедуру. Но она была вызвана из этой же процедуры. Поэтому программа продолжает свое выполнение дальше с момента возврата.
Таким образом, сколько витков программа наматывает на так называемый клубок, столько витков она и размотает. Программа на выполнении проходит все дерево вложенных каталогов, выполняя один и тот же кусок программного кода! И при этом данные условий поиска не перепутываются, и для каждой уникальной процедуры они сохраняются.

Рассмотрим пример. Создайте новый проект. Для создания отдельной процедуры поиска нам нужно объявить ее в соответствующем разделе (создаем ее вручную, поэтому и самостоятельно объявляем).

В разделе public пишем строку:

procedure FindFile(Dir:String);

А в разделе кода программы, до слова "end." вставляем пустой каркас процедуры

procedure TForm1.FindFile(Dir:String);
begin

end;


На форму вставляем компонент списка ListBox1, Button1, Edit1. Для компонента Edit1 свойство Text устанавливаем в "c:\delphi\". Обратите внимание на последний символ, знак "\", присутствие которого в начальном пути поиска обязательно. Дальше процедура OnClick для кнопки Button1 выглядит следующим образом:

procedure TForm1.Button1Click(Sender: TObject);
begin
ListBox1.Clear;
// очистка списка файлов
FindFile(Edit1.Text);
// поиск файлов с начальными условиями, заданных в Edit1
end;


Созданная нами вручную процедура поиска:

procedure TForm1.FindFile(Dir:String);
Var SR:TSearchRec;
FindRes:Integer;
begin
FindRes:=FindFirst(Dir+'*.*',faAnyFile,SR);
While FindRes=0 do
begin
if ((SR.Attr and faDirectory)=faDirectory) and
((SR.Name='.')or(SR.Name='..')) then
begin
FindRes:=FindNext(SR);
Continue;
end;

if ((SR.Attr and faDirectory)=faDirectory) then
// если найден каталог, то
begin
FindFile(Dir+SR.Name+'\');
// входим в процедуру поиска с параметрами текущего каталога + каталог, что мы нашли
FindRes:=FindNext(SR);
// после осмотра вложенного каталога мы продолжаем поиск в этом каталоге
Continue;
// продолжить цикл
end;

ListBox1.Items.Add(SR.Name);
FindRes:=FindNext(SR);
end;
FindClose(SR);
end;


Если вы в компоненте Edit1 в качестве начального условия поиска файлов зададите корневую папку диска, например "С:\", то вы получите полный перечень всех файлов на данном диске. Обратите внимание на скорость поиска файлов и скорость работы вашей программы.

Сообщения для пользователя

В этом уроке мы с вами рассмотрим организацию некоторых сообщений в программе.

Сообщения присутствуют повсюду: когда вы пытаетесь закрыть не сохраненный проект, при появлении ошибки, когда программа сообщает о некотором событии.
Сообщения, "вылетающие" при работе программы, можно разделить на те, которые программист предусмотрел, и системные сообщения.
В большинстве случаев второй тип сообщений имеет непонятный для обычного пользователя вид. Как правило, сообщается англоязычный термин, иногда имеется и шестнадцатеричный адрес ошибки. Например, сообщение "I/O Error" говорит программисту или пользователю об ошибке ввода-вывода. Это может быть попытка записи данных в неоткрытый файл, попытка открыть несуществующий файл и т.п. Если такая ошибка в вашей русскоязычной версии программы имеет место, то, скорее всего данной ситуации программист просто не предусмотрел. В таких случаях, программа может себя повести совершенно непредсказуемо. Ведь вы помните из прошлых уроков, что не проконтролированный кусок программы на присутствие ошибки ведет к моментальному выходу из обрабатываемой процедуры, со всеми вытекающими из этого последствиями.
Но это маленькое отклонение от темы. Идея такова, надо самостоятельно просчитывать все возможные случаи и самостоятельно обрабатывать эти ситуации. Иногда, если надо, предупреждать пользователя об ошибках, может даже сообщать об окончании обработки данных. Вот о таких сообщениях мы и поговорим в этом уроке.

Можно разделить все программные сообщения на: информационные сообщения ("Загрузка данных с дискеты завершена") , предупреждающие сообщения ("Файл модифицирован. Сохранить?"), сообщения об ошибке ("Файл данных программы не найден. Требуется переустановка программы"). Эта разбивка на типы сообщений является, естественно, не полным, его можно продолжать, но об этом немного позже.

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

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

ShowMessage('Привет!');

Тип данных в скобках - String.
Все довольно просто, мы с вами эту команду неоднократно применяли в прошлых уроках.
На этой команде работа процедуры (не всей программы!) приостанавливается. Пока пользователь не нажмет на кнопку Ok, работа с приложением становится невозможным, т.е. нельзя "добраться" до окна, расположенного позади. Т.е. это сообщение открывается модально.

Как вы заметили, заголовок окна простой. Он содержит в себе текст, который отображен на панели задач. По умолчанию имеет название запускаемого EXE файла. Изначально это Project1, в последствии вы его можете сохранить под другим именем ("Save Project As..."), при этом название проекта, вместе с ним название компилируемого EXE файла меняется.
Изменить название запущенной программы в панели задач можно в любом месте программы с помощью команды:

Application.Title:='Название программы';

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

Изначально, еще до запуска программы на выполнение, на этапе разработки, вы можете это задать название программы в панели задач с помощью главного меню delphi "Project", дальше пункт "Options...", в открывшемся окне на вкладке Application указать в поле Title необходимую строку. Эта строка и будет отображена в панели задач. При этом следует помнить, что слишком длинная фраза в кнопке на панели задач полностью не будет показана. При этом она будет обрезана троеточием, а для того, чтобы узнать полное название запущенной программы, нужно будет подвести мышку (всплывающая подсказка Hint вам покажет полное название).

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

В delphi есть, можно сказать, встроенная команда отображения окна сообщения. Звучит оно так:

MessageDLG(ТЕКСТ_СООБЩЕНИЯ,ТИП_СООБЩЕНИЯ,КНОПКИ,ИНДЕКС_ПОМОЩИ);

Скажу сразу, что к нашим программам мы пока не пишем дополнительно файлов справки, поэтому ИНДЕКС_ПОМОЩИ у нас всегда будет нулевым. Для информации скажу, что если у нас таковой файл имеется, то можно в таком сообщении сделать кнопку "Help". Если пользователь озадачен вопросом или сообщением, то может, не закрывая этого окна, узнать подробнее о дальнейших этапах работы при выборе того или иного пункта.
ТЕКСТ_СООБЩЕНИЯ - строковая величина. Как в предыдущей команде, сообщение показывается внутри окна.
ТИП_СООБЩЕНИЯ - может принимать несколько значений. От этих значений зависит содержимое заголовка и иконка в левом верхнем углу окна.

Тип сообщения Описание Вид окна
mtWarning Можно использовать в предупреждающих сообщениях. Например, "Вы действительно желаете удалить все данные с диска С:"
mtError Обычное окошко вывода сообщения об ошибки. Всем знаком его вид т.к. это наиболее частое окно в windows :)
mtInformation Какая-нибудь информация. Например, "Не найден файл настройки, создается заново"
mtConfirmation Это запрос. Запрос на сохранение перед выходом, спрашивает перед удалением параметра, и т.п. На ваш собственный вкус 05.gif (1177 bytes)
mtCustom Это сообщение полностью аналогично ShowMessage

КНОПКИ - содержит в себе массив кнопок, которые следует показывать в сообщении.
Даю перечень кнопок.


* mbYes
* mbNo
* mbOK
* mbCancel
* mbHelp
* mbAbort
* mbRetry
* mbIgnore
* mbAll

Рассказывать про каждую кнопку не буду, т.к. все равно ее название нельзя сменить. А если вам англоязычный термин непонятен, то тогда какой смысл ее применять :).
Массив кнопок задается в квадратных кавычках []. Например, нам надо задать три кнопки Yes, No, Cancel. Это делается так [mbYes,mbNo,mbCancel].
Поскольку кнопки в сообщении могут быть разные, то MessageDLG является функцией. Она возвращает результат нажатой кнопки.
Соответственно указанным выше кнопкам результат может принимать следующие значения


* mrNone - окно сообщения закрыто не с помощью кнопки (Alt+F4 или кнопкой "закрыть")
* mrAbort
* mrYes
* mrOk
* mrRetry
* mrNo
* mrCancel
* mrIgnore
* mrAll

Рассмотрим пример. Нам надо спросить у пользователя о дальнейших действиях перед выходом из программы.
1. Сохранить файл.
2. Не сохранять файл.
3. Продолжить редактирование.

Var R:Word; // переменная, в которой хранится результат
...
R:=MessageDLG('Сохранить файл перед выходом?',mtConfirmation,[mbYes,mbNo,mbCancel],0);
if R=mrYes then
// если нажата кнопка Yes
begin
// сохраняем файл и завершаем программу
end;
if R=mrNo then
// если нажата кнопка No
begin
// завершаем работу программы без сохранения
end;
if R=mrCancel then
// если нажата кнопка Cancel
begin
// продолжаем работу без сохранения
end;


Мы рассмотрели команду MessageDLG. Это очень гибкая команда, есть много достоинств, но есть один существенный недостаток - англоязычный интерфейс.

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

MessageBox(Handle,ТЕКСТ_СООБЩЕНИЯ,ЗАГОЛОВОК_ОКНА,ТИП_СООБЩЕНИЯ);

Первый параметр - указатель на владельца окна сообщения. Этот параметр вам пока ничего не говорит, устанавливайте его в Handle (это ссылка на окно, откуда это сообщение вызывается).
ТЕКСТ_СООБЩЕНИЯ и ЗАГОЛОВОК_ОКНА - имеют тип PChar, поэтому, во избежание недоразумений и появления неизвестного рода ошибок, выдаваемых компилятором, меняйте тип String в PChar "на ходу". Например:

MessageBox(Handle,PChar('ТЕКСТ_СООБЩЕНИЯ'),PChar('ЗАГОЛОВОК_ОКНА'),...

Это был перевод из одного типа строковой величины в другой тип.

Теперь поговорим о немного сложном параметре ТИП_СООБЩЕНИЯ. Он включает в себя иконку и кнопки.

Кнопки:
* MB_ABORTRETRYIGNORE - кнопки "Прервать", "Повторить", "Пропустить".
* MB_OK - кнопка "Ok".
* MB_OKCANCEL - кнопки "Ok", "Отмена".
* MB_RETRYCANCEL - кнопки "Повторить" и "Отмена".
* MB_YESNO - две кнопки "Да" и "Нет".
* MB_YESNOCANCEL - кнопки "Да", "Нет", "Отмена".

Для того, чтобы отобразить иконку, нужно указать:
* MB_ICONEXCLAMATION
* MB_ICONWARNING
* MB_ICONINFORMATION
* MB_ICONASTERISK
* MB_ICONQUESTION
* MB_ICONSTOP
* MB_ICONERROR
* MB_ICONHAND
Если у вас в сообщении несколько кнопок, а по умолчанию нужно выбрать определенную, то такая кнопка задается:
MB_DEFBUTTON1 - где последняя цифра указывает номер кнопки, выбранной по умолчанию. Это свойство может быть полезным, например, чтобы обезопасить данные от случайного уничтожения. "Удалить файл?". Две кнопки - "Да", "Нет". По умолчанию мы программно выбираем вторую кнопку. Если пользователь сразу нажал на Enter, не осознавая своего поступка, можно сказать по привычке, то ничего страшного не произойдет.

Как же указать параметры иконки, кнопок, кнопки по умолчанию в одном параметре ТИП_СООБЩЕНИЯ. Очень просто. Простым знаком +
Например:
MessageBox(Handle,PChar('Выйти из программы?'),PChar('Мое сообщение'),MB_ICONINFORMATION+MB_OKCANCEL+MB_DEFBUTTON2);

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

Итак, MessageBox можно считать идеальной командой вывода сообщения пользователю, которая будет совместима со всеми языковыми версиями windows.

Контроль нажатия на кнопку в MessageBox мы осуществляем аналогично MessageDLG, только возвращаемая величина может принимать следующие значение (соответственно нажатой кнопке):

* IDABORT
* IDCANCEL
* IDIGNORE
* IDNO
* IDOK
* IDRETRY
* IDYES

Всплывающая подсказка

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

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

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

Для того, чтобы у интересующего вас параметра появлялась всплывающая подсказка, необходимо, прежде всего установить сам текст подсказки. Это свойство Hint. Для каждого компонента программы Hint может быть разный.
Второй шаг, это указать, что подсказку для интересующего нас компонента надо показывать. Это свойство ShowHint. Его нужно установить в true для отображения, и false наоборот.
Два вышеперечисленных свойства доступны еще до запуска программы, во время редактирования проекта. По умолчанию свойство ShowHint имеет значение false, т.е. подсказка не высвечивается.
Целесообразнее устанавливать эти свойства в инспекторе объектов, чем программно, этим самым вы не засоряете текст кода программы. В редакторе кода порой бывает так много строк, что трудно сразу разобраться в возможной ошибке и подобные строки могут сбивать с толку. Исключением в нашем случае может быть постоянно меняющаяся всплывающая подсказка. Например, при изменении функционального назначения кнопки, меняется и текст подсказки.

Программная установка текста всплывающей подсказки для кнопки Button1:

Button1.Hint:='Нажми на меня, и ты узнаешь, что ты наделал';

Программная активация подсказки для кнопки Button1 (только дает возможность всплывающей подсказке высвечиваться, а не выводит ее на экран!)

Button1.ShowHint:=true;

Теперь поговорим про цвет фона подсказки и про некоторые другие параметры.

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

Application.HintColor:=ЦВЕТ;

Цвет задается через зарезервированные константы или цифровым кодом. Например, белый цвет может быть задан с помощью константы clWhite, с помощью шестнадцатеричного представления $00FFFFFF, или обычным, привычным нам десятичным числом 16777215. Ниже представлена таблица цветов, которые определены как стандартные константы цветов.

Константа

Цвет

clAqua
clBlack
clBlue
clFuchsia
clGray
clGreen
clLime
clMaroon
clNavy
clOlive
clPurple
clRed
clSilver
clTeal
clWhite
clYellow

Стандартный цвет всплывающей подсказки, установленный в свойствах экрана windows, хранится в константе clInfoBk. Она является элементом со статусом "только для чтения" и изменение недопустимо.

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

Application.HintPause:=ЗНАЧЕНИЕ;

Значение задается в миллисекундах.
Изменение этого свойства может вам показаться бессмысленным. Но иногда необходимо, чтобы пользователь видел всплывающую подсказку немедленно после наведения на компонент мышкой. Иногда нужно вообще убрать на продолжительный срок появление подсказки, чтобы она лишний раз не привлекала к себе внимание. Обычное начальное значение в windows 500 мс (полсекунды).
Время отображения подсказки на экране равно 2500 мс (2,5 с). Программно для вашего приложения это время задается следующим образом.

Application.HintHidePause:=ЗНАЧЕНИЕ;

Это все по общим свойствам всплывающей подсказки. Этот пример может быть интересен и специалистам в области программирования.
В нем объясняется принцип отображения подсказки в компоненте TStatusBar без каких-либо дополнительных компонентов.
Еще примечание к примеру: числовые значения вводятся в текстовом виде в компонентах Edit1 и Edit2. На ходу происходит их преобразование в числовые с помощью циклов try-except Некорректное введение числового значения контролируется. Но если программу запускать на выполнение через оболочку delphi (run), а не из проводника windows или другим методом запуска, то delphi контролирует самостоятельно такое преобразование и внутри цикла try-except. И в случае обнаружения ошибки прервет выполнение программы. Это неудобство встречается в delphi3, delhpi4, delphi5 (опробовано лично мной). Поэтому такой некорректный ввод чисел лучше всего тестировать, отдельно запуская программу, или с помощью процедуры Val.

Рисование на форме или на компоненте PaintBox.

Большое спасибо Александру, за идею примера, который мы рассмотрим в этом уроке. Извиняюсь за то, что перевел тему с генератора на построитель функции синуса и косинуса.
У формы и у компонента PaintBox (страница System палитры компонентов) есть свойство Canvas. Это свойство представляет собой растровое изображение, на котором можно рисовать или в которое можно загрузить рисунок. Я не буду рассказывать подробно об особенностях рисования, тем более, что я в этом не силен, но основные сведения дам.
Свойство Canvas доступно только во время работы приложения и с помощью его можно:
* Canvas.Brush - свойство фона. У него можно установить свойство Canvas.Brush.Color в необходимый цвет и с помощью следующей за ней командой Canvas.FillRect(ClientRect) можно очистить всю рабочую область компонента под заданный цвет. С помощью свойтва Canvas.Brush можно в качестве фона установить рисунок. Для этого есть свойство Canvas.Brush.Bitmap, которому нужно присваивать переменную с растровым рисунком.
* Canvas.MoveTo(x,y) - устанавливает перо в заданную точку, где x и y - координаты точки, относительно компонента. Начало координат, точка [0,0] находится в верхнем левом углу. После этой команды перо установлено, но точка не нарисована. Чтобы провести линию от текущего положения пера до заданного Canvas.LineTo(x,y). Поставить точку определенного цвета на холсте Canvas.Pixels[x,y]:=ЦВЕТ_ТОЧКИ.
* Через Canvas можно писать текст, рисовать дуги, сектор круга, овал, прямоугольник, ломаную линию, кривую.
* Свойства пера содержатся в Canvas.Pen. Здесь можно задать толщину пера Canvas.Pen.Width:=ТОЛЩИНА_В_ТОЧКАХ. Задать цвет Canvas.Pen.Color:=ЦВЕТ.

Рассмотрим пример генератора колебаний/ Генератор сам по себе есть только на экране. Колебания, которые он создает, рассчитываются по формуле.
Следовательно, он вам может оказаться полезным. Можно изменить рассчетную функцию и вы получите для этой функции график.

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

1. Программа имеет только одно окно Form1, у которого сразу переименовываем заголовок на подходящее название.
2. Устанавливаем свойство Form1.Position в poDesktopCenter, чтобы окно при каждом запуске и при любом экранном разрешении всегда было ровно посередине экрана.
3. Устанавливаем свойство Form1.BorderStyle в bsSingle, для неизменяемого размера окна. Оставляем во вложенных свойствах BorderIcons только biSystemMenu в true, остальные в false. Это для того, чтобы окно нельзя было свернуть в значек, развернуть во весь экран и окно имело иконку в заголовке.
4. Устанавливаем в форму компонент PaintBox, два компонента RadioButton, CheckBox, три кнопки Button и TrackBar, расположенный на странице Win32.
5. RadioButton1.Caption переименовываем в "Sin". Этот флаг будет признаком рисования синусоиды. RadioButton2.Caption переименовываем в "Cos" - косинусоида. Начальное значение флага Checked для RadioButton1 в true.
6. CheckBox1.Caption переименовываем в "Все". Если флаг установлен, то будет рисоваться два графика.
7. Названия кнопок Button1 - "Старт", Button2 - "Стоп (пауза)" и Button3 - "Выход". Названия на кнопках меняются через свойство Caption. Теперь назначение этих кнопок понятно.
8. Компонент TrackBar1 свойство минимального значения Min устанавливаем в 1, максимальное значение Max - 50.
9. PaintBox1, на котором будет непосредственно рисоваться график размеры высоты Height=140, ширина Width=500.

Привожу текст модуля для окна Form1.

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

Var stop:boolean; // признак рисования
x:Integer;
// координата оси X

Реакция на событие нажатия на кнопку Button1 (Начало рисования)

procedure TForm1.Button1Click(Sender: TObject);
Var y:Integer;
// ось Y
begin
if x=0 then
// если точка в начале координат, то:
begin
PaintBox1.Canvas.Brush.Color:=clWhite;
// цвет фона белый
PaintBox1.Canvas.FillRect(ClientRect);
// заливка всей рабочей области
end;
stop:=false;
// флаг старта процесса рисования
While not stop do
// бесконечный цикл, пока флаг остановки не поднят:
begin
if (RadioButton1.Checked)or(CheckBox1.Checked) then
// если установлен "Sin" или "Все", то:
begin
y:=Round(Sin(pi*x/100)*50)+70;
// вычисление положения синусоиды
PaintBox1.Canvas.Pixels[x,y]:=clBlack;
// нарисовать черную точку
end;
if (RadioButton2.Checked)or(CheckBox1.Checked) then
// если установлен "Cos" или "Все", то:
begin
y:=Round(Cos(pi*x/100)*50)+70;
// вычисление положения косинусоиды
PaintBox1.Canvas.Pixels[x,y]:=clBlack;
// нарисовать черную точку
end;
inc(x);
// увеличить значение X на едицину. Аналог X:=X+1
if x>500 then
// если X вышел за пределы PaintBox1, то:
begin
x:=0;
// установить X на начало координат
PaintBox1.Canvas.Brush.Color:=clWhite;
// Цвет фона белый
PaintBox1.Canvas.FillRect(ClientRect);
// Очистка рабочей области PaintBox1
end;

Sleep(TrackBar1.Position);
// Процедура "засыпает" на заданное время в миллисекундах
Application.ProcessMessages;
// Обработка всей очереди сообщений
end;
end;


Коротко расскажу работу этой процедуры.
Как только нажата кнопка "Старт" Компонент PaintBox1 очищается и начинается бесконечный цикл While, выйти из которого можно только, пока переменная Stop не примет значение true. Это можно сделать кнопкой Button2, соответствующая процедура которой обработается во время Application.ProcessMessages. С помощью бегунка TrackBar1 можно менять скорость рисования кривой. Этот параметр передается в команду Sleep.

Процедура нажатия на кнопку остановки Button2:

procedure TForm1.Button2Click(Sender: TObject);
begin
Stop:=true;
// установить флаг остановки процесса рисования
end;


Процедура создания окна Form1OnCreate:

procedure TForm1.FormCreate(Sender: TObject);
begin
x:=0;
// начальное значение X
end;


Если нажата кнопка "Выход", то реакция на это событие будет таким:

procedure TForm1.Button3Click(Sender: TObject);
begin
Close;
// закрыть окно
end;


И реакция перед закрытием окна OnClose. Без этой процедуры, если рисование включено, то окно не закроется.

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Stop:=true;
// остановить (если включен) цикл рисования
end;


После запуска программы, установки флажка "Все" и нажатии на кнопку "Старт" на экране отобразится этот график:


В принципе, с нашей программой можно отслеживать практически любые функции. Просто надо описать свою отслеживаемую функцию вместо моих строк Y:=Sin...
Если вы хотите убрать постоянную прорисовку графика функции, то следующий код в программе

if x>500 then // если X вышел за пределы PaintBox1, то:
begin
x:=0;
// установить X на начало координат
PaintBox1.Canvas.Brush.Color:=clWhite;
// Цвет фона белый
PaintBox1.Canvas.FillRect(ClientRect);
// Очистка рабочей области PaintBox1
end;

измените на:

if x>500 then // если X вышел за пределы PaintBox1, то:
begin
x:=0;
// установить X на начало координат
Stop:=true;
// остановка рисования
end;


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

Y:=140 - ФУНКЦИЯ;

Как видите, поле для экспериментов велико.

Корректное построение программного кода.

Бесконечные циклы, громоздкие операции и прочие зависшие программы.

Ну кто из вас хоть раз не завершал зависшее приложение через Ctrl+Alt+Del? А ведь в большинстве случаев в ситуации, когда программа зависает, виноват программист, а не пользователь, который ее довел до этого состояния. Зачастую программы имеют в себе до 30% различной проверочных команд. Когда проверяются переполнение списков, проверяется корректность созданного объекта, компонента, проверяется или запущено то или иное внешнее приложение. Именно от этих условий и зависит дальнейшее корректное выполнение вашего приложения.

Рассмотрим принцип работы следующей программы.
Допустим, в оконном модуле есть две процедуры. Первая процедура - реакция на событие нажатия на кнопку Button1,
вторая процедура - реакция на событие нажатия на кнопку Button2. Исходный текст процедур:

procedure TForm1.Button1Click(Sender: TObject);
begin
while true do
begin
// бесконечный цикл
end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
ShowMessage('Нажата кнопка Button1');
end;


В первой процедуре мы видим типичный программный код, при выполнении которого программа зависнет. Или говоря языком юзера - бесконечно выполняет одно и то же. Естественно, что выхода из такой процедуры не существует, а сама программа ожидает выхода из нее, чтобы выполнять другие события, в том числе и системные.
Принцип работы операционной системы windows основан на посылке сообщений компонентам программы. События при этом становятся в очередь и ожидают своей обработки. Поскольку выхода из первой процедуры нами не предусмотрено, то очередь сообщений не будет обрабатываться, и при нажатии на кнопки Ctrl+Alt+Del через некоторое время, мы видим, что приложение "не отвечает на системные запросы".
В подобных случаях это совсем не означает, что программа не работает. Она может зациклиться на обработки одной или нескольких команд, ожидая выхода из цикла по определенному условию (например, прочтения данных с диска) или обрабатывать большой объем данных опять таки в одной процедуре. А, как известно, корректная работа программы забота программиста, то есть нас с вами. Мы должны учесть все возможные ситуации, просчитать приблизительное время обработки операций даже на слабых компьютерах, при необходимости применить компонент ProgressBar, для того, чтобы пользователь не скучал, глядя на "неотвечающую" программу.

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

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

Application.ProcessMessages;

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

Исключительные ситуации.

Это другой неприятный момент для пользователя и программиста. Это когда появляются неожиданные англоязычные ошибки и выполняемая процедура завершает свою работу с места появления ошибки. Разработчики Borland Delphi таким контролем за ошибками и выходом из работающих модулей программы позаботились об остальных частях программы. Если ошибка появилась в первой строке, то имеет ли смысл выполнять вторую?

Рассмотрим такой пример:

procedure TForm1.Button1Click(Sender: TObject);
Var x,y,z:Double;
// переменные.
begin
x:=0;
z:=1;
y:=z/x;
// деление на нуль
ShowMessage(FloatToStr(y));
end;


При выполнении данного кода мы получаем сообщение "Floating point division by zero". И процедура дальше уже не обрабатывается. После команды деления мы не видим его результат ShowMessage(РЕЗУЛЬТАТ) Или еще такой пример:

procedure TForm1.Button1Click(Sender: TObject);
begin
Form1.ShowModal;
// открыть окно модально
ShowMessage('Окно открыто модально');
end;


Если у нас окно Form1 уже открыто, то повторная попытка открыть его модально приведет к возникновению ошибки "Cannot make a visible window modal". И опять программа завершает обработку процедуры. Это приведены примитивные процедуры, но бывает такие, что даже сам программист не ожидает появления подобной ситуации, но нужно предусмотреть все.
Что же делать, если мы открыли несколько файлов, заполняем список, создали вручную некоторые объекты. Выход из процедуры приводит к тому, что файлы не закрыты и повторный вызов этой процедуры приведут к возникновению другой ошибки - открытие уже открытого файла. Созданные объекты мы теряем при выходе из процедуры, а при повторном их создании, если они глобальные, получаем еще одну ошибку. А эта трата драгоценной памяти, которая занимается объектом и остается даже после выхода их программы. И так далее со всеми вытекающими последствиями.

Во-первых, надо при всех операциях деления предусматривать что-то подобное

if x<>0 then y:=z/x; // если x не равно нулю, то делить.

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

Var st:String;
y:Double;
...
InputQuery('Ввод числа','введите любое число',st);
y:=StrToFloat(st);


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

При сомнениях, насчет отображения того или иного окна на экране использовать, например:

if not Form1.Visible then Form1.ShowModal; // если окна нет на экране, то открыть его модально

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

В уроке15 мы рассматривали пример, в котором есть директива компилятору об отключении контроля ошибок ввода-вывода. Мы открывали файл, при отключенном автоматическом контроле, после чего сами обрабатывали ошибку открытия.

{$I-} // отключение контроля ошибок ввода-вывода
Reset(f);
// открытие файла для чтения
{$I+}
// включение контроля ошибок ввода-вывода
if IOResult<>0 then
// если есть ошибка открытия, то
begin
ShowMessage('Ошибка открытия файла C:\1.TXT');
Exit;
// выход из процедуры при ошибке открытия файла
end;


Такой метод можно применять только с старыми форматами команд, совместимыми с языком программирования pascal и работающими только с устройствами ввода-вывода.
В delphi можно применять следующее:

try // начало опасно-ошибочной части процедуры:
Form1.ShowModal;
except
// если возникла ошибка, то выполняется следующее:
ShowMessage('Ошибка открытия окна');
end;
// конец try
ShowMessage('Дальнейшая обработка процедуры');


Заметьте, что этот кусок кода не вызовет ошибки выполнения. Программа просто вам сообщит об ошибке открытия окна, после чего процедура продолжит свое выполнение. Еще обратите внимание, что если в части, после except возникнет другая ошибка выполнения, то процедура таки точно завершит свою работу немедленно. Все команды, которые могут вызвать ошибку надо писать после try и до except.
Теперь можно без опаски потери данных, после except корректно закрыть все открытые файлы, при необходимости окна, сообщить пользователю о возникшей ошибке и самому выйти из процедуры с помощью Exit.

Аналогом try-except есть try-finally

procedure TForm1.Button1Click(Sender: TObject);
Var StringList:TStringList;
// список строк
begin
try
StringList:=TStringList.Create;
// создание списка строк
finally
// при успешной обработки создания списка:
StringList.Free;
// удалить и освободить память
end;
ShowMessage('Дальнейшая обработка процедуры');
end;


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

Корректное построение программного кода.

Бесконечные циклы, громоздкие операции и прочие зависшие программы.

Ну кто из вас хоть раз не завершал зависшее приложение через Ctrl+Alt+Del? А ведь в большинстве случаев в ситуации, когда программа зависает, виноват программист, а не пользователь, который ее довел до этого состояния. Зачастую программы имеют в себе до 30% различной проверочных команд. Когда проверяются переполнение списков, проверяется корректность созданного объекта, компонента, проверяется или запущено то или иное внешнее приложение. Именно от этих условий и зависит дальнейшее корректное выполнение вашего приложения.

Рассмотрим принцип работы следующей программы.
Допустим, в оконном модуле есть две процедуры. Первая процедура - реакция на событие нажатия на кнопку Button1,
вторая процедура - реакция на событие нажатия на кнопку Button2. Исходный текст процедур:

procedure TForm1.Button1Click(Sender: TObject);
begin
while true do
begin
// бесконечный цикл
end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
ShowMessage('Нажата кнопка Button1');
end;


В первой процедуре мы видим типичный программный код, при выполнении которого программа зависнет. Или говоря языком юзера - бесконечно выполняет одно и то же. Естественно, что выхода из такой процедуры не существует, а сама программа ожидает выхода из нее, чтобы выполнять другие события, в том числе и системные.
Принцип работы операционной системы windows основан на посылке сообщений компонентам программы. События при этом становятся в очередь и ожидают своей обработки. Поскольку выхода из первой процедуры нами не предусмотрено, то очередь сообщений не будет обрабатываться, и при нажатии на кнопки Ctrl+Alt+Del через некоторое время, мы видим, что приложение "не отвечает на системные запросы".
В подобных случаях это совсем не означает, что программа не работает. Она может зациклиться на обработки одной или нескольких команд, ожидая выхода из цикла по определенному условию (например, прочтения данных с диска) или обрабатывать большой объем данных опять таки в одной процедуре. А, как известно, корректная работа программы забота программиста, то есть нас с вами. Мы должны учесть все возможные ситуации, просчитать приблизительное время обработки операций даже на слабых компьютерах, при необходимости применить компонент ProgressBar, для того, чтобы пользователь не скучал, глядя на "неотвечающую" программу.

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

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

Application.ProcessMessages;

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

Исключительные ситуации.

Это другой неприятный момент для пользователя и программиста. Это когда появляются неожиданные англоязычные ошибки и выполняемая процедура завершает свою работу с места появления ошибки. Разработчики Borland Delphi таким контролем за ошибками и выходом из работающих модулей программы позаботились об остальных частях программы. Если ошибка появилась в первой строке, то имеет ли смысл выполнять вторую?

Рассмотрим такой пример:

procedure TForm1.Button1Click(Sender: TObject);
Var x,y,z:Double;
// переменные.
begin
x:=0;
z:=1;
y:=z/x;
// деление на нуль
ShowMessage(FloatToStr(y));
end;


При выполнении данного кода мы получаем сообщение "Floating point division by zero". И процедура дальше уже не обрабатывается. После команды деления мы не видим его результат ShowMessage(РЕЗУЛЬТАТ) Или еще такой пример:

procedure TForm1.Button1Click(Sender: TObject);
begin
Form1.ShowModal;
// открыть окно модально
ShowMessage('Окно открыто модально');
end;


Если у нас окно Form1 уже открыто, то повторная попытка открыть его модально приведет к возникновению ошибки "Cannot make a visible window modal". И опять программа завершает обработку процедуры. Это приведены примитивные процедуры, но бывает такие, что даже сам программист не ожидает появления подобной ситуации, но нужно предусмотреть все.
Что же делать, если мы открыли несколько файлов, заполняем список, создали вручную некоторые объекты. Выход из процедуры приводит к тому, что файлы не закрыты и повторный вызов этой процедуры приведут к возникновению другой ошибки - открытие уже открытого файла. Созданные объекты мы теряем при выходе из процедуры, а при повторном их создании, если они глобальные, получаем еще одну ошибку. А эта трата драгоценной памяти, которая занимается объектом и остается даже после выхода их программы. И так далее со всеми вытекающими последствиями.

Во-первых, надо при всех операциях деления предусматривать что-то подобное

if x<>0 then y:=z/x; // если x не равно нулю, то делить.

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

Var st:String;
y:Double;
...
InputQuery('Ввод числа','введите любое число',st);
y:=StrToFloat(st);


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

При сомнениях, насчет отображения того или иного окна на экране использовать, например:

if not Form1.Visible then Form1.ShowModal; // если окна нет на экране, то открыть его модально

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

В уроке15 мы рассматривали пример, в котором есть директива компилятору об отключении контроля ошибок ввода-вывода. Мы открывали файл, при отключенном автоматическом контроле, после чего сами обрабатывали ошибку открытия.

{$I-} // отключение контроля ошибок ввода-вывода
Reset(f);
// открытие файла для чтения
{$I+}
// включение контроля ошибок ввода-вывода
if IOResult<>0 then
// если есть ошибка открытия, то
begin
ShowMessage('Ошибка открытия файла C:\1.TXT');
Exit;
// выход из процедуры при ошибке открытия файла
end;


Такой метод можно применять только с старыми форматами команд, совместимыми с языком программирования pascal и работающими только с устройствами ввода-вывода.
В delphi можно применять следующее:

try // начало опасно-ошибочной части процедуры:
Form1.ShowModal;
except
// если возникла ошибка, то выполняется следующее:
ShowMessage('Ошибка открытия окна');
end;
// конец try
ShowMessage('Дальнейшая обработка процедуры');


Заметьте, что этот кусок кода не вызовет ошибки выполнения. Программа просто вам сообщит об ошибке открытия окна, после чего процедура продолжит свое выполнение. Еще обратите внимание, что если в части, после except возникнет другая ошибка выполнения, то процедура таки точно завершит свою работу немедленно. Все команды, которые могут вызвать ошибку надо писать после try и до except.
Теперь можно без опаски потери данных, после except корректно закрыть все открытые файлы, при необходимости окна, сообщить пользователю о возникшей ошибке и самому выйти из процедуры с помощью Exit.

Аналогом try-except есть try-finally

procedure TForm1.Button1Click(Sender: TObject);
Var StringList:TStringList;
// список строк
begin
try
StringList:=TStringList.Create;
// создание списка строк
finally
// при успешной обработки создания списка:
StringList.Free;
// удалить и освободить память
end;
ShowMessage('Дальнейшая обработка процедуры');
end;


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

Сохранение данных в реестр windows.

На прошлом уроке мы с вами рассмотрели методику записи и чтения пользовательских данных и данных состояния компонентов в ini файл. Эта методика хранения параметров программы является устаревшей. Рекомендуется для этих целей использовать реестр.

Для начала краткая информация о реестре. Проще говоря, реестр windows, эта огромная база данных, хранящая в себе всевозможные пользовательские и системные данные. Установленное оборудование, драйвера, установленные шрифты, установленные принтеры и многое другое записано в соответствующих разделах.
Как же все-таки увидеть данные реестра? Очень просто. Надо нажать на кнопку "Пуск", выбрать пункт "Выполнить" и ввести название программы REGEDIT, которая отображает реестр для чтения и редактирования. Эта программа находится в каталоге windows. Помните, что некорректное изменение некоторых системных ключей в реестре может привести к постоянным сбоям в системе, может привести даже к полному краху операционной системы, поэтому изменения и удаление следует производить очень осторожно.
Внешний вид этой программы состоит из двух панелей. Левая древовидная и правая, в которой отображаются параметры ключей. Ключ - это элемент реестра, который может содержать некоторые данные или содержать другие ключи. Это древовидная структура, которая хранит в себе вложенные параметры, объединенные общей тематикой. Не буду рассказывать назначение отдельных ключей, это может занять не одну книгу, скажу только, что программы, которые работают с реестром в качестве ini файла, автоматически записывают и читают данные из глобального ключа HKEY_CURRENT_USER. Он же дублируется в ключе HKEY_USERS\ИМЯ_ТЕКУЩЕГО_ПОЛЬЗОВАТЕЛЯ.
Дальше рассмотрим запись и чтение данных. Тут методика аналогична работе с ini файлами.
Для начала в разделе подключаемых модулей Uses нужно указать модуль Registry, который необходим для использования команд работы с реестром.
Объявление реестровой переменной

Var ПЕРЕМЕННАЯ:TRegIniFile;

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

ПЕРЕМЕННАЯ:=TRegIniFile.Create(НАЗВАНИЕ_КЛЮЧА);

Пример объявления, создания и удаления.

procedure TForm1.FormShow(Sender: TObject);
Var RegIniFile:TRegIniFile;
// реестровая переменная
begin
RegIniFile:=TRegIniFile.Create('MySelfRegistryApplication');
// создание реестровой переменной
RegIniFile.Free;
// уничтожение вручную созданного объекта
end;


Подробно о командах чтения и записи.

Чтение и запись целочисленного значения, типа integer:

RegIniFile.ReadInteger(СЕКЦИЯ,ПАРАМЕТР,ЗНАЧЕНИЕ_ПО_УМОЛЧАНИЮ);
RegIniFile.WriteInteger(СЕКЦИЯ,ПАРАМЕТР,ЗНАЧЕНИЕ);


Дальше аналогично вышерассмотренной команде следует чтение и запись:
двоичного значения ReadBool и WriteBool;
строкового значения ReadString и WriteString;

Если необходимо сохранить данные не в отдельной секции, а в ключе, то вместо параметра СЕКЦИЯ необходимо указать пустую строку, или две кавычки ''.

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

RegIniFile.ValueExists(ПАРАМЕТР)

Эта команда является функцией, возвращающей истинно (true) или ложно (false), и ее можно использовать в паре с чтением определенного параметра. Например, чтение числа с плавающей точкой:

if RegIniFile.ValueExists('MyFloat') then // если данный параметр существует, то
Edit2.Text:=FloatToStr(RegIniFile.ReadFloat('MyFloat'));
// прочитать параметр

Пример.
В новом проекте помещаем в форму следующие компоненты:
· 2 компонента CheckBox
· компонент ComboBox, установите в свойстве Items некоторые строки.
· 2 компонента Edit
· компонент DateTimePicker (страница Win32 палитры компонентов)

Процедура OnShow для окна Form1

procedure TForm1.FormShow(Sender: TObject);
Var RegIniFile:TRegIniFile;
// реестровый объект
begin
RegIniFile:=TRegIniFile.Create('MySelfRegistryApplication');
// создание реестровой переменной
Form1.Left:=RegIniFile.ReadInteger('Form1','Form1Left',Form1.Left);
// левая граница окна
Form1.Top:=RegIniFile.ReadInteger('Form1','Form1Top',Form1.Top);
// верхняя граница окна
Form1.Height:=RegIniFile.ReadInteger('Form1','Form1Height',Form1.Height);
// высота окна
Form1.Width:=RegIniFile.ReadInteger('Form1','Form1Width',Form1.Width);
// ширина окна
// Восстановление состояния компонентов
CheckBox1.Checked:=RegIniFile.ReadBool('Form1','CheckBox1Checked',CheckBox1.Checked);
CheckBox2.Checked:=RegIniFile.ReadBool('Form1','CheckBox2Checked',CheckBox2.Checked);
ComboBox1.ItemIndex:=RegIniFile.ReadInteger('Form1','ComboBox1ItemIndex',ComboBox1.ItemIndex);
Edit1.Text:=RegIniFile.ReadString('Form1','Edit1Text',Edit1.Text);
if RegIniFile.ValueExists('MyFloat') then
// если такой параметр существует, то:
Edit2.Text:=FloatToStr(RegIniFile.ReadFloat('MyFloat'));
// чтение числа с запятой
if RegIniFile.ValueExists('MyDate') then
// если такой параметр существует, то:
DateTimePicker1.Date:=RegIniFile.ReadDate('MyDate');
// чтение даты

RegIniFile.Free;
// уничтожение вручную созданного объекта
end;


Процедура OnClose для окна Form1

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
Var RegIniFile:TRegIniFile;
begin
RegIniFile:=TRegIniFile.Create('MySelfRegistryApplication');
RegIniFile.WriteInteger('Form1','Form1Left',Form1.Left);
RegIniFile.WriteInteger('Form1','Form1Top',Form1.Top);
RegIniFile.WriteInteger('Form1','Form1Height',Form1.Height);
RegIniFile.WriteInteger('Form1','Form1Width',Form1.Width);
// Сохранение состояния компонентов
RegIniFile.WriteBool('Form1','CheckBox1Checked',CheckBox1.Checked);
RegIniFile.WriteBool('Form1','CheckBox2Checked',CheckBox2.Checked);
RegIniFile.WriteInteger('Form1','ComboBox1ItemIndex',ComboBox1.ItemIndex);
RegIniFile.WriteString('Form1','Edit1Text',Edit1.Text);

RegIniFile.WriteFloat('MyFloat',StrToFloat(Edit2.Text));
RegIniFile.WriteDate('MyDate',DateTimePicker1.Date);

RegIniFile.Free;
end;

Обратите внимание, что в этом примере для записи состояния компонента Edit2 используется преобразование строковой величины в число. Если в этом компоненте будут недопустимые символы, то при закрытии окна будет выдаваться ошибка. Для разделения дробной от целой части (запятая), используйте соответствующий символ, установленный в конфигурации windows как символ разделителя.

После запуска и закрытия этой программы можно запустить редактор реестра и просмотреть результат записи данных. Они находятся в ключе HKEY_CURRENT_USER, подключе MySelfRegistryApplication, как было указано в программе. Данные состояния окна, компонентов CheckBox1, CheckBox2 и Edit1 находятся в секции Form1, остальные данные: число в компоненте Edit2, дата в компоненте DateTimePicker1 находятся непосредственно в ключе MySelfRegistryApplication.
Посмотреть на параметры, записанные вышерассмотренной программой автоматически при закрытии, можно на двух рисунках. В первом рисунке вы видите данные даты и дробного числа, во втором рисунке вы видите содержание секции Form1. Эта секция является одновременно подключом ключа MySelfRegistryApplication.
Удаление того или иного параметра приведет к сбросу сохраняемой величины и при запуске программы будет установлено начальное значение по умолчанию. Удаление всего ключа MySelfRegistryApplication приведет к сбросу всех данных нашей программы. Удалить параметр или ключ в редакторе реестра можно кнопкой Del или выбрав соответствующий пункт из выпадающего меню по правой кнопке мышки.
Программное удаление параметра осуществляется командой
RegIniFile.DeleteKey(СЕКЦИЯ, ПАРАМЕТР);

Этот пример можно забрать по ссылке тут.

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


Некоторые команды для работы со строками

Вспомним, а для некоторых узнаем некоторые команды для работы со строками.
Строка является набором символов. Строковые переменные можно склеивать, разрезать, копировать, удалять символы.
Для склеивания нескольких строк можно воспользоваться знаком + как в арифметической операции сложения.
Копирование части строки в другую строку производится функцией Copy. Она возвращает результат копирования:

РЕЗУЛЬТАТ:=Copy(ИСХОДНАЯ_СТРОКА, ПЕРВЫЙ_СИМВОЛ, ДЛИНА_ЧАСТИ_СТРОКИ);

Удаления части в строке производится процедурой Delete:

Delete(СТРОКА,ПЕРВЫЙ_СИМВОЛ, ДЛИНА);

Для вставки части строки в другую применяем процедуру Insert

Insert(ВСТАВЛЯЕМАЯ_СТРОКА, РЕЗУЛЬТИРУЮЩАЯ_СТРОКА, НОМЕР_СИМВОЛА_ВСТАВКИ);

В любой момент можно узнать длину строки с помощью функции Length(СТРОКА), а установить длину строки можно процедурой SetLength(СТРОКА, НОВАЯ_ДЛИНА).

В pascal'е строковая переменная объявляется как тип String. Если объявляем такую переменную, значит мы будем ее использовать для работы со строками. Но в отличие от языка программирования pascal, в таких строках запрещен доступ к нулевому символу (в этом символе хранилось длина строки). Поскольку в операционной системе windows все строки имеют стандарт PChar (строки, заканчивающиеся символом #0), то тип String здесь оставлен для совместимости. Фактически длина строки String не ограничена 255 символами, как в pascal. Но применяя этот тип, вы незаметно для себя, применяете тип PChar. Все операции перевода одного типа в другой delphi производит автоматически.
Вот пример процедуры обработки строк.

procedure StringOper;
Var st1,st2,st3,st4:String;
// объявление строковых переменных
i:integer;
// целочисленная переменная
begin
st1:='это 1 строка';
st2:='это 2 строка';
st3:=st1+' '+st2;
// результат 'это 1 строка это 2 строка'
st4:=Copy(st3,1,5);
// копирование части строки. Результат 'это 1'
Delete(st4,1,4);
// удаление части строки. Результат '1'
Insert(' строка',st4,2);
// вставка части строки в строку st4. Результат '1 строка'
st4[1]:='2';
// изменение первого символа строки с 1 на 2
i:=Length(st4);
// определение длины строки i=8
SetLength(st4,7);
// установка новой длины строки. Результат '1 строк'
end;