Chinh sua UI

This commit is contained in:
Nguyễn Thái Phong 2025-10-15 14:35:02 +07:00
parent 8f144c1a8b
commit 8cca84ceec
24 changed files with 698 additions and 458 deletions

Binary file not shown.

View File

@ -6,29 +6,45 @@
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form1.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form2.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form2.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form2.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form2.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form1.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\response\\quiz.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:response\\quiz.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\response\\questionoption.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:response\\questionoption.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\service\\executeexcelservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:service\\executeexcelservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\response\\question.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:response\\question.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\dbhelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:dbhelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\service\\datautil.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\service\\datautil.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:service\\datautil.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:service\\datautil.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|E:\\QuizMaster\\QuizMaster\\form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form", "AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\service\\excelvalidator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form1.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}|Form" "RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:service\\excelvalidator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form2.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\form2.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form2.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:form2.designer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|e:\\quizmaster\\quizmaster\\dbhelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{582DBFA6-720D-44B4-B48C-909F4DD98783}|QuizMaster.csproj|solutionrelative:dbhelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
} }
], ],
"DocumentGroupContainers": [ "DocumentGroupContainers": [
@ -48,7 +64,7 @@
"RelativeDocumentMoniker": "Form1.cs", "RelativeDocumentMoniker": "Form1.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form1.cs", "ToolTip": "E:\\QuizMaster\\QuizMaster\\Form1.cs",
"RelativeToolTip": "Form1.cs", "RelativeToolTip": "Form1.cs",
"ViewState": "AQIAAAYEAAAAAAAAAAAcwBEEAAApAAAA", "ViewState": "AQIAAE8AAAAAAAAAAAAuwGUAAABVAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-05-26T01:54:54.164Z", "WhenOpened": "2025-05-26T01:54:54.164Z",
"IsPinned": true, "IsPinned": true,
@ -56,28 +72,16 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 1, "DocumentIndex": 2,
"Title": "Form2.cs", "Title": "Form2.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form2.cs", "DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form2.cs",
"RelativeDocumentMoniker": "Form2.cs", "RelativeDocumentMoniker": "Form2.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form2.cs", "ToolTip": "E:\\QuizMaster\\QuizMaster\\Form2.cs",
"RelativeToolTip": "Form2.cs", "RelativeToolTip": "Form2.cs",
"ViewState": "AQIAAGMAAAAAAAAAAAAAAGwAAAAxAAAA", "ViewState": "AQIAAF0AAAAAAAAAAAAIwG8AAAAUAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-02T06:28:48.666Z", "WhenOpened": "2025-10-02T06:28:48.666Z",
"IsPinned": true, "IsPinned": true
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "Form1.cs [Design]",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form1.cs",
"RelativeDocumentMoniker": "Form1.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form1.cs [Design]",
"RelativeToolTip": "Form1.cs [Design]",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-08T02:47:37.753Z"
}, },
{ {
"$type": "Bookmark", "$type": "Bookmark",
@ -95,52 +99,111 @@
"$type": "Bookmark", "$type": "Bookmark",
"Name": "ST:17:0:{e8034f19-ab72-4f06-83fd-f6832b41aa63}" "Name": "ST:17:0:{e8034f19-ab72-4f06-83fd-f6832b41aa63}"
}, },
{
"$type": "Document",
"DocumentIndex": 6,
"Title": "Question.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Response\\Question.cs",
"RelativeDocumentMoniker": "Response\\Question.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Response\\Question.cs",
"RelativeToolTip": "Response\\Question.cs",
"ViewState": "AQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-14T06:24:38.262Z"
},
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 3, "DocumentIndex": 3,
"Title": "Quiz.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Response\\Quiz.cs",
"RelativeDocumentMoniker": "Response\\Quiz.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Response\\Quiz.cs",
"RelativeToolTip": "Response\\Quiz.cs",
"ViewState": "AQIAAAAAAAAAAAAAAAAAAA0AAAAWAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-13T08:59:40.254Z"
},
{
"$type": "Document",
"DocumentIndex": 5,
"Title": "ExecuteExcelService.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Service\\ExecuteExcelService.cs",
"RelativeDocumentMoniker": "Service\\ExecuteExcelService.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Service\\ExecuteExcelService.cs",
"RelativeToolTip": "Service\\ExecuteExcelService.cs",
"ViewState": "AQIAAA8AAAAAAAAAAAAAADAAAAAAAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-13T03:41:10.651Z"
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "QuestionOption.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Response\\QuestionOption.cs",
"RelativeDocumentMoniker": "Response\\QuestionOption.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Response\\QuestionOption.cs",
"RelativeToolTip": "Response\\QuestionOption.cs",
"ViewState": "AQIAAAAAAAAAAAAAAAAAAAYAAAAWAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-10T06:59:55.277Z"
},
{
"$type": "Document",
"DocumentIndex": 9,
"Title": "ExcelValidator.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Service\\ExcelValidator.cs",
"RelativeDocumentMoniker": "Service\\ExcelValidator.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Service\\ExcelValidator.cs",
"RelativeToolTip": "Service\\ExcelValidator.cs",
"ViewState": "AQIAAFcAAAAAAAAAAAAQwEwAAAAJAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-10T03:12:07.301Z"
},
{
"$type": "Document",
"DocumentIndex": 8,
"Title": "DataUtil.cs", "Title": "DataUtil.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Service\\DataUtil.cs", "DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Service\\DataUtil.cs",
"RelativeDocumentMoniker": "Service\\DataUtil.cs", "RelativeDocumentMoniker": "Service\\DataUtil.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Service\\DataUtil.cs", "ToolTip": "E:\\QuizMaster\\QuizMaster\\Service\\DataUtil.cs",
"RelativeToolTip": "Service\\DataUtil.cs", "RelativeToolTip": "Service\\DataUtil.cs",
"ViewState": "AQIAABsBAAAAAAAAAAAwwEABAAAAAAAA", "ViewState": "AQIAAAAAAAAAAAAAAAAAABEAAAAJAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-05-29T01:45:12.308Z" "WhenOpened": "2025-05-29T01:45:12.308Z"
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 2, "DocumentIndex": 1,
"Title": "Form1.Designer.cs", "Title": "Form1.Designer.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form1.Designer.cs", "DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form1.Designer.cs",
"RelativeDocumentMoniker": "Form1.Designer.cs", "RelativeDocumentMoniker": "Form1.Designer.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form1.Designer.cs", "ToolTip": "E:\\QuizMaster\\QuizMaster\\Form1.Designer.cs",
"RelativeToolTip": "Form1.Designer.cs", "RelativeToolTip": "Form1.Designer.cs",
"ViewState": "AQIAABoBAAAAAAAAAAAYwCcBAAAwAAAA", "ViewState": "AQIAAE8AAAAAAAAAAAAuwGUAAAAxAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-09-30T04:14:48.709Z", "WhenOpened": "2025-09-30T04:14:48.709Z"
"EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 5, "DocumentIndex": 10,
"Title": "Form2.Designer.cs", "Title": "Form2.Designer.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form2.Designer.cs", "DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\Form2.Designer.cs",
"RelativeDocumentMoniker": "Form2.Designer.cs", "RelativeDocumentMoniker": "Form2.Designer.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\Form2.Designer.cs", "ToolTip": "E:\\QuizMaster\\QuizMaster\\Form2.Designer.cs",
"RelativeToolTip": "Form2.Designer.cs", "RelativeToolTip": "Form2.Designer.cs",
"ViewState": "AQIAAAAAAAAAAAAAAAAAACcAAAAbAAAA", "ViewState": "AQIAAAAAAAAAAAAAAAAAADYAAAAgAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-02T06:23:49.946Z" "WhenOpened": "2025-10-02T06:23:49.946Z"
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 6, "DocumentIndex": 7,
"Title": "DbHelper.cs", "Title": "DbHelper.cs",
"DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\DbHelper.cs", "DocumentMoniker": "E:\\QuizMaster\\QuizMaster\\DbHelper.cs",
"RelativeDocumentMoniker": "DbHelper.cs", "RelativeDocumentMoniker": "DbHelper.cs",
"ToolTip": "E:\\QuizMaster\\QuizMaster\\DbHelper.cs", "ToolTip": "E:\\QuizMaster\\QuizMaster\\DbHelper.cs",
"RelativeToolTip": "DbHelper.cs", "RelativeToolTip": "DbHelper.cs",
"ViewState": "AQIAADAAAAAAAAAAAAAYwBIAAAAPAAAA", "ViewState": "AQIAADYAAAAAAAAAAAAYwBIAAAAPAAAA",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-05-26T01:35:21.294Z" "WhenOpened": "2025-05-26T01:35:21.294Z"
} }

View File

@ -98,8 +98,8 @@
panelcb.BorderStyle = BorderStyle.FixedSingle; panelcb.BorderStyle = BorderStyle.FixedSingle;
panelcb.Location = new Point(50, 50); panelcb.Location = new Point(50, 50);
panelcb.Name = "panelcb"; panelcb.Name = "panelcb";
panelcb.Size = new Size(600, 300); panelcb.Size = new Size(770, 200);
panelcb.MaximumSize = new Size(600, 300); panelcb.MaximumSize = new Size(770, 400);
panelcb.TabIndex = 1; panelcb.TabIndex = 1;
panelcb.AutoScroll = true; panelcb.AutoScroll = true;
panelcb.AutoSize = true; panelcb.AutoSize = true;
@ -113,8 +113,8 @@
panelop.Controls.Add(lblNumberOfCopies); panelop.Controls.Add(lblNumberOfCopies);
panelop.Controls.Add(txtNumberOfCopies); panelop.Controls.Add(txtNumberOfCopies);
panelop.Name = "panelop"; panelop.Name = "panelop";
panelop.Size = new Size(600, 100); panelop.Size = new Size(770, 100);
panelop.MaximumSize = new Size(600, 300); panelop.MaximumSize = new Size(770, 300);
panelop.TabIndex = 2; panelop.TabIndex = 2;
panelop.AutoScroll = true; panelop.AutoScroll = true;
panelop.AutoSize = true; panelop.AutoSize = true;

View File

@ -1,20 +1,15 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.ComponentModel;
using System.Data; using System.Data;
using System.Diagnostics;
using System.Drawing.Printing;
using System.Reflection.Metadata;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms;
using OfficeOpenXml; using OfficeOpenXml;
using QuizMaster.Response; using QuizMaster.Response;
using QuizMaster.Service; using QuizMaster.Service;
using Xceed.Document.NET; using Xceed.Document.NET;
using Xceed.Words.NET; using Xceed.Words.NET;
using static System.Net.Mime.MediaTypeNames;
using static System.Windows.Forms.AxHost;
namespace QuizMaster namespace QuizMaster
{ {
@ -32,19 +27,19 @@ namespace QuizMaster
private List<string> categorys = new List<string>(); private List<string> categorys = new List<string>();
private List<string> lstDep = new List<string>(); private List<string> lstDep = new List<string>();
private DataGridView dgvCategories;
private static string dfDep = "Chọn phòng ban"; private static string dfDep = "Chọn phòng ban";
private bool isUpdating = false;
List<Button> buttons = new List<Button>(); List<Button> buttons = new List<Button>();
private List<ComboBox> comboBoxes = new List<ComboBox>(); private List<ComboBox> comboBoxes = new List<ComboBox>();
private List<TextBox> textBoxes = new List<TextBox>(); private List<TextBox> textBoxes = new List<TextBox>();
private List<TextBox> textBoxesHard = new List<TextBox>(); // Số câu khó
public static int startY = 27; // Vị trí Y bắt đầu public static int startY = 27; // Vị trí Y bắt đầu
public static int spacing = 40; // Khoảng cách giữa các ComboBox
public static int pointX = 19; public static int pointX = 19;
private static readonly Random Rng = new Random(); private static readonly Random Rng = new Random();
private static List<T> Shuffle<T>(IEnumerable<T> source) private static List<T> Shuffle<T>(IEnumerable<T> source)
{ {
var list = source.ToList(); var list = source.ToList();
@ -87,7 +82,7 @@ namespace QuizMaster
AddButtonHoverEffects(); AddButtonHoverEffects();
ComboBox comboMain = new ComboBox(); ComboBox comboMain = new ComboBox();
comboMain.Location = new Point(pointX + 90, startY + 20); comboMain.Location = new Point(pointX + 150, startY + 20);
comboMain.Size = new Size(400, 35); comboMain.Size = new Size(400, 35);
comboMain.Name = $"comboMain"; comboMain.Name = $"comboMain";
comboMain.SelectedIndexChanged += comboBoxMain_SelectedIndexChanged; comboMain.SelectedIndexChanged += comboBoxMain_SelectedIndexChanged;
@ -98,8 +93,61 @@ namespace QuizMaster
panelcb.Controls.Add(comboMain); panelcb.Controls.Add(comboMain);
// Thêm validation cho TextBox số bản ghi dgvCategories = new DataGridView();
txtNumberOfCopies.KeyPress += OnlyAllowPositiveIntegers; dgvCategories.Location = new Point(pointX, comboMain.Bottom + 10);
dgvCategories.Size = new Size(720, 300);
dgvCategories.MaximumSize = new Size(720, 300);
dgvCategories.AllowUserToAddRows = false;
dgvCategories.RowHeadersVisible = false;
dgvCategories.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dgvCategories.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvCategories.SelectionMode = DataGridViewSelectionMode.CellSelect;
dgvCategories.Font = new System.Drawing.Font("Segoe UI", 10F, FontStyle.Regular);
dgvCategories.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
dgvCategories.ColumnHeadersDefaultCellStyle.BackColor = Color.Azure;
dgvCategories.EnableHeadersVisualStyles = false;
dgvCategories.CellValueChanged += DgvCategories_CellValueChanged;
dgvCategories.CurrentCellDirtyStateChanged += DgvCategories_CurrentCellDirtyStateChanged;
dgvCategories.EditingControlShowing += DgvCategories_EditingControlShowing;
dgvCategories.Columns.Add("colCategory", "Lĩnh vực");
var colTotal = new DataGridViewTextBoxColumn();
colTotal.Name = "colTotal";
colTotal.HeaderText = "Tổng câu hỏi";
colTotal.ValueType = typeof(int);
dgvCategories.Columns.Add(colTotal);
var colHard = new DataGridViewTextBoxColumn();
colHard.Name = "colHard";
colHard.HeaderText = "Khó";
colHard.ValueType = typeof(int);
dgvCategories.Columns.Add(colHard);
var colEasy = new DataGridViewTextBoxColumn();
colEasy.Name = "colEasy";
colEasy.HeaderText = "Dễ";
colEasy.ValueType = typeof(int);
dgvCategories.Columns.Add(colEasy);
var colRandom = new DataGridViewCheckBoxColumn();
colRandom.Name = "colRandom";
colRandom.HeaderText = "Random";
dgvCategories.Columns.Add(colRandom);
// Cho phép nhập số vào cột Khó, Dễ, Random
foreach (DataGridViewColumn col in dgvCategories.Columns)
{
if (col.Name == "colTotal" || col.Name == "colHard" || col.Name == "colEasy")
col.ValueType = typeof(int);
}
panelcb.Controls.Add(dgvCategories);
panelop.Location = new Point(
panelcb.Location.X,
panelcb.Location.Y + panelcb.Height + 20
);
try try
{ {
@ -109,7 +157,6 @@ namespace QuizMaster
depList.AddRange(lstDep); depList.AddRange(lstDep);
comboMain.DataSource = depList; comboMain.DataSource = depList;
categorys = await DataUtil.GetCategoriesAsync(); categorys = await DataUtil.GetCategoriesAsync();
} }
catch (Exception ex) catch (Exception ex)
@ -119,174 +166,25 @@ namespace QuizMaster
} }
} }
private void ComboBox_SelectedIndexChanged(object? sender, EventArgs e)
{
if (isUpdating || sender == null) return; // Đang cập nhật hoặc sender là null, bỏ qua
isUpdating = true; // Bắt đầu cập nhật
var cbbChange = sender as ComboBox;
if (cbbChange == null)
{
isUpdating = false;
return;
}
// Lấy các giá trị đã được chọn, bỏ qua "-- Chọn --"
var daChon = comboBoxes
.Where(cb => cb != null && cb.SelectedIndex > 0 && cb.SelectedItem != null)
.Select(cb => cb.SelectedItem.ToString())
.Where(s => s != null)
.ToList();
foreach (var cb in comboBoxes.Where(cb => cb != null))
{
if (cb == cbbChange) continue; // KHÔNG cập nhật lại ComboBox đang thay đổi
var oldSelected = cb.SelectedItem?.ToString();
var danhSachMoi = categorys
.Where(x => !daChon.Contains(x) || x == oldSelected)
.Prepend(defaultCombobox)
.ToList();
cb.DataSource = null; // Tránh sự kiện SelectedIndexChanged bị gọi lại giữa chừng
cb.DataSource = danhSachMoi;
int index = danhSachMoi.IndexOf(oldSelected ?? "");
cb.SelectedIndex = index >= 0 ? index : 0;
}
// Ẩn combobox thừa
int soLuongDaChon = daChon.Count;
for (int i = 0; i < comboBoxes.Count; i++)
{
if (comboBoxes[i] != null)
{
// Chỉ hiện tối đa số lượng mục gốc
comboBoxes[i].Visible = i < categorys.Count;
}
}
// ======= THÊM PHẦN HIỂN THỊ TEXTBOX TƯƠNG ỨNG =======
int indexCombo = comboBoxes.IndexOf(cbbChange);
string selectedText = cbbChange.SelectedItem?.ToString() ?? "";
string txtBoxName = selectedText;
// Nếu chọn khác "-- Chọn --" thì hiển thị TextBox
if (selectedText != defaultCombobox)
{
// Ẩn tất cả TextBox thuộc cùng ComboBox (cùng index)
foreach (var tb in textBoxes.Where(tb => tb.Tag is int && (int)tb.Tag == indexCombo))
{
tb.Visible = false;
}
var existingTextBox = panelcb.Controls.Find(txtBoxName, true).FirstOrDefault() as TextBox;
if (existingTextBox == null)
{
TextBox txt = new TextBox();
txt.Name = txtBoxName;
txt.Size = new Size(300, 35);
txt.Location = new Point(cbbChange.Right + 20, cbbChange.Top);
txt.PlaceholderText = $"Nhập số câu hỏi về {selectedText}";
txt.TextAlign = HorizontalAlignment.Right;
txt.Font = new System.Drawing.Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point);
txt.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
txt.BackColor = Color.FromArgb(248, 249, 250);
txt.KeyPress += OnlyAllowPositiveIntegers;
txt.Tag = indexCombo; // Gắn textbox với ComboBox này
txt.Visible = true;
txt.BringToFront();
textBoxes.Add(txt);
panelcb.Controls.Add(txt);
}
else
{
existingTextBox.Tag = indexCombo; // Đảm bảo gắn đúng ComboBox
existingTextBox.PlaceholderText = $"Nhập số câu hỏi về {selectedText}";
existingTextBox.Location = new Point(cbbChange.Right + 20, cbbChange.Top);
existingTextBox.Visible = true;
existingTextBox.BringToFront();
}
}
else
{
// Nếu chọn lại "-- Chọn --", thì ẩn mọi TextBox thuộc ComboBox này
foreach (var tb in textBoxes.Where(tb => tb.Tag is int && (int)tb.Tag == indexCombo))
{
tb.Visible = false;
}
}
// ==============================================
isUpdating = false; // Kết thúc cập nhật
}
private async void comboBoxMain_SelectedIndexChanged(object? sender, EventArgs e) private async void comboBoxMain_SelectedIndexChanged(object? sender, EventArgs e)
{ {
ComboBox? comboBoxMain = sender as ComboBox; ComboBox? comboBoxMain = sender as ComboBox;
string? selected = comboBoxMain?.SelectedItem?.ToString(); string? selected = comboBoxMain?.SelectedItem?.ToString();
foreach (var cb in comboBoxes) dgvCategories.Rows.Clear(); // Xóa bảng cũ
{
panelcb.Controls.Remove(cb);
cb.Dispose();
}
foreach (var btn in buttons) if (string.IsNullOrEmpty(selected) || selected == dfDep)
{ {
panelop.Controls.Remove(btn); dgvCategories.Visible = false;
btn.Dispose();
}
foreach (var txt in textBoxes)
{
panelcb.Controls.Remove(txt);
txt.Dispose();
}
buttons.Clear();
textBoxes.Clear();
comboBoxes.Clear();
if (selected == null || selected == dfDep)
{
// Xóa các ComboBox đã thêm trước đó khỏi giao diện
foreach (var cb in comboBoxes)
{
panelcb.Controls.Remove(cb);
cb.Dispose();
}
// Xóa các ComboBox đã thêm trước đó khỏi giao diện
foreach (var btn in buttons)
{
panelop.Controls.Remove(btn);
btn.Dispose();
}
// Xóa các ComboBox đã thêm trước đó khỏi giao diện
foreach (var txt in textBoxes)
{
panelcb.Controls.Remove(txt);
txt.Dispose();
}
// Ẩn các controls số bản ghi và checkbox
HidePrintControls(); HidePrintControls();
buttons.Clear();
textBoxes.Clear();
comboBoxes.Clear(); // Xóa khỏi danh sách quản lý
return; return;
} }
try try
{ {
// Lấy categories theo department được chọn
this.Cursor = Cursors.WaitCursor; this.Cursor = Cursors.WaitCursor;
// Lấy danh sách lĩnh vực theo phòng ban
var departmentCategories = await DataUtil.GetCategoriesByDepartmentAsync(selected); var departmentCategories = await DataUtil.GetCategoriesByDepartmentAsync(selected);
if (departmentCategories.Count == 0) if (departmentCategories.Count == 0)
@ -295,55 +193,25 @@ namespace QuizMaster
return; return;
} }
List<string> initCombobox = new List<string> { defaultCombobox }; dgvCategories.Visible = true;
initCombobox.AddRange(departmentCategories);
// Tạo các ComboBox mới dựa trên số lượng categories của department // Thêm các hàng vào DataGridView
for (int i = 0; i < departmentCategories.Count; i++) foreach (var category in departmentCategories)
{ {
ComboBox cb = new ComboBox(); int rowIndex = dgvCategories.Rows.Add();
cb.Location = new Point(pointX + 20, startY + spacing + (i * spacing) + 30); var row = dgvCategories.Rows[rowIndex];
cb.Size = new Size(200, 35); row.Cells["ColCategory"].Value = category;
cb.Name = $"comboBox{i + 1}"; row.Cells["colCategory"].ReadOnly = true;
cb.TabIndex = i; row.Cells["ColTotal"].Value = 0;
cb.FormattingEnabled = true; row.Cells["ColHard"].Value = 0;
cb.DropDownStyle = ComboBoxStyle.DropDownList; row.Cells["ColEasy"].Value = 0;
cb.Font = new System.Drawing.Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); row.Cells["ColRandom"].Value = false;
cb.BackColor = Color.WhiteSmoke;
cb.FlatStyle = FlatStyle.Flat;
cb.SelectedIndexChanged += ComboBox_SelectedIndexChanged;
// Gán danh sách categories của department
cb.DataSource = new List<string>(initCombobox);
comboBoxes.Add(cb);
panelcb.Controls.Add(cb);
int lastCbb = comboBoxes.Last().Location.Y;
int heightCbb = comboBoxes.Last().Height;
int panelopX = panelcb.Size.Width;
panelcb.Size = new Size(panelopX, lastCbb + heightCbb + 20);
int x = panelcb.Location.X;
int y = panelcb.Location.Y;
int pcbHeight = panelcb.Size.Height;
panelop.Location = new Point(x,y + pcbHeight + 20);
} }
// Cập nhật categorys để dùng cho logic khác
categorys = departmentCategories;
if (comboBoxes.Count > 0)
{
int rightCbb = comboBoxes.Last().Right;
// Hiển thị các controls số bản ghi và checkbox
ShowPrintControls(10); ShowPrintControls(10);
// Tạo nút Generate ở vị trí dưới các controls mới
Button btGenerate = new Button(); Button btGenerate = new Button();
btGenerate.Location = new Point(rightCbb + 20, 70); // Tăng khoảng cách để chừa chỗ cho controls mới btGenerate.Location = new Point(320, 70);
btGenerate.Size = new Size(200, 40); btGenerate.Size = new Size(200, 40);
btGenerate.Text = "📝 Tạo Files"; btGenerate.Text = "📝 Tạo Files";
btGenerate.UseVisualStyleBackColor = false; btGenerate.UseVisualStyleBackColor = false;
@ -353,7 +221,7 @@ namespace QuizMaster
btGenerate.FlatAppearance.BorderSize = 0; btGenerate.FlatAppearance.BorderSize = 0;
btGenerate.Font = new System.Drawing.Font("Segoe UI", 11F, FontStyle.Bold, GraphicsUnit.Point); btGenerate.Font = new System.Drawing.Font("Segoe UI", 11F, FontStyle.Bold, GraphicsUnit.Point);
btGenerate.Cursor = Cursors.Hand; btGenerate.Cursor = Cursors.Hand;
btGenerate.Click += btnGenClick; btGenerate.Click += btnGenerate_Click;
btGenerate.MouseEnter += (s, e) => btGenerate.MouseEnter += (s, e) =>
{ {
@ -370,7 +238,6 @@ namespace QuizMaster
buttons.Add(btGenerate); buttons.Add(btGenerate);
panelop.Controls.Add(btGenerate); panelop.Controls.Add(btGenerate);
} }
}
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show($"Lỗi khi tải danh sách lĩnh vực: {ex.Message}", "Lỗi", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show($"Lỗi khi tải danh sách lĩnh vực: {ex.Message}", "Lỗi", MessageBoxButtons.OK, MessageBoxIcon.Error);
@ -390,79 +257,140 @@ namespace QuizMaster
} }
} }
private async void btnGenClick(object? sender, EventArgs e) private async void btnGenerate_Click(object sender, EventArgs e)
{ {
try try
{ {
if (string.IsNullOrWhiteSpace(txtNumberOfCopies.Text) || !int.TryParse(txtNumberOfCopies.Text, out int numberOfCopies) || numberOfCopies <= 0) // Kiểm tra DataGridView có dữ liệu không
if (dgvCategories.Rows.Count == 0)
{ {
MessageBox.Show("Vui lòng nhập số bản ghi hợp lệ (số nguyên dương)!", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning); MessageBox.Show("Vui lòng thêm ít nhất một lĩnh vực!", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtNumberOfCopies.Focus();
return; return;
} }
Dictionary<string, string> lstCategory = new Dictionary<string, string>(); // Dictionary lưu số lượng câu hỏi cho từng lĩnh vực
foreach (var txt in textBoxes.Where(t => t.Visible)) Dictionary<string, (bool isRandom, int total, int hard, int easy)> lstCategory = new();
foreach (DataGridViewRow row in dgvCategories.Rows)
{ {
if (string.IsNullOrWhiteSpace(txt.Text)) if (row.IsNewRow) continue;
string categoryName = row.Cells["ColCategory"].Value?.ToString();
int hard = int.TryParse(row.Cells["ColHard"].Value?.ToString(), out int h) ? h : 0;
int easy = int.TryParse(row.Cells["ColEasy"].Value?.ToString(), out int ea) ? ea : 0;
int total = int.TryParse(row.Cells["ColTotal"].Value?.ToString(), out int t) ? t : 0;
bool isRandom = Convert.ToBoolean(row.Cells["colRandom"].Value ?? false);
if (total == 0)
continue;
if (!isRandom && hard == 0 && easy == 0)
{ {
txt.Focus(); MessageBox.Show($"Vui lòng nhập số câu KHÓ hoặc DỄ cho lĩnh vực '{categoryName}' hoặc tick Random!", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
await FlashControl(txt);
return; return;
} }
lstCategory.Add(txt.Name, txt.Text);
lstCategory[categoryName] = (isRandom, total, hard, easy);
bool allZero = dgvCategories.Rows.Cast<DataGridViewRow>()
.Where(r => !r.IsNewRow)
.All(r =>
{
int hard = int.TryParse(r.Cells["ColHard"].Value?.ToString(), out int h) ? h : 0;
int easy = int.TryParse(r.Cells["ColEasy"].Value?.ToString(), out int e) ? e : 0;
int total = int.TryParse(r.Cells["ColTotal"].Value?.ToString(), out int t) ? t : 0;
return hard == 0 && easy == 0 && total == 0;
});
if (allZero)
{
MessageBox.Show("Vui lòng nhập ít nhất một số câu hỏi cho các lĩnh vực!", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
lstCategory[categoryName] = (isRandom, total, hard, easy);
} }
if (lstCategory.Count == 0) if (lstCategory.Count == 0)
{ {
MessageBox.Show("Vui lòng chọn ít nhất một lĩnh vực và nhập số câu hỏi!", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning); MessageBox.Show("Vui lòng nhập dữ liệu cho ít nhất một lĩnh vực!", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return; return;
} }
// Hiển thị progress
this.Cursor = Cursors.WaitCursor; this.Cursor = Cursors.WaitCursor;
// validate số câu hỏi cần gen phải nhỏ hơn số câu có trong DB // Kiểm tra số lượng câu hỏi có sẵn trong DB
var totalCategory = await DataUtil.GetTotalCategoriesAsync(lstCategory.Keys.ToList()); var totalCategory = await DataUtil.GetTotalCategoriesAsync(lstCategory.Keys.ToList());
var totalHard = await DataUtil.GetTotalOptionQuestionsAsync(lstCategory.Keys.ToList(), "Khó");
var totalEasy = await DataUtil.GetTotalOptionQuestionsAsync(lstCategory.Keys.ToList(), "Dễ");
foreach (var category in lstCategory) foreach (var category in lstCategory)
{ {
string categoryName = category.Key; string categoryName = category.Key;
int requestedCount = int.Parse(category.Value); int totalCount = category.Value.total;
int hardCount = category.Value.hard;
int easyCount = category.Value.easy;
// Kiểm tra số câu hỏi có sẵn trong DB
var categoryInfo = totalCategory.FirstOrDefault(c => c.Category == categoryName); var categoryInfo = totalCategory.FirstOrDefault(c => c.Category == categoryName);
int availableCount = categoryInfo?.Total ?? 0; int availableCount = categoryInfo?.Total ?? 0;
int totalAvailable = totalCategory.FirstOrDefault(c => c.Category == categoryName)?.Total ?? 0;
int hardAvailable = totalHard.FirstOrDefault(c => c.Category == categoryName)?.Total ?? 0;
int easyAvailable = totalEasy.FirstOrDefault(c => c.Category == categoryName)?.Total ?? 0;
if (requestedCount > availableCount)
if (hardCount > hardAvailable)
{ {
MessageBox.Show( MessageBox.Show(
$"=== LỖI ===\n" + $"=== LỖI ===\n" +
$"Lĩnh vực: {categoryName}\n" + $"Lĩnh vực: {categoryName}\n" +
$"Số câu hỏi yêu cầu: {requestedCount}\n" + $"Số câu KHÓ yêu cầu: {hardCount}\n" +
$"Số câu hỏi có sẵn: {availableCount}\n\n" + $"Số câu KHÓ có sẵn: {hardAvailable}\n" +
"Vui lòng giảm số câu hỏi yêu cầu hoặc thêm câu hỏi vào database.", $"Vui lòng giảm số câu KHÓ yêu cầu hoặc thêm câu hỏi vào ngân hàng dữ liệu!",
"Lỗi Generate", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
MessageBoxButtons.OK, return;
MessageBoxIcon.Warning }
);
if (easyCount > easyAvailable)
{
MessageBox.Show(
$"=== LỖI ===\n" +
$"Lĩnh vực: {categoryName}\n" +
$"Số câu DỄ yêu cầu: {easyCount}\n" +
$"Số câu DỄ có sẵn: {easyAvailable}\n" +
$"Vui lòng giảm số câu DỄ yêu cầu hoặc thêm câu hỏi vào ngân hàng dữ liệu!",
"Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (totalCount > totalAvailable)
{
MessageBox.Show(
$"=== LỖI ===\n" +
$"Lĩnh vực: {categoryName}\n" +
$"Tổng số câu yêu cầu: {totalCount}\n" +
$"Số câu có sẵn: {totalAvailable}\n" +
$"Vui lòng giảm số câu yêu cầu hoặc thêm câu hỏi vào ngân hàng dữ liệu!",
"Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return; return;
} }
} }
// Nếu số bản ghi = 1, chuyển sang Form2 như cũ if (!int.TryParse(txtNumberOfCopies.Text, out int numberOfCopies) || numberOfCopies <= 0)
{
MessageBox.Show("Vui lòng nhập số bản ghi hợp lệ!", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtNumberOfCopies.Focus();
return;
}
// Nếu số bản ghi = 1, mở Form2 hoặc tạo file đơn
if (numberOfCopies == 1) if (numberOfCopies == 1)
{ {
// Nếu tích checkbox "in có đáp án", tạo cả 2 file
if (chkIncludeAnswers.Checked) if (chkIncludeAnswers.Checked)
{
await GenerateSingleQuizWithAnswers(lstCategory); await GenerateSingleQuizWithAnswers(lstCategory);
}
else else
{
await GenerateSingleQuiz(lstCategory); await GenerateSingleQuiz(lstCategory);
} }
}
else else
{ {
// Nếu số bản ghi > 1, xuất trực tiếp ra Word // Nếu số bản ghi > 1, xuất trực tiếp ra Word
@ -471,7 +399,7 @@ namespace QuizMaster
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show($" Lỗi khi generate đề thi:\n{ex.Message}", "Lỗi", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show($"Lỗi khi generate đề thi:\n{ex.Message}", "Lỗi", MessageBoxButtons.OK, MessageBoxIcon.Error);
} }
finally finally
{ {
@ -479,26 +407,6 @@ namespace QuizMaster
} }
} }
private HashSet<Control> flashingControls = new HashSet<Control>();
private async Task FlashControl(Control control)
{
if (flashingControls.Contains(control))
return; // Đã nhấp nháy => bỏ qua
flashingControls.Add(control);
var originalColor = control.BackColor;
for (int i = 0; i < 3; i++)
{
control.BackColor = Color.LightPink;
await Task.Delay(200);
control.BackColor = originalColor;
await Task.Delay(200);
}
flashingControls.Remove(control);
}
private async void btnOpen_Click(object sender, EventArgs e) private async void btnOpen_Click(object sender, EventArgs e)
{ {
using (OpenFileDialog openFileDialog = new OpenFileDialog()) using (OpenFileDialog openFileDialog = new OpenFileDialog())
@ -571,7 +479,6 @@ namespace QuizMaster
.Where((q, index) => !errorRows.Contains(index + 2)) // +2 nếu dòng header là dòng 1 .Where((q, index) => !errorRows.Contains(index + 2)) // +2 nếu dòng header là dòng 1
.ToList(); .ToList();
} }
// Loại bỏ các bản ghi trùng lặp đã tồn tại trong DB (content + category) // Loại bỏ các bản ghi trùng lặp đã tồn tại trong DB (content + category)
try try
{ {
@ -638,8 +545,8 @@ namespace QuizMaster
// Hỏi user muốn lưu vào đâu // Hỏi user muốn lưu vào đâu
DialogResult choice = MessageBox.Show( DialogResult choice = MessageBox.Show(
$"Sẽ lưu {result.Count} câu hỏi vào Database (đã loại trừ bản ghi lỗi và trùng lặp).\n\n" + $"Sẽ lưu {result.Count} câu hỏi vào Ngân hàng câu hỏi (đã loại trừ bản ghi lỗi và trùng lặp).\n\n" +
"OK = Lưu vào Database\n" + "OK = Lưu vào Ngân hàng câu hỏi\n" +
"Cancle = Hủy bỏ", "Cancle = Hủy bỏ",
"Lưu dữ liệu", "Lưu dữ liệu",
MessageBoxButtons.OKCancel, MessageBoxButtons.OKCancel,
@ -680,7 +587,7 @@ namespace QuizMaster
} }
} }
private async Task GenerateSingleQuiz(Dictionary<string, string> lstCategory) private async Task GenerateSingleQuiz(Dictionary<string, (bool isRandom, int total, int hard, int easy)> lstCategory)
{ {
string randomCode = Rng.Next(100, 999).ToString(); string randomCode = Rng.Next(100, 999).ToString();
@ -693,29 +600,59 @@ namespace QuizMaster
foreach (var category in lstCategory) foreach (var category in lstCategory)
{ {
string categoryName = category.Key; string categoryName = category.Key;
int requestedCount = int.Parse(category.Value); int randomCount = category.Value.total;
int hardCount = category.Value.hard;
int easyCount = category.Value.easy;
var questions = await DataUtil.getAllQA(categoryName); var questions = await DataUtil.getAllQA(categoryName);
var grouped = questions
.GroupBy(q => new { q.QuestionId, q.Question }) // Gom nhóm theo QuestionId + DoKho
var groupedQuestions = questions
.GroupBy(q => new { q.QuestionId, q.Question, q.DoKho })
.Select(group => new .Select(group => new
{ {
QuestionId = group.Key.QuestionId, QuestionId = group.Key.QuestionId,
Question = group.Key.Question, Question = group.Key.Question,
DoKho = group.Key.DoKho,
Options = Shuffle(group.Select(o => new Options = Shuffle(group.Select(o => new
{ {
Text = o.Option, Text = o.Option,
IsCorrect = o.IsCorrect == 1 IsCorrect = o.IsCorrect == 1
})) }))
}) })
.OrderBy(g => Guid.NewGuid())
.Take(requestedCount)
.ToList(); .ToList();
var hardQuestions = groupedQuestions
.Where(q => q.DoKho == "Khó")
.OrderBy(_ => Guid.NewGuid())
.Take(hardCount)
.ToList();
var takenIds = new HashSet<int>(hardQuestions.Select(q => q.QuestionId));
var easyQuestions = groupedQuestions
.Where(q => q.DoKho == "Dễ" && !takenIds.Contains(q.QuestionId))
.OrderBy(_ => Guid.NewGuid())
.Take(easyCount)
.ToList();
takenIds.UnionWith(easyQuestions.Select(q => q.QuestionId));
var randomQuestions = groupedQuestions
.Where(q => !takenIds.Contains(q.QuestionId))
.OrderBy(_ => Guid.NewGuid())
.Take(randomCount)
.ToList();
var allQuestions = new List<dynamic>();
allQuestions.AddRange(easyQuestions);
allQuestions.AddRange(hardQuestions);
allQuestions.AddRange(randomQuestions);
// Hiển thị tất cả câu hỏi được generate // Hiển thị tất cả câu hỏi được generate
for (int i = 0; i < grouped.Count; i++) for (int i = 0; i < allQuestions.Count; i++)
{ {
var question = grouped[i]; var question = allQuestions[i];
result.AppendLine($"\nCâu {number}: {question.Question}"); result.AppendLine($"\nCâu {number}: {question.Question}");
// Hiển thị tất cả các options // Hiển thị tất cả các options
@ -728,16 +665,17 @@ namespace QuizMaster
optionLabel++; optionLabel++;
} }
number++; number++;
}
result.AppendLine(); result.AppendLine();
} }
}
Form2 frm = new Form2(result.ToString()); Form2 frm = new Form2(result.ToString());
frm.MaDe = randomCode; frm.MaDe = randomCode;
frm.Text = "ĐỀ TRẮC NHIỆM"; frm.Text = "ĐỀ TRẮC NHIỆM";
frm.ShowDialog(); frm.ShowDialog();
} }
private async Task GenerateSingleQuizWithAnswers(Dictionary<string, string> lstCategory) private async Task GenerateSingleQuizWithAnswers(Dictionary<string, (bool isRandom, int total, int hard, int easy)> lstCategory)
{ {
try try
{ {
@ -776,34 +714,59 @@ namespace QuizMaster
} }
} }
private async Task<List<dynamic>> GenerateQuestionsData(Dictionary<string, string> lstCategory) private async Task<List<dynamic>> GenerateQuestionsData(Dictionary<string, (bool isRandom, int total, int hard, int easy)> lstCategory)
{ {
var allQuestions = new List<dynamic>(); var allQuestions = new List<dynamic>();
foreach (var category in lstCategory) foreach (var category in lstCategory)
{ {
string categoryName = category.Key; string categoryName = category.Key;
int requestedCount = int.Parse(category.Value); int randomCount = category.Value.total;
int hardCount = category.Value.hard;
int easyCount = category.Value.easy;
// Lấy câu hỏi cho category này // Lấy tất cả câu hỏi trong category
var questions = await DataUtil.getAllQA(categoryName); var questions = await DataUtil.getAllQA(categoryName);
var grouped = questions
// Nhóm câu hỏi và shuffle options
var groupedQuestions = questions
.GroupBy(q => new { q.QuestionId, q.Question }) .GroupBy(q => new { q.QuestionId, q.Question })
.Select(group => new .Select(group => new
{ {
QuestionId = group.Key.QuestionId, QuestionId = group.Key.QuestionId,
Question = group.Key.Question, Question = group.Key.Question,
DoKho = group.First().DoKho,
Options = Shuffle(group.Select(o => new Options = Shuffle(group.Select(o => new
{ {
Text = o.Option, Text = o.Option,
IsCorrect = o.IsCorrect == 1 IsCorrect = o.IsCorrect == 1
})) }))
}) })
.OrderBy(g => Guid.NewGuid())
.Take(requestedCount)
.ToList(); .ToList();
allQuestions.AddRange(grouped.Cast<dynamic>()); var hardQuestions = groupedQuestions
.Where(q => q.DoKho == "Khó")
.OrderBy(g => Guid.NewGuid())
.Take(hardCount)
.ToList();
var takenIds = new HashSet<int>(hardQuestions.Select(q => q.QuestionId));
var easyQuestions = groupedQuestions
.Where(q => !takenIds.Contains(q.QuestionId) && q.DoKho == "Dễ")
.OrderBy(g => Guid.NewGuid())
.Take(easyCount)
.ToList();
takenIds.UnionWith(easyQuestions.Select(q => q.QuestionId));
var randomQuestions = groupedQuestions
.Where(q => !takenIds.Contains(q.QuestionId))
.OrderBy(g => Guid.NewGuid())
.Take(randomCount)
.ToList();
allQuestions.AddRange(easyQuestions.Cast<dynamic>());
allQuestions.AddRange(hardQuestions.Cast<dynamic>());
allQuestions.AddRange(randomQuestions.Cast<dynamic>());
} }
return allQuestions; return allQuestions;
} }
@ -832,7 +795,7 @@ namespace QuizMaster
if (includeAnswers && option.IsCorrect) if (includeAnswers && option.IsCorrect)
{ {
result.AppendLine($" {optionLabel}.* {option.Text}"); result.AppendLine($" {optionLabel}*. {option.Text}");
} }
else else
{ {
@ -846,7 +809,7 @@ namespace QuizMaster
return result.ToString(); return result.ToString();
} }
private async Task GenerateMultipleQuizzes(Dictionary<string, string> lstCategory, int numberOfCopies, bool includeAnswers) private async Task GenerateMultipleQuizzes(Dictionary<string, (bool isRandom, int total, int hard, int easy)> lstCategory, int numberOfCopies, bool includeAnswers)
{ {
using (FolderBrowserDialog folderDialog = new FolderBrowserDialog()) using (FolderBrowserDialog folderDialog = new FolderBrowserDialog())
{ {
@ -897,7 +860,7 @@ namespace QuizMaster
} }
} }
private async Task<Dictionary<string, List<dynamic>>> CreateSingleQuizFileWithCache(string fileName, Dictionary<string, string> lstCategory, int copyIndex, bool includeAnswers, string randomCode) private async Task<Dictionary<string, List<dynamic>>> CreateSingleQuizFileWithCache(string fileName, Dictionary<string, (bool isRandom, int total, int hard, int easy)> lstCategory, int copyIndex, bool includeAnswers, string randomCode)
{ {
var questionsCache = new Dictionary<string, List<dynamic>>(); var questionsCache = new Dictionary<string, List<dynamic>>();
@ -926,30 +889,59 @@ namespace QuizMaster
foreach (var category in lstCategory) foreach (var category in lstCategory)
{ {
string categoryName = category.Key; string categoryName = category.Key;
int requestedCount = int.Parse(category.Value); int randomCount = category.Value.total;
int hardCount = category.Value.hard;
int easyCount = category.Value.easy;
// Lấy câu hỏi cho category này // Lấy toàn bộ câu hỏi trong category
var questions = await DataUtil.getAllQA(categoryName); var questions = await DataUtil.getAllQA(categoryName);
var grouped = questions
.GroupBy(q => new { q.QuestionId, q.Question }) var groupedQuestions = questions
.GroupBy(q => new { q.QuestionId, q.Question, q.DoKho })
.Select(group => new .Select(group => new
{ {
QuestionId = group.Key.QuestionId, QuestionId = group.Key.QuestionId,
Question = group.Key.Question, Question = group.Key.Question,
DoKho = group.Key.DoKho,
Options = Shuffle(group.Select(o => new Options = Shuffle(group.Select(o => new
{ {
Text = o.Option, Text = o.Option,
IsCorrect = o.IsCorrect == 1 IsCorrect = o.IsCorrect == 1
})) }))
}) })
.OrderBy(g => Guid.NewGuid())
.Take(requestedCount)
.ToList(); .ToList();
questionsCache[categoryName] = grouped.Cast<dynamic>().ToList(); var hardQuestions = groupedQuestions
.Where(q => q.DoKho == "Khó")
.OrderBy(_ => Guid.NewGuid())
.Take(hardCount)
.ToList();
var takenIds = new HashSet<int>(hardQuestions.Select(q => q.QuestionId));
var easyQuestions = groupedQuestions
.Where(q => q.DoKho == "Dễ" && !takenIds.Contains(q.QuestionId))
.OrderBy(_ => Guid.NewGuid())
.Take(easyCount)
.ToList();
takenIds.UnionWith(easyQuestions.Select(q => q.QuestionId));
var randomQuestions = groupedQuestions
.Where(q => !takenIds.Contains(q.QuestionId))
.OrderBy(_ => Guid.NewGuid())
.Take(randomCount)
.ToList();
var allQuestions = new List<dynamic>();
allQuestions.AddRange(easyQuestions);
allQuestions.AddRange(hardQuestions);
allQuestions.AddRange(randomQuestions);
questionsCache[categoryName] = allQuestions;
// Tạo câu hỏi // Tạo câu hỏi
foreach (var question in grouped) foreach (var question in allQuestions)
{ {
var questionParagraph = doc.InsertParagraph($"Câu {questionNumber}: {question.Question}"); var questionParagraph = doc.InsertParagraph($"Câu {questionNumber}: {question.Question}");
questionParagraph.FontSize(12).Bold(); questionParagraph.FontSize(12).Bold();
@ -986,7 +978,7 @@ namespace QuizMaster
return questionsCache; return questionsCache;
} }
private async Task CreateSingleQuizFileFromCache(string fileName, Dictionary<string, string> lstCategory, int copyIndex, bool includeAnswers, Dictionary<string, List<dynamic>> questionsCache, string randomCode) private async Task CreateSingleQuizFileFromCache(string fileName, Dictionary<string, (bool isRandom, int total, int hard, int easy)> lstCategory, int copyIndex, bool includeAnswers, Dictionary<string, List<dynamic>> questionsCache, string randomCode)
{ {
using (var doc = DocX.Create(fileName)) using (var doc = DocX.Create(fileName))
{ {
@ -1024,8 +1016,7 @@ namespace QuizMaster
if (string.IsNullOrWhiteSpace(option.Text)) if (string.IsNullOrWhiteSpace(option.Text))
continue; continue;
// Nếu đáp án đúng, thêm dấu * sau nhãn string displayLabel = option.IsCorrect ? $"{optionLabel}*." : $"{optionLabel}.";
string displayLabel = option.IsCorrect ? $"{optionLabel}.*" : $"{optionLabel}.";
var optionParagraph = doc.InsertParagraph(); var optionParagraph = doc.InsertParagraph();
string label = displayLabel; string label = displayLabel;
@ -1048,6 +1039,97 @@ namespace QuizMaster
} }
} }
private void DgvCategories_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (dgvCategories.CurrentCell.OwningColumn.Name == "colTotal" ||dgvCategories.CurrentCell.OwningColumn.Name == "colHard" || dgvCategories.CurrentCell.OwningColumn.Name == "colEasy")
{
if (e.Control is TextBox textBox)
{
textBox.KeyPress -= OnlyAllowPositiveIntegers;
textBox.KeyPress += OnlyAllowPositiveIntegers;
}
}
else
{
if (e.Control is TextBox textBox)
{
textBox.KeyPress -= OnlyAllowPositiveIntegers;
}
}
}
private void DgvCategories_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvCategories.IsCurrentCellDirty)
{
dgvCategories.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
private void DgvCategories_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0) return;
var row = dgvCategories.Rows[e.RowIndex];
var columnName = dgvCategories.Columns[e.ColumnIndex].Name;
bool isRandom = Convert.ToBoolean(row.Cells["colRandom"].Value ?? false);
int.TryParse(row.Cells["colTotal"].Value?.ToString(), out int total);
int.TryParse(row.Cells["colHard"].Value?.ToString(), out int hard);
int.TryParse(row.Cells["colEasy"].Value?.ToString(), out int easy);
if (isRandom)
{
row.Cells["colHard"].Value = 0;
row.Cells["colEasy"].Value = 0;
row.Cells["colHard"].ReadOnly = true;
row.Cells["colEasy"].ReadOnly = true;
row.Cells["colHard"].Style.BackColor = Color.LightGray;
row.Cells["colEasy"].Style.BackColor = Color.LightGray;
return;
}
if (total == 0)
{
row.Cells["colHard"].Value = 0;
row.Cells["colEasy"].Value = 0;
row.Cells["colHard"].ReadOnly = true;
row.Cells["colEasy"].ReadOnly = true;
row.Cells["colHard"].Style.BackColor = Color.LightGray;
row.Cells["colEasy"].Style.BackColor = Color.LightGray;
return;
}
else
{
row.Cells["colHard"].ReadOnly = false;
row.Cells["colEasy"].ReadOnly = false;
row.Cells["colHard"].Style.BackColor = Color.White;
row.Cells["colEasy"].Style.BackColor = Color.White;
}
if (columnName == "colHard" && total > 0)
{
row.Cells["colEasy"].Value = Math.Max(total - hard, 0);
}
else if (columnName == "colEasy" && total > 0)
{
row.Cells["colHard"].Value = Math.Max(total - easy, 0);
}
else if (columnName == "colTotal")
{
if (hard > 0)
row.Cells["colEasy"].Value = Math.Max(total - hard, 0);
else if (easy > 0)
row.Cells["colHard"].Value = Math.Max(total - easy, 0);
else
{
row.Cells["colHard"].Value = 0;
row.Cells["colEasy"].Value = 0;
}
}
}
private void ShowPrintControls(int yPosition) private void ShowPrintControls(int yPosition)
{ {
lblNumberOfCopies.Location = new Point(pointX, yPosition); lblNumberOfCopies.Location = new Point(pointX, yPosition);
@ -1072,7 +1154,6 @@ namespace QuizMaster
private void AddButtonHoverEffects() private void AddButtonHoverEffects()
{ {
// Thêm hiệu ứng hover cho button1 (Download)
button1.MouseEnter += (s, e) => button1.MouseEnter += (s, e) =>
{ {
button1.BackColor = Color.FromArgb(95, 103, 110); button1.BackColor = Color.FromArgb(95, 103, 110);
@ -1084,7 +1165,6 @@ namespace QuizMaster
button1.Size = new Size(186, 40); button1.Size = new Size(186, 40);
}; };
// Thêm hiệu ứng hover cho BtnOpen
BtnOpen.MouseEnter += (s, e) => BtnOpen.MouseEnter += (s, e) =>
{ {
BtnOpen.BackColor = Color.FromArgb(0, 86, 179); BtnOpen.BackColor = Color.FromArgb(0, 86, 179);
@ -1096,7 +1176,6 @@ namespace QuizMaster
BtnOpen.Size = new Size(151, 34); BtnOpen.Size = new Size(151, 34);
}; };
// Thêm hiệu ứng hover cho btnUpload
btnUpload.MouseEnter += (s, e) => btnUpload.MouseEnter += (s, e) =>
{ {
btnUpload.BackColor = Color.FromArgb(30, 126, 52); btnUpload.BackColor = Color.FromArgb(30, 126, 52);

View File

@ -35,6 +35,7 @@
this.txtResult.Location = new System.Drawing.Point(0, 0); this.txtResult.Location = new System.Drawing.Point(0, 0);
this.txtResult.Name = "txtResult"; this.txtResult.Name = "txtResult";
this.txtResult.Size = new System.Drawing.Size(800, 450); this.txtResult.Size = new System.Drawing.Size(800, 450);
this.txtResult.WordWrap = true;
this.txtResult.TabIndex = 0; this.txtResult.TabIndex = 0;
// //
//btnExoortWord //btnExoortWord
@ -49,8 +50,8 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450); this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.txtResult);
this.Controls.Add(this.btnExportWord); this.Controls.Add(this.btnExportWord);
this.Controls.Add(this.txtResult);
this.Name = "Form2"; this.Name = "Form2";
this.Text = "Kết quả Generate"; this.Text = "Kết quả Generate";
this.ResumeLayout(false); this.ResumeLayout(false);

View File

@ -4,7 +4,6 @@ using System.Windows.Forms;
using Xceed.Words.NET; using Xceed.Words.NET;
using Xceed.Document.NET; using Xceed.Document.NET;
namespace QuizMaster namespace QuizMaster
{ {
public partial class Form2 : Form public partial class Form2 : Form
@ -16,7 +15,7 @@ namespace QuizMaster
{ {
InitializeComponent(); InitializeComponent();
_content = content; _content = content;
txtResult.Text = content; txtResult.Text = _content.Replace("\n", Environment.NewLine);
} }
private void btnExportWord_Click(object sender, EventArgs e) private void btnExportWord_Click(object sender, EventArgs e)

View File

@ -6,5 +6,7 @@
public string Question { get; set; } public string Question { get; set; }
public string Option { get; set; } public string Option { get; set; }
public int IsCorrect { get; set; } // 1 = đúng, 0 = sai public int IsCorrect { get; set; } // 1 = đúng, 0 = sai
public string DoKho { get; set; }
} }
} }

View File

@ -10,6 +10,7 @@ namespace QuizMaster.Response
public string? B { get; set; } public string? B { get; set; }
public string? C { get; set; } public string? C { get; set; }
public string? D { get; set; } public string? D { get; set; }
public required string DapAn { get; set; } public required List<string> DapAn { get; set; } = new List<string>();
public string DoKho { get; set; }
} }
} }

View File

@ -103,6 +103,48 @@ namespace QuizMaster.Service
return categories; return categories;
} }
public static async Task<List<CategoryInfo>> GetTotalOptionQuestionsAsync(List<string> lstCategory, string DoKho)
{
var categories = new List<CategoryInfo>();
if (lstCategory == null || lstCategory.Count == 0)
{
return categories;
}
string whereCate = string.Join(",", lstCategory.Select(c => $"'{c.Replace("'", "''")}'"));
await using var conn = new NpgsqlConnection(DbHelper.connectionString);
await conn.OpenAsync();
try
{
var cmd = new NpgsqlCommand($@"
SELECT c.name, COUNT(q.id) AS Total
FROM question q
INNER JOIN category c ON q.category_id = c.id
WHERE c.name IN ({whereCate})
AND q.level = '{DoKho}'
GROUP BY c.name
ORDER BY c.name;
", conn);
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
categories.Add(new CategoryInfo
{
Category = reader.GetString(0),
Total = reader.GetInt32(1)
});
}
}
catch (Exception ex)
{
MessageBox.Show($"Lỗi khi lấy danh sách số câu khó: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
throw;
}
return categories;
}
public static async Task<List<CategoryInfo>> GetTotalCategoriesAsync(List<string> lstCategory) public static async Task<List<CategoryInfo>> GetTotalCategoriesAsync(List<string> lstCategory)
{ {
var categories = new List<CategoryInfo>(); var categories = new List<CategoryInfo>();
@ -144,7 +186,6 @@ namespace QuizMaster.Service
return categories; return categories;
} }
public static async Task<List<QuestionOption>> getAllQA(string Category) public static async Task<List<QuestionOption>> getAllQA(string Category)
{ {
var questionOption = new List<QuestionOption>(); var questionOption = new List<QuestionOption>();
@ -154,13 +195,12 @@ namespace QuizMaster.Service
try try
{ {
var cmd = new NpgsqlCommand(@" var cmd = new NpgsqlCommand(@"SELECT q.id, q.content, q.level, o.option_text, o.is_correct
SELECT q.id, q.content, o.option_text, o.is_correct
FROM question q FROM question q
INNER JOIN question_option o ON o.question_id = q.id INNER JOIN question_option o ON o.question_id = q.id
INNER JOIN category c ON q.category_id = c.id INNER JOIN category c ON q.category_id = c.id
WHERE c.name = @category WHERE c.name = @category
ORDER BY q.id, o.id; ORDER BY q.id, o.id
", conn); ", conn);
cmd.Parameters.AddWithValue("category", Category); cmd.Parameters.AddWithValue("category", Category);
@ -170,10 +210,11 @@ namespace QuizMaster.Service
{ {
questionOption.Add(new QuestionOption questionOption.Add(new QuestionOption
{ {
QuestionId = reader.IsDBNull(0) ? 0 : reader.GetInt32(0), QuestionId = reader.GetInt32(0),
Question = reader.IsDBNull(1) ? string.Empty : reader.GetString(1), Question = reader.GetString(1),
Option = reader.IsDBNull(2) ? string.Empty : reader.GetString(2), DoKho = reader.IsDBNull(2) ? "" : reader.GetString(2),
IsCorrect = reader.IsDBNull(3) ? 0 : reader.GetInt32(3) Option = reader.IsDBNull(3) ? string.Empty : reader.GetString(3),
IsCorrect = reader.IsDBNull(4) ? 0 : reader.GetInt32(4)
}); });
} }
} }
@ -186,37 +227,78 @@ namespace QuizMaster.Service
return questionOption; return questionOption;
} }
public static async Task<bool> InsertQuestionAndOptionsAsync(List<Quiz> questions) public static async Task<bool> InsertQuestionAndOptionsAsync(List<Quiz> questions)
{ {
if (questions == null || questions.Count == 0)
return false;
await using var conn = new NpgsqlConnection(DbHelper.connectionString); await using var conn = new NpgsqlConnection(DbHelper.connectionString);
await conn.OpenAsync(); await conn.OpenAsync();
await using var transaction = await conn.BeginTransactionAsync(); await using var transaction = await conn.BeginTransactionAsync();
try try
{ {
// --- 1. Cache categoryId ---
var categoryCache = new Dictionary<string, int>();
foreach (var q in questions) foreach (var q in questions)
{ {
// Lấy category_id if (!categoryCache.ContainsKey(q.LinhVuc))
var cmdCategory = new NpgsqlCommand("SELECT id FROM category WHERE name = @name", conn, transaction); {
var cmdCategory = new NpgsqlCommand(
"SELECT id FROM category WHERE name = @name", conn, transaction);
cmdCategory.Parameters.AddWithValue("name", q.LinhVuc); cmdCategory.Parameters.AddWithValue("name", q.LinhVuc);
var categoryIdObj = await cmdCategory.ExecuteScalarAsync(); var categoryIdObj = await cmdCategory.ExecuteScalarAsync();
if (categoryIdObj == null) if (categoryIdObj == null)
throw new Exception($"Category '{q.LinhVuc}' không tồn tại."); throw new Exception($"Category '{q.LinhVuc}' không tồn tại.");
categoryCache[q.LinhVuc] = (int)categoryIdObj;
}
}
int categoryId = (int)categoryIdObj; // --- 2. Batch insert questions ---
var batchQuestions = new NpgsqlBatch(conn, transaction);
// Insert Question foreach (var q in questions)
var cmdQuestion = new NpgsqlCommand(@" {
INSERT INTO question (content, category_id) var cmd = new NpgsqlBatchCommand(@"
VALUES (@content, @categoryId) INSERT INTO question (content, category_id, level)
VALUES (@content, @categoryId, @level)
RETURNING id; RETURNING id;
", conn, transaction); ");
cmdQuestion.Parameters.AddWithValue("content", q.CauHoi); cmd.Parameters.AddWithValue("content", q.CauHoi);
cmdQuestion.Parameters.AddWithValue("categoryId", categoryId); cmd.Parameters.AddWithValue("categoryId", categoryCache[q.LinhVuc]);
cmd.Parameters.AddWithValue("level", q.DoKho);
batchQuestions.BatchCommands.Add(cmd);
}
// --- 3. Execute batch question và lấy questionId ---
var questionIds = new List<int>();
await using (var reader = await batchQuestions.ExecuteReaderAsync())
{
foreach (var _ in questions)
{
if (!await reader.ReadAsync())
throw new Exception("Không lấy đủ questionId từ batch.");
questionIds.Add(reader.GetInt32(0));
// Chuyển sang result set tiếp theo (mỗi INSERT RETURNING tạo 1 result set)
await reader.NextResultAsync();
}
}
// --- 4. Batch insert options ---
var batchOptions = new NpgsqlBatch(conn, transaction);
for (int i = 0; i < questions.Count; i++)
{
var q = questions[i];
var qid = questionIds[i];
if (q.DapAn == null || q.DapAn.Count == 0 || q.DapAn.Any(a => !"ABCD".Contains(a)))
{
var dapAnStr = q.DapAn != null ? string.Join(",", q.DapAn) : "(null)";
throw new Exception($"Câu hỏi '{q.CauHoi}' có đáp án không hợp lệ: {dapAnStr}");
}
int questionId = (int)await cmdQuestion.ExecuteScalarAsync();
// Insert QuestionOption
var options = new Dictionary<string, string?> var options = new Dictionary<string, string?>
{ {
{ "A", q.A }, { "A", q.A },
@ -227,18 +309,19 @@ namespace QuizMaster.Service
foreach (var opt in options) foreach (var opt in options)
{ {
var cmdOption = new NpgsqlCommand(@" var cmdOption = new NpgsqlBatchCommand(@"
INSERT INTO question_option (question_id, option_text, is_correct) INSERT INTO question_option (question_id, option_text, is_correct)
VALUES (@questionId, @optionText, @isCorrect); VALUES (@questionId, @optionText, @isCorrect);
", conn, transaction); ");
cmdOption.Parameters.AddWithValue("questionId", qid);
cmdOption.Parameters.AddWithValue("questionId", questionId);
cmdOption.Parameters.AddWithValue("optionText", opt.Value ?? (object)DBNull.Value); cmdOption.Parameters.AddWithValue("optionText", opt.Value ?? (object)DBNull.Value);
cmdOption.Parameters.AddWithValue("isCorrect", opt.Key == q.DapAn ? 1 : 0); cmdOption.Parameters.AddWithValue("isCorrect", q.DapAn.Contains(opt.Key) ? 1 : 0);
batchOptions.BatchCommands.Add(cmdOption);
}
}
await cmdOption.ExecuteNonQueryAsync(); // --- 5. Execute batch options ---
} await batchOptions.ExecuteNonQueryAsync();
}
await transaction.CommitAsync(); await transaction.CommitAsync();
return true; return true;
@ -250,6 +333,7 @@ namespace QuizMaster.Service
} }
} }
public static async Task<int> GetTotalQuestionCount() public static async Task<int> GetTotalQuestionCount()
{ {
string query = "SELECT COUNT(*) FROM Question"; string query = "SELECT COUNT(*) FROM Question";
@ -267,26 +351,6 @@ namespace QuizMaster.Service
return Convert.ToInt32(result); return Convert.ToInt32(result);
} }
//public static async Task<List<Question>> GetRandomQuestions(int requestedCount)
//{
// int totalCount = await GetTotalQuestionCount();
// if (requestedCount <= 0)
// {
// MessageBox.Show("Vui lòng nhập số lượng lớn hơn 0.", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
// return new List<Question>();
// }
// if (requestedCount > totalCount)
// {
// MessageBox.Show($"Chỉ có {totalCount} câu hỏi trong cơ sở dữ liệu.", "Thông báo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
// return new List<Question>();
// }
// string query = $"SELECT * FROM Question ORDER BY RANDOM() LIMIT {requestedCount}";
// return await DbHelper.ExecuteSelectQuery<Question>(query);
//}
public static async Task<List<string>> GetDepartmentsAsync() public static async Task<List<string>> GetDepartmentsAsync()
{ {
List<string> deplist = new List<string>(); List<string> deplist = new List<string>();
@ -316,5 +380,6 @@ namespace QuizMaster.Service
return deplist; return deplist;
} }
} }
} }

View File

@ -2,8 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using OfficeOpenXml; using OfficeOpenXml;
using Xceed.Document.NET;
namespace QuizMaster.Service namespace QuizMaster.Service
{ {
@ -11,7 +13,7 @@ namespace QuizMaster.Service
{ {
private static readonly List<string> ExpectedHeaders = new() private static readonly List<string> ExpectedHeaders = new()
{ {
"Lĩnh vực", "Câu hỏi", "A", "B", "C", "D", "Đáp án" "Lĩnh vực", "Câu hỏi", "A", "B", "C", "D", "Đáp án", "Độ khó"
}; };
public sealed class RowData public sealed class RowData
@ -23,7 +25,10 @@ namespace QuizMaster.Service
public string B { get; init; } = string.Empty; public string B { get; init; } = string.Empty;
public string C { get; init; } = string.Empty; public string C { get; init; } = string.Empty;
public string D { get; init; } = string.Empty; public string D { get; init; } = string.Empty;
public string Answer { get; init; } = string.Empty; public List<string> Answers { get; init; } = new List<string>();
public string Level { get; init; } = string.Empty;
} }
@ -50,6 +55,9 @@ namespace QuizMaster.Service
if (string.IsNullOrWhiteSpace(data.Question)) if (string.IsNullOrWhiteSpace(data.Question))
errors.Add($"Dòng {data.Row}: 'Câu hỏi' không được trống"); errors.Add($"Dòng {data.Row}: 'Câu hỏi' không được trống");
if (string.IsNullOrWhiteSpace(data.Level))
errors.Add($"Dòng {data.Row}: 'Độ khó' không được trống");
return errors; return errors;
} }
@ -71,31 +79,40 @@ namespace QuizMaster.Service
public static List<string> ValidateRowAnswer(RowData data) public static List<string> ValidateRowAnswer(RowData data)
{ {
var errors = new List<string>(); var errors = new List<string>();
if (string.IsNullOrWhiteSpace(data.Answer))
if (data.Answers == null || data.Answers.Count == 0)
{ {
errors.Add($"Dòng {data.Row}: 'Đáp án' không được trống"); errors.Add($"Dòng {data.Row}: 'Đáp án' không được trống");
return errors; return errors;
} }
var valid = new[] { "A", "B", "C", "D" }; var validOptions = new HashSet<string> { "A", "B", "C", "D" };
if (!valid.Contains(data.Answer))
foreach (var ans in data.Answers)
{ {
errors.Add($"Dòng {data.Row}: 'Đáp án' phải là A, B, C hoặc D (hiện tại là '{data.Answer}')"); if (!validOptions.Contains(ans))
return errors; {
errors.Add($"Dòng {data.Row}: 'Đáp án' phải là A, B, C hoặc D (phát hiện '{ans}')");
continue;
} }
bool answerPointsToEmpty = (data.Answer == "A" && string.IsNullOrWhiteSpace(data.A)) || bool isEmptyOption =
(data.Answer == "B" && string.IsNullOrWhiteSpace(data.B)) || (ans == "A" && string.IsNullOrWhiteSpace(data.A)) ||
(data.Answer == "C" && string.IsNullOrWhiteSpace(data.C)) || (ans == "B" && string.IsNullOrWhiteSpace(data.B)) ||
(data.Answer == "D" && string.IsNullOrWhiteSpace(data.D)); (ans == "C" && string.IsNullOrWhiteSpace(data.C)) ||
if (answerPointsToEmpty) (ans == "D" && string.IsNullOrWhiteSpace(data.D));
if (isEmptyOption)
{ {
errors.Add($"Dòng {data.Row}: 'Đáp án' {data.Answer} không hợp lệ vì phương án {data.Answer} đang trống"); errors.Add($"Dòng {data.Row}: 'Đáp án' {ans} không hợp lệ vì phương án {ans} đang trống");
}
} }
return errors; return errors;
} }
//________________________________________________________________________________________________
public static List<string> ValidateRowDuplicate(RowData data, Dictionary<string, int> questionOptionDict) public static List<string> ValidateRowDuplicate(RowData data, Dictionary<string, int> questionOptionDict)
{ {
var errors = new List<string>(); var errors = new List<string>();
@ -134,6 +151,12 @@ namespace QuizMaster.Service
var firstCell = worksheet.Cells[row, 1]; var firstCell = worksheet.Cells[row, 1];
if (string.IsNullOrWhiteSpace(firstCell.Text)) break; // Dòng trống → kết thúc if (string.IsNullOrWhiteSpace(firstCell.Text)) break; // Dòng trống → kết thúc
var answerText = worksheet.Cells[row, 7].Text.Trim().ToUpper();
var answers = Regex.Matches(answerText.ToUpper(), "[ABCD]")
.Select(m => m.Value.Trim())
.Distinct()
.ToList();
var data = new RowData var data = new RowData
{ {
Row = row, Row = row,
@ -143,7 +166,8 @@ namespace QuizMaster.Service
B = worksheet.Cells[row, 4].Text.Trim(), B = worksheet.Cells[row, 4].Text.Trim(),
C = worksheet.Cells[row, 5].Text.Trim(), C = worksheet.Cells[row, 5].Text.Trim(),
D = worksheet.Cells[row, 6].Text.Trim(), D = worksheet.Cells[row, 6].Text.Trim(),
Answer = worksheet.Cells[row, 7].Text.Trim().ToUpper() Answers = answers,
Level = worksheet.Cells[row, 8].Text.Trim()
}; };
errors.AddRange(ValidateRowRequired(data)); errors.AddRange(ValidateRowRequired(data));

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using OfficeOpenXml; using OfficeOpenXml;
using QuizMaster.Response; using QuizMaster.Response;
@ -21,6 +22,8 @@ namespace QuizMaster.Service
for (int row = 2; row <= rowCount; row++) // Bắt đầu từ dòng 2, bỏ dòng tiêu đề for (int row = 2; row <= rowCount; row++) // Bắt đầu từ dòng 2, bỏ dòng tiêu đề
{ {
var answerText = worksheet.Cells[row, 7].Text.Trim().ToUpper();
var ch = new Quiz var ch = new Quiz
{ {
LinhVuc = worksheet.Cells[row, 1].Text, LinhVuc = worksheet.Cells[row, 1].Text,
@ -29,13 +32,16 @@ namespace QuizMaster.Service
B = worksheet.Cells[row, 4].Text, B = worksheet.Cells[row, 4].Text,
C = worksheet.Cells[row, 5].Text, C = worksheet.Cells[row, 5].Text,
D = worksheet.Cells[row, 6].Text, D = worksheet.Cells[row, 6].Text,
DapAn = worksheet.Cells[row, 7].Text
};
DapAn = Regex.Matches(answerText.ToUpper(), "[ABCD]")
.Select(m => m.Value.Trim())
.Distinct()
.ToList(),
DoKho = worksheet.Cells[row, 8].Text
};
result.Add(ch); result.Add(ch);
} }
} }
return result; return result;
} }
} }