0%

MFC课程设计——学生成绩管理系统

关键词:

  • 广州大学
  • MFC程序设计
  • 学生管理系统

源码

由于本人非常地懒,不会回答大家的问题,遇到问题请百度谷歌解决吧。


环境

  • 操作系统: Windows 10 家庭中文版
  • 编写软件: Visual Studio 2017
  • 预编译头: 除了原有自带的,添加了_CRT_SECURE_NO_WARNINGS。

课程设计题目及内容

  • 设计一个学生类Student,包括数据成员:姓名、学号、二门课程(面向对象程序设计、高等数学)的成绩。
  • 创建一个管理学生的类Management,包括实现学生的数据的增加、删除、修改、按课程成绩排序、保存学生数据到文件及加载文件中的数据等功能。
  • 创建一个基于对话框的MFC应用程序,程序窗口的标题上有你姓名、学号和应用程序名称。使用(1)和(2)中的类,实现对学生信息和成绩的输入和管理。
  • 创建一个单文档的MFC应用程序,读取(3)中保存的文件中的学生成绩,分别用直方图和折线方式显示所有学生某课程的成绩分布图。

程序功能模块的设计

用户层面

底层

只要用户更新了数据,就保存数据到tmp.csv中。
只有用户点击了保存按钮,才会保存到data.csv中。


文件

- main
|– Manager.exe
|– Document.exe
|– data.csv
|– tmp.csv
|– README.txt

  • Manager.exe和Document.exe是两个程序的入口,分别是学生管理和可视化文档。
  • data.csv存放数据,仅支持utf-8编码。
  • tmp.csv存放临时数据。

关键源程序

Manager 对话框

底层 (base.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#pragma once
#include <fstream>
#include <codecvt>
using namespace std;
class Student {
private:
CString name;
CString number;
int gradeOOP;
int gradeMath;
public:
Student(){}
void set(CString _name, CString _number, const int & _gradeOOP = 0, const int & _gradeMath = 0)
{
name = _name;
number = _number;
gradeOOP = _gradeOOP;
gradeMath = _gradeMath;
}
Student(CString _name, CString _number, const int & _gradeOOP = 0, const int & _gradeMath = 0) {
this->set(_name, _number, _gradeOOP, _gradeMath);
}
CString getName() const {
return name;
}
CString getNumber() const {
return number;
}
const int getGradeOOP() const {
return gradeOOP;
}
const int getGradeMath() const {
return gradeMath;
}
friend class Management;
};

// 假线性表
template<typename T>
class myVector
{
private:
T * p;
unsigned __sz;
public:
myVector()
{
p = new T[10000];
__sz = 0;
}
void push(const T & newObj)
{
p[__sz] = newObj;
__sz++;
}
T& operator[] (const unsigned & idx) const
{
return p[idx];
}
unsigned size() const
{
return this->__sz;
}
void erase(int n) {
const int sz = __sz - 1;
for (int i = n; i < sz; i++) {
p[i] = p[i + 1];
}
__sz--;
}
};


// 文件为utf8编码,用csv的逗号分隔符的格式读入
class Management {
myVector<Student> v;
public:
Management() {
v = myVector<Student>();
}

// 文件 => 内存
Management(const wchar_t * fileString) {
v = myVector<Student>();

// utf8
locale utf8(locale(""), new codecvt_utf8<wchar_t, 0x10ffff, little_endian>);
wifstream ifs(fileString);
ifs.imbue(utf8);

//不存在文件
if (!ifs.is_open()) {
return;
}

// curString 即 buffer
wchar_t * curString = new wchar_t[2048];
int * idx = new int[128];
while (!ifs.eof()) {

ifs.getline(curString, 2048);
int p = 0;
const int len = wcslen(curString);

for (int i = 0; i < len; i++) {
if (curString[i] == L',') {
curString[i] = L'\0';
idx[p++] = i + 1;
}
}
if (len <= 0) {
break;
}

CString name, number;
int gradeOOP = 0, gradeMath = 0;
name = curString;
number = curString + idx[0];
swscanf(curString + idx[1], L"%d", &gradeOOP);
swscanf(curString + idx[2], L"%d", &gradeMath);
add(Student(name, number, gradeOOP, gradeMath));
}
delete[] idx;
delete[] curString;
ifs.close();
}


void add(const Student & obj)
{
CString s = obj.number;
if (find(s) == -1) {
v.push(obj);
}
else {
throw bad_exception();
}
}

// 遍历寻找 找到返回下标 找不到返回-1
unsigned find(CString s)
{
const int len = v.size();
for (int i = 0; i < len; i++)
{
if (s == v[i].number)
{
return i;
}
}
return -1;
}

private:
static bool cmp0(const Student & a, const Student & b) {
return a.number < b.number;
}

static bool cmp1(const Student & a, const Student & b) {
return a.name < b.name;
}

static bool cmp2(const Student & a, const Student & b) {
return a.gradeOOP < b.gradeOOP;
}

static bool cmp3(const Student & a, const Student & b) {
return a.gradeMath < b.gradeMath;
}

public:
int sort(const int index = 0, const bool reverse = 0) {
const int n = v.size();
bool(*p)(const Student &, const Student &) = cmp0;
switch (index) {
//number
case 0: p = cmp0; break;
//name
case 1: p = cmp1; break;
//gradeOOP
case 2: p = cmp2; break;
//gradeMath
case 3: p = cmp3; break;
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (p(v[j], v[i]) ^ reverse) {
Student tmp = v[i];
v[i] = v[j];
v[j] = tmp;
}
}
}
return index * 2 + (int)reverse;
}

// 内存 => 文件
void output(const wchar_t * fileString) {
locale utf8(locale(""), new codecvt_utf8<wchar_t, 0x10ffff, little_endian>);
wofstream ofs(fileString);
ofs.imbue(utf8);
const int len = v.size();
for (int i = 0; i < len; i++)
{
const wchar_t * name = v[i].name;
const wchar_t * number = v[i].number;
ofs << name << L',' << number << L',' << v[i].gradeOOP << L',' << v[i].gradeMath << endl;
}
ofs.close();
}

void erase(int n) {
v.erase(n);
}

friend class CManagerDlg;
friend class CChgDlg;
};

主窗口 (CManagerDlg)

m_list初始化
1
2
3
4
5
6
7
// m_list初始化
const CString STR[] = { L"姓名", L"学号", L"面向对象程序设计成绩", L"高等数学成绩" };
const int LENTH[] = {100, 142, 145, 145};
for (int i = 0; i < 4; i++) {
m_list.InsertColumn(i, STR[i], 0, LENTH[i]);
}
m_list.SetExtendedStyle(m_list.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
获取数据的路径以及数据的读取

放在初始化的代码中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// get source path
CString path = AfxGetApp()->m_pszHelpFilePath;
int idx = 0;
const int len = path.GetLength();
for (int i = 0; i < len; i++) {
if (path[i] == '\\') {
idx = i;
}
}
filePath = path.Left(idx + 1);

// update from source path
manager = Management(filePath + L"data.csv");
status = manager.sort();
update(manager);
禁止拖动表头
1
2
3
4
5
6
void CManagerDlg::OnBegintrackList1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
*pResult = TRUE;
}
添加数据
1
2
3
4
5
6
7
8
//add
void CManagerDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CAddDlg dlg;
dlg.DoModal();
update(app->manager);
}
修改数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//chg
void CManagerDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
POSITION line = m_list.GetFirstSelectedItemPosition();
if (line == NULL) {
AfxMessageBox(L"你没有选中任何人!");
}
else {
app->chg_pos = m_list.GetNextSelectedItem(line);
CChgDlg dlg;
dlg.DoModal();
update(app->manager);
}
}
删除数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//del
void CManagerDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码
POSITION line = m_list.GetFirstSelectedItemPosition();
CString str;
if (line == NULL) {
AfxMessageBox(L"你没有选中任何人!");
}
else {
Management & manager = app->manager;
int nItem = m_list.GetNextSelectedItem(line);
manager.erase(nItem);
update(manager);
}
}
保存数据
1
2
3
4
5
6
7
//save
void CManagerDlg::OnBnClickedButton4()
{
// TODO: 在此添加控件通知处理程序代码
app->manager.output(filePath + L"data.csv");
AfxMessageBox(L"保存成功");
}
点击表头进行排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//sort
void CManagerDlg::OnColumnclickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
int idx = pNMLV->iSubItem; // get the clicked column
idx = (idx == 0 || idx == 1) ? (1 - idx) : idx;
bool rev = false;

switch (idx) {
case 0:rev = status == 0; break;
case 1:rev = status == 2; break;
case 2:rev = status == 4; break;
case 3:rev = status == 6; break;
}
Management & manager = app->manager;
status = manager.sort(idx, rev);
update(manager);


*pResult = 0;
}

添加学生对话框 (CAddDlg)

初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
BOOL CAddDlg::OnInitDialog() {

CDialogEx::OnInitDialog();

app = (CManagerApp *)AfxGetApp();

m_edit_name.SetLimitText(20);
m_edit_number.SetLimitText(20);
m_edit_oop.SetLimitText(20);
m_edit_math.SetLimitText(20);

m_edit_name.SetWindowTextW(L"张三");
m_edit_number.SetWindowTextW(L"1806300001");
m_edit_oop.SetWindowTextW(L"0");
m_edit_math.SetWindowTextW(L"0");

return TRUE;
}
点击确认时代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CAddDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
m_edit_name.GetWindowTextW(app->add_name);
m_edit_number.GetWindowTextW(app->add_number);
m_edit_oop.GetWindowTextW(app->add_oop);
m_edit_math.GetWindowTextW(app->add_math);
try {
app->manager.add(Student(app->add_name, app->add_number, _wtoi(app->add_oop), _wtoi(app->add_math)));
CDialogEx::OnOK();
}
catch (bad_exception) {
AfxMessageBox(L"数据中已含有相同的学号");
CDialogEx::OnInitDialog();
}
}

修改学生对话框 (CChgDlg)

初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOL CChgDlg::OnInitDialog() {

CDialogEx::OnInitDialog();

const int idx = app->chg_pos;
Management & manager = app->manager;
m_edit_name.SetWindowTextW(manager.v[idx].getName());
m_edit_number.SetWindowTextW(manager.v[idx].getNumber());
CString str;
str.Format(L"%d", manager.v[idx].getGradeOOP());
m_edit_oop.SetWindowTextW(str);
str.Format(L"%d", manager.v[idx].getGradeMath());
m_edit_math.SetWindowTextW(str);

return TRUE;
}
点击确认时代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CChgDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
Management & manager = app->manager;
const int idx = app->chg_pos;
CString chg_name;
CString chg_number = manager.v[idx].getNumber();
CString chg_oop;
CString chg_math;
m_edit_name.GetWindowTextW(chg_name);
m_edit_oop.GetWindowTextW(chg_oop);
m_edit_math.GetWindowTextW(chg_math);
const int oop = _wtoi(chg_oop), math = _wtoi(chg_math);
manager.v[idx] = Student(chg_name, chg_number, oop, math);
CDialogEx::OnOK();
}

Manager.h 中的“全局”变量

1
2
3
4
5
Management manager;
CString add_name;
CString add_number;
CString add_oop;
CString add_math;

Document 单文档

底层 (bash.h)

与Manager中大致相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#pragma once
#include <fstream>
#include <codecvt>
#include <cstring>
using namespace std;
constexpr int MAXIMUM = 10000;
class list {
private:
int * OOP;
int * MAT;
list(){
OOP = new int[MAXIMUM];
MAT = new int[MAXIMUM];
memset(OOP, 0, sizeof(int) * MAXIMUM);
memset(MAT, 0, sizeof(int) * MAXIMUM);
}
friend class Management;
friend class CChildView;
};

// 文件为utf8编码,用csv的逗号分隔符的格式读入
class Management {
private:
int sz;
list data;
public:
Management() {
sz = 0;
data = list();
}
// 文件 => 内存
void set(const wchar_t * fileString) {
sz = 0;
data = list();

// utf8
locale utf8(locale(""), new codecvt_utf8<wchar_t, 0x10ffff, little_endian>);
wifstream ifs(fileString);
ifs.imbue(utf8);

//不存在文件
if (!ifs.is_open()) {
return;
}

// curString 即 buffer
wchar_t * curString = new wchar_t[2048];
int * idx = new int[128];
while (!ifs.eof()) {
ifs.getline(curString, 2048);
int p = 0;
const int len = wcslen(curString);
for (int i = 0; i < len; i++) {
if (curString[i] == L',') {
curString[i] = L'\0';
idx[p++] = i + 1;
}
}
if (len <= 0) {
break;
}
int gradeOOP = 0, gradeMath = 0;
swscanf(curString + idx[1], L"%d", &gradeOOP);
swscanf(curString + idx[2], L"%d", &gradeMath);
data.OOP[sz] = gradeOOP;
data.MAT[sz] = gradeMath;
sz++;
}
delete[] idx;
delete[] curString;
ifs.close();
}
friend class CChildView;
};

视图类头文件 (ChildView.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

// ChildView.h: CChildView 类的接口
//


#pragma once
#include "base.h"

// CChildView 窗口

class CChildView : public CWnd
{
// 构造
public:
CChildView();

// 特性
public:

// 操作
public:

// 重写
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

// 实现
public:
virtual ~CChildView();

// 生成的消息映射函数
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
public:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
private:
CButton OOPA;
CButton OOPB;
CButton MATA;
CButton MATB;
protected:
void PRINTOOPA();
void PRINTOOPB();
void PRINTMATA();
void PRINTMATB();
private:
void histogram(CDC *, const int *, int);
void listChart(CDC *, const int *, int);
Management manager;
CString filePath;
int a[MAXIMUM];
};

视图类源文件 (ChildView.cpp)

消息注册
1
2
3
4
5
6
7
8
BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
ON_WM_CREATE()
ON_COMMAND(1, PRINTOOPA)
ON_COMMAND(2, PRINTOOPB)
ON_COMMAND(3, PRINTMATA)
ON_COMMAND(4, PRINTMATB)
END_MESSAGE_MAP()
创建文档时代码(初始化)

创建按钮

1
2
3
4
5
// create
OOPA.Create(L"OOP成绩直方图", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(10, 10, 130, 40), this, 1);
OOPB.Create(L"OOP成绩折线图", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(140, 10, 260, 40), this, 2);
MATA.Create(L"数学成绩直方图", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(10, 50, 130, 80), this, 3);
MATB.Create(L"数学成绩折线图", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(140, 50, 260, 80), this, 4);

获取数据并存储数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// get source path
CString path = AfxGetApp()->m_pszHelpFilePath;
int idx = 0;
const int len = path.GetLength();
for (int i = 0; i < len; i++) {
if (path[i] == '\\') {
idx = i;
}
}
filePath = path.Left(idx + 1);

// update from source path
manager = Management();
manager.set(filePath + L"data.csv");
点击按钮时的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void CChildView::PRINTOOPA()
{
// TODO: 在此处添加实现代码.
memcpy(a, manager.data.OOP, sizeof(int) * MAXIMUM);
InvalidateRect(NULL);
UpdateWindow();
histogram(GetDC(), a, manager.sz);
}

void CChildView::PRINTOOPB()
{
// TODO: 在此处添加实现代码.
memcpy(a, manager.data.OOP, sizeof(int) * MAXIMUM);
InvalidateRect(NULL);
UpdateWindow();
listChart(GetDC(), a, manager.sz);
}

void CChildView::PRINTMATA()
{
// TODO: 在此处添加实现代码.
memcpy(a, manager.data.MAT, sizeof(int) * MAXIMUM);
InvalidateRect(NULL);
UpdateWindow();
histogram(GetDC(), a, manager.sz);
}

void CChildView::PRINTMATB()
{
// TODO: 在此处添加实现代码.
memcpy(a, manager.data.MAT, sizeof(int) * MAXIMUM);
InvalidateRect(NULL);
UpdateWindow();
listChart(GetDC(), a, manager.sz);
}
绘制直方图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void CChildView::histogram(CDC * pDC, const int *score, int n)
{
// TODO: 在此处添加实现代码.

// 数据处理
int data[5] = { 0 };
for (int i = 0; i < n; i++) {
int cur = score[i] / 10 - 5;
if (cur < 0) {
cur = 0;
}
else if (cur > 4) {
cur = 4;
}
data[cur]++;
}
int mx = 0;
for (int i = 0; i < 5; i++) {
mx = mx > data[i] ? mx : data[i];
}

// print
CRect rc;
GetClientRect(rc);
rc.DeflateRect(280, 40);
CBrush brush1(HS_FDIAGONAL, RGB(0, 0, 192)), brush2(HS_BDIAGONAL, RGB(0, 0, 192));
CPen pen(PS_INSIDEFRAME, 2, RGB(0, 0, 192));
CBrush * oldBrush(pDC->SelectObject(&brush1));
CPen * oldPen(pDC->SelectObject(&pen));
CRect rcSeg(rc);
const int width = rc.Width() / 5;
rcSeg.right = rcSeg.left + width;
const CString str[] = { L"小于60", L"60到69", L"70到79", L"80到89", L"大于等于90" };
for (int i = 0; i < 5; i++) {
pDC->SelectObject(i & 1 ? &brush2 : &brush1);
if (mx != 0) {
rcSeg.top = rcSeg.bottom - data[i] * (rc.Height() / mx) - 2;
}
pDC->Rectangle(rcSeg);
if (data[i] > 0) {
CString tmp;
tmp.Format(L"%d", data[i]);
pDC->DrawText(tmp, rcSeg, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
CRect rcStr = rcSeg;
rcStr.top = rcStr.bottom + 2;
rcStr.bottom += 20;
pDC->DrawText(str[i], rcStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rcSeg.OffsetRect(width, 0);
}
pDC->SelectObject(oldBrush);
pDC->SelectObject(oldPen);

}
绘制折线图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void CChildView::listChart(CDC* pDC, const int* score, int n)//折线图
{
// TODO: 在此处添加实现代码.

// 数据处理
int data[5] = { 0 };
for (int i = 0; i < n; i++) {
int cur = score[i] / 10 - 5;
if (cur < 0) {
cur = 0;
}
else if (cur > 4) {
cur = 4;
}
data[cur]++;
}
int mx = 0;
for (int i = 0; i < 5; i++) {
mx = mx > data[i] ? mx : data[i];
}

// print
CDC* pControlDC = pDC;
pControlDC->SelectStockObject(BLACK_BRUSH);
CString tmp;
const CString str[] = { L"<60", L"60-70", L"70-80", L"80-90", L">=90" };
pControlDC->MoveTo(280, 40);
pControlDC->LineTo(280, 380);
pControlDC->MoveTo(280, 380);
pControlDC->LineTo(1050, 380);
if (n != 0) {
for (int i = 0; i < 5; i++) {
const int x = i * 140 + 350, y = 380 - (380 * data[i] / n);
if (i == 0) {
pControlDC->MoveTo(x, y);
}
else {
pControlDC->LineTo(x, y);
}
tmp.Format(L"%d人", data[i]);
pControlDC->TextOut(i * 140 + 350, 380 - (380 * data[i] / n) - 20, tmp);
pControlDC->TextOut(i * 140 + 350, 390, str[i]);
}
}
}

运行时效果图

刚打开时默认是按照学号进行排序的。(字典序排序)

点击表头可以进行排序。例如此时我点击姓名的表头。

此时是按照内部的utf8编码进行字典序排序。
再点一下就变成倒序。

点击面向对象程序设计成绩的表头,则会按照面向对象程序设计成绩进行排序。

而高等数学成绩也是一样。

点击添加按钮,弹出一个对话框。默认有一些参数。

添加的数据学号不能重复,否则会弹出一个MessageBox表示不能添加。

添加成功后会在最后加入一行数据。

红色框即刚才添加的数据。

修改学生或删除学生时时需要选中一名学生,否则会出现以下情况。

假如此时我要删除学生“李四”,需要选中“李四”,再点击删除。

如果要修改学生“王五”,则要选中“王五”,然后点击修改。

可以修改该学生的姓名、OOP成绩与数学成绩,但不可以修改学号(edit控件锁定)。保证学号的唯一性。这里我们修改“王五”的数学成绩为74。

点击确定,这时“王五”的数学成绩改为了74。

点击保存,弹出一个MessageBox,表示保存成功。

关闭学生信息管理系统。打开Document.exe。

点击按钮就可以直方图和折线图的查看了。




实验结果分析

  • 功能:完全做出预定功能,例如学生添加、修改、删除等操作以及单文档显示都可以做到。
  • 用户体验:从用户体验出发而设计窗口,用户一打开应用程序,就自动读取了数据,用户相当于数据库的操作者,但并非使用数据库的语言,而是可视化的窗口,而且可视化的体验会相当好。
  • 实现意义:可以可视化地处理数据,并且考虑到了学号是不应该改变的,而且许多操作可以基于学号不可相同性,这就代表了一个学号最多对应一位学生,利用学生进行映射。而学生的姓名是有可能相同的,利用姓名进行操作是不可取的。

实验体会

写底层时的体会

  • 一个Management类中有很多个Student类对象,它们不是继承的关系,而是包含的关系。
  • 在编写这两个类之前,首先最好把这两个类的功能、接口想清楚,再进行编写,这样编写的代码bug少,而且编写代码的时间也会更短。而不是盲目的写。(像是Management类的成员函数中又定义了一个Mangement类对象的操作是不能理解的)
  • 编写文件输入和文件输出的时候,需要注意编码问题,若是不使用wchar_t,或者不统一编码,则很可能会导致乱码的情况发生。同时文件的格式要统一。比如我是使用的是csv文件的逗号分隔符文件,编写简单,但安全性欠佳。若是正常非实验使用的应用程序,需要封装成其他安全的文件格式,并且需要进行加密(哪怕是最简单的base64加密)。

写对话框时的体会

  • 预先理清楚架构,想好需要处理哪些功能,创建好对话框,组织好对话框,再编写代码,这样会更清楚地知道该如何编写。
  • 每一个对话框用的不是同样的数据,就是说每两个对话框都是独立的。但如果需要进行对话框之间的变量交互,可以通过一个对话框们都能访问到的一个app对象,即AfxGetApp()的返回值。将需要共享的变量放在Manager.h中,传值的时候就可以通过这个app对象实现。
  • MFC的功能非常多,如果你想实现一个功能,但又不知道怎么用什么接口去实现这个功能,这个时候最好是寻找官方文档,查询这些功能的接口,或者利用搜索引擎寻求帮助。
  • 需要实现的功能非常多。耐心是非常重要的,一步一步地来,最后总能做到想要的功能和达到想要的效果。
  • Visual Studio 可以帮你生成许多代码,如果你需要生成一个对话框,只需要动几下鼠标和键盘就可以实现的。有许多东西都可以自动生成,而无需自己跑到在头文件中添加某个函数,然后又再cpp文件中又再写一次。如果全部代码都是自己写的,那么你学的其实只是普通VC++而不是MFC。

写单文档时的体会

  • 和对话框一样,许多代码是自动生成的,完全没要必要自己写。自动生成可以加快编写代码的时间。
  • 单文档应该和对话框共享一个data.csv文件,这样在manager类中产生的数据可以直接被单文档所享用。
  • 写单文档时也需要有耐心,从官方文档中查询功能的接口,并慢慢实现,一步一步构建代码。

缺点

  • 不可以对学生进行分开管理。
  • 代码重构将非常复杂。

回到开头