1.背景
想对一个MFC程序做自动压测功能,根据判断程序界面某块区域是否达到预定状态,来自动执行鼠标点击或者键盘输入的操作,以解决测试人员需要重复手动压测问题。
1.涉及的技术
串口控制,基于MFC橡皮筋类(CRectTracker)做一个简单的截图对话框,GDI,模拟鼠标点击(mouse_event),模拟键盘输入(keybd_event),MD5等
2.串口控制继电器上下电
如何你要压测的程序需要通过控制USB上下电,可用串口控制继电器来达到此目的。
-
以下是串口初始化的代码
bool CComTest::InitialCom(int iComID, int iComPort, DWORD iBaudRate)
{
if (iComPort < 1 || iComPort > 255)
{
return FALSE;
}if (INVALID_HANDLE_VALUE != m_hCom) { CloseCom(iComID); } if (INVALID_HANDLE_VALUE != m_hCom) { CloseHandle(m_hCom); } m_iComPort = iComPort; //串口通信端口 m_iBaudRate = iBaudRate; //串口通信速率 DCB Dcb; CString str; COMMTIMEOUTS TimeOut; int Data = 8; int Stop = 0; int Parity = 0; str.Format(_T("COM%d"), iComPort); m_hCom = CreateFile(str, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, NULL, NULL); if (m_hCom == INVALID_HANDLE_VALUE) { return FALSE; } GetCommState(m_hCom, &Dcb); Dcb.BaudRate = iBaudRate; Dcb.ByteSize = Data; Dcb.StopBits = Stop; Dcb.Parity = Parity; if (!SetCommState(m_hCom, &Dcb)) { CloseHandle(m_hCom); m_hCom[iComID] = INVALID_HANDLE_VALUE; return FALSE; } memset(&TimeOut, 0, sizeof(TimeOut)); TimeOut.ReadIntervalTimeout = MAXDWORD; SetCommTimeouts(m_hCom[iComID], &TimeOut); SetupComm(m_hCom, 1024, 1024); PurgeComm(m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR); return TRUE;
}
-
读取串口:
DWORD CComTest::ReadCom(int ComNo, BYTE *pBuff, int nCount)
{
if (m_hCom == INVALID_HANDLE_VALUE)
{
return 0;
}DWORD read = 0; ReadFile(m_hCom, pBuff, nCount, &read, NULL); return read;
}
写串口
BOOL CComTest::WriteCom(int ComNo, BYTE *pBuff, int nCount)
{
if (m_hCom == INVALID_HANDLE_VALUE)
{
return FALSE;
}
DWORD written = 0;
BOOL ret = WriteFile(m_hCom, pBuff, nCount, &written, NULL);
return ret;
}
发串口命令示例
BYTE pData[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
WriteCom(m_iComID, pData, 6);
继电器控制命令需参考具体供应商提供的使用文档。
3.程序界面状态监控(截图对话框的实现)
想要通过检测某块区域是否达到预设的状态来自动执行被压测程序的下一步动作,可在压测前将被压测程序执行到预定状态,然后截取该区域的位图并保存,在压测时不停的检测该区域位图,和前面保存的位图做对比(MD5),如果MD5一致则执行模拟点击或模拟输入动作。
-
截图对话框的OnInitDialog函数代码如下
BOOL CCaptureDlg::OnInitDialog()
{
CDialog::OnInitDialog();copyScreenToBitmap(m_ScreenBmp); //将屏幕内容拷贝到Bitmap类型的对象中 //获取屏幕当前分辨率的宽度和高度(以像素为单位,传递参数不同X,Y) int screenWidth = GetSystemMetrics(SM_CXSCREEN); int screenHeight = GetSystemMetrics(SM_CYSCREEN); //调用MoveWindow或者SetWindowPos将当前的窗口设置成与屏幕大小相同 //使用两个,软件写好后要使用SetWindowPos,但使用SetWindowPos设置成顶层窗口就不能调试了,使用MoveWindow进行调试 MoveWindow(-3, -3, screenWidth + 6, screenHeight + 6); //比屏幕膜大3个像素不然白边出现,好看一些 //SetWindowPos(&wndTopMost, -3, -3, screenWidth + 6, screenHeight + 6, SWP_SHOWWINDOW); //橡皮筋类的操作 m_rectTracker.m_nStyle = CRectTracker::resizeOutside | CRectTracker::dottedLine; //矩形框虚线 m_rectTracker.m_rect.SetRect(0, 0, 0, 0); //初始化矩形大小 return TRUE; // return TRUE unless you set the focus to a control
}
m_ScreenBmp会保存截图对话框显示前的屏幕实时位图。
-
截图对话框背景显示原来屏幕的背景:
BOOL CCaptureDlg::OnEraseBkgnd(CDC* pDC)
{
//将bitmap对象作为背景画到对话框上
//创建内存DC
CDC memDC;
memDC.CreateCompatibleDC(pDC); //使内存DC与pDC兼容
memDC.SelectObject(&m_ScreenBmp); //选入设备环境//将内容从内存DC拷贝到pDC中(本模态对话框窗口的DC) CRect rect; GetClientRect(&rect); //获取对话框大小,在初始化时设置了 pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY); //将内容从内存DC拷贝到pDC中 memDC.DeleteDC(); //释放 return TRUE; //直接在此返回,不进行下一步操作,否则就画不上了 return CDialog::OnEraseBkgnd(pDC);
}
-
鼠标消息处理,使橡皮筋对象矩形可操作:
void CCaptureDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
//如果点击绘制橡皮筋区域的外部,重新构建一个可拖拽的区域
if (m_rectTracker.HitTest(point) == CRectTracker::hitNothing)
{
m_rectTracker.TrackRubberBand(this, point, TRUE);
}
else{
//点击在了区域内部,允许用户大小调整进行区域描画
m_rectTracker.Track(this, point, TRUE);
m_rectTracker.m_rect.NormalizeRect(); //NormalizeRect可以进行左右上下值调整,从右下向左上框柱
}
Invalidate(TRUE); //更新,使WM_PAINT描画消息触发CDialog::OnLButtonDown(nFlags, point);
}
-
修改橡皮筋框类光标的形态:
BOOL CCaptureDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
//如果传递的是本窗口,SetCursor成功了
if (pWnd == this && m_rectTracker.SetCursor(this, nHitTest))
{
SetCursor(LoadCursor(NULL, IDC_CROSS));
return TRUE;
}
else
{
return CDialog::OnSetCursor(pWnd, nHitTest, message);
}
} -
在OnPaint中实时显示橡皮筋框
void CCaptureDlg::OnPaint()
{
//CPaintDC只适合OnPaint里面,所以使用GetDC来获取DC,进行描绘
CDC *pDC = GetDC();
m_rectTracker.Draw(pDC);
ReleaseDC(pDC); //get与release成对使用
} -
双击时将橡皮筋对话框坐标和大小保存,并此区域内的对话框未显示前的屏幕位图保存为图片
void CCaptureDlg::OnLButtonDblClk(UINT nFlags, CPoint point)
{
// 保存双击时坐标
m_startPointX = point.x;
m_startPointY = point.y;//如果双击在了矩形区域的内部就进行保存工作 if (m_rectTracker.HitTest(point) != CRectTracker::hitMiddle) { MessageBox(_T("截图失败,请重新截图!")); return; } CDC *pDC = GetDC(); CDC memDC; memDC.CreateCompatibleDC(pDC); memDC.SelectObject(&m_ScreenBmp); CRect rect; rect = m_rectTracker.m_rect; CBitmap mBmp, *pOldBmp = NULL; mBmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); m_rectInfo.m_Top1 = rect.top; m_rectInfo.m_Bottom1 = rect.bottom; m_rectInfo.m_Left1 = rect.left; m_rectInfo.m_Right1 = rect.right; CDC dstDC; dstDC.CreateCompatibleDC(pDC); pOldBmp = dstDC.SelectObject(&mBmp); //内容拷贝到目标,目标肯定比屏幕小,所以坐标0,0,宽高用户选择的,rect.left, rect.top是源缓冲区的坐标 dstDC.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, rect.left, rect.top, SRCCOPY); CImage Img; Img.Attach(mBmp); //关联 CString strModuleCapturePath; CString strCapturePath = "你想要保存的路径\\capture1.jpg"; Img.Save(strCapturePath ); //保存地址 mBmp.DeleteObject(); memDC.DeleteDC(); dstDC.DeleteDC(); ReleaseDC(pDC); //保存完了后,说明本次截图操作完成了,要把当前显示的模态对话框,全屏的对话框关闭 CDialog::OnCancel();
}
-
以上步骤可实现压测前某块区域的位图、区域和双击坐标的保存,以下函数可截取屏幕某个区域的位图并保存到本地:
void CCaptureDlg::Screen(CRect cRect, int iNumber)
{
CRect rect = cRect;
CDC *pDC;//屏幕DC
pDC = CDC::FromHandle(::GetDC(NULL));//获取当前整个屏幕DC
int BitPerPixel = pDC->GetDeviceCaps(BITSPIXEL);//获得颜色模式CDC memDC;//内存DC memDC.CreateCompatibleDC(pDC); CBitmap memBitmap, *oldmemBitmap;//建立和屏幕兼容的bitmap memBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); oldmemBitmap = memDC.SelectObject(&memBitmap);//将memBitmap选入内存DC memDC.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, rect.left, rect.top, SRCCOPY);//备注 CImage Img; Img.Attach(memBitmap); //关联 CString strCapturePath = _T("你想要保存的路径\\capture1.jpg"); memDC.SelectObject(oldmemBitmap); memDC.DeleteDC(); ::ReleaseDC(NULL, pDC->m_hDC);
}
4.模拟鼠标点击
可通过以下代码来实现:
SetCursorPos(m_strPointX, m_strPointY);
mouse_event(MOUSEEVENTF_LEFTDOWN, m_strPointX, m_strPointY, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, m_strPointX, m_strPointY, 0, 0);
5.模拟键盘输入
比如想在某个编辑框中自动输入字符串,可使用前面叙述的截图对话框截取编辑框区域,并通过模拟鼠标点击将光标焦点移动到编辑框内(SetCursorPos的坐标使用双击截图对话框时保存的坐标)
// 截取编辑框时,双击的坐标
SetCursorPos(m_strPointX, m_strPointY);
mouse_event(MOUSEEVENTF_LEFTDOWN, m_strPointX, m_strPointY, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, m_strPointX, m_strPointY, 0, 0);
char szImput[10] = "123456789";
keybd_event(szImput[0], 0, 0, 0);
...
keybd_event(szImput[9], 0, 0, 0