Текст программы
После того как будет создана форма программы, можно приступить к кодированию (набору текста). Сначала надо внести дополнения в объявление формы (листинг 10.1) — объявить массив компонентов
RadioButton , функцию обработки события click на кнопке выбора ответа и функции, обеспечивающие отображение и удаление вопроса. Следует обратить внимание на то, что объявление массива компонентов
RadioButton (указателей на компоненты) только устанавливает факт существования компонентов, сами же компоненты будут созданы в начале работы программы. Делает это конструктор формы. Он же задает функции обработки события
click для компонентов массива. Другой важный момент, на который следует обратить внимание, это объявление функций
swowVopros и EraseVopros как методов объекта Form1 . Это сделано для того, чтобы обеспечить этим функциям прямой доступ к компонентам формы.
Текст модуля главной формы приведен в листинге 10.2.
Листинг10.1. Программа тестирования (заголовочный файл)
#ifndef tester_H
#define tester_H
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
#include <Dialogs.hpp>
#include <Graphics.hpp>
// вопрос struct TVopros {
AnsiString Vopr; // вопрос
AnsiString Img; // иллюстрация (имя BMP-файла)
AnsiString Otv[4]; // варианты ответа
int nOtv; // кол-во вариантов ответа
int rOtv; // номер правильного ответа };
// форма
class TForml : public TForm { published:
// IDE-managed Components
TLabel *Labell; // информационное сообщение, вопрос
Tlmage *Imagel; // иллюстрация к вопросу
TButton *Buttonl; // кнопка OK / Дальше
void__fastcall FormActivate(TObject *Sender);
void __fastcall ButtonlClick(TObject *Sender);
private:
TRadioButton *RadioButton[4];
// варианты ответа - кнопки выбора
void __fastcall RadioButtonClick(TObject *Sender);
// щелчок на
// кнопке выбора ответа
void __fastcall ShowVopros(TVopros v);
// выводит вопрос
void __fastcall EraseVopros(void);
// удаляет вопрос
public:
__fastcall TForml (TCornponent* Owner) ;
};
extern PACKAGE TForml *Forml;
#endif
Листинг 10.2. Программа тестирования
/* Универсальная .программа тестирования.
Тест загружается из файла, имя которого
должно быть указано в командной строке.
Программа демонстрирует создание и настройку
компонентов во время работы программы. */
# include <vcl.h>
#pragma hdrstop
#include "tester_.h"
#include <stdio.h
// для доступа к функции sscanf
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1; // форма
int f;
// дискриптор файла теста
// имя файла теста берем из командной строки
int level[4];// кол-во правильных ответов, необходимое
// для достижения уровня AnsiString mes[4];
// сообщение о достижении уровня
TVopros Vopros; // вопрос
int otv; // номер выбранного ответа
int right =0; // кол-во правильных ответов
// функции, обеспечивающие чтение вопроса из файла теста
int Getlnt(int f);
// читает целое
int GetString(int f, AnsiString *st); // читает строку
// конструктор
__fastcall TForml::TForml(TComponent* Owner) : TFormfOwner)
{
int i;
int left =10;
// создадим радиокнопки для выбора
// правильного ответа, но сделаем их невидимыми
for (i =0; i < 4; i++)
{
// создадим радиокнопку
RadioButton[i] = new TRadioButton(Forml);
// установим значения свойств
RadioButton[i]->Parent = Forml;
RadioButton[i]->Left = left;
RadioButton[i]->Width = Forml->ClientWidth - left - 20;
RadioButton[i]->Visible = false;
RadioButton[i]->Checked = false;
// зададим функцию обработки события
Click RadioButton[i]->OnClick = RadioButtonClick; } }
void __fastcall TForml::FormActivate(TObject *Sender)
{
AnsiString st;
// имя файла теста должно быть указано в командной строке
int n = ParamCount();
if ( n < 1) {
Labell->Font->Style = TFontStyles()« fsBold;
Labell->Caption =
"В командной строке запуска надо задать имя файла теста";
Buttonl->Tag = 2;
return; }
// открыть файл теста
f = FileOpen(ParamStr(1), fmOpenRead);
if ( f == -1)
{
Labell->Font->Style = TFontStyles()« fsBold;
Labell->Caption =
"Ошибка доступа к файлу теста " + ParamStr(l);
Buttonl-XTag = 2; return;
}
// вывести информацию о тесте
GetString(f, Sst); // прочитать название теста
Forml->Caption = st;
GetString(f, sst); // прочитать вводную информацию
Labell->Width = Forml->ClientWidth - Labell->Left -20;
Labell->Caption = st; Labell->AutoSize = true;
// прочитать информацию об уровнях оценки
for (int i=0; i<4; i++)
{
level[i] = Getlnt(f);
GetString(f, &mes[i]); } }
// читает из файла очередной вопрос
bool GetVopros(TVopros *v)
{
AnsiString st;
int p; // если р=1, то к вопросу есть иллюстрация
if ( GetStringtf, &(v->Vopr)) != 0) {
// прочитать кол-во вариантов ответа, номер правильного ответа
// и признак наличия иллюстрации
v->nOtv = Getlnt(f);
v->rOtv = Getlnt(f);
p = Getlnt(f);
if (p)
// к вопросу есть иллюстрация
GetString(f,&(v->Img)); else v->Img = "";
// читаем варианты ответа
for(int i = 0;i < v->nOtv; i++)
{
GetString(f,&(v->0tv[i])); }
return true; }
else return false;
}
// выводит вопрос
void __fastcall TForml::ShowVopros(TVopros v)
{
int top; int i;
// вопрос
Labell->Width = ClientWidth - Labell->Left -20;
Labell->Caption = v.Vopr;
Labell->AutoSize = true;
if (v.Img != //к вопросу есть иллюстрация {
/* определим высоту области,
которую можно использовать для вывода иллюстрации */
int RegHeight = Buttonl->Top
- (Labell-ХГор + Labell->Height +10) // область вывода вопроса
- (RadioButton[l]->Height + 10) * v.nOtv;
Image1->Top = Labell->Top + Labell->Height + 10;
// загрузим картинку и определим ее размер
Image1->Visible = false;
Image1->AutoSize = true;
Image1->Picture->LoadFromFile(v.Img);
if (Imagel->Height > RegHeight)
// картинка не помещается
(
Image1->AutoSize = false;
Imagel->Height = RegHeight;
Imagel->Proportional = true; )
Image1->Visible = true;
// положение полей отсчитываем от иллюстрации
top = Imagel-ХГор + Imagel->Height + 10; )
else // положение полей отсчитываем от вопроса
top = Labell-ХГор + Labell->Height + 10;
// варианты ответа
for (i =0; i < v.nOtv; i++)
{
RadioButton[i]->Top = top;
RadioButton[i]->Caption = v.0tv[i];
RadioButton[i]->Visible = true;
RadioButton[i]->Checked = false;
top += 20; }
}
// щелчок на радиокнопке выбора ответа
void__fastcall TForml::RadioButtonClick(TObject *Sender)
{
int 1=0;
while ( ! RadioButton[i]->Checked) 1++;
otv = 1+1;
// ответ выбран, сделаем доступной кнопку Дальше
Buttonl->Enabled = true; }
// удаляет вопрос с экрана
void __fastcall TForml::EraseVopros(void)
{
Imagel->Visible = false;
// скрыть поле вывода иллюстрации
// скрыть поля выбора ответа for (int i=0; i <4; i++) (
RadioButton[i]->Visible = false;
RadioButton[i]->Checked = false; }
Button1->Enabled = false;
// сделать недоступной кнопку Дальше }
// щелчок на кнопке ОК/Дальше/ОК
void __fastcall TForml::ButtonlClick(TObject *Sender)
{
bool ok; // результат чтения из файла очередного вопроса
switch (Buttonl-XTag) {
case 0: // щелчок на кнопке ОК в начале работы программы
// прочитать и вывести первый вопрос GetVopros(SVopros);
ShowVopros(Vopros);
Buttonl->Caption = "Дальше";
Buttonl->Enabled = false;
Button1->Tag = 1; break;
case 1: // щелчок на кнопке Дальше
if (otv == Vopros.rOtv) // выбран правильный ответ
right++; EraseVopros(}; ok = GetVopros(SVopros); if ( ok)
ShowVopros(Vopros) ; else
// вопросов больше нет {
FileClose(f);
// вывести результат
AnsiString st; // сообщение
int i; // номер достигнутого уровня
Form1->Caption = "Результат тестирования";
st.printf("Правильных ответов: %i\n",right);
// определим оценку
i = 0; // предположим, что испытуемый
// ответил на все опросы while
(( right < level[i]) && (i < 3)) i++;
st = st + mes[i]; Labell->Caption = st;
Button1->Caption = "OK";
Buttonl->Enabled = true;
Buttonl->Tag =2; } break;
case 2: // щелчок на кнопке OK в конце работы программы
Form1->Close(}; // завершить работу программы } }
// Функция GetString читает строку из файла
// значение функции — количество прочитанных символов
int GetString(int f, AnsiString *st)
{
unsigned char buff300]; // строка (буфер)
unsigned char *p = buf;
// указатель на строку
int n;
// кол-во прочитанных байт (значение функции FileRead)
int len =0; // длина строки
n = FileRead(f, p, 1);
while ( n != 0)
{
if ( *p == '\r')
{
n = FileRead(f, p, 1); // прочитать '\n' break;
}
len++;
P++;
n = FileRead(f, p, 1); }
*p = '\0'; if ( len !=0)
st->printf("%s", buf); return len;
}
// читает из файла целое число
int Getlnt(int f)
{
char buf[20]; // строка (буфер)
char *p = buf; // указатель на строку
int n;// кол-во прочитанных байт (значение функции FileRead)
int a; // число, прочитанное из файла
n = FileRead(f, p, 1);
while ( (*p >= '0') (*p <= '9') && (n > 0))
{
P++;
n = FileRead(f, p, 1) ; }
if ( *p == '\r')
n = FileRead(f, p, 1)
// прочитать '\n'
*p = '\0';
// изображение числа в буфере, преобразуем
строку в целое sscanf(buf,"%i", &a);
return a;
}
Как было сказано ранее, объявление массива компонентов не создает компоненты, а только устанавливает факт их существования. Создает и настраивает компоненты RadioButton конструктор формы (функция TForm1: : TForm1) . Непосредственное создание компонента (элемента массива) выполняет оператор RadioButton[i] = new TRadioButton(Forml)
Следующие за этим оператором инструкции обеспечивают настройку компонента. В том числе, они путем присваивания значения свойству Onclick задают функцию обработки события click . В рассматриваемой программе для обработки события click на всех компонентах RadioButton используется одна и та же функция, которая путем опроса значения свойства checked фиксирует номер выбранного ответа и делает доступной кнопку Дальше (Button1) .
После запуска программы и вывода на экран стартовой формы происходит событие onActivate . Функция обработки этого события проверяет, указан ли в командной строке параметр — имя файла теста. Реализация программы предполагает, что если имя файла теста задано без указания пути доступа к нему, т файл теста и файлы с иллюстрациями находятся в том же каталоге, что и программа тестирования. Если же путь доступа указан, то файлы с иллюстрациями должны находиться в том же каталоге, что и файл теста. Такой подход позволяет сгруппировать все файлы одного теста в одном каталоге.
Если файл теста задан, функция открывает его, читает название теста и вводную информацию и затем выводит их в диалоговое окно, причем название выводится в заголовок, а вводная информация — в поле Label1 .
Непосредственное чтение строк из файла выполняет функция Getstring . Значением функции является длина строки. Следует обратить внимание на то, что функция GetString Возвращает строку Ans iString.
После того как прочитана общая информация о тесте, программа считывает из файла теста информацию об уровнях оценки и фиксирует ее в массивах level и mes . Критерий достижения уровня (количество правильных ответов) считывает функция Getint .
После вывода информационного сообщения программа ждет, пока пользователь не нажмет кнопку Ok (Button№ ).
Командная кнопка Button№ используется:
- для завершения работы программы, если в командной строке не указан файл теста;
- для активизации процесса тестирования (после вывода информационного сообщения);
- для перехода к следующему вопросу (после выбора варианта ответа);
- для завершения работы программы (после вывода результата тестирования).
Таким образом, реакция программы на нажатие кнопки Buttonl зависит от состояния программы. Состояние программы фиксирует свойство Tag кнопки Button 1 .
После вывода информации о тесте значение свойства Tag кнопки Button1 равно нулю. Поэтому в результате щелчка на кнопке Button 1 выполняется та часть программы, которая обеспечивает вывод первого вопроса и замену находящегося на кнопке текста ОК на текст Дальше, и заменяет значение свойства Tag на единицу.
В процессе тестирования значение свойства Tag кнопки Button 1 равно единице. Поэтому функция обработки события click сравнивает номер выбранного ответа (увеличенный на единицу номер компонента RadioButton ) с номером правильного ответа, увеличивает на единицу счетчик правильных ответов (в том случае, если выбран правильный ответ) и активизирует процесс чтения следующего вопроса. Если попытка чтения очередного вопроса завершилась неудачно (это значит, что вопросы исчерпаны), функция выводит результаты тестирования, заменяет текст на командной кнопке на ОК и подготавливает операцию завершения работы программы (свойству Tag присваивает значение 2).
Чтение вопроса (вопрос, информация о количестве альтернативных ответов, номер правильного ответа и признак наличия иллюстрации, а также имя файла иллюстрации и альтернативные ответы) из файла теста выполняет функция GetVopros .
Вывод вопроса, иллюстрации и альтернативных ответов выполняет функция showVopros . Сначала функция выводит вопрос — присваивает значение свойству caption компонента Labe l1 . Затем, если к вопросу есть иллюстрация, функция вычисляет размер области, которую можно выделить для отображения иллюстрации, и загружает иллюстрацию. Если размер иллюстрации больше размера области, функция устанавливает максимально возможный размер компонента imagei и присваивает значение false свойству AutoSize и true — свойству Proportional , обеспечивая тем самым масштабирование иллюстрации. После этого функция выводит альтернативные ответы. Положение компонентов, обеспечивающих вывод альтернативных ответов, отсчитывается от нижней границы компонента image 1 , если к вопросу есть иллюстрация, или компонента Label1 , если иллюстрации нет.
Сразу после вывода вопроса кнопка Дальше (Button1) недоступна. Сделано это для того, чтобы блокировать возможность перехода к следующему вопросу, если не дан ответ на текущий. Доступной кнопку Дальше делает функция обработки события Click на одном из компонентов RadioButton .
Кроме того, функция путем сканирования (проверки значения свойства checked компонентов массива RadioButton ) определяет, на каком из компонентов массива испытуемый сделал щелчок и, следовательно, какой из вариантов ответа выбран. Окончательная фиксация номера выбранного ответа и сравнение его с номером правильного ответа происходит в результате нажатия кнопки Дальше.