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

本文介绍了一款基于C/S架构的任务管理系统,采用TCP协议通信,MySQL数据库存储数据,支持多用户在线操作。文章详细阐述了系统的开发思路和技术实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 任务管理系统功能简述

该系统基于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();


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值