大二的在校生,佛山某放假大学,早在圣诞节就结束期期末考试的我们,算是进入了“准放假”,但是学院老师们并不会让我们回家也不会让我们那么闲,按照传统艺能,两周留下来敲代码是必不可少的。这一次,学院老师要折腾我们的是用MFC做一个小软件。
我一听傻了,啥事MFC???
百度了下这东西,又臭又长,可以可以说完全是0基础,所以弄了一周才弄完,目前仍有很多bug
是算法逻辑上的bug(或者说功能还没完善)但是程序怎么运行,应该都是不会崩溃了的
因为github上传组件出了点问题,还上传不到GitHub,又想趁着刚做完东西还没忘赶紧把过程中学到的东西记录下来,代码就先打包放到百度云上了。
链接: https://pan.baidu.com/s/1NRThoMOVHwv0UAZaOjr8qg 提取码: g5tm
目前是长这样的,没有设计天赋,所以外观样式就看且看吧。
先说一下怎么使用这个东西,在文件包里有个 公共自行车站点信息.csv
在导入文件那里点击导入文件,就可以进行一系列操作,图片上有的功能都可以实现,可能包上的版本多了个刷新功能,不过那个没什么用,就删除了
这里必须说一下:贴的代码很多都是基于我项目那个类的成员函数,若想移植代码,切不可盲目移植,否则只会扰乱你的代码阅读
CS20180710128 是工程名
CCS20180710128Dlg 是工程目录
想要移植,得把工程名改成你自己工程的名字,还要在工程的类中额外声明你自己的函数(或者只将函数内容移植加到你自己响应函数中,这样就不用额外声明)
代码片
.
//打开文件//打开文件//打开文件//打开文件//打开文件//打开文件//打开文件//打开文件//打开文件//打开文件
void CCS20180710128Dlg::OnChoose()
{ if(S_FILE_OPEN_FLAG=1) m_combox1.ResetContent();
// TODO: Add your control notification handler code here
BOOL isOpen = TRUE; //是否打开(否则为保存)
CString defaultDir = L"D:\\"; //默认打开的文件路径
CString fileName = L""; //默认打开的文件名
CString filter = L"文件 (*.txt; *.csv)|*.txt;*.csv;||"; //文件过虑的类型
CFileDialog openFileDlg(isOpen, defaultDir, fileName, OFN_HIDEREADONLY|OFN_READONLY, filter, NULL); //寻找路劲
INT_PTR result = openFileDlg.DoModal();
if(result ==IDOK) {
fileName = openFileDlg.GetPathName();
CStdioFile f;
CFileException e;
if(!f.Open(fileName, CFile::modeRead, &e))
{
TRACE(_T("File could not be opened %d\n"), e.m_cause);
}
else{
MessageBox("导入文件成功!");
S_FILE_OPEN_FLAG=1; //文件打开成功,可以进行后续的一些操作
}
CString str="";
f.ReadString(str);//每次读取一行,并且赋值给str,过滤第一行
f.ReadString(str);
int i=0;
while (str!="" )
{
CStringArray* result = DivString(str);//按照excel上的顺序依次下来
station_num.Add(_ttoi(result->GetAt(0)));//站点序号存进去,后续查找功能要用到,如果不需要查找功能,这三句都可以不要
station_name.Add(result->GetAt(1));//站点姓名 同上
// int m_num = _ttoi(m_no_str); //m_no_str是每一行的第一个串,也就是序号那一列
// MessageBox(m_no);
// if(m_num%10==0) //筛选,序号是10的倍数才进入box可供选择
// {
temp_str.Add(str); //全局串变量 temp_str,每次循环到这,他就添加多一条串(上不封顶?)
m_combox1.InsertString(i,str);//按i的顺序插入项目
// }
f.ReadString(str);
i++;
}
m_combox1.SetCurSel(0); //设置第nIndex项为显示的内容
}
// CWnd::SetDlgItemTextW(IDC_EDIT_SRC, filePath);
}
大概的步骤,在注释中已经解释清楚
m_combox1,是我组框的关联变量,调整该变量的值,在相应的组框上就会有显示
temp_str,是一个全局的字符串数组类型变量 CStringArray temp_str 至于为什么叫这个名字?当时想的是只使用这个变量一下下,觉得会是暂时的,但是到后面才发现这个变量用处实在太大了!!改起来很多地方都要改,于是就没再去修改名字
并且要说一下 代码段中的DivString(),不是系统函数,而是自己添加在,函数定义在CS20180710128Dlg.h这个头文件中
//字符串分隔//字符串分隔//字符串分隔//字符串分隔//字符串分隔//字符串分隔//字符串分隔//字符串分隔//字符串分隔//字符串分隔
CStringArray* DivString(CString test)
{
CStringArray* m_result = new CStringArray; //开辟一个cstringarray地址
while(TRUE)
{
int index = test.Find(_T(",")); //一直寻找 "," 直到没有,进入if
if(index == -1)
{
m_result->Add(test);
return m_result; //返回一个cstringarray的地址,这个地址是个分割了字符串的数组
}
CString test1 = test.Left(index); //没进入if之前,建立test存储index左边(逗号左边)的字符串
m_result->Add(test1); //这个cstringarray变量m_result就逐个储存
test = test.Right(test.GetLength()-index-1); //test随着循环变成第i个逗号右边的串(i=1,2,3....)
}
}
字符串分割,在很多地方都能用到
如果试过在组框内选了一个站点后再按“选择该点”,会发现下面可以显示其相关信息
话不多少,贴代码
void CCS20180710128Dlg::OnChosePoint()
{ if(S_FILE_OPEN_FLAG==1)
{
int nIndex = m_combox1.GetCurSel();//组合框内选择的选项,传递到这里
// MessageBox(temp_str.GetAt(nIndex)); //提示用
CStringArray* result = DivString(temp_str.GetAt(nIndex));
m_no=result->GetAt(0); //站点序号
m_name1 =result->GetAt(1); //站点名
m_adress1=result->GetAt(2); //站点地址
m_sum1=result->GetAt(3); //站点单车总数
m_useful1=result->GetAt(4); //可用单车数目
m_serviec1=result->GetAt(5); //服务状态
m_long1=result->GetAt(6); //经度
m_lati1=result->GetAt(7); //纬度
m_longlati1=result->GetAt(8)+","+result->GetAt(9); //经纬度
UpdateData(false);
}
else
MessageBox("未检测到导入文件,请先导入文件!");
}
比较简单
m_combox1.GetCurSel();是获取组框当前选择的序号并返回一个整形数值
UpdateData(false); 用于负责信息的更新显示在静态文本控件上对应代码段分割的9段
类向导中的变量设置,设置完成后,摆放到特定的位置,就能在点击响应后获取信息并更新
坐标点的着色绘制涉及到两个功能,显示站点、查询用户轨迹,原理都很简单
首先要声明绘图函数 在CS20180710128lg.h的public声明中有
void DrawEllipse(int x,int y,int r,COLORREF color);
接着你可以在任意头文件中添加以下定义代码
//根据圆心和半径画圆
void CCS20180710128Dlg::DrawEllipse(int x,int y,int r,COLORREF color)
{ CDC *dc;
dc = m_map01.GetDC(); // m_map01是picture控件对应的变量
//将兼容dc上的绘图,拷贝到目标控件上
dc->BitBlt(0,0,rect.Width(),rect.Height(),&m_MemDC,0,0,SRCCOPY);//将图片左上角作为坐标(0,0)
CBrush brush,*oldbrush;
brush.CreateSolidBrush(color);
oldbrush=dc->SelectObject(&brush);
dc->Ellipse(x-r,y-r,x+r,y+r);
dc->SelectObject(oldbrush);
}
//参数中中x,y,r分别为坐标以及半径,color参数是颜色参数 COLORREF的类对象的实例化
//比如 COLORREF red=RGB(255,0,0);
获取颜色的RGB值有很多方法,百度上有一大堆,这里就不细说
void CCS20180710128Dlg::OnBUTTON4test_draw()
{
if(S_FILE_OPEN_FLAG==1)
{
CString longitude; //经纬度串以及经纬度数值
CString latitude;
double longitude_num;
double latitude_num;
COLORREF blue; //颜色变量,RGB值
blue=RGB(123,104,238);
int i=0;
for(i=0;i<585;i++)
{
CStringArray* result = DivString(temp_str.GetAt(i));
longitude=result->GetAt(6); //经度
latitude =result->GetAt(7); //纬度
longitude_num=((42.1-atof(longitude))/0.4)*580;
latitude_num=((atof(latitude)+87.8000)/0.2506)*275;
DrawEllipse(latitude_num,longitude_num,3,blue);
}
}
else
MessageBox("未检测到导入文件,请先导入文件!");
}
S_FILE_OPEN_FLAG==1,是定义的一个全局整形变量,用来判断是否导入文件,导入文件之前值为0
导入之后值为1
主要代码是 五百多次 循环中
在导入文件那一功能中说过的,temp_str 这一全局字符串
GetAt是数组类变量的方法,在字符串数组中同样适用,不熟悉的同学将他看作成
int a「10」,a[0],a[1],a[2]…等,效果一样,会用就行
CString longitude; //经纬度串以及经纬度数值
CString latitude;
是来暂时存放经纬度对应字符串的字符串
atof(CString);将一字符串转换为其对应双精度浮点数
longitude_num=((42.1-atof(longitude))/0.4)*580;
latitude_num=((atof(latitude)+87.8000)/0.2506)275;
580275 是图片的分辨率,也是通过如此将经纬度坐标转换成图片上的像素点并绘图。
and图片的来源是经度-87.8到-87.5694
纬度41.7到42.1这一区间(作业要求)
根据图片长短宽高580 与 275两个数值需要更改,是图片的像素大小
大概差不多就行
void CCS20180710128Dlg::OnButton_point_diaplay() //选择一个点,这个点的颜色变红
{ if(S_FILE_OPEN_FLAG==1)
{
//颜色部分
COLORREF red;
red=RGB(255,0,0);
//计算部分
int nIndex = m_combox1.GetCurSel();//组合框内选择的选项,传递到这里
CStringArray* result = DivString(temp_str.GetAt(nIndex));
CString longitude; //经纬度串以及经纬度数值
CString latitude;
double longitude_num;
double latitude_num;
longitude=result->GetAt(6); //经度
latitude=result->GetAt(7); //纬度
longitude_num=((42.1-atof(longitude))/0.4)*580;
latitude_num=((atof(latitude)+87.8000)/0.2506)*275;
//绘图部分
//CDC *pDC; //绘图控件
//pDC = m_map01.GetDC();
//pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_MemDC,0,0,SRCCOPY);
DrawEllipse(int(latitude_num),int(longitude_num),5,red);
}
else
MessageBox("未检测到导入文件,请先导入文件!");
}
int nIndex = m_combox1.GetCurSel();组合框的选项传递给nindex,再从串数组中获取该段,再获取该段对应的经纬度部分,转换成数值,绘制再地图上,没有什么花里胡哨的操作
void CCS20180710128Dlg::OnBUTTOwithdraw()
{
Invalidate(TRUE);
}
没啥好说的,一行代码搞定,直接ctrl cv,换都不用换
关于用户,至少先要有个用户结构体或者类吧?
是的肯定要,我定义了一个用户类(这tm就是结构体吧?)
class User
{ public:
CString name; //名
CString sex; //性别
CString age; //年龄
CString startpoint_name; //起始点名称
CString endpoint_name; //
CString startpointll; //起始点经纬度
CString endpointll;
int starttime; //起始时间
int endtime;
double distance;//距离
double long_start;
double long_end;
double lati_start;
double lati_end;
};
以上是用户类的声明,至于为什么这么繁琐,可能需要看到下面的代码才知道,当然很多地方都是可以优化的,但是本人比较懒
//显示用户列表//显示用户列表//显示用户列表//显示用户列表//显示用户列表//显示用户列表//显示用户列表//显示用户列表
void CCS20180710128Dlg::OnButton_user_list()
{if(U_FILE_OPEN_FLAG==1){MessageBox("用户列表已经打开了哦。\n请勿重复打开"); return ;} //打开一次后就不能再打开(否则异常)
if(S_FILE_OPEN_FLAG==1)
{
CString fileName ; //默认打开的文件名
fileName = _T("C:\\Users\\70426\\Desktop\\user.csv"); //文件的绝对路径,使用相对路径会崩溃 why?
CStdioFile f;
CFileException e;
if(!f.Open(fileName, CFile::modeRead, &e))
{
TRACE(_T("File could not be opened %d\n"), e.m_cause);
}
else{
U_FILE_OPEN_FLAG=1; //文件打开成功,可以进行后续的一些操作
}
CString str="";
f.ReadString(str); //两次实现,过滤掉第一行
f.ReadString(str);
int i=0;
while(str!="" && i<USER_NUM)
{ CString temp;
user_str.Add(str);
CStringArray* user_info = DivString(user_str.GetAt(i)); //这里是按user.csv文件赋值 user_information用户信息
CStringArray* station_start =DivString(temp_str.GetAt((rand() % (585-1+1))+ 1)); //随机取一个站点信息给这个b,起点
CStringArray* station_end =DivString(temp_str.GetAt((rand() % (585-1+1))+ 1)); //随机取一个站点给这个b,终点
user[i].name=user_info->GetAt(0); //name
user[i].age=user_info->GetAt(1); //age
user[i].sex=user_info->GetAt(2); //sexual
user[i].startpoint_name=station_start->GetAt(1); //startpoint_name
user[i].endpoint_name = station_end->GetAt(1); //endpoint
user[i].starttime = ((rand() % (3-1+0))+ 0)*10; //starttimr created by a random-function
user[i].endtime=((rand() % (15-1+4))+ 4)*10; //same as shangmian
user[i].startpointll=station_start->GetAt(8)+","+station_start->GetAt(9); //start-longitude&latitude
user[i].endpointll= station_end ->GetAt(8)+","+station_end->GetAt(9); //end-longitude&latitude
user[i].long_start=atof(station_start->GetAt(6));
user[i].long_end= atof(station_end->GetAt(6));
user[i].lati_start=atof(station_start->GetAt(7));
user[i].lati_end =atof(station_end->GetAt(7));
double long_dis=user[i].long_start-user[i].long_end; //计算经纬度代表的真实距离
double lati_dis=user[i].lati_start-user[i].lati_end;
double temp_distance=sqrt((long_dis*long_dis)+(lati_dis*lati_dis))*1110000; //1经纬度111公里
user[i].distance=temp_distance;
//以上为信息赋值部分,以下是列表框的添加以及循环
m_list_user.InsertString(i,user[i].name); //把名字按顺序显示在列表框上
i++;
f.ReadString(str);
}
}
else
MessageBox("未检测到地区数据文件,请先导入文件!");
}
整体操作都比较简单,如果看得懂前面的功能,那么这个用户信息赋值所用到的语句,那肯定也都能看得懂
((rand() % (max-1+min))+ min)*10;
代码段中出现这样的语句是随机数(其实并不是随机的,但由于种种因素可以将它看成是随机的)
作用是在min到max之间选取一个整数,闭区间
关于随机函数的使用,CSDN上有很多优质的解析,点击搜索就能出很多,这里就深入说。
distance最后乘以111000,是百度上说的比例,真实值如何不得而知,但是作为作业,能用就行了对嘛!~
可以看到代码中有涉及到文件的读写,
fileName = _T(“C:\Users\70426\Desktop\user.csv”);
这需要改成压缩包内user.csv的路径,或许采取相对路径会好很多,但是不知道为什一直都会崩溃?
//显示用户信息//显示用户信息//显示用户信息//显示用户信息//显示用户信息//显示用户信息//显示用户信息//显示用户信息
void CCS20180710128Dlg::OnBUTTONuser()
{CString str_strattime,str_endtime,str_distance;
int index=-1;
index=m_list_user.GetCurSel();
if(index!=(-1))
{ //辅助变量
str_strattime.Format("%d",user[index].starttime);
str_endtime.Format("%d",user[index].endtime);
str_distance.Format("%lf",user[index].distance);
//前方施工^_^
CString information="姓名:"+user[index].name+"\n"+
"性别:"+user[index].sex+"\n"+
"年龄:"+user[index].age+"\n"+
"骑行起点:"+user[index].startpoint_name+"\n"+
"骑行终点:"+user[index].endpoint_name+"\n"+
"起点坐标:"+user[index].startpointll+"\n"+
"终点坐标:"+user[index].endpointll+"\n"+
"骑行开始时间:"+str_strattime+"\n"+
"骑行结束时间:"+str_endtime+"\n"+
"骑行距离:"+str_distance+"米";
//施工完成^_^
if( MyMessageBox(NULL, information, "用户个人信息", MB_OKCANCEL)==1)//点击查看返回1 ,取消返回2
{// Invalidate(TRUE);
int i;
// CString str1,str2,str3,str4;
double x,y,k,x0,y0;
double longitude_num;
double latitude_num;
COLORREF color;
color=RGB(30,144,255);
CString start_str=user[index].name+" 的起点在这",end_str=user[index].name+" 的终点在这";
CString journey_str;
CDC *pDC;
pDC = m_map01.GetDC(); // m_map是picture控件对应的变量
//将兼容dc上的绘图,拷贝到目标控件上
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_MemDC,0,0,SRCCOPY);
for(i=0;i<6;i++)
{ //虚假的斜截式
k=(user[index].long_end-user[index].long_start)/(user[index].lati_end-user[index].lati_start);
x=user[index].lati_start+(i/5.0)*(user[index].lati_end-user[index].lati_start);
x0=user[index].lati_start;
y0=user[index].long_start;
y=k*(x-x0)+y0; //真正的斜截式
journey_str=user[index].name+"的旅途经过这里~";
longitude_num=((42.1-y)/0.4)*580; //经纬度转换坐标
latitude_num=((x+87.8000)/0.2506)*275;
/* 检验代码
str1.Format("%lf",longitude_num);
str2.Format("%lf",latitude_num);
str3.Format("%lf",user[index].lati_end);
str4.Format("%lf",user[index].lati_start);
str1=str1+"\n"+str2+"\n"+str3+"\n"+str4;
MessageBox(str1);
*/
//绘图部分
DrawEllipse(int(latitude_num),int(longitude_num),5,color);
//画圆成功,但是要去改一下原函数(或者将00坐标设置为左上角)
/* pDC->SetPixel(latitude_num, longitude_num, color);
*/ //文本的制作
if(0<i&&i<5) pDC->DrawText(journey_str,CRect(int(latitude_num)+6, int(longitude_num)+50,int(latitude_num)+200,int(longitude_num)-50),DT_SINGLELINE|DT_LEFT|DT_VCENTER);
else if(i==0)pDC->DrawText(start_str,CRect(int(latitude_num)+6, int(longitude_num)+50,int(latitude_num)+200,int(longitude_num)-50),DT_SINGLELINE|DT_LEFT|DT_VCENTER);
else {pDC->DrawText(end_str,CRect(int(latitude_num)+6, int(longitude_num)+50,int(latitude_num)+200,int(longitude_num)-50),DT_SINGLELINE|DT_LEFT|DT_VCENTER);
}
Sleep(1000);
}
}
}
else
MessageBox("请选择一个用户。");
}
原理很简单在listbox框内选择一个,传入一个index值,并把对应的用户信息调出来,以messagebox的方法显示出来,有更好的方法可以显示出来,但笔者是直男,直男审美嘛,就不要太在意了
调出信息后可以点击查看用户轨迹,原理是两点成线,已知两点坐标,可以求出这条直线,再在起始点之间分隔出几个点,进行标明或者连线就可以了(我这里使用的方法是用点来标明)
void CCS20180710128Dlg::OnBUTTON_search()
{if(S_FILE_OPEN_FLAG==1){
if(m_search_num==0) //num按钮选中时为0,name按钮选中时为1,其余状况是没选到
{
int nIndex=half_saerch(&station_num,_ttoi(m_search));
if(nIndex!=(-1))
{
UpdateData(true);
CStringArray* result = DivString(temp_str.GetAt(nIndex));
m_no=result->GetAt(0); //站点序号
m_name1 =result->GetAt(1); //站点名
m_adress1=result->GetAt(2); //站点地址
m_sum1=result->GetAt(3); //站点单车总数
m_useful1=result->GetAt(4); //可用单车数目
m_serviec1=result->GetAt(5); //服务状态
m_long1=result->GetAt(6); //经度
m_lati1=result->GetAt(7); //纬度
m_longlati1=result->GetAt(8)+","+result->GetAt(9); //经纬度
UpdateData(false);
MessageBox("查找成功,该站点信息已在右侧呈现。");
}
else
{
MessageBox("差找不到该序号站点,请检查导入文件是否包含该站点。");
}
}
else if(m_search_num==1)
{ int IS_FIND=0;
int nIndex;
for(nIndex=0;nIndex<station_name.GetSize();nIndex++)
{
if((station_name.GetAt(nIndex)).Find(m_search,0)!=(-1))
{
IS_FIND=1;
break;
}
}
if(IS_FIND)
{
UpdateData(true);
CStringArray* result = DivString(temp_str.GetAt(nIndex));
m_no=result->GetAt(0); //站点序号
m_name1 =result->GetAt(1); //站点名
m_adress1=result->GetAt(2); //站点地址
m_sum1=result->GetAt(3); //站点单车总数
m_useful1=result->GetAt(4); //可用单车数目
m_serviec1=result->GetAt(5); //服务状态
m_long1=result->GetAt(6); //经度
m_lati1=result->GetAt(7); //纬度
m_longlati1=result->GetAt(8)+","+result->GetAt(9); //经纬度
UpdateData(false);
MessageBox("查找成功,该站点信息已在右侧呈现。");
}
else
{
MessageBox("差找不到该站点名,请检查导入文件是否包含该站点。");
}
}
else MessageBox("请选择一个选项哦。");
}
else MessageBox("未检测到导入文件,请先导入文件");
}
half_search();折半查找,因为查找对象是有序的,用折半查找法进行查找,是最优查找方法
其定义:
int half_saerch(CArray <int,int> *C, int num) //者半查找
{
int low=0,high=C->GetSize()-1;
int mid=(low+high)/2;
while(low<=high)
{
if(C->GetAt(mid)==num) return mid;
else if(num>C->GetAt(mid)) low=mid+1;
else if(num<C->GetAt(mid)) high=mid-1;
mid=(low+high)/2;
}
return -1;
}
station_num和station_name是两个全局的数组,用来储存站点的序号和名称数据,其存入数据的过程再文件导入那一部分中
根据名称查找用的方法则是字符串匹配,名字的话没有什么规律,所以只能通过循环一个个的看一遍
输入框输入变量m_search和按钮变量m_search_num,当选中“序号”按钮的时候返回值为0,选择“名称”的时候按钮返回值为1,搭配if语句来选择是以什么形式进行查询
者半查找法是数据结构的内容,搬过来改了一下,原理是比较简单的,不懂得小伙伴可以参考一下论坛上的文章,通俗易懂。
功能的话,好像就没了吧,虽然作为一个新手,不怎么会做是正常,但花了四五天的时间才弄成一个这个东西,效率是不太好的,毕竟摸鱼是人的本性。
文章的初心是记录自己的学习经验经历,讲解的东西看不懂也很正常,只能说是一个思路吧(一周的制作经历告诉我,思路比实现过程重要得多,有思路,大概率就能将实现过程实现,但是没有思路,就可能要一个地方试错很多次)这对效率来说是一个很大的分界。
希望能帮到有兴趣或者正在制作作业的你,这是我的第一篇文章。
好吧,看我这篇没营养的东西估计没什么用,倒不如贴点实在的链接:
MFC中 CDC类祥解
combox(组框)的运用
如何将CString类型转换成int整形?
实现CDC绘图重绘
关于CList的用法
数组CArray操作方法
Listbox控件的使用
0基础MFC如何建立第一个对话框?
emmm,就酱吧“_^