Introduction
This
HitTester demo application was written just to
illustrate the basic drawing
application framework
using GDI/GDI+. It
also features the useful curve/line
hit test technique together with the implementation of CObject
class and CObArray
collection class which can be extended for serialization
in future. I also added extra features for storing the drawing
into *.png file format. CObject
and
CObArray
Background
Hit test
is very important for developing good drawing
kind of applications to determine whether the object is
currently selected or not. There are various methods
that can be used to perform curve
or line hit test.
For diagonal (straight) line,
performing hit test can be done by comparing the two
slopes of the object line and the test line. Test line
is a line that is virtually constructed starting from
the first point of the line shape to the test point. If
both slopes match, the object line is hit.
For the
case of curved-line, it is a bit difficult to perform
hit test. Normally, flatten technique will be used to
convert the curved line to a series of connected
straight lines before performing slope comparison for
each line segment. Phew! Thanks to GDI+
for the GraphicsPath
class and its IsOutlineVisible()
member function, this hit test tucancode.net can be done very
easily.
You can
refer to this article from MSDN regarding hit
test using Win32. And this one as well for creating
cables and connectors...
Using the
code
Below are
the steps for implementing the object class
and GDI+
initialization.
Step 1:
Add the
lines below to StdAfx.h file:
#include <gdiplus.h>
#include <Gdiplusinit.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
Step 2:
Add the
lines below to HitTester.h file:
GdiplusStartupInput m_gdiplusStartupInput;
ULONG_PTR m_gdiplusToken;
Step 3:
Add the
line below to HitTester.cpp file, under the CHitTesterApp::InitInstance()
function, before the return
statement:
GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);
Step 4:
Add the
line below to HitTester.cpp file, under the CHitTesterApp::Exitnstance()
function:
GdiplusShutdown(m_gdiplusToken);
Step 5:
Add the
line below to HitTesterView.h file:
Step 6:
Add the
lines below to HitTesterView.cpp:
Collapse
void CHitTesterView::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect rect;
GetClientRect(rect);
if(rect.PtInRect(point))
{
if(m_bSelectMode)
{
CDC* pDC = this->GetDC();
VERIFY(pDC != NULL);
if(pDC != NULL)
{
int nR2 = pDC->SetROP2(R2_NOTXORPEN);
CPen* newPen = new CPen(PS_SOLID|PS_GEOMETRIC, 1, RGB(255,0,255));
CPen* pOldPen = pDC->SelectObject(newPen);
for(int i=0; i<m_obArray->GetSize(); i++)
{
CHCN_Object* pBase = m_obArray->GetObjectBaseClass(i);
if(pBase->GetObjectType() == pBase->HCN_Line)
{
CHCN_ObjectLine* pLine = (CHCN_ObjectLine*)pBase;
if(m_bSelectSegment)
{
if(pLine->IsSegmentHit(point))
{
if(pLine->m_bSelected)
{
pLine->m_bSelected = FALSE;
pLine->m_bShowNode = FALSE;
}
else
{
pLine->m_bSelected = TRUE;
pLine->m_bShowNode = TRUE;
}
}
}
if(m_bSelectNode)
{
if(pLine->IsNodeHit(point))
{
if(pLine->m_bSelected)
{
pLine->m_bSelected = FALSE;
pLine->m_bShowNode = FALSE;
}
else
{
pLine->m_bSelected = TRUE;
pLine->m_bShowNode = TRUE;
}
}
}
}
Invalidate();
}
VERIFY(nR2 >= 0);
pDC->SetROP2(nR2);
pDC->SelectObject(pOldPen);
pDC->Detach();
pDC = NULL;
}
}
if(m_bDrawMode)
{
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_CROSS));
if(m_bDrawLine || m_bDrawCurve)
{
if(!m_bDrawBegin)
{
m_bDrawBegin = TRUE;
m_ptArray = new CHCN_ObjectArray();
}
AddPointToArray(point);
}
}
}
CView::OnLButtonDown(nFlags, point);
}
Collapse
void CHitTesterView::OnMouseMove(UINT nFlags, CPoint point)
{
CRect rect;
GetClientRect(rect);
if( rect.PtInRect(point))
{
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
if(m_bDrawMode)
{
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_CROSS));
if(m_bDrawBegin && (nFlags != MK_LBUTTON))
{
CDC* pDC = this->GetDC();
VERIFY(pDC != NULL);
if(pDC != NULL)
{
int nR2 = pDC->SetROP2(R2_NOTXORPEN);
if(m_bDrawLine || m_bDrawCurve)
{
pDC->DPtoLP(&m_pt1);
pDC->DPtoLP(&m_pt2);
pDC->MoveTo(m_pt1);
pDC->LineTo(m_pt2);
m_pt2 = point;
pDC->DPtoLP(&m_pt2);
pDC->MoveTo(m_pt1);
pDC->LineTo(m_pt2);
}
VERIFY(nR2 >= 0);
pDC->SetROP2(nR2);
pDC->Detach();
pDC = NULL;
}
}
}
}
else
{
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
}
CView::OnMouseMove(nFlags, point);
}
void CHitTesterView::OnDraw(CDC* pDC)
{
CHitTesterDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
if(m_obArray->GetSize() > 0)
{
CRect rect;
GetClientRect(rect);
Bitmap bmp(rect.Width(),rect.Height());
Graphics* graph = Graphics::FromImage(&bmp);
for(int i=0; i<m_obArray->GetSize(); i++)
{
CHCN_Object* pBase =
reinterpret_cast<CHCN_Object*>(m_obArray->GetAt(i));
if(pBase->GetObjectType() == pBase->HCN_Line)
{
CHCN_ObjectLine* pLine = (CHCN_ObjectLine*)m_obArray->GetAt(i);
pLine->DrawObject(*graph);
}
}
Rect rc(rect.left,rect.top,rect.Width(),rect.Height());
m_Bitmap = bmp.Clone(rc, PixelFormatDontCare);
Graphics graphics(pDC->m_hDC);
graphics.DrawImage(m_Bitmap, rc);
}
}
void CHitTesterView::OnFileSave()
{
const char szFilter[] = "Image Files (*.png)|*.png|All Files (*.*)|*.*||";
const char szExt[] = "png";
CFileDialog* dlg = new CFileDialog(FALSE, szExt, NULL,
OFN_CREATEPROMPT|OFN_OVERWRITEPROMPT, szFilter, this);
if(dlg->DoModal() == IDOK)
{
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
m_Bitmap->Save(dlg->GetPathName().AllocSysString(), &pngClsid, NULL);
}
}
int CHitTesterView::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1;
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL)
return -1;
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j = 0; j < num; ++j)
{
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}