1.有关控件的设置
1.1控件的放置
1.2设置控件参数
1.3添加变量
2. 类的添加
2.1窗口类的添加
2.2普通类的添加
3. 窗口间的通信
3.1创建子窗口
3.2生成和打开子窗口
3.3子窗口调用父窗口的成员
3.4父窗口调用子窗口控件
4.子线程的建立与使用
4.1创建新线程
4.2子线程调用主线程函数
5.消息处理函数
5.1系统内部定义的消息处理函数
5.2自定义消息
5.3发送消息
6.文件的创建与写入
6.1获取程序路径
6.2创建文件并写入
7. 背景的设置
7.1设置背景
7.2出现的错误
8. 控件与背景的自适应变化
8.1背景自适应变化
8.2控件及字体自适应变化
8.3出现的错误
9. 其他错误
9.1断言错误
9.2堆栈被破坏
9.3参数类型错误
1.有关控件的设置
1.1控件的放置
创建出主窗口后,可从左方的对话框编辑器中拖拽所需控件到窗口上即可实现控件的放置。若没有所需控件则可点击上方“工具(T)”,点击“选择工具箱项”,在弹出的窗口内选择自己需要的控件,添加值工具箱内。
1.2设置控件参数
在资源视图中鼠标左键单击控件或窗口空白处,在右下方会出现“属性”框。或在类视图中右击类,点击菜单栏最下方的“属性”,亦可出现“属性”框,在属性框中可对控件或窗口的ID进行修改,以便进行管理。还可以对控件的“行为”项以及“外观”项进行修改,例如:是否可写、是否可见以及设定控件的外观等方面。
1.3添加变量
右击控件,在菜单栏中选择“添加变量”,在弹出的“添加成员变量向导”窗口中可以选择要添加的变量的类型以及名称,点击确定即可成功添加变量。其中“control”类型的变量搭配成员函数可以对控件进行各方位的设置与操作。具有编辑框功能的控件可以添加“value”类型的变量,但此变量只能获取和更改编辑框的内容。每个控件中每种类型的变量只能添加一次。
2.类的添加
2.1窗口类的添加
右击窗口空白处,在菜单栏中选择“添加类”,可在“类向导”窗口中设定类的名称,以此添加的类,会自动添加基本的对话框数据,以及其上方的控件的ID和变量。在类视图的“属性”框中可以点击消息按钮和重写按钮来重新设定一些函数的内部流程。
2.2普通类的添加
或在“类视图”或“解决方案资源管理器”中右击项目名称,在菜单栏中选择“添加类”,在“添加类”窗口中设定类的名称,以此添加的类,可在头文件中自由添加成员变量和成员函数。
3.窗口间的通信
3.1创建子窗口
在资源视图中右击“Dialog”选项卡,在菜单栏中选择“插入Dialog(E)”,即可成功创建出新的窗口,之后经过添加控件、修改ID、生成窗口类等操作就可以完成子窗口的创建。
3.2生成和打开子窗口
子窗口分为模态和非模态两种,模态窗口生成后不能对其他窗口进行操作,只有关闭模态窗口后才可以操作其他窗口。而非模态窗口再打开后仍然可以对其他窗口进行操作。
模态对话框的弹出函数示例:
1.INT_PTR nRes; // 用于保存DoModal函数的返回值
2.
3. CTipDlg tipDlg; // 构造对话框类CTipDlg的实例
4. nRes = tipDlg.DoModal(); // 弹出对话框
非模态对话框的弹出函数示例:
1.if (NULL == m_pTipDlg)
2. {
3. // 创建非模态对话框实例
4. m_pTipDlg = new CTipDlg();
5. m_pTipDlg->Create(IDD_TIP_DIALOG, this);
6. }
7. // 显示非模态对话框
8. m_pTipDlg->ShowWindow(SW_SHOW);
非模态对话框关闭后并不会销毁对话框,而是将其隐藏起来。在上方示例中,若将最后一行去掉,那么在创建出非模态对话框后其并不会弹出,但其仍然是存在的。
3.3子窗口调用父窗口的成员
首先,若想要调用其他窗口的成员函数或成员变量,需要先包含其头文件。
其次,子窗口并不能直接调用父窗口的成员变量或成员函数。一般来说有两种方法,一种是定义一个父窗口类的对象指针,通过这个指针来对父窗口中的控件和函数进行调用。第二种是子窗口向父窗口发送消息,父窗口接收到消息后,由父窗口调用自己的控件或成员函数。
通过指针进行调用示例:
CITcard_test4Dlg m_transmitNET;//建立父窗口类成员指针
m_transmitNET=(CITcard_test4Dlg )GetParent();//获取父窗口指针
后面即可通过m_transmitNET->进行调用。
通过发送消息调用示例:
#define UM_SAVETEST WM_USER+240//1264父窗口与子窗口必须都包含消息声明
m_transmitNET->PostMessageW(UM_SAVETEST,(WPARAM)strWparam.AllocSysString(),NULL);//在子窗口内向父窗口发送消息
LRESULT CITcard_test4Dlg::WindowProc(UINT message,WPARAM WParam,LPARAM LParam)
{
if(message == UM_OPENPOWER )
{
//OnOpenPower(WParam,LParam);
CommstrTemp2 = CITcard_test4Dlg::ReadData(2);
return 1;
}
·······
else
{
return CDialog::WindowProc(message,WParam,LParam);
}
}//父窗口接收到后会进行判断,并调用相关函数或控件
3.4父窗口调用子窗口控件
父窗口调用子窗口内的控件同样可以通过发送消息来进行调用。但通过指针调用的话就需要另一种方法,因为非模态对话框在被创建出来后就会销毁句柄,所以想要获得子窗口的指针就需要在其初始化时将其保留起来。
4.子线程的建立与使用
4.1创建新线程
首先在头文件中进行声明
UINT DetectionRxThread(LPVOID pParam);
CWinThread m_DetectionRxThread;
在源文件中创建线程
m_DetectionRxThread = AfxBeginThread(DetectionRxThread,(LPVOID)this,0);
UINT DetectionRxThread(LPVOID pParam)
{
return 0;
}
4.2子线程调用主线程函数
在 MFC 编程中子线程是不能直接访问主线程里的控件对象的,这样极容易造成访问异常,导致消息混乱程序卡死,MSDN 中也有说明,子线程直接访问主线程的控件对象是不安全的;为此,微软也提供了相应的解决方案:
1、通过传递控件句柄 HWND 给子线程,来对控件进行访问;
2、通过消息传送机制,也就是通过在子线程里给主线程传递访问控件对象的消息,然后在主线程的消息响应函数里面对控件对象进行操作。
1.在主线程初始化时保留其指针,将主线程的指针传递给子线程,子线程可以通过主线程的句柄向主线程发送消息,主线程在消息响应函数中对控件或函数进行操作。
//主线程初始化时
cums = (CITcard_test4Dlg)AfxGetMainWnd();
//在子线程中对主线程发送消息
CITcard_test4Dlg pDlg = cums;
::PostMessageW(pDlg->m_hWnd,UM_SAVETEST,(WPARAM)strWparam.AllocSysString(),NULL);
//主线程接收到消息后调用函数
if(message == UM_SAVETEST)
{
BSTR b = (BSTR)WParam;
CString strTestContents(b);
SysFreeString(b);
Savetest(strTestContents);
return 1;
}
2.在建立子线程时将主线程的指针传送过去,子线程可利用指针调用主线程的函数
//创建子线程
m_DetectionRxThread = AfxBeginThread(DetectionRxThread,(LPVOID)this,0);
//子线程利用指针直接调用函数
UINT DetectionRxThread(LPVOID pParam)
{
CITcard_test4Dlg pp = (CITcard_test4Dlg*)pParam;
CString ComstrTemp1 = pp->ReadData(1);
CString ComstrTemp2 = pp->ReadData(2);
CString ComstrTemp3 = pp->ReadData(3);
CString ComstrTemp4 = pp->ReadData(4);
CString ComstrTemp5 = pp->ReadData(5);
return 0;
}
5.消息处理函数
5.1系统内部定义的消息处理函数
在“类视图”右下方的“属性栏”中有一个消息按钮,点击按钮会出现许多系统内部定义的消息,点击消息,下拉右方的组合框,选择“添加···”即可出现消息处理函数,可以在函数内编写代码,实现自己想要的效果。
5.2自定义消息
首先需要自己定义一个消息,在定义消息时最好定义的大一点,避免和系统内部消息造成冲突。
#define UM_SAVETEST WM_USER+240//1264
在头文件里添加消息处理函数的声明
afx_msg LRESULT OnOpenPower(WPARAM WParam, LPARAM LParam);
在源文件添加消息映射
ON_MESSAGE(UM_READDATA1,&CITcard_test4Dlg::OnReadData1)
以及消息处理函数
LRESULT CITcard_test4Dlg::OnSendCMGR(WPARAM WParam,LPARAM LParam)//用于发送AT指令
{
return 0;
}
5.3发送消息
在传递消息时常用的有两个函数分别是PostMessageW()函数和SendMessage() 函数,这两个函数的区别就是,前一个函数只负责发送消息,发送完毕就会返回,而后一个则是发送消息后会等待相应的消息处理函数处理完毕后再返回,但是这样可能会引起阻塞。
如果担心自己发送的消息没有被及时处理,可以重载WindowProc(UINT message,WPARAM WParam,LPARAM LParam)函数,在内部对消息进行处理。
6.文件的创建与写入
6.1获取程序路径
1.方法1
char pBuf[MAX_PATH]; //存放路径的变量
GetCurrentDirectory(MAX_PATH,pBuf); //获取程序的当前目录
strcat(pBuf,"\");
strcat(pBuf,AfxGetApp()->m_pszExeName);
strcat(pBuf,".exe"); //获取程序的全文件名
2.方法2
//函数返回应用程序所在的路径
CString CClientApp::ReturnPath()
{
CString sPath;
GetModuleFileName(NULL,sPath.GetBufferSetLength(MAX_PATH+1),MAX_PATH);
sPath.ReleaseBuffer ();
int nPos;
nPos=sPath.ReverseFind(’\’);
sPath=sPath.Left(nPos);
return sPath;
}
3.对比及建议
方法1获取的是程序的工作路径,如某个程序安装在C,D盘或者其它任何,当你从[开始]-[程序]后的菜单中打开该文件,此时获取的是用户工作路径,如:C:\Documents and Settings[计算机当前用户名]…
如果你想通过这个路径来加载你放在程序目录下的文件,必定出错。通常这种情况在你编译调试程序时是不会出错的,你跟踪得到的绝对路径,但打包安装后一定出问题。
方法2获取的是程序的绝对路径,用这个路径加载同目录下的文件是不会有问题的。
6.2创建文件并写入
void CITcard_test4Dlg::Savelog(CString LogContents)
{
// TODO: 在此添加控件通知处理程序代码
//获取路径并在此路径创建文件
CFile file;
CString sPath;
GetModuleFileName(NULL,sPath.GetBufferSetLength(MAX_PATH+1),MAX_PATH);
sPath.ReleaseBuffer ();
int nPos;
nPos=sPath.ReverseFind(’\’);
sPath=sPath.Left(nPos);
sPath = sPath + _T("\log.txt");//为文件命名
int bo = file.Open(sPath,CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite);//创建(打开)文件,为文件设置属性,例如(可写、可读等)
if(-1 == bo)
{
MessageBox(_T(“文件打开失败!”));
return ;
}
file.Write(LogContents,wcslen(LogContents)*sizeof(wchar_t));//写入内容
file.Close();//关闭文件
}
当文件正在程序中被打开时,不可再次打开文件,否则将会出现错误,提示文件句柄丢失。
7.背景的设置
7.1设置背景
右击“资源视图”的Bitmap选项卡,选择添加资源,弹出添加资源窗口,自己画图就选新建,有现成图片就选择导入。
在OnPaint()的else条件中注释掉CDialog::OnPaint();,并添加以下程序
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect); //得到客户端的大小;
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);//创建内存DC;
CBitmap pbmpOld=dcMem.SelectObject(&bmpBackground); //选择位图,将其装入内存设备上下文;
dc.StretchBlt(130nrectWidth/765,100nrectHeight/487,rect.Width(),rect.Height(),&dcMem,0,0, nBmpWidth765/231, nBmpHeight487/99, SRCCOPY);
dcMem.SelectObject( pbmpOld );//将原来的位图重新载入
dcMem.DeleteDC();//删除内存DC
7.2出现的错误
不要随意删除资源文件,这样会造成程序找不到资源文件直接崩溃,甚至无法打开项目。
8.控件与背景的自适应变化
8.1背景自适应变化
在进行最大化与还原时,系统都会对界面进行重绘,对于背景的重绘是在OnPaint()中完成的,想要背景跟随界面变大变小,只要以界面的特定长度比例来作为背景图片的长度,就可以实现背景的自适应变化,也就是7.1中设置背景的那一句
dc.StretchBlt(130nrectWidth/765,100nrectHeight/487,rect.Width(),rect.Height(),&dcMem,0,0, nBmpWidth765/231, nBmpHeight487/99, SRCCOPY);
8.2控件及字体自适应变化
想要完成控件的自适应变化需要对消息WM_SIZE进行处理,添加OnSize()函数,当界面的大小发生变化时就会产生WM_SIZE消息,程序初始化时将界面的客户区大小记录下来,当界面大小发生变化时,获取变化后的客户区大小,计算出界面变化的比例,将其上方的控件以及控件中的文字也以相同比例进行变化,即可做到控件的自适应变化。
float fsp[2];
POINT Newp; //获取现在对话框的大小
CRect recta;
GetClientRect(&recta); //取客户区大小
Newp.x=recta.right-recta.left;
Newp.y=recta.bottom-recta.top;
fsp[0]=(float)Newp.x/old.x;
fsp[1]=(float)Newp.y/old.y;
CRect Rect;
int woc;
int nindex =0;
CPoint OldTLPoint,TLPoint; //左上角
CPoint OldBRPoint,BRPoint; //右下角
HWND hwndChild=::GetWindow(m_hWnd,GW_CHILD); //列出所有控件
while(hwndChild)
{
woc=::GetDlgCtrlID(hwndChild);//取得ID
GetDlgItem(woc)->GetWindowRect(Rect);
ScreenToClient(Rect);
OldTLPoint = Rect.TopLeft();
TLPoint.x = long(OldTLPoint.xfsp[0]);
TLPoint.y = long(OldTLPoint.y*fsp[1]);
OldBRPoint = Rect.BottomRight();
BRPoint.x = long(OldBRPoint.x *fsp[0]);
BRPoint.y = long(OldBRPoint.y *fsp[1]);
Rect.SetRect(TLPoint,BRPoint);
if(Rect.right < RectGather[nindex].right)
{
Rect = RectGather[nindex];
}
GetDlgItem(woc)->MoveWindow(Rect,TRUE);
hwndChild=::GetWindow(hwndChild, GW_HWNDNEXT);
nindex++;
}
8.3出现的错误
1.放大时静态文本框的字体不变
需要为静态文本框重新命名ID,这样静态文本框才能够跟随变大。
2.背景遮盖子窗口
在背景需要重绘时,会发送WM_PAINT消息,在重绘背景时使用的是CDC类型的对象,此对象在重绘后不会清除WM_PAINT消息,这就导致了背景一直被重绘,遮盖子窗口。改为使用CPaintDC类型的对象,重绘后会清除WM_PAINT消息,这样就不会再遮盖子窗口。
3.在经过多次放大还原后,所有控件的位置向左上方移动,字体变小。
由于在进行浮点型向整型类型转换时会造成误差,多次放大还原后会使得误差变大,从而使得控件偏离原本位置。只需加一个判断语句,当发生偏离时将其位置重新设定即可。
4. 系统初始化时会先调用OnSize()函数,而这时控件还未被放置,所以需要避免第一次的控件字体自适应变化。
添加判断语句,避过第一次自适应变化即可。
9.其他错误
9.1断言错误
主要是关于句柄方面的问题,可以仔细查找该句柄指代的控件或窗口,其是否被销毁,或是在调用时有什么违法操作,可通过加断点的方式来调试出错误原因,或通过堆栈帧查找是在哪一个语句出现的错误。
9.2堆栈被破坏
一般是数组内部的内容超出了本身的范围。比较特殊的是在关闭程序时出现堆栈周围被破坏的提示。
9.3参数类型错误
在某些函数中有比较特殊的参数,在调用时需要先进行类型转换。