结对编程实验报告简易在线考试系统

组员学号:2452520、2452512
开发环境:Visual Studio + C# WinForm
完成功能:题目管理、考生答题、自动判分、错题解析报告、限时考试

一、实验概述
本次实验围绕结对编程模式,两人一组合作完成简易在线考试系统的设计与实现。系统包含题目管理、考生答题、自动判分、错题解析、限时考试等核心功能,严格遵循结对编程规范:一人编码、一人审核、定时轮换角色,最终完成可稳定运行的完整考试系统。
二、结对编程模式说明
本次实验全程采用标准结对编程方式开展:

  1. 角色分工
    驾驶员(Driver):负责编写代码、实现功能、调试界面
    观察员(Navigator):负责审核代码、检查逻辑、发现BUG、提出优化建议

  2. 定时轮换
    每完成一个模块(题目录入/答题界面/判分逻辑),两人立即交换角色,确保双方都参与设计、编码、测试全过程。

  3. 全程协作
    所有需求讨论、界面布局、逻辑判断、BUG修复均双人共同完成,不单独写代码、不单独提交结果。
    三、需求分析(结对共同完成)
    通过双人讨论,我们明确系统必须实现以下功能:

  4. 题目管理:支持单选、多选、判断题录入、保存

  5. 考生答题:限时考试、上下题切换、最后一题自动变为提交

  6. 自动判分:实时判断对错、生成错题与解析报告

  7. 流程规范:未开始不能答题、交卷后锁定控件、不重复记录错题

  8. 界面友好:布局整齐、操作简单、符合真实考试流程
    四、系统设计(结对讨论确定)

  9. 三层界面结构
    Tab1:题目管理(录入、保存、持久化到文件)
    Tab2:在线答题(倒计时、题目、选项、上下题按钮)
    Tab3:成绩报告(错题、答案、解析、判分结果)

  10. 题目类设计
    基类:Question
    子类:单选题、多选题、判断题

  11. 数据存储
    使用 TXT 文件保存题目,实现重启不丢失。
    五、结对编程开发过程(重点体现)

  12. 模块1:题目管理界面(A驾驶,B审核)
    A 负责搭建输入框、按钮、题型选择器
    B 实时检查控件命名、布局合理性、空值判断逻辑
    双人共同确定保存格式:编号|题型|题目|选项|答案`
    B 发现未判断非空问题,A 立即修正,一次通过

  13. 模块2:在线答题界面(B驾驶,A审核)
    B 编写动态控件生成、TableLayoutPanel 布局
    A 提出优化:一行一个选项、按钮放在最下方
    双人协作解决:开始考试前禁用所有答题控件
    A 发现布局错乱问题,两人共同调整,快速修复

  14. 模块3:答题与判分逻辑(A驾驶,B审核)
    A 实现 RadioButton/CheckBox 原生选择题控件
    B 检查判分逻辑,提出必须防止重复记录错题
    双人共同设计:用 HashSet 记录已答题,避免重复报告
    B 发现“上一题/下一题切换重复生成错题”BUG,两人一起定位并修复

  15. 模块4:交卷与成绩统计(B驾驶,A审核)
    B 实现提交考试、结束考试、时间到三种交卷方式
    A 提出:三种交卷效果必须完全统一
    双人共同封装FinishExam()统一方法,代码更简洁、健壮
    A 检查成绩报告格式,确保题目、答案、解析完整显示

  16. 整体测试与优化(双人共同测试)
    两人轮流进行考试模拟
    发现并修复:
    按钮未禁用
    错题重复记录
    最后一题不变提交
    结束考试不生成成绩
    所有问题均双人快速定位、共同解决,效率远高于单人开发

六、核心功能展示

  1. 题目录入:支持单选、多选、判断,自动保存到文件
  2. 限时考试:倒计时显示,时间到自动交卷
  3. 智能答题:
    未开始不能答题
    一行一个选项
    最后一题自动变为“提交考试”
  4. 自动判分:实时提示正确/错误

七、核心代码展示

点击展开重构后的完整源代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace Pair_Program
{
public partial class Form1 : Form
{
private int allsecond;
private bool isrun = false;
private int index;
private static int id = 0;
private string question;
private string ch1;
private string ch2;
private string ch3;
private string ch4;
private string answer;
private string answeranalyze;
// 已作答题目记录(防止重复记录错题)
private HashSet answeredQuestions = new HashSet();

    List<Question> questionList = new List<Question>();
    private int currentQIndex = 0;

    private Label lblTitle;
    private Label lblOptA, lblOptB, lblOptC, lblOptD;
    private TextBox txtUserAns;
    
    private TextBox txtResult;
    

    // 单选题控件(圆形,只能选一个)
    private RadioButton radA, radB, radC, radD;

    // 多选题控件(方形,可多选)
    private CheckBox chkA, chkB, chkC, chkD;

    public Form1()
    {
        InitializeComponent();
        // 计时器初始化
        allsecond = 60 * 600;
        Timelabel.Text = "60:00";

        

        btnPrev.Enabled = false;   // 上一题 禁用
        btnNext.Enabled = false;   // 下一题 禁用
        tableLayoutPanel2.Enabled = false; // 题目选项区域 禁用

        LoadQuestionsFromTxt();
        CreateExamControls();
        CreateResultTextBox();

        if (questionList.Count > 0)
            LoadCurrentQuestion();
    }

    

    private void CreateExamControls()
    {
        // 只用你的 tableLayoutPanel2,绝不乱改布局
        TableLayoutPanel panel = tableLayoutPanel2;


        // ======================
        // 第 0 行:题目 (跨2列)
        // ======================
        lblTitle = questionlabel;
        lblTitle.Font = new Font("微软雅黑", 14, FontStyle.Bold);
        lblTitle.Dock = DockStyle.Fill;
        lblTitle.TextAlign = ContentAlignment.MiddleLeft;
        panel.Controls.Add(lblTitle, 0, 0);
        panel.SetColumnSpan(lblTitle, 1);

        // ======================
        // 第 1 行:选项 A
        // ======================
        radA = new RadioButton();
        radA.Font = new Font("微软雅黑", 12);
        radA.Dock = DockStyle.Fill;
        panel.Controls.Add(radA, 1, 1);
        panel.SetColumnSpan(radA, 1);

        // ======================
        // 第 2 行:选项 B
        // ======================
        radB = new RadioButton();
        radB.Font = new Font("微软雅黑", 12);
        radB.Dock = DockStyle.Fill;
        panel.Controls.Add(radB, 1, 2);
        panel.SetColumnSpan(radB, 1);

        // ======================
        // 第 3 行:选项 C
        // ======================
        radC = new RadioButton();
        radC.Font = new Font("微软雅黑", 12);
        radC.Dock = DockStyle.Fill;
        panel.Controls.Add(radC, 1, 3);
        panel.SetColumnSpan(radC, 1);

        // ======================
        // 第 4 行:选项 D
        // ======================
        radD = new RadioButton();
        radD.Font = new Font("微软雅黑", 12);
        radD.Dock = DockStyle.Fill;
        panel.Controls.Add(radD, 1, 4);
        panel.SetColumnSpan(radD, 1);

        
    }

   

    private void CreateResultTextBox()//生成结果
    {
        Panel p = tabControl1.TabPages[2];
        p.Controls.Clear();

        txtResult = new TextBox();
        txtResult.Dock = DockStyle.Fill;
        txtResult.Multiline = true;
        txtResult.ReadOnly = true;
        txtResult.ScrollBars = ScrollBars.Vertical;
        txtResult.Font = new Font("微软雅黑", 11);
        p.Controls.Add(txtResult);
    }

    private void LoadCurrentQuestion()
    {
        if (lblTitle == null || radA == null) return;
        if (currentQIndex >= questionList.Count) return;

        Question q = questionList[currentQIndex];
        lblTitle.Text = $"第{currentQIndex + 1}题:{q.QuestionText}";

        radA.Checked = false;
        radB.Checked = false;
        radC.Checked = false;
        radD.Checked = false;

        radA.Text = "A:" + q.Choices[0];
        radB.Text = "B:" + q.Choices[1];
        radC.Text = "C:" + q.Choices[2];
        radD.Text = "D:" + q.Choices[3];

        // ====================== 关键代码 ======================
        // 判断是不是最后一题 → 自动切换按钮文字
        if (currentQIndex == questionList.Count - 1)
        {
            btnNext.Text = "提交考试";
        }
        else
        {
            btnNext.Text = "下一题";
        }
    }



    // 上一题按钮
    private void btnPrev_Click(object sender, EventArgs e)
    {
        // 加空判断,杜绝崩溃
        if (questionList == null || currentQIndex <= 0) return;

        currentQIndex--;
        LoadCurrentQuestion();
    }

    // 下一题按钮
    private void btnNext_Click(object sender, EventArgs e)
    {
        if (questionList == null || currentQIndex >= questionList.Count) return;

        // 获取答案
        string userAns = "";
        if (radA.Checked) userAns = "A";
        else if (radB.Checked) userAns = "B";
        else if (radC.Checked) userAns = "C";
        else if (radD.Checked) userAns = "D";

        //if (string.IsNullOrWhiteSpace(userAns))
        //{
        //    MessageBox.Show("请选择一个答案!");
        //    return;
        //}

        string rightAns = questionList[currentQIndex].AnswerText.Trim();
        bool isRight = userAns.Equals(rightAns, StringComparison.OrdinalIgnoreCase);

        // ====================== 修复:只记录第一次答案 ======================
        if (!answeredQuestions.Contains(currentQIndex))
        {
            txtResult.AppendText($"【第{currentQIndex + 1}题】\r\n");
            txtResult.AppendText($"题目:{questionList[currentQIndex].QuestionText}\r\n");
            txtResult.AppendText($"你的答案:{userAns}\r\n");
            txtResult.AppendText($"正确答案:{rightAns}\r\n");
            txtResult.AppendText($"结果:{(isRight ? "答对" : "答错")}\r\n");
            txtResult.AppendText("-----------------------------------------\r\n\r\n");

            answeredQuestions.Add(currentQIndex); // 标记已答
        }

       
        // 提交考试
        if (btnNext.Text == "提交考试")
        {
            FinishExam();
            return;
        }

        currentQIndex++;
        LoadCurrentQuestion();
    }

    private void FinishExam()//结束考试
    {
        timer1.Stop();
        Timebutton.Text = "开始考试";
        isrun = false;

        btnPrev.Enabled = false;
        btnNext.Enabled = false;
        tableLayoutPanel2.Enabled = false;

        MessageBox.Show("✅ 考试已完成!成绩已生成。");
        tabControl1.SelectedIndex = 2;
    }


    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (comboBox1.SelectedIndex == 0)
        {
            choice3.Visible = true;
            choice4.Visible = true;
            index = 0;
        }
        if (comboBox1.SelectedIndex == 1)
        {
            choice3.Visible = true;
            choice4.Visible = true;
            index = 1;
        }
        if (comboBox1.SelectedIndex == 2)
        {
            choice3.Visible = false;
            choice4.Visible = false;
            index = 2;
        }
    }

    // ========== 完整计时器 ==========
    private void timer1_Tick(object sender, EventArgs e)
    {
        allsecond--;
        int minute = allsecond / 600;
        int second = allsecond % 600 / 10;
        Timelabel.Text = $"{minute:D2}:{second:D2}";

        if (allsecond <= 0)
        {
            timer1.Stop();
            Timebutton.Text = "开始考试";
            isrun = false;

            // 时间到 → 禁用
            btnPrev.Enabled = false;
            btnNext.Enabled = false;
            tableLayoutPanel2.Enabled = false;

            MessageBox.Show("⏰ 时间到!考试结束");
            tabControl1.SelectedIndex = 2;
        }
    }



    private void Timebutton_Click(object sender, EventArgs e)
    {
        if (!isrun)
        {
            if (questionList.Count == 0)
            {
                MessageBox.Show("请先添加题目!");
                return;
            }

            // 开始考试
            btnPrev.Enabled = true;
            btnNext.Enabled = true;
            tableLayoutPanel2.Enabled = true;
            txtResult.Clear();
            currentQIndex = 0;
            answeredQuestions.Clear();
            allsecond = 60 * 600;
            timer1.Start();
            Timebutton.Text = "结束考试";
            isrun = true;
            tabControl1.SelectedIndex = 1;
            LoadCurrentQuestion();
        }
        else
        {
            // 结束考试 = 直接交卷(和提交考试一样)
            FinishExam();
        }
    }

    private void Submitbutton_Click(object sender, EventArgs e)
    {
        if (QuestionText.Text == "" || Answer.Text == "" || AnswerAnalyze.Text == "" || choice1.Text == "" || choice2.Text == "")
        {
            MessageBox.Show("请填写完整信息!");
            return;
        }

        id++;
        question = QuestionText.Text;
        answer = Answer.Text;
        answeranalyze = AnswerAnalyze.Text;
        ch1 = choice1.Text;
        ch2 = choice2.Text;

        Question q = null;

        if (index == 0)
        {
            if (choice3.Text == "" || choice4.Text == "")
            {
                MessageBox.Show("多选题需填写4个选项!");
                return;
            }
            ch3 = choice3.Text;
            ch4 = choice4.Text;
            q = new MultipleAnswersQuestion(id, question, answer, ch1, ch2, ch3, ch4);
        }

        if (index == 1)
        {
            if (choice3.Text == "" || choice4.Text == "")
            {
                MessageBox.Show("单选题需填写4个选项!");
                return;
            }
            ch3 = choice3.Text;
            ch4 = choice4.Text;
            q = new MultipleChoiceQuestion(id, question, answer, ch1, ch2, ch3, ch4);
        }

        if (index == 2)
        {
            q = new TrueFalseQuestion(id, question, answer);
        }

        questionList.Add(q);
        SaveQuestionsToTxt();
        MessageBox.Show("题目保存成功!");

        QuestionText.Clear();
        Answer.Clear();
        AnswerAnalyze.Clear();
        choice1.Clear();
        choice2.Clear();
        choice3.Clear();
        choice4.Clear();

        LoadCurrentQuestion();
    }

    private void SaveQuestionsToTxt()
    {
        string path = Path.Combine(Application.StartupPath, "questions.txt");
        List<string> lines = new List<string>();

        foreach (var item in questionList)
        {
            string type = "";
            if (item is MultipleChoiceQuestion) type = "单选";
            if (item is MultipleAnswersQuestion) type = "多选";
            if (item is TrueFalseQuestion) type = "判断";

            string c1 = "", c2 = "", c3 = "", c4 = "";
            if (item.Choices != null && item.Choices.Length >= 4)
            {
                c1 = item.Choices[0];
                c2 = item.Choices[1];
                c3 = item.Choices[2];
                c4 = item.Choices[3];
            }

            lines.Add($"{item.Id}|{type}|{item.QuestionText}|{c1}|{c2}|{c3}|{c4}|{item.AnswerText}");
        }

        File.WriteAllLines(path, lines, Encoding.UTF8);
    }

    private void LoadQuestionsFromTxt()
    {
        string path = Path.Combine(Application.StartupPath, "questions.txt");
        if (!File.Exists(path)) return;

        string[] lines = File.ReadAllLines(path, Encoding.UTF8);
        questionList.Clear();
        id = 0;

        foreach (string line in lines)
        {
            string[] p = line.Split('|');
            if (p.Length < 8) continue;

            int qid = int.Parse(p[0]);
            string type = p[1];
            string title = p[2];
            string c1 = p[3];
            string c2 = p[4];
            string c3 = p[5];
            string c4 = p[6];
            string ans = p[7];

            id = qid;
            Question q = null;

            if (type == "单选")
                q = new MultipleChoiceQuestion(qid, title, ans, c1, c2, c3, c4);
            else if (type == "多选")
                q = new MultipleAnswersQuestion(qid, title, ans, c1, c2, c3, c4);
            else if (type == "判断")
                q = new TrueFalseQuestion(qid, title, ans);

            if (q != null) questionList.Add(q);
        }
    }
}

}

点击展开重构后的完整源代码 using System;

namespace Pair_Program
{
public class Question
{
protected int id;
protected string question;
protected int type;
protected string answer;
protected string[] choice;

    public int Id => id;
    public string QuestionText => question;
    public string AnswerText => answer;
    public string[] Choices => choice;

    public Question() { }

    public Question(int id, string question, int type, string answer, string[] choice)
    {
        this.id = id;
        this.question = question;
        this.type = type;
        this.answer = answer;
        this.choice = choice;
    }
}

}

点击展开重构后的完整源代码

八、核心效果展示
image
image
image
image
image

九、实验总结
通过本次结对编程实验,我们不仅顺利完成了简易在线考试系统的全部功能,更深刻理解了结对编程的价值:
提高代码质量、减少BUG、提升效率、促进学习、强化协作。

整个开发过程中,我们从需求分析到界面设计,从代码编写到系统测试,全程保持双人协作、角色轮换、共同决策,最终完成了稳定、完整、符合实验要求的在线考试系统。

这次实践让我们真正体会到:
好的软件不是写出来的,是协作出来的。

posted @ 2026-04-22 12:29  qwwgrdgfvb  阅读(5)  评论(0)    收藏  举报