需求
1.消息触发,用户从界面获取响应消息;
2.鼠标创建初始的矩形ROI;
3.支持多ROI;
4.支持选择,拉伸,移动和删除;
5.开发环境,MFC 对话框程序
橡皮筋类(CRectTracker)
要想实现图形的拉伸功能,可以借用vs函数库中封装的橡皮筋类(CRectTracker),达到事半功倍的效果。
首先,简要介绍一下CRectTracker这个类:
Windows自带的画图软件中可以用虚线框选择图像的某个区域,之后便可以拖动、放大、缩小该区域,这是通过橡皮筋类(CRectTracker)来实现的,它将实现用线框选中一个区域,并可以拖动、放大、缩小该区域。
CRectTracker类允许一个项被显示,移动,以不同的方式改变大小。虽然CRectTracker类是设计来支持用户以图形化界面与OLE项交互的,但是它的使用不仅限于支持OLE的应用程序。它可以使用在任何需要用户界面的地方。
CRectTracker的边框可以是实线,也可以是点线。可给予项一种阴影式边框或用一种阴影样式覆盖项,用来指示项的不同状态。你可以在项的外界或内部放置八个调整大小把手。(有关八个调整大小把手的解释,参见GetHandleMask。)最后,一个CRectTracker允许你在调整项的大小时改变项的方向。
要使用CRectTracker,首先要构造一个CRectTracker对象,并指定用哪种显示状态来初始化。然后,应用程序就可以使用这个界面,提供给用户有关与CRectTracker对象相关联的OLE项当前状态的直观反馈了。
#include <afxext.h>
请参阅:
COleResizeBar, CRect, CRectTracker::GetHandleMask
CRectTracker类成员
数据成员
m_nHandleSize | 确定调整大小把手的尺寸 |
m_rect | 矩形的以像素表示的当前位置 |
m_sizeMin | 确定矩形宽度和高度的最小值 |
m_nStyle | 跟踪器的当前风格 |
构造
构造一个CRectTracker对象 |
操作
Draw | 显示矩形,并显示八个调整把手(调用时若要正常显示调整把手,不能只是在重绘函数中调用,还需要在程序当前位置调用一次) |
GetTrueRect | 返回矩形的宽度和高度,包括改变大小句柄 |
HitTest | 返回与CRectTracker对象关联的光标的当前位置(返回值>=0:在矩形内,小于0:矩形外) |
NormalizeHit | 规范化一个单击测试代码 |
SetCursor | 根据光标在矩形上方的位置来设置光标 |
Track | 支持用户操作矩形(会捕捉鼠标左键弹起消息,一直到鼠标左键弹起才会执行其下面的程序;双击鼠标左键,将会当做响应Track完毕,并执行一次左键按下一次,左键弹起一起) |
TrackRubberBand | 支持用户“橡皮筋”似的拉伸选择(此函数主要用于拉取矩形,用于选择区域内的矩形区域) |
可重载
AdjustRect | 当矩形被改变大小时此函数被调用 |
DrawTrackerRect | 当画一个CRectTracker对象的边框时此函数被调用 |
OnChangedRect | 当矩形被改变大小或被移动时,此函数被调用 |
GetHandleMask | 调用此函数来获得一个CRectTracker项的调整大小把手的掩码 |
☆需要注意的是:CRectTracker类并不是用来画矩形区域的,而是用来选择区域的!!!如果要实现画区域边框,还得利用其它绘制函数~
下面是实现的源代码:
首先,在stdafx.h头文件中加入
#define MAX_RECT_NUM 100 //允许画的最多的矩形个数
在头文件中添加相关的变量和函数声明,
//用于创建ROI相关的 S
CRectTracker m_rctCurTracker; //当前选中的矩形区域
CRectTracker m_rctTracker[MAX_RECT_NUM]; //用于存储已画的矩形区域
bool m_IsChose; //标记是否被选中
bool m_IsDraw; //标记“绘制”按钮是否按下
int m_rectNum; //当前实际已经画的矩形的个数
int m_rctChoseNum;//当前选中的矩形的编号
int m_FlaMoveStep;//键盘方向键每响应一次的图像移动的像素单位上的步长
int dirct; //用于标记那个方向键按下。1:左,2:右,3:上,4:下,5:delete(删除)
//鼠标在ROI区域时光标变换
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
//绘制ROI 触发按键
afx_msg void OnBnClickedBtnDrawroi();
// 键盘消息时的移动方向
virtual BOOL PreTranslateMessage(MSG* pMsg);
//移动+删除操作,改变区域
void ChangeRectPt(int ChangeDirct);
用于创建ROI相关的 E
//键盘消息时的移动方向
ROI相关变量在InitDialog对话框中初始化
BOOL CXXXDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中 // IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动 // 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
/ROI相关的初始化 S
m_rctCurTracker.m_rect.SetRect(0, 0, 0, 0);//设置矩形区域大小
m_rctCurTracker.m_nStyle = CRectTracker::dottedLine | CRectTracker::resizeInside;
m_rctCurTracker.m_nHandleSize = 6;
for (int i = 0; i < MAX_RECT_NUM; i++)
{
m_rctTracker[i].m_rect.SetRect(0, 0, 0, 0);//设置矩形区域大小
m_rctTracker[i].m_nStyle = CRectTracker::dottedLine | CRectTracker::resizeInside;
m_rctTracker[i].m_nHandleSize = 6;
}
m_IsChose = FALSE;//表示未选中
m_IsDraw = false;
m_rectNum = 0;
m_rctChoseNum = 0;
m_FlaMoveStep = 2;
dirct = 0;
/ROI相关的初始化 E
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
在Paint函数中绘制当前ROI
void CXXXDlg::OnPaint()
{
CPaintDC dc(this); // 用于绘制的设备上下文
if (IsIconic())
{
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
if (m_IsChose)
{
//若选择了该区域,则显示边框以及8个调整点
m_rctCurTracker.Draw(&dc);
//输出坐标信息
CRect rect = m_rctCurTracker.m_rect;
CString strLT = _T("");
strLT.Format(_T("%d,%d"),rect.left,rect.top);
SetDlgItemTextA(IDC_EDIT_CURROILT, strLT);
CString strRB = _T("");
strRB.Format(_T("%d,%d"), rect.right, rect.bottom);
SetDlgItemTextA(IDC_EDIT_CURROIRB, strRB);
//输出当前ROI的长宽信息
CString strWidth = _T("");
strWidth.Format(_T("%d"), rect.Width());
SetDlgItemTextA(IDC_EDIT_ROIWIDTH, strWidth);
CString strHeight = _T("");
strHeight.Format(_T("%d"), rect.Height());
SetDlgItemTextA(IDC_EDIT_ROIHEIGHT, strHeight);
}
CPen pen(PS_SOLID, 1, RGB(100, 255, 200));
dc.SelectObject(&pen);
CBrush *pbrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
dc.SelectObject(pbrush);
CRect rect;
m_rctCurTracker.GetTrueRect(&rect);//得到矩形区域的大小
dc.Rectangle(&rect);//画出矩形
CSize rct_size;
for (int i = 0; i < MAX_RECT_NUM; i++)
{
m_rctTracker[i].GetTrueRect(&rect);//得到矩形区域的大小
rct_size = m_rctTracker[i].m_rect.Size();
if (rct_size.cx * rct_size.cy == 0 || i == m_rctChoseNum)
{
continue;
}
dc.Rectangle(&rect);//画出矩形
}
//CRect rect1;
/*rect1.top=rect.top+3;
rect1.bottom=rect.bottom-3;
rect1.left=rect.left+3;
rect1.right=rect.right-3;
pbrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
dc.SelectObject(&brush);
dc.Rectangle(&rect1);*/
CDialogEx::OnPaint();
}
}
鼠标左键按下后,开始绘制ROI
void CXXXDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO:在此添加消息处理程序代码和/或调用默认值
bool IsInRct = false;
int i = 0;
do
{
if (m_rctTracker[i].HitTest(point) < 0)
{
IsInRct = false;
}
else
{
IsInRct = true;
m_rctChoseNum = i;
m_rctCurTracker = m_rctTracker[m_rctChoseNum];
m_IsChose = true;
break;
}
i++;
} while (i < m_rectNum);
if (!IsInRct)
{
CRectTracker tempRectTracker;
CRect rect;
tempRectTracker.TrackRubberBand(this, point);
tempRectTracker.m_rect.NormalizeRect();
if (rect.IntersectRect(tempRectTracker.m_rect, m_rctCurTracker.m_rect))
m_IsChose = TRUE;
else
{
m_IsChose = false;
if (m_IsDraw)
{
//m_IsChose=FALSE;
m_rctTracker[m_rectNum].m_rect = tempRectTracker.m_rect;
m_rctCurTracker.m_rect = m_rctTracker[m_rectNum].m_rect;
CClientDC dc(this);
m_rctCurTracker.Draw(&dc);
//注意!!在这里一定要调用绘制边框的程序,否则单凭onpaint中绘制,不能显示出来
m_rctChoseNum = m_rectNum;
m_rectNum++;
if (m_rectNum >= MAX_RECT_NUM)
{
m_rectNum = MAX_RECT_NUM;
MessageBoxA("已画矩形超过上限,不能再画矩形区域", "警告", MB_OK);
}
m_IsChose = TRUE;
m_IsDraw = false;
Invalidate();
}
}
Invalidate();
}
else
{
CClientDC dc(this);
m_rctCurTracker.Draw(&dc);
m_rctCurTracker.Track(this, point);
m_rctCurTracker.m_rect.NormalizeRect();
m_rctTracker[m_rctChoseNum] = m_rctCurTracker;
m_IsChose = TRUE;
Invalidate();
}
CDialogEx::OnLButtonDown(nFlags, point);
}
绘制ROI指令入口
void CXXXDlg::OnBnClickedBtnDrawroi()
{
// TODO: 在此添加控件通知处理程序代码
m_IsDraw = true;
}
鼠标在ROI区域时,光标变换
BOOL CXXXDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
//TODO: 在此添加消息处理程序代码和/或调用默认值
if (pWnd == this && m_rctCurTracker.SetCursor(this, nHitTest))
{
return TRUE;
}
return CDialogEx::OnSetCursor(pWnd, nHitTest, message);
}
ROI 移动删除 通过键盘来实现
BOOL CAOITestDemoDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加专用代码和/或调用基类
if (pMsg->message == WM_KEYDOWN)
{
switch (pMsg->wParam)
{
case VK_LEFT:
dirct = 1;
break;
case VK_RIGHT:
dirct = 2;
break;
case VK_UP:
dirct = 3;
break;
case VK_DOWN:
dirct = 4;
break;
case VK_DELETE:
dirct = 5;
break;
default:
dirct = 0;
}
}
ChangeRectPt(dirct);
return CDialogEx::PreTranslateMessage(pMsg);
}
ROI区域改变后需重新绘制,函数定义如下,
void CXXXDlg::ChangeRectPt(int ChangeDirct)
{
CRect rct;
rct = m_rctCurTracker.m_rect;
switch (ChangeDirct)
{
case 1://左移
rct.TopLeft().x -= m_FlaMoveStep;
rct.BottomRight().x -= m_FlaMoveStep;
break;
case 2://右移
rct.TopLeft().x += m_FlaMoveStep;
rct.BottomRight().x += m_FlaMoveStep;
break;
case 3://下移
rct.TopLeft().y -= m_FlaMoveStep;
rct.BottomRight().y -= m_FlaMoveStep;
break;
case 4://上移
rct.TopLeft().y += m_FlaMoveStep;
rct.BottomRight().y += m_FlaMoveStep;
break;
case 5://删除
m_rctCurTracker.m_rect.SetRect(0, 0, 0, 0);
m_rctTracker[m_rctChoseNum] = m_rctCurTracker;
dirct = 0;
Invalidate();
return;
}
m_rctCurTracker.m_rect.SetRect(rct.TopLeft(), rct.BottomRight());
m_rctTracker[m_rctChoseNum] = m_rctCurTracker;
if (ChangeDirct != 0)
{
Invalidate();
}
dirct = 0;
}
以上功能添加完成后,效果如下,
到这一步,ROI绘制相关的需求已完成。这里ROI不是绘制在Picture控件上,而是整个窗体,离我们项目中的需求还有一段距离。待继续实现:
1.ROI的坐标改为相对Picture控件左上顶点为原点;
2.在Picture控件上绘制,移动ROI。
待续。