Chinh sua UI
This commit is contained in:
parent
8f144c1a8b
commit
8cca84ceec
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
8
QuizMaster/Form1.Designer.cs
generated
8
QuizMaster/Form1.Designer.cs
generated
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
3
QuizMaster/Form2.Designer.cs
generated
3
QuizMaster/Form2.Designer.cs
generated
@ -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);
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Binary file not shown.
@ -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; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user