以前对于MFC界面和多线程这块,一直都是看别人编写的界面,以及听别人说多线程的程序,感觉很神奇,自己一直没有动手去实现,这次终于有机会动手尝试
现将在开发中遇到的问题记下来,供以后使用,也希望对其他朋友有一定的作用:
程序功能:对图像序列的分析,其中有开始分析、暂停、继续、下一帧、停止和退出等功能
主要存在的问题,由于在点击“开始分析”时,程序要处理1-10000帧,这时该线程一直被该程序占用,无法响应其它的按钮的响应,因此,需要采用多线程的方法实现,其中程序默认线程是主线程,它主要负责各个按钮的响应,而当点击“开始分析”时,该按钮特别耗时,因此,为其创建子线程,由该子线程完全负责对他的处理。
具体的步骤:
1. 线程响应的创建
一般线程需要执行的函数,都是全局函数或者static 函数
static UINT ThreadProc_Start_Analysis(LPVOID pParam); -- 线程具体的实现
CWinThread* m_pThread;
线程的创建和启动
m_pThread = AfxBeginThread(ThreadProc_Start_Analysis,this, 0,0, 0,0);
this,对应的响应函数所在的类,这样在实现时,需要调用
MFCDlg *mfcdlg = (MFCDlg *)pParam ;
2. 线程响应的实现中,需要注意的问题
如果是全局函数,则比较好处理,但是如果是类的成员函数的话,则需要使用静态函数,同时,在静态函数中,仅能够使用静态变量和函数,在使用的变量中都需要使用指针MFCDlg,这样才不会报错!
3. 线程的挂起
//挂起线程
m_pThread->SuspendThread();
4.线程的恢复
//恢复线程
m_pThread->ResumeThread();
另外,“开始分析”,“暂停”,“继续”,“下一帧”等按钮中,需要合理使用BOOL型变量,这样才能使他们正常工作,例如,“下一帧”的话,就不能使用原来的程序了
因此,而应该根据对应的变量,重新启动某些代码。
5. 线程的通信
1)最简单的方法是通过全局变量进行通信,由于本程序都是类的成员函数和变量,因此,本程序全部使用类的公共变量进行通信
2)然而,有时不是变量值的变化,而是更新界面等操作,因此这里可以使用消息的方法 mfcdlg->PostMessage(WM_THREADMSG_UPDATEDATA,0,0) ;
BEGIN_MESSAGE_MAP(CStereo_Match_Depth_MFCDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(WM_THREADMSG_UPDATEDATA, OnMsgFunc_UpDateData)
ON_MESSAGE(WM_THREADMSG_PAINT, OnMsgFunc_Paint)
ON_WM_MOUSEMOVE() // 对应的映射
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
其中 WM_THREADMSG_UPDATEDATA消息调用函数OnMsgFunc_UpDateData
// 消息映射函数
LRESULT MFCDlg::OnMsgFunc_Paint(WPARAM,LPARAM)
{
UpdateData(FALSE) ;
OnPaint(); // 重绘对话框
UpdateWindow();
return 1 ;
}
6. 鼠标的响应
首先需要添加 ON_WM_MOUSEMOVE() // 对应的映射
ON_WM_LBUTTONDOWN()
函数映射语句,然后就是添加对应的具体的获取鼠标的操作了
// ClientToScreen(&point);//将鼠标坐标转换成屏幕坐标
// CRect rect;//定义一个矩形框,包含左上角和右下角可访问成员
// GetDlgItem(IDC_STATIC_DEPTH_IMAGE)->GetClientRect(rect);//获取Picture控件的位置信息,存入rect中
// GetDlgItem(IDC_STATIC_DEPTH_IMAGE)->ClientToScreen(rect);//转换成屏幕坐标
CRect rect_ctr;
(this->GetDlgItem(IDC_STATIC_DEPTH_IMAGE))->GetWindowRect(&rect_ctr);//获取Picture控件相对屏幕左上角的坐标,
//存储到rect_ctr中
//CRect rect_dlg;
//this->GetWindowRect(&rect_dlg);//获取对话框相对屏幕左上角的坐标,存储到rect_dlg中
//***************** test ***********************************
//Picture控件左上角相对对话框客户区左上角的坐标
// ScreenToClient(rect_ctr);//这里的客户区不包括对话框的状态栏
// point.x = rect_ctr.left;//
// point.y = rect_ctr.top;
//***************** test ***********************************
//对话框窗口左上角相对对话框客户区左上角的坐标
// ScreenToClient(rect_dlg);//这里的客户区不包括对话框的状态栏
// point.x = rect_dlg.left;
// point.y = rect_dlg.top;
this->ScreenToClient(rect_ctr);//获取Picture控件相对对话框客户区左上角的坐标
point.x -= rect_ctr.left;//point获取的是鼠标相对对话框客户区左上角的坐标,减去rect_ctr.left和
point.y -= rect_ctr.top;//rect_ctr.top后,即为鼠标相对Picture控件左上角的坐标
int rw = rect_ctr.BottomRight().x - rect_ctr.TopLeft().x; // 求出图片控件的宽和高
int rh = rect_ctr.BottomRight().y - rect_ctr.TopLeft().y ; // rect_ctr.bottom - rect_ctr.top;
if (rw > Depth_Image->width)
{
point.x = point.x - ((rw - Depth_Image->width)>>1) ;
}
else
{
point.x = point.x + ((Depth_Image->width -rw)>>1) ;
}
if (rh > Depth_Image->height)
{
point.y = point.y - ((rh-Depth_Image->height)>>1) ;
}
else
{
point.y = point.y + ((Depth_Image->height-rh)>>1) ;
}
if (point.x< 0 || point.y < 0 || (point.x > Depth_Image->width) || ((point.y) > Depth_Image->height))
{
m_nCurrent_X_Coordinate = -1 ;
m_nCurrent_Y_Coordinate = -1 ;
}
else
{
m_nCurrent_X_Coordinate = point.x ;
m_nCurrent_Y_Coordinate = point.y ;
}
if (m_nCurrent_X_Coordinate < 0 || m_nCurrent_Y_Coordinate < 0 )
{
m_dCurrent_Point_Depth = 0 ;
}
else
m_dCurrent_Point_Depth = stereo_depth_info.DepthInfo->data.fl[m_nCurrent_Y_Coordinate*Depth_Image->width+m_nCurrent_X_Coordinate]; //Depth_Image->imageData[m_nCurrent_Y_Coordinate*Depth_Image->width+m_nCurrent_X_Coordinate] ;
if (m_nCurrent_X_Coordinate >= 0 || m_nCurrent_Y_Coordinate >=0 || m_nCurrent_X_Coordinate<=Depth_Image->width || m_nCurrent_Y_Coordinate<=Depth_Image->height)
{
UpdateData(FALSE) ;
}
CDialog::OnMouseMove(nFlags, point);
以上是为了获得以图像左上角为原点的图像坐标
7. 线程的释放
DWORD dwCode;
GetExitCodeThread(m_pThread->m_hThread, &dwCode);
if (dwCode == STILL_ACTIVE)
{
printf("The Thread is still running!\n");
}
TerminateThread(m_pThread->m_hThread, dwCode);
CloseHandle(m_pThread->m_hThread);
以上能工作,但不是很安全,可能程序的有些资源没有释放完全
8. UpdateData的使用
该函数对于UpdateData非常有用,需要用其对界面进行更新和从界面获得对应的值
// UpdateData(true)从界面将值传到变量,UpdateData(false)将值从变量传到界面
9. 多字节问题
有时 LPCSTR 和 const char []之间会出现转换问题,例如mfcdlg->MessageBox( "已完成要求的计算!");在单字节的设置下就有问题,但是多字节就没有问题
10. 对于Radio控件
注意设置group按钮的属性,是TRUE还是FALSE,如果相邻的控件,只有一个Group属性,则他们归属于一组,否则他们就是两个独立的控件
11. 变量的初始化和内存的分配
可以将其全部放在OnInitDialog,在变量的初始化时,不要忘了设置UPDATEDATA(FALSE),这样才能更新界面
12. 图像的显示
在OnPaint函数中添加
CDialog::OnPaint(); // 重绘对话框
CDialog::UpdateWindow(); // 更新windows窗口,如果无这步调用,图片显示还会出现问题
Show_ORI_Image(Ori_Left_Image,IDC_STATIC_LEFT_ORI_IMAGE) ; // IDC_STATIC_LEFT_ORI_IMAGE对应的Picture控件的ID,需要显示的图片Ori_Left_Image
void MFCDlg::Show_ORI_Image(IplImage* img, UINT ID)
{
CDC* pDC = GetDlgItem( ID ) ->GetDC(); // 获得显示控件的 DC
HDC hDC = pDC ->GetSafeHdc(); // 获取 HDC(设备句柄) 来进行绘图操作
CRect rect;
GetDlgItem(ID) ->GetClientRect( &rect );
int rw = rect.right - rect.left; // 求出图片控件的宽和高
int rh = rect.bottom - rect.top;
int iw = img->width; // 读取图片的宽和高
int ih = img->height;
int tx = (int)(rw - iw)/2; // 使图片的显示位置正好在控件的正中
int ty = (int)(rh - ih)/2;
SetRect( rect, tx, ty, tx+iw, ty+ih );
CvvImage cimg;
cimg.CopyOf( img ); // 复制图片
cimg.DrawToHDC( hDC, &rect ); // 将图片绘制到显示控件的指定区域内
ReleaseDC( pDC );
}