Опубликован: 05.08.2007 | Уровень: специалист | Доступ: платный
Лекция 8:

Объекты DataTable, DataRow и DataColumn

Переходим в код формы, подключаем пространство имен:

using System.Data.SqlClient;

В классе создаем перечисление QuestionType - вопросу с одним правильным вариантом будет соответствовать постоянная SingleVariant, вопросу с несколькими вариантами - MultiVariant:

public enum QuestionType
	{
		SingleVariant,
		MultiVariant
}

Создаем перечисление Direction для навигации по вопросам:

public enum Direction
	{
		First,
		Prev,
		Next,
		Last
}

В методе LoadDataBase заполняем объект DataSet данными из базы данных:

private void LoadDataBase()
{
	SqlConnection conn = new SqlConnection("Data Source=.;Initial
	 Catalog=Tests;Integrated Security=SSPI;");

	SqlDataAdapter questAdapter = 
	 new SqlDataAdapter("select * from questions", conn);
	SqlDataAdapter variantsAdapter =
	 new SqlDataAdapter("select * from variants", conn);
	conn.Open();
	//Заполняем таблицу "Questions" данными из questAdapter
	questAdapter.Fill(dsTests.Tables["Questions"]);
	//Заполняем таблицу "Variants" данными из variantsAdapter
	variantsAdapter.Fill(dsTests.Tables["Variants"]);
	conn.Close();
}

Создаем два экземпляра класса Hashtable1Дополнительные сведения об этом классе см. в конце "Объект DataView. Вывод связанных таблиц" .:

// Экземпляр clientAnswers для хранения ответов пользователей
Hashtable clientAnswers = new Hashtable();
// Экземпляр keys для хранения правильных ответов 
Hashtable keys = new Hashtable();

Создаем метод InitAnswersKeysTable, в котором связываем элементы экземпляров Hashtable с записями таблиц

private void InitAnswersKeysTable()
{ 
	// Создаем цикл, длина которого равна числу записей в таблице "Questions"
	for(int i = 0; i < dsTests.Tables["Questions"].Rows.Count; i++)
	{
		// Выбираем записи из таблицы "Questions".
		DataRow drquestion = dsTests.Tables["Questions"].Rows[i];

		// Выбираем записи из таблицы "Variants".
		DataRow[] drvariants = 
		 drquestion.GetChildRows (dsTests.Relations["QuestionsVariants"]);
		// Устанавливаем значение j, равное false для всех вариантов.
		bool[] answers = new bool[drvariants.Length];
		for(int j = 0; j < answers.Length; j++)
		answers[j] = false;
		
		// Добавляем значения к экземплярам Hashtable
		keys.Add(drquestion, drvariants);
		clientAnswers.Add(drquestion, answers);
	}
}

Объект DataRow предназначен для просмотра и изменения содержимого отдельной записи в объекте DataTable. Для обращения к конкретной записи используется свойство Rows[i], где i - номер записи. Метод GetChildRows позволяет обращаться к дочерним записям, он принимает название отношения. Здесь мы фактически обращаемся к записям таблицы Variants. В экземпляре keys ключами будут записи из таблицы Questions, а значениями - элементы массива записей с вариантами ответов. В экземпляре clientAnswers ключами также будут записи из таблицы Questions, а значениями - элементы массива типа bool, зависящие от ответа пользователя.

В классе формы создаем экземпляр cmTest класса CurrencyManager для перемещения по записям:

CurrencyManager cmTest = null;

В методе InitDefaultSettings определяем настройки по умолчанию:

private void InitDefaultSettings()
{
	//В свойство Tag каждой кнопки помещаем константу из перечисления Direction
	btnFirst.Tag = Direction.First;
	btnPrev.Tag = Direction.Prev;
	btnNext.Tag = Direction.Next;
	btnLast.Tag = Direction.Last;
		
	//Для всех кнопок будет один обработчик btnFirst_Click
	btnFirst.Click += new EventHandler(btnFirst_Click);
	btnPrev.Click += new EventHandler(btnFirst_Click);
	btnNext.Click += new EventHandler(btnFirst_Click);
	btnLast.Click += new EventHandler(btnFirst_Click);
	//Вызываем метод 
	LoadDataBase();
	//Определяем действия для случая, 
	//если нет записей в таблице "Questions"
	if(dsTests.Tables["Questions"].Rows.Count == 0)
	{
		txtQuestion.Text = "Нет данных о вопросах";
		btnFirst.Enabled= false;
		btnPrev.Enabled= false;
		btnNext.Enabled= false;
		btnLast.Enabled= false;
		btnCheck.Enabled= false;
	}
	else
	{
		//Вызываем метод. 
		InitAnswersKeysTable();
		//Связываем эземпляр cmTest с содержимым таблицы "Questions"
		cmTest = (CurrencyManager)this.BindingContext[dsTests, "Questions"];
		//Определяем обработчик для события
		cmTest.PositionChanged += new EventHandler(cmTest_PositionChanged);
		ShowQuestion(dsTests.Tables["questions"].Rows[0]);
		//Включаем доступность кнопок ">" и ">>"
		btnFirst.Enabled= false;
		btnPrev.Enabled= false;
		btnNext.Enabled= true;
		btnLast.Enabled= true;
	}
}

Создаем метод cmTest_PositionChanged, для обработки события PositionChanged объекта cmTest, в котором определяем доступность кнопок навигации:

private void cmTest_PositionChanged(object sender, EventArgs e)
{
	if (cmTest.Position == 0)
	{
		btnPrev.Enabled = false;
		btnFirst.Enabled = false;
		btnNext.Enabled = true;
		btnLast.Enabled = true;
	}
	else if(cmTest.Position == dsTests.Tables["questions"].Rows.Count - 1)
	{
		btnNext.Enabled = false;
		btnLast.Enabled = false;
		btnPrev.Enabled = true;
		btnFirst.Enabled = true;
	}
	else
	{
		btnPrev.Enabled = true;
		btnFirst.Enabled = true;
		btnNext.Enabled = true;
		btnLast.Enabled = true;
	}
}

Создаем метод btnFirst_Click - общий обработчик для всех кнопок навигации:

private void btnFirst_Click(object sender, EventArgs e)
{
	Button btn = (Button)sender;
	Direction direction = (Direction)btn.Tag;

	switch (direction)
	{
		case Direction.First:
			cmTest.Position = 0;
		break;
		case Direction.Prev:
			--cmTest.Position;
		break;
		case Direction.Next:
			++cmTest.Position;
		break;
		case Direction.Last:
			cmTest.Position = dsTests.Tables["questions"].Rows.Count - 1;
		break;
	}

	int rowIndex = cmTest.Position;
//Вызываем метод ShowQuestion, который выводит вопросы на форму
	ShowQuestion(dsTests.Tables["questions"].Rows[rowIndex]);
}

Вызываем метод InitDefaultSettings в конструкторе формы:

public Form1()
{
	InitializeComponent();
	InitDefaultSettings();
}

В методе ShowQuestion выводим вопрос на форму:

private void ShowQuestion(DataRow drquestion)
{
	txtQuestion.Text = drquestion["question"].ToString();
	//Вызываем метод ShowVariants, который выводит на форму варианты ответов
	ShowVariants(drquestion);			
}

В методе ShowVariants в зависимости от типа вопроса формируется набор элементов RadioButton или CheckBox c вариантами ответов, который затем выводится в элемент gbVariants:

private void ShowVariants(DataRow question)
{
	//Удаляем все элементы из GroupBox
	gbVariants.Controls.Clear();
	//Снова создаем экземпляр childVariants 
	//для обращения к записям таблицы "Variants"
	DataRow[] childVariants =
	 question.GetChildRows (dsTests.Relations["QuestionsVariants"]);
			
	//Определяем тип вопроса
	bool[] vars = (bool[])clientAnswers[question];
	int i = 0;			
	QuestionType questType = (QuestionType)question["questType"];
	switch(questType)
	{
		//Если вопрос имеет всего один правильный вариант, 
		//на форме будут созданы элементы Radiobutton
		case QuestionType.SingleVariant:
		foreach(DataRow childVariant in childVariants)
		{
			RadioButton rb = new RadioButton();
			#region Ищем выбранный ответ в таблице ответов
			bool selectedAnswer = (bool)vars[i++];
			rb.Checked = selectedAnswer;
			#endregion
			//Определяем свойства созданного элемента RadioButton
			rb.Text = childVariant["variant"].ToString();
			rb.Tag = childVariant;
			rb.CheckedChanged += new EventHandler(rb_CheckedChanged);
			int y = (gbVariants.Controls.Count == 0)?20:
			((RadioButton)gbVariants.Controls[gbVariants.Controls.Count
			 - 1]).Bottom + 2;
			//Определяем размеры создаваемых элементов RadioButton
			//500 - ширина в пикселях, rb.Height+5 - высота
			rb.Size = new Size(500, rb.Height+5);
			rb.Location = new Point(10, y);
			gbVariants.Controls.Add(rb);
		}
		break;
		//Если вопрос имеет несколько правильных вариантов, 
		//на форме будут созданы элементы CheckBox
		case QuestionType.MultiVariant:
		foreach(DataRow childVariant in childVariants)
		{
			CheckBox chb = new CheckBox();
			#region Ищем выбранный ответ в таблице ответов
			bool selectedAnswer = (bool)vars[i++];
			chb.Checked = selectedAnswer;
			#endregion
			//Определяем свойства созданного элемента RadioButton
			chb.Text = childVariant["variant"].ToString();
			chb.Tag = childVariant;
			chb.CheckedChanged += new EventHandler(chb_CheckedChanged);
			int y = (gbVariants.Controls.Count == 0)?20:
			((CheckBox)gbVariants.Controls[gbVariants.Controls.Count
			 - 1]).Bottom + 2;
			//Определяем размеры создаваемых элементов RadioButton
			//500 - ширина в пикселях, chb.Height+5 - высота
			chb.Size = new Size( 500, chb.Height+5);
			chb.Location = new Point(10, y);
			gbVariants.Controls.Add(chb);
		}
		break;
	}
}

Когда пользователь отметит галочкой элементы CheckBox или выберет RadioButton, будет происходить событие CheckedChanged. В этом событии мы фиксируем положение отмеченного элемента - при возврате к решенному вопросу пользователь будет видеть свои ответы:

private void rb_CheckedChanged(object sender, EventArgs e)
{
	RadioButton rb = (RadioButton)sender;
	if(!rb.Checked)
		return;
	//Создаем объект drvariant класса DataRow, с которым связываем 
	//свойство Tag элемента RadioButton
	DataRow drvariant = (DataRow)rb.Tag;
	//Отмечаем текущее положение объекта cmTest
	int questIndex = cmTest.Position;
	DataRow drquestion = dsTests.Tables["Questions"].Rows[questIndex];
	int answIndex = gbVariants.Controls.IndexOf(rb);
	//Выводим элемент RadioButton отмеченным, если он уже был выбран
	bool[] answers = (bool[])clientAnswers[drquestion];
	for(int i = 0; i < answers.Length; i++)
	{
		if(i == answIndex)
		answers[i] = rb.Checked;
		else
		answers[i] = !rb.Checked;
	}
}

private void chb_CheckedChanged(object sender, EventArgs e)
{
	CheckBox chb = (CheckBox)sender;
	//Создаем объект drvariant класса DataRow, с которым связываем 
	//свойство Tag элемента CheckBox
	DataRow drvariant = (DataRow)chb.Tag;
	//Отмечаем текущее положение объекта cmTest
	int rowIndex = cmTest.Position;
	DataRow drquestion = dsTests.Tables["Questions"].Rows[rowIndex];
	int answIndex = gbVariants.Controls.IndexOf(chb);
	//Выводим элемент CheckBox отмеченным, если он уже был выбран
	bool[] answers = (bool[])clientAnswers[drquestion];
	for(int i = 0; i< answers.Length; i++)
	{
		if (i == answIndex)
		{
			answers[i] = chb.Checked;
			break;
		}
	}
}

Переключаемся в режим дизайна, щелкаем на кнопке "Результат" - в обработчике события подсчитываем количество правильных ответов:

private void btnCheck_Click(object sender, System.EventArgs e)
{
	//Создаем счетчик
	double counter = 0;
	//Перебираем все ключи экземпляра key класса Hashtable, 
	//в котором хранятся ответы пользователя
	foreach(object key in keys.Keys)
	{
		bool flag = true;
		DataRow[] drvariants = (DataRow[])keys[key];
		bool[] answers = (bool[])clientAnswers[key];
		int i = 0;
		foreach(DataRow variant in drvariants)
		{
			if(((bool)variant["isRight"]) == answers[i++])
				continue;
			else
			{
				flag = false;
				break;
			}
		}
		if (flag)
			++counter;
	}
	//Делим количество правильных ответов на общее число ответов, 
	//результат умножаем на 100
	int result = (int)(counter / dsTests.Tables["questions"].Rows.Count * 100);
	MessageBox.Show(String.Format("Вы ответили правильно на {0}% вопросов.",
	 result), "Результат тестирования", MessageBoxButtons.OK,
	 MessageBoxIcon.Information);
}

Запускаем приложение (рис. 8.6). Мы рассмотрели простейший случай подсчета результатов - конечно же, в реальных приложениях кнопка "Результат" не может быть доступна в любой момент времени. Впрочем, это достаточно легко изменить.

Готовое приложение Tests. А - вид формы с двумя правильными ответами, Б - вид формы с одним правильным ответом, В - результат тестирования

увеличить изображение
Рис. 8.6. Готовое приложение Tests. А - вид формы с двумя правильными ответами, Б - вид формы с одним правильным ответом, В - результат тестирования

В программном обеспечении к курсу вы найдете приложение Tests (Code\Glava4\Tests).

Александра Тимофеева
Александра Тимофеева
Украина, Киев
Bakke Aleksander
Bakke Aleksander
Россия, Mуниципальный округ N 4