Шаг 141.
Microsoft Visual C++ 2010. Начала.
Программа "Экзаменатор". Текст программы

    На этом шаге мы приведем текст программы и рассмотрим ее работу.

    Объявления переменных, конструктор формы, функции обработки событий и другие функции приведены ниже:

.    .    .    .    .
public:
	// конструктор
	Form1(String^ fpath, String^ fname) 
	{
		InitializeComponent(); 
		// 
		picpath = fpath; // путь к файлам иллюстраций 
		radioButton1->Visible = false; 
		radioButton2->Visible = false; 
		radioButton3->Visible = false; 
		try 
		{
			// прочитать xml-файл 
			xmlReader = gcnew System::Xml::XmlTextReader(fpath + fname); 
			xmlReader->Read(); 
			mode = 0; 
			n = 0; 
			// загрузить и показать заголовок теста 
			this->showHead(); 
			// загрузить и показать описание теста 
			this->showDescription(); 
		}
		catch(Exception^ e) 
		{
			MessageBox::Show(e->Message, 
				"Экзаменатор", 
				MessageBoxButtons::OK, 
				MessageBoxIcon::Error); 
			mode = 2; 
		}
	}
.    .    .    .    .
private: 
	// XmlReader обеспечивает чтение данных xml-файла 
	System::Xml::XmlReader^ xmlReader; 
	String^ qw; // вопрос
	// варианты ответа - массив строк 
	static array<String^>^ answ = gcnew array<String^>(3); 
	String^ picpath; // путь к файлу иллюстрации 
	String^ pic; // файл иллюстрации 
	int right; // правильный ответ (номер) 
	int otv; // выбранный ответ (номер) 
	int n; // количество правильных ответов 
	int nv; // общее количество вопросов 
	int mode; // состояние программы: 
	          // 0 - начало работы; 
	          // 1 - тестирование; 
	          // 2 - завершение работы

// выводит название (заголовок) теста 
void showHead() 
{
	// ищем узел <head> 
	do 
	{ 
		xmlReader->Read(); 
	}
	while(xmlReader->Name != "head"); 
	// считываем заголовок 
	xmlReader->Read(); 
	// выводим название теста в заголовок окна 
	this->Text = xmlReader->Value; 
	// выходим из узла <head> 
	xmlReader->Read(); 
} 

// вывод описания теста 
void showDescription() 
{ 
	// ищем узел <description> 
	do 
	{ 
		xmlReader->Read(); 
	}
	while(xmlReader->Name != "description"); 
	// считываем описание теста 
	xmlReader->Read(); 
	// выводим описание теста 
	label1->Text = xmlReader->Value;
	// выходим из узла <description> 
	xmlReader->Read(); 
	// ищем узел вопросов <qw> 
	do 
		xmlReader->Read(); 
	while(xmlReader->Name != "qw"); 
	// входим в узел "qw" 
	xmlReader->Read(); 
} 

// чтение вопроса из файла теста 
bool getQw() 
{ 
	// считываем тег <q> 
	xmlReader->Read(); 
	if (xmlReader->Name == "q") 
	{
		// здесь прочитан тег <q>, 
		// атрибут text которого содержит вопрос, а 
		// атрибут src - имя файла иллюстрации 
		// извлекаем значение атрибутов: 
		qw = xmlReader->GetAttribute("text"); 
		pic = xmlReader->GetAttribute("src"); 
		if (!pic->Equals(String::Empty)) 
			pic = picpath + pic; 
		// входим внутрь узла 
		xmlReader->Read(); 
		int i = 0; 
		// считываем данные узла вопроса <q> 
		while (xmlReader->Name != "q") 
		{
			xmlReader->Read(); 
			// варианты ответа 
			if (xmlReader->Name == "a") 
			{
				// если есть атрибут right, то это правильный ответ 
				if (xmlReader->GetAttribute("right") == "yes") 
					right = i;
				// считываем вариант ответа 
				xmlReader->Read(); 
				if (i < 3) answ[i] = xmlReader->Value; 
				// выходим из узла <a> 
				xmlReader->Read(); 
				i++; 
			}
		}
		// выходим из узла вопроса <q> 
		xmlReader->Read(); 
		return true; 
	}
	// если считанный тег не <q> 
	else 
		return false; 
} 

// вывод вопроса и вариантов ответа 
void showQw() 
{ 
	// выводим вопрос 
	label1->Text = qw; 
	// иллюстрация 
	if (pic->Length != 0) 
	{
		try 
		{
			pictureBox1->Image = gcnew Bitmap(pic); 
			pictureBox1->Visible = true; 
			radioButton1->Top = pictureBox1->Bottom + 16; 
		}
		catch (Exception^) 
		{
			if (pictureBox1->Visible) 
				pictureBox1->Visible = false; 
			label1->Text += "\n\n\nОшибка доступа к файлу " + pic + ".";
			radioButton1->Top = label1->Bottom + 8; 
		}
	}
	else 
	{
		if (pictureBox1->Visible) 
			pictureBox1->Visible = false; 
		radioButton1->Top = label1->Bottom + 16; 
	}
	// показать варианты ответа 
	radioButton1->Text = answ[0]; 
	radioButton2->Top = radioButton1->Top + 24;; 
	radioButton2->Text = answ[1]; 
	radioButton3->Top = radioButton2->Top + 24;; 
	radioButton3->Text = answ[2]; 
	radioButton4->Checked = true; 
	button1->Enabled = false; 
}

// Щелчок на кнопке выбора ответа. 
// Функция обрабатывает событие Click 
// компонентов radioButton1-radioButton3 
private: System::Void radioButton1_Click(System::Object^ sender, 
     System::EventArgs^ e) {
	 if ((RadioButton^)sender == radioButton1) otv = 0; 
	 if ((RadioButton^)sender == radioButton2) otv = 1; 
	 if ((RadioButton^)sender == radioButton3) otv = 2; 
	 button1->Enabled = true; 
} 

private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
	 switch (mode) 
	 {
		 case 0: 
			 // Щелчок на кнопке OK, когда в окне отображается 
			 // информация о тесте и задание
			 radioButton1->Visible = true; 
			 radioButton2->Visible = true; 
			 radioButton3->Visible = true; 
			 getQw(); // читать вопрос 
			 showQw(); // отобразить вопрос 
			 mode = 1; 
			 button1->Enabled = false; 
			 radioButton4->Checked = true; 
			 break; 
		 case 1:
		 // щелчок, подтверждающий выбор ответа 
			 nv++; 
			 // правильный ли ответ выбран 
			 if (otv == right) n++; 
			 if (getQw()) 
				 showQw(); 
			 else { // больше вопросов нет 
				 radioButton1->Visible = false; 
				 radioButton2->Visible = false; 
				 radioButton3->Visible = false; 
				 pictureBox1->Visible = false; 
				 // обработка и вывод результата 
				 showLevel(); 
				 // следующий щелчок на кнопке OK 
				 // должен закрыть окно программы 
				 mode = 2; 
			 }
			 break; 
		 case 2:
			 // завершение работы программы 
			 this->Close(); // закрыть окно 
			 break; 
	 }
 }
// вывод оценки 
void showLevel() 
{ 
	// ищем узел <levels> 
	do 
		xmlReader->Read(); 
	while (xmlReader->Name != "levels"); 
	// входим внутрь узла 
	xmlReader->Read(); 
	// читаем данные узла 
	while (xmlReader->Name != "levels") 
	{
		xmlReader->Read(); 
		if (xmlReader->Name == "level") 
			// n - количество правильных ответов. 
			// Для достижения уровня количество правильных ответов 
			// должно быть равно или превышать значение, 
			// заданное атрибутом score 
			if (n >= Convert::ToInt32( 
				xmlReader->GetAttribute("score"))) 
				break; 
	}
	// выводим оценку 
	label1->Text = 
		"Тестирование завершено.\n" + 
		"Всего вопросов: " + nv.ToString() + "\n" + 
		"Правильных ответов: " + n.ToString() + "\n\n" + 
		xmlReader->GetAttribute("text"); 
}
Архив проекта можно взять здесь.

    Работает программа "Экзаменатор" так. Сначала функция main проверяет, указан ли при запуске программы параметр — имя файла. Если параметр не указан (в этом случае размер массива args равен нулю), то выводится сообщение о необходимости указать файл теста при запуске программы и работа программы на этом заканчивается. Если параметр указан, то проверяется, указано ли полное имя файла (включая путь). Если указано полное имя файла, то в переменную fpath записывается путь к файлу теста. Делается это для того, чтобы в дальнейшем можно было получить доступ к файлам иллюстраций (по условию задачи, файлы иллюстраций должны находиться в том же каталоге, что и файл теста). Если имя файла краткое, то в переменную fpath записывается имя каталога, в котором находится exe-файл. Имя файла теста записывается в переменную fname. Далее выполняется проверка, существует ли указанный файл теста. Если файла теста нет, то выводится сообщение об ошибке и программа завершает работу. Если файл теста существует, то создается окно приложения. Делает это инструкция Application::Run(gcnew Form1(fpath,fname)). Обратите внимание, конструктору формы передаются два параметра: путь к файлу теста и его имя.

    Конструктор формы вызывает функции showHead() и showDescription(), которые соответственно читают из файла заголовок и описание теста (узлы <head> и <description>) и отображают их. В результате в окне программы отображаются описание теста и кнопка OK. После того как испытуемый прочтет информацию о тесте и сделает щелчок на кнопке OK, начинается процесс тестирования.

    Основную работу выполняет функция обработки события на кнопке button1. Следует обратить внимание, что эта кнопка используется для активизации процесса тестирования, перехода к следующему вопросу (после выбора варианта ответа) и завершения работы программы. Какое из перечисленных действий будет выполнено в результате щелчка на кнопке button1, определяет значение переменной mode. В начале работы программы значение переменной равно нулю. Поэтому в результате первого щелчка на кнопке выполняется та часть программы, которая обеспечивает отображение первого вопроса и записывает в переменную mode единицу. В процессе тестирования значение в переменной mode равно единице. Поэтому функция обработки события Click увеличивает на единицу счетчик правильных ответов (если выбран правильный ответ) и вызывает функцию getQw(), которая считывает из файла очередной вопрос, и затем — функцию showQw(), которая выводит его на экран. Если текущий вопрос последний (в этом случае функция getQw() возвращает False), то вызывается функция showLewel(), которая выводит на экран результат тестирования и в переменную mode записывает двойку. В результате, после следующего щелчка на кнопке button1, программа завершает работу.

    Читает очередной вопрос из файла функция getQw(). Сначала она читает узел <qw> и в переменную qw записывает текст вопроса — значение атрибута text. В переменную pic записывается значение атрибута src — имя файла иллюстрации. Далее из подузлов <q> читаются вопросы. В процессе чтения контролируется значение атрибута right. Если значение равно yes, то в переменной right фиксируется номер правильного ответа (порядковый номер подузла).

    Функция showQw() выводит вопрос на экран. Необходимо обратить внимание на то, что положение области отображения первого ответа (компонента radioButton1) отсчитывается от нижней границы области отображения иллюстрации (компонента pictureBox1) или от нижней границы области отображения вопроса (компонента label1). Если к вопросу есть иллюстрация, то она отображается и свойство top компонента radioButton1 устанавливается так, чтобы он располагался немного ниже нижней границы иллюстрации. Если иллюстрации нет, то положение компонента radioButton1 отсчитывается от нижней границы компонента label1.

    Обработку события Click на переключателях radioButton1radioButton3 выполняет одна, общая для всех компонентов, функция TForm1.RadioButtonClick. Она фиксирует в переменной otv номер выбранного ответа (номер переключателя) и делает доступной кнопку перехода к следующему вопросу.

    Функция showLevel() (она запускается в результате щелчка на кнопке button1, если значение переменной mode равно 2) читает информацию об уровнях оценки (подузлы <level> узла <levels>) и выводит на экран результат тестирования.

    На следующем шаге мы рассмотрим запуск программы.




Предыдущий шаг Содержание Следующий шаг