Объекты 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).