VC++/MFC入门项目开发实战之任务管理系统

一 任务管理系统功能简述

该系统基于C/S架构的TCP协议开发,利用MySQL数据库存放客户数据。客户端通过服务器从数据库中实现对
数据的增删改查,支持多用户同时在线操作。


二 开发思路
因为选择的是TCP协议的开发,因此首先要确定协议,即客户端先发送命令号,在发送数据,服务器则接受命令号,再根据命令号选择不同的处理函数处理。在此介绍一下协议的开发。
1 在客户端跟服务器的工程里都需要包含相同的命令号,例:

enum cmd
{
  INFO_LOGIN=1,
  INFO_EXIT,
  INFO_REGIST,
  INFO_NEW,
  INFO_REFRESH,
  INFO_SEARCH,
  INFO_Edit,
  INFO_DELETE
};

2 将需要发送/接受的数据放入结构体中比较方便,因此双方都需要包含相同类型的结构体,例:

struct task{
	char creator[10];
	char taskname[50];
	char taskcontent[1000];
	char taskstarttime[15];
	char taskendtime[15];
	char taskcreattime[20];
	};	char creator[10];
	char taskname[50];
	char taskcontent[1000];
	char taskstarttime[15];
	char taskendtime[15];
	char taskcreattime[20];
	};

以上声明最好放在stdafx.h中共享。

3 客户端

CSocket m_sock;
int nCmd = NFO_EXIT;
task sendf;
int nRec;
 m_sock.Send(&nCmd,sizeof(nCmd));
m_sock.Send(&sendf,sizeof(sendf));
 m_sock.Recvive(&nRec,sizeof(nRec);//接受结果

   服务器端

CSocketL m_sock;
int nCmd;
task recvf;
m_sock.Recvive(&nCmd,sizeof(nCmd));
m_sock.Recvive(&recvf,sizeof(recvf));
int nRet = deal();//数据处理完之后
m_sock.Send(&nRet,sizeof(nRet));//发送结果,返回值的类型根据需要定

  当然在发送命令号和数据之间也可以收发一下反馈数据
4 服务器在接受到数据后转入相应的处理函数,在处理函数中与数据库交换数据,从而返回结果。
5 流程图:

三 开发环境及所需知识储备
1 开发工具:VC++6.0 、Mysql 5.6
2 开发环境的配置:
(1)到MySQL官网(Oracle)上下载 MySQL-connector-C文件,选择32位的,解压之后将include文件夹和lib文件夹
放到工程文件夹里(推荐放,不放也行,只要记住文件夹路径即可)
(2)打开VC -> 工具 -> 选项 -> 目录 下的 目录\include files: //以我的文件路径为例
加上include文件夹路径,例:C:\test\Server\include   
  打开VC -> 工具 -> 选项 -> 目录 下的 目录\Library files:
加上lib文件夹路径,例:C:\test\Server\lib
(3)新建工程后,在StdAfx.h文件中添加以下代码:

#include <winsock2.h>  //若用 winsock.h会报错
#include <mysql.h>  
#pragma comment (lib,"ws2_32.lib")  
#pragma comment(lib,"libmysql.lib") 

(4)将下载的lib文件夹中的libmysql.dll文件放入自己Debug/release文件夹
(5)若运行时显示无法连接至数据库,可能是用户没有远程登入权限:
解决远程数据库登入失败的问题://给root用户在IP为192.168.1.171的客户机上远程连接并操作所有数据的权限,791745为密码

grant all privileges on *.* to 'root' @'192.168.1.171' identified by '791745' with grant option;
#使修改生效
grant all privileges on *.* to 'root' @'192.168.1.171' identified by '791745' with grant option;

grant all privileges on *.* to 'root' @'192.168.1.171' identified by '791745' with grant option;
至此,该工程已经具备了和MySQL数据库连接的前提条件。
3 所需必备知识:
(1)MFC框架基础控件的运用,包括编辑框、CList列表、日期、按钮、静态文本等控件
a CList控件:
首先设置属性为report型,不需要排列,关联value型变量m_list

	//在列表里插入标签
	m_list.InsertColumn(0,"创建者",LVCFMT_CENTER,50);//插入第一个标签,名为创建者,居中显示,长度为50
	m_list.InsertColumn(1,"任务名称",LVCFMT_CENTER,80);
	//在列表中插入数据
	m_list.InsertItem(int nItem,LPCTSTR lpszItem );//插入第nItem行第一列的数据
	SetItemText(int nItem,int nSubItem,LPCTSTR lpszText ); //插入第nItem行第nSubItem(>1)列的数据
	//获取鼠标选中的行号,从0开始
	POSITION pos = pDlg->m_list.GetFirstSelectedItemPosition();
	int nItem = pDlg->m_list.GetNextSelectedItem(pos);//在列表里插入标签
	m_list.InsertColumn(0,"创建者",LVCFMT_CENTER,50);//插入第一个标签,名为创建者,居中显示,长度为50
	m_list.InsertColumn(1,"任务名称",LVCFMT_CENTER,80);
	//在列表中插入数据
	m_list.InsertItem(int nItem,LPCTSTR lpszItem );//插入第nItem行第一列的数据
	SetItemText(int nItem,int nSubItem,LPCTSTR lpszText ); //插入第nItem行第nSubItem(>1)列的数据
	//获取鼠标选中的行号,从0开始
	POSITION pos = pDlg->m_list.GetFirstSelectedItemPosition();
	int nItem = pDlg->m_list.GetNextSelectedItem(pos);

b 日期控件:

	//控件日期(CTime类)转CString字符串:首先关联该空间变量m_time,
	CString str;
	str.Format("%d-%d-%d",m_time.GetYear(),m_time.GetMonth(),m_time.GetDay());
	//CString字符串转成控件日期(COleDateTime)以便初始化时显示在该控件上
	COleDateTime temp
	CString str;
	temp.ParseDateTime(str,VAR_DATEVALUEONLY);//第二个参数表示只显示日期
	m_time.SetDate(temp.GetYear(),temp.GetMonth(),temp.GetDay());

c 需要静态文本控件响应点击的消息
打开属性,选中样式中的"通知"项,并更改ID默认的ID号,双击建立响应函数
d 新建的子对话框一般是没有初始化函数的,若需要的话,添加该子对话框类的window message handle函数WM_INITDIALG
e 需要在子类中运用父类的变量和方法:

	CClientDlg *pDlg = (CClientDlg *)theApp.m_pMainWnd;
	pDlg->SetWindowText(str);//例如设置主对话框的标题

f

	//Char数组转CString对象:
	CString str;
	char szBuff[10];
	str.Format( "%s ",szBuff);
	 //CString对象转char数组:
	char  *szBuff; 
	CString  str = "Hello"; 
	szBuff=str.GetBuffer(0); 
	str.ReleaseBuffer();//别忘释放内存//Char数组转CString对象:
	CString str;
	char szBuff[10];
	str.Format( "%s ",szBuff);
	 //CString对象转char数组:
	char  *szBuff; 
	CString  str = "Hello"; 
	szBuff=str.GetBuffer(0); 
	str.ReleaseBuffer();//别忘释放内存

g 对于控件关联变量,别忘了在代码中刷新值
UpdateData(true);//将关联变量的值刷新在空间上
UpdateData(false);//将值刷入关联变量

(2)MySQL基本的查询语句

	#SELETE:指定两个字段的条件,查找其他字段中含有特定字符串的数据,括号不能忘
	SELECT * FROM task WHERE (TaskName LIKE '%s' OR TaskContent LIKE '%s' )AND (CreatorTime BETWEEN '%s' AND '%s') AND (Creator = '%s');
	#UPDATE:更新特定某一行的值
	UPDATE task  SET Creator = 'new',TaskName = 'new' WHERE Creator = 'old' AND TaskName = 'old';
	#INSERT:插入一行新数据
	INSERT INTO task(Creator,TaskName,TaskContent,TaskStartTime,TaskEndTime,CreatorTime) 
	VALUE('%s','%s','%s','%s','%s','%s');
	#若查询语句中有中文,则要进行字符编码的转换,在操作数据之前设定
	mysql_query(&mysql_rcs,"set names 'gbk'");
	#补充:要查询数据库的字符编码,则用命令:SHOW VARIABLES LIKE 'character%';

(2)C++的基础知识

		//利用vector类代替数组储存结构体,并作为返回值以及参数,因为vector的大小可以动态的改变
		//实例解析:这是一个刷新数据的函数,即将登入用户的所有任务数据取出来
		std::vector<task>  RefreshTask(LPCSTR szName);//函数声明
		std::vector<task> CConnectDB::RefreshTask(LPCSTR szName)//定义一个刷新数据的函数,返回一个结构体task类型的模板
		{
			using namespace std;//vector包含在名称空间std中
			MYSQL_RES *result;//定义一个MySQL语句查询的结果集,用来存放从数据库取来的数据
			MYSQL_ROW row;//MYSQL_ROW 类型表示是结果集里的一条数据,例如数据表里有10个字段,row[0]~row[9]里面保存的就是这10个字段的内容。
			vector<task> recvfromDB;//定义一个容器
			result = NULL;//初始化结果集指针为NULL
			char querystr[MAX_SQL_QUERY_LEN];//定义一个字符串用来存放查询语句
			memset(querystr, 0, MAX_SQL_QUERY_LEN);//将该数组清零,
			sprintf(querystr,"SELECT * FROM task WHERE Creator = '%s'",szName);//将查询语句放入数组
			mysql_query(&mysql_rcs,"set names 'gbk'");//因为查询语句中有中文,因此要转码
			if(!mysql_query(&mysql_rcs, querystr))//该语句返回0表示查询成功(不代表有数据返回),返回其他则查询出错
			{
				result = mysql_store_result(&mysql_rcs);//将查询结果集存入result
				if(result)//对于SELECT,需要有结果返回,因此result不为null则表示有结果集返回,此时
				{//可以通过result->row_count这个成员变量查看结果集有几条数据,而对于UPDATE,DELETE,INSERT语句不需要返回数据,因此result为空表示操作成功
					int nCol = mysql_num_fields(result);//结果集的列
					int  nRow = mysql_num_rows(result);//结果集的行
					while(row = mysql_fetch_row(result))//循环将每一条(行)数据放到结构体中
					{
						task recv;//每次循环新建一个结构体,把数据库中一行数据存进去
						sprintf(recv.creator,"%s",row[0]);
						sprintf(recv.taskname,"%s",row[1]);
						sprintf(recv.taskcontent,"%s",row[2]);
						sprintf(recv.taskstarttime,"%s",row[3]);
						sprintf(recv.taskendtime,"%s",row[4]);
						sprintf(recv.taskcreattime,"%s",row[5]);
						recvfromDB.push_back(recv);//将存好数据的结构体插入容器尾部
					}
					//int nSize = recvfromDB.size();计算容器的容量,nSize个结构体
				}
			}
			return recvfromDB;//返回该容器
		}
		//下面调用这个函数
		void CServeC::OnRefresh()
		{
			std::vector<task> Recv; //定义一个模板
			Recv = cnt.RefreshTask(recv.user);//根据用户发来的信息查询,返回一个模板到Recv
			std::vector<task>::iterator pd = Recv.begin();//获取容器中第一个数据的指针,iterator为迭代器名(广义上的指针)
			int nSize = Recv.size();//获取容器大小
			this->Send(&nSize,sizeof(nSize));//向客户端发送检索到的数据条数
			for(pd;pd != Recv.end();pd++)//遍历发送至客户端,这是通过结构体传送,不能直接传递vector
			{
				this->Send(pd,sizeof(*pd));
			}
		}
		
		//使用全局变量的时候,为了防止互相包含头文件导致的重复定义,在工程中应该
		//在StdAfx.cpp中定义,在其他要使用的文件中声明 
		//例如:Bool nFlags  在StdAfx.cpp
		//	  extern nFlags  在$.cpp
					 
		//对于函数的默认参数的使用
		//函数原型:对于多个默认值的函数,应该从右向左定义
		void OnReceive(LPCSTR szName = NULL,int nFlags );//错,应从右开始定义,必须调换一下参数的顺序
		void OnReceive(LPCSTR szName = NULL,int nFlags = 7);//否则只能给另一个参数定义默认参数
		void OnReceive(LPCSTR szName ,int nFlags = 7,float fTest = 3.0);//正确
		//定义时不需要加默认参数值
		void OnReceive(LPCSTR szName ,int nFlags,float fTest);//正确
				
		//有一些函数和变量需要共享,以便在任何地方都能轻松调用
		//可以将需要共享的函数和参数定义成app类的公有成员函数或参数
		//然后在需要调用的地方通过theApp这个全局变量调用
		//例:
			#include "SocketC.h""
			class CClientApp: public CWinApp
			{
			public:
				CClientApp();//类构造函数
				CSocketC m_sock;//需要共享的参数和函数
				void OnRefresh();
				……
			}
		//在需要使用的文件中
			extern CClientApp theApp;
			theApp.m_sock.Recvive();
			theApp.OnRefresh();	//有一些函数和变量需要共享,以便在任何地方都能轻松调用
		//可以将需要共享的函数和参数定义成app类的公有成员函数或参数
		//然后在需要调用的地方通过theApp这个全局变量调用
		//例:
			#include "SocketC.h""
			class CClientApp: public CWinApp
			{
			public:
				CClientApp();//类构造函数
				CSocketC m_sock;//需要共享的参数和函数
				void OnRefresh();
				……
			}
		//在需要使用的文件中
			extern CClientApp theApp;
			theApp.m_sock.Recvive();
			theApp.OnRefresh();


 

#include <iostream> #include <fstream> #include <string> #include<time.h> using namespace std; void welcome(); //欢迎子函数 void choose(); //选择子函数 void banciluru(); //班次录入子函数 void liulanbancixinxi(); //浏览班次信息子函数 void shoupiao(); //售票子函数 void tuipiao(); //退票子函数 void quit(); //退出子函数 void chaxun(); //查询子函数 int loading(); //文件载入子函数 const int MAX=20; //使数组最大值可调 const int N=50; struct time //时间结构体,为了与系统时间做比较. { int hour; int minutes; }; struct ticket //车票结构体,包含车次,时间结构体,起点,终点,行车时间,额定载客量,已订票人数 { int Number; struct time Setout; char Qidian[MAX]; char Zhongdian[MAX]; float Lasttime; int Fixnumber; int Fixednumber; }car[N]; //主函数 int main() { welcome(); return 0; } //欢迎子函数 void welcome() { cout<<"________________________________________________________"<<endl; cout<<"| 欢 * 迎 * 使 * 用 * 火 * 车 * 票 * 管 * 理 * 系 * 统 |"<<endl; cout<<"| |"<<endl; cout<<"| 本管理系统有以下功能: |"<<endl; cout<<"| |"<<endl; cout<<"| 1 班次录入 |"<<endl; cout<<"| 2 浏览班次信息 |"<<endl; cout<<"| 3 查询火车信息 |"<<endl; cout<<"| 4 购买火车票 |"<<endl; cout<<"| 5 退订火车票 |"<<endl; cout<<"| 6 退出系统 |"<<endl; cout<<"|______________________________________________________|"<<endl; cout<<"请输入所要执行功能前的数字: "<<endl; choose(); } //选择子函数 void choose() { string i; cin>>i; if(i=="1") banciluru(); if(i=="2") liulanbancixinxi(); if(i=="3") chaxun(); if(i=="4") shoupiao(); if(i=="5") tuipiao(); if(i=="6") quit(); else { cout<<"输入有误!请在数字1-5之间重新您的输入选择!"<<endl; choose(); } } //班次录入子函数 void banciluru() { string t="1"; ofstream outfile; outfile.open("班次信息.txt",ios::app); for(int i=0;t=="1";i++) { cout<<"请输入需要添加的火车班次:"<<endl; cin>>car[i].Number; cout<<"请输入此班次发车时间(格式:先输入小时,回车,再输入分钟):"<<endl; cin>>car[i].Setout.hour; cin>>car[i].Setout.minutes; cout<<"请输入出发站:"<<endl; cin>>car[i].Qidian; cout<<"请输入终点站:"<<endl; cin>>car[i].Zhongdian; cout<<"请输入行车时间:"<<endl; cin>>car[i].Lasttime; cout<<"请输入额定载客量:"<<endl; cin>>car[i].Fixnumber; cout<<"请输入已订票人数:"<<endl; cin>>car[i].Fixednumber; outfile<<car[i].Number<<" "<<car[i].Setout.hour<<" "<<car[i].Setout.minutes<<" "<<car[i].Qidian<<" "<<car[i].Zhongdian<<" "<<car[i].Lasttime<<" "<<car[i].Fixnumber<<" "<<car[i].Fixednumber<<endl; cout<<"是否继续录入?(是请输1,否请输任意键返回主菜单)"<<endl; cin>>t; if(t!="1") { welcome(); } } outfile.close(); cout<<"输入回车键返回"<<endl; getchar(); getchar(); welcome(); } //浏览班次子函数 void liulanbancixinxi() { int c; c=loading(); cout<<"班次"<<" "<<"发车时间"<<" "<<"起点站"<<" "<<"终点站"<<" "<<"行车时间"<<" "<<"额定载客量"<<" "<<"已订票人数"<<endl; for(int i=0;i<c;i++) { cout<<car[i].Number<<" "<<car[i].Setout.hour<<":"<<car[i].Setout.minutes<<" "<<car[i].Qidian<<" "<<car[i].Zhongdian<<" "<<car[i].Lasttime<<" "<<car[i].Fixnumber<<" "<<car[i].Fixednumber<<endl; time_t tval; struct tm *now; tval = time(NULL); now = localtime(&tval); if((now->tm_hour==car[i].Setout.hour&&now->tm_min>car[i].Setout.minutes)||(now->tm_hour>car[i].Setout.hour)) cout<<"此车已发出"<<endl; } cout<<"输入回车键返回"<<endl; getchar(); getchar(); welcome(); } //车次查询子函数 void Numbersearch(); void Finalsearch(); void chaxun() { string t; cout<<"1.按班次查询"<<endl; cout<<"2.按终点站查询"<<endl; cout<<"请输入您想选择的查询方法前的数字:"<<endl; cin>>t; if(t=="1") Numbersearch(); if(t=="2") Finalsearch(); if(t!="1"||t!="2") { cout<<"输入有误,请重新输入"<<endl; chaxun(); } }
学生管理系统(★★★) 使用下面的数据,用C设计一个简单的学籍管理系统实现出最基本的功能。 学生基本信息文件(A.TXT)由以下内容: 学号 姓名 性别 宿舍号码 电话号码 01 张成成 男 501 87732111 02 李成华 女 101 87723112 03 王成凤 女 101 87723112 04 张明明 男 502 87734333 05 陈东 男 501 87732111 06 李果 男 502 87734333 07 张园园 女 102 87756122 … …. .. … ……….. 学生成绩基本信息文件(B.TXT)有以下内容: 学号 课程编号 课程名称 学分 平时成绩 实验成绩 卷面成绩 综合成绩 实得学分 01 A01 大学物理 3 66 78 82 02 B03 高等数学 4 78 -1 90 01 B03 高等数学 4 45 -1 88 02 C01 VF 3 65 76 66 … …. ………. .. .. … 功能要求及说明: (1)数据录入及插入功能: 对A..TXT和B.TXT进行数据录入,只录入每个学生的学号、课程编号、课程名称、学分、平时成绩、实验成绩、卷面成绩共7个数据. 综合成绩、学分由程序根据条件自动运算。并且最后也可插入学生数据。 综合成绩的计算:如果本课程的实验成绩为-1,则表示无实验,综合成绩=平时成绩*30%+卷面成绩*70%; 如果实验成绩不为-1,表示本课程有实验,综合成绩=平时成绩*15%+实验成绩*.15%+卷面成绩*70% . 实得学分的计算: 采用等级学分制. 综合成绩在90-100之间 ,应得学分=学分*100% 综合成绩在80-90之间 ,应得学分=学分*80% 综合成绩在70-80之间 ,应得学分=学分*75% 综合成绩在60-70之间 ,应得学分=学分*60% 综合成绩在60以下 ,应得学分=学分*0% (2)显示功能:要求可显示原A、B文件中的内容,并且插入信息后还可显示新内容。 (3)删除功能:当在A.TXT中删除一个学生时,自动地在B.TXT中删除此人所有信息,并显示输出结果。 (4) 排序功能:能实现选择按综合成绩或实得学分升序或降序排序并显示数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值