| Украина, Киев |
Объекты 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). Мы рассмотрели простейший случай подсчета результатов - конечно же, в реальных приложениях кнопка "Результат" не может быть доступна в любой момент времени. Впрочем, это достаточно легко изменить.
увеличить изображение
Рис. 8.6. Готовое приложение Tests. А - вид формы с двумя правильными ответами, Б - вид формы с одним правильным ответом, В - результат тестирования
В программном обеспечении к курсу вы найдете приложение Tests (Code\Glava4\Tests).
