С уважением, ведущий уроков Semen *****@***net

Урок20. Сообщения для пользователя

Автор уроков по delphi для начинающих благодарит вебмастера, программиста, просто хорошего человека jurii *****@***ru, без которого написание этого урока было бы просто невозможным.

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

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

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

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

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

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

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

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

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

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

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

НЕ нашли? Не то? Что вы ищете?

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

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

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

Тип сообщения

Описание

Вид окна

mtWarning

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

02.gif (1128 bytes)

mtError

Обычное окошко вывода сообщения об ошибки. Всем знаком его вид т. к. это наиболее частое окно в windows :)

03.gif (1044 bytes)

mtInformation

Какая-нибудь информация. Например, "Не найден файл настройки, создается заново"

04.gif (1183 bytes)

mtConfirmation

Это запрос. Запрос на сохранение перед выходом, спрашивает перед удалением параметра, и т. п. На ваш собственный вкус

05.gif (1177 bytes)

mtCustom

Это сообщение полностью аналогично ShowMessage

06.gif (827 bytes)

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


* 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, только возвращаемая величина может принимать следующие значение (соответственно нажатой кнопке):

07.gif (1447 bytes) 

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

Оъявление. Автор уроков для начинающих по delphi ищет темы, какие вам было бы интересно узнать. Свои предложения отсылайте мне, Semen'у, по адресу *****@***net, указав в теме письма слово "предложение". Ваше предложение не должно быть очень сложным для программного решения, понятным для начинающего, тема не должна отклоняться от тематики ведения уроков (например, не рассматривается управление базами данных, SQL, internet и пр.). Материал, написанный по вашему предложению, ориентировочно должен быть дан в объеме одного урока. Предложение в этот урок должно быть отправлено до пятницы.

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

С уважением, ведущий уроков Semen *****@***net

Урок21. Программа психологических тестов

Автор благоларит jurii *****@***ru, за идею этого урока.

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

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

Рассмотрим пошаговую работу над созданием программы.

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

Итак, вот пример такого планирования.

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

Каждый тест должен имееть следующую структуру:
1. Название.
2. Количество вопросов.
<Начало блока вопросов>
  3. Вопрос.
  4. Возможные варианты ответов (для нашей программы сделаем четыре варианта   ответов).
  5. Баллы, присуждаемые за тот или иной ответ.
<Конец блока вопросов>
6. Несколько вариантов результатов теста, зависимых от набранных баллов, которые показываются тестируемому человеку (для нашей программы сделаем четыре результата теста).

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

Тест, взятый для примера в этом уроке, был любезно выдерт мною из первого попавшегося журнала на столе моей сестры. Это журнал "Cool girl" №23 от 1998 года. Староват, да ничего, подойдет. Да простят меня мужчины, но это тест для молодых девушек. Впрочем, если что не нравится - текстовый редактор вам в руки и вперед. Если вы отошлете файл своих собственных тестов (рабочий, проверенный файл тестов, в частности, ищу тест определения темперамента человека) мне, автору уроков, то это будет замечательно. Все ваши тесты постараюсь разместить прямо в этом уроке.

Как создать файл тестов. Создаем в любом текстовом редакторе, желательно в блокноте windows новый файл.
Важно, в таком редакторе убрать опцию "автоперенос слов", чтобы нас не сбивало с толку перенесенные строки, ведь они у нас иногда будут очень длинными.
Первая строчка в файле должна содержать название теста. Для моего теста "Умеешь ли ты отдыхать?". Каждая такая строчка должна завершаться нажатием на клавишу Enter (перевод строки).
Следующая строка содержит число вопросов в тесте. Их у нас 16.
Дальше у нас следует блок вопросов и ответов на них. Этот блок должен повторяться количество раз, заданное в строке количества вопросов - 16.
Первая строка - вопрос.
Вторая строка - первый ответ.
Третья строка - количество баллов за этот ответ.
Четвертая строка - второй ответ.
и т. д.
В конце файла теста следуют результаты теста.
Первая строка - число, начальный интервал баллов.
Вторая строка - число, конечный интервал баллов.
Третья строка - вариант результата теста.
и т. д. для всех результатов теста.

Файл теста мы с вами рассмотрели, теперь примемся за написание программы.

Снова спланируем нашу задачу.
1. Сделаем, чтобы при запуске программы, она спрашивала у нас, какой тест необходимо загрузить. Если теста нет или пользователь отказывается открывать тест, то программа завершает свою работу.
2. При успешном выборе теста, программа открывает этот файл, считывает название, считывает количество вопросов, и предлагает первый вопрос с ответами на него.
3. После перебора всех вопросов открывается окно результатов теста.
4. Завершение работы программы.
Внешний вид программы можно спланировать так, вопрос и возможные варианты ответов будут размещены на разных панелях. На панелях разместим компоненты отображения текста (Label) и по одной кнопке на каждый вариант ответа.
Окно результатов теста планировать не будем, текст результата можно высветить на первых порах разработки программы с помощью команды ShowMessage. В последствии вы можете самостоятельно дорабатывать уже свою программу красочным оформлением, интересными особенностями.

В delphi создаем новый проект.
Для главной формы проекта Form1 устанавливаем свойство начальной позиции окна Position по центру рабочего стола poDesktopCenter. Свойтсво BorderStyle в bsDialog, чтобы окно имело неизменяемый размер.
Оформляем внешний вид программы.
В форме размещаем пять компонентов Panel, после каждого добавленного компонента изменяем свойство Align в alTop, и убирая текст из свойства Caption. Для последнего, пятого компонента Panel5 свойство Align устанавливаем в alClient.
Для более красочного оформления и отличия панели вопроса от панелей ответов, панель Panel1 свойство BevelInner установим в bvLowered. Установим размер компонентов и формы сверху вниз, как показано на рисунке.

01.gif (1678 bytes)

Теперь в панелях разместим по компоненту Label, убрав свойство автоматического изменения размера AutoSize в False, и установив свойство автоматического перевода строки WordWrap в True. Если текст не будет помещаться по длине компонента Label, то эти слова будут перенесены в другую строку. Для каждого вопроса устанавливаем по одной кнопке Button (или BitBtn для размещения в кнопке рисунка). Переименовываем названия кнопок на названия "Ответ 1", "Ответ 2" и т. п.
После размещения всех визуальных компонентов в программу, установим размер компонентов Label1 - Label5 в максимально возможное, чтобы вопросы и ответы полностью были видны.
Для улучшения восприятия вопросов и ответов для этих же компонентов установите свойство шрифта Font на свой вкус.
Последний компонент - OpenDialog для открытия файла вопросов. Можете установить фильтр для открываемых файлов, например только для файлов с расширением TES для того, чтобы случайно не открыть другой, не файл теста.
Окончательный внешний вид окна вашей программы смотрите на этом рисунке.

02.GIF (2798 bytes)

Теперь примемся за написание кода программы.
Объявим несколько глобальных переменных, которые будут действительны за пределами одного модуля.
После слова implementation в редакторе кода пишем:

Var f: TextFile; // текстовый файл теста
  FOpen: Boolean; // признак открытого файла
  TestName: String; // название теста
  QCurrent: Integer; // текущий номер вопроса
  QCount: Integer; // всего вопросов в тесте
  QUser: Integer; // сумма баллов

В инспекторе объектов выбираем компонент Form1 и для события (страница Events) OnShow (сработает до отображения окна на экране) пишем:

procedure TForm1.FormShow(Sender: TObject);
begin
FOpen:=false; // снимаем признак открытого файла (мы его еще не открывали)
if OpenDialog1.Execute then // если файл теста выбран,
  begin // то:
  AssignFile(f, OpenDialog1.FileName); // привязка имени выбранного файла в диалоге к файловой переменной
  {$I-} // отключения автоматической обработки ошибок ввода-вывода
  Reset(f); // открываем файл
  {$I+} // возвращаем опцию контроля ошибок ввода-вывода
  if IOResult<>0 then // если была ошибка открытия файла, то
  begin
  MessageDLG('Ошибка открытия файла',mtError,[mbOk],0); // сообщение на экран
  Application. Terminate; // завершить работу программы
  end;
  FOpen:=true; // включаем признак, что у нас файл был открыт
  ReadLn(f, TestName); // читаем из файла название теста
  Form1.Caption:='Психологический тест - '+TestName; // заголовок окна устанавливаем в название теста
  ReadLn(f, QCount); // читаем количество вопросов теста
  QCurrent:=0; // текущий вопрос - нулевой
  QUser:=0; // сброс количества баллов за ответы
  LoadQuestion; // переход к процедуре чтения вопроса
  end else Application. Terminate; // если файл теста не выбран, то завершить работу программы
end;

Единственной непонятной строкой для компилятора будет строка LoadQuestion. Эту процедуру мы создадим самостоятельно. В редакторе кода в разделе public объявляем новую процедуру:

procedure LoadQuestion;

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

procedure TForm1.LoadQuestion;
begin

end;

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

Пишем внутри этой процедуры обработку чтения одного вопроса из файла:

procedure TForm1.LoadQuestion;
Var Str_F: String; // временная строковая переменная для чтения данных из файла
begin
Inc(QCurrent); // увеличиваем порядковый номер текущего вопроса на 1 (равноценно команде QCurrent:=QCurrent+1)
// ЧИТАЕМ ВОПРОС
ReadLn(f, Str_F); // читаем вопрос из файла
Label1.Caption:=Str_F; // присваиваем компоненту текст вопроса
// ЧИТАЕМ ПЕРВЫЙ ОТВЕТ
ReadLn(f, Str_F); // читаем первый ответ из файла
Label2.Caption:=Str_F; // присваиваем компоненту текст ответа
ReadLn(f, Str_F); // читаем количество баллов за этот ответ
Button1.Tag:=StrToInt(Str_F); // присваиваем пользовательскому свойству Tag балл за этот ответ компоненту Button1
// ЧИТАЕМ ВТОРОЙ ОТВЕТ
ReadLn(f, Str_F);
Label3.Caption:=Str_F;
ReadLn(f, Str_F);
Button2.Tag:=StrToInt(Str_F);
// ЧИТАЕМ ТРЕТИЙ ОТВЕТ
ReadLn(f, Str_F);
Label4.Caption:=Str_F;
ReadLn(f, Str_F);
Button3.Tag:=StrToInt(Str_F);
// ЧИТАЕМ ЧЕТВЕРТЫЙ ОТВЕТ
ReadLn(f, Str_F);
Label5.Caption:=Str_F;
ReadLn(f, Str_F);
Button4.Tag:=StrToInt(Str_F);
end;

Возможно, вы еще не встречались со свойством Tag для компонентов. Это, можно сказать, пользовательское свойство. Оно имеет тип Integer, и ни на что конкретно в программе не влияет. Программист может его использовать в собственных целях, что иногда уменьшает количество объявляемых переменных в программе. Еще бывает удобно, когда компонент имеет в свойстве Tag какое-нибудь служебное число, как в нашем случае.

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

procedure TForm1.Button1Click(Sender: TObject);
Var i: Integer; // временная числовая переменная для выявления балла за отвеченный вопрос
s1: String; // временная переменная первого результата теста
p11,p12: Integer; // цифровой промежуток первого результата теста (минимальное число и максимальное)
s2: String;
p21,p22: Integer;
s3: String;
p31,p32: Integer;
s4: String;
p41,p42: Integer;
begin
i:=0; // сброс переменной
if Sender=Button1 then i:=Button1.Tag; // если была нажата кнопка Button1, то в i занести значение кол-ва баллов за этот ответ
if Sender=Button2 then i:=Button2.Tag;
if Sender=Button3 then i:=Button3.Tag;
if Sender=Button4 then i:=Button4.Tag;
QUser:=QUser+i; // увеличение общего количества баллов

if QCurrent=QCount then //если количество вопросов исчерпано, то
  begin
  ReadLn(f, p11); // чтение результатов и промежутков баллов из файла
  ReadLn(f, p12);
  ReadLn(f, s1);
  ReadLn(f, p21);
  ReadLn(f, p22);
  ReadLn(f, s2);
  ReadLn(f, p31);
  ReadLn(f, p32);
  ReadLn(f, s3);
  ReadLn(f, p41);
  ReadLn(f, p42);
  ReadLn(f, s4);
  if (QUser>=p11) and (QUser<=p12) then ShowMessage(s1); // выявление попадания в тот или иной промежуток и выдача результата теста
  if (QUser>=p21) and (QUser<=p22) then ShowMessage(s2);
  if (QUser>=p31) and (QUser<=p32) then ShowMessage(s3);
  if (QUser>=p41) and (QUser<=p42) then ShowMessage(s4);
  Close; // закрыть программу
  end else LoadQuestion; // если кол-во вопросов не исчерпано, то прочитать из файла новый вопрос (переход к процедуре чтения)
end;

Далее, для того, чтобы эта процедура вызывалась при нажатии и на другие кнопки (Button2 - Button4) нужно для каждой отдельно выбранной кнопке в инспекторе объектов установить реакцию на событие OnClick из ниспадающего списка в Button1Click.

И последнее, для завершения работы надо корректно закрыть открытый нами ранее файл. Это делается с помощью события Form1Close (выбрать компонент Form1 и создать процедуру OnClose) с помощью признака открытого файла FOpen написать одну строку внутри процедуры:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if FOpen then CloseFile(f); // если файл был открыт, то закрыть его
end;

Ваша программа готова к работе.

Рассмотренную программу вместе с прилагаемым тестом можно забрать по этой ссылке (7,5КБ).

С уважением, ведущий уроков Semen *****@***net

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

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


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

1.gif (5110 bytes)

Вспомним правила поиска файлов. Вы можете задать как имя искомого файла, так и его маску, если название неизвестно или необходимо найти несколько. Т. е. применяя специальный шаблон поиска, вы можете организовать условия выборки найденных файлов. Сразу оговорюсь, что поиск можно применять как к файлам, так и к каталогам. Будем их называть элементами файловой системы. В шаблон маски искомых элементов может входить:
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) and // если найденный элемент каталог и
  ((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.Date then // если у файла (каталога) дата создания меньше, чем установлено в 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 в качестве начального условия поиска файлов зададите корневую папку диска, например "С:\", то вы получите полный перечень всех файлов на данном диске. Обратите внимание на скорость поиска файлов и скорость работы вашей программы.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8