Click here to Skip to main content
15,894,315 members
Articles / Web Development / HTML

2D Graph ActiveX Control in C++ with ATL (No MFC Dependency)

Rate me:
Please Sign up or sign in to vote.
4.92/5 (25 votes)
8 Mar 2012CPOL7 min read 123.4K   11.9K   93  
Plots multiple data sets, interactive tooltip info, zoom/pan, edit color/width/format, annotations, print/save
// (c) Copyright The Code Project Open License (CPOL)
//  http://www.codeproject.com/info/cpol10.aspx
//
//  Marius Samoila, Ivan Krivyakov, 2011
//
// FILE NAME
//		RectTracker.cpp: implementation for the CRectTracker class
//
// CLASS NAME
//		CRectTracker
//
// DESCRIPTION
// 
// MODIFICATIONS
//		01-Dec-2011 MSamoila created
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "RectTracker.h"

// Code based on:
// http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c331

static COLORREF const CLR_WHITE = RGB(255,255,255);

static void WINAPI NormalizeRect(LPRECT prc);
static void WINAPI DrawRect(HDC hDC, const LPPOINT pTopLeft, const LPPOINT pBottomRight);


//------------------------------------------------------
CRectTracker::CRectTracker()
{
    SetDefaults();
    ZeroMemory(&CurPos.Begin, sizeof(POINT));
    ZeroMemory(&CurPos.End, sizeof(POINT));
};

//------------------------------------------------------
CRectTracker::CRectTracker( POINT const& Begin, POINT const& End )
{
    SetDefaults();
    CurPos.Begin = Begin;
    CurPos.End = End;
};

//------------------------------------------------------
void CRectTracker::SetDefaults()
{
    LineWidth = 1;
    LineColor = CLR_WHITE;
    HandleSize.cx = HandleSize.cy = 8;
    HandleColor = CLR_WHITE;
    VicinitySize = 10;
    ZeroMemory(&ValidRect, sizeof(RECT));
};

//------------------------------------------------------
void CRectTracker::GetPos( POINT* Begin, POINT* End ) const
{
    *Begin = CurPos.Begin;
    *End = CurPos.End;
};

//------------------------------------------------------
void CRectTracker::SetPos( POINT const& Begin, POINT const& End )
{
    CurPos.Begin = Begin;
    CurPos.End = End;
};

//------------------------------------------------------    
void CRectTracker::SetValidRect( RECT const& Rect )
{
    ValidRect = Rect; 
    NormalizeRect(&ValidRect); 
}; 

//------------------------------------------------------    
BOOL CRectTracker::IsValidPos( KLinePos const& Pos )
{
    if (IsRectEmpty(&ValidRect))
        return TRUE;

    return PtInRect(&ValidRect, Pos.Begin) && PtInRect(&ValidRect, Pos.End);
};

//------------------------------------------------------    
RECT CRectTracker::GetHandleRect( KHandleType H ) const
{
    RECT R;
    
    if (H == handleBegin)
    {
        R.left = CurPos.Begin.x - HandleSize.cx/2;
        R.top  = CurPos.Begin.y - HandleSize.cy/2;
    }
    else
    {
        R.left = CurPos.End.x - HandleSize.cx/2;
        R.top  = CurPos.End.y - HandleSize.cy/2;
    };

    R.right = R.left + HandleSize.cx;
    R.bottom = R.top + HandleSize.cy;
    return R;
};

//------------------------------------------------------    
void CRectTracker::Draw( HDC hDC ) 
{
    SaveDC(hDC);
    HPEN LinePen = CreatePen( PS_SOLID, LineWidth, LineColor );
    HBRUSH HandleBrush = CreateSolidBrush( HandleColor );

    HPEN hOldPen = (HPEN)SelectObject(hDC, LinePen);

	DrawRect(hDC, &CurPos.Begin, &CurPos.End);

	SelectObject(hDC, hOldPen);

    FillRect(hDC, &GetHandleRect(handleBegin), HandleBrush );
    FillRect(hDC, &GetHandleRect(handleEnd), HandleBrush );

	DeleteObject(hOldPen);
	DeleteObject(HandleBrush);
};

//------------------------------------------------------    
int CRectTracker::HitTest( const LPPOINT point ) const
{
	ATLASSERT(point);

    // check handles
    RECT R = GetHandleRect(handleBegin);

    if (PtInRect(&R, *point))
        return hitBegin;

    R = GetHandleRect(handleEnd);
    if (PtInRect(&R, *point))
        return hitEnd;

    SetRect(&R, CurPos.Begin.x, CurPos.Begin.y, CurPos.End.x, CurPos.End.y );
    NormalizeRect(&R);
    RECT VicX = R;
    RECT VicY = R;

    InflateRect(&VicX, VicinitySize, 0 );
    InflateRect(&VicY, 0, VicinitySize );

    if (!PtInRect(&VicX, *point) && !PtInRect(&VicY, *point))
        return hitNothing;

    // check distance from the point to the line by x and by y
    int dx = CurPos.End.x - CurPos.Begin.x;
    int dy = CurPos.End.y - CurPos.Begin.y;
	double r = sqrt((double)(dx*dx + dy*dy));

    // compute coordinates relative to the origin of the line
    int x1 = point->x - CurPos.Begin.x;
    int y1 = point->y - CurPos.Begin.y;

    // compute coordinates relative to the line
    double x2 = (x1*dx + y1*dy)/r;
    double y2 = (-x1*dy + y1*dx)/r;

    if ((x2>=0) && (x2<=r) && (y2<=VicinitySize) && (y2 >= -VicinitySize))
        return hitMiddle;

    return hitNothing;
};

//------------------------------------------------------    
BOOL CRectTracker::SetCursor( HWND hWnd, UINT nHitTest ) const
{
    // trackers should only be in client area
    if (nHitTest != HTCLIENT)
	    return FALSE;

    // convert cursor position to client co-ordinates
    POINT point;
    GetCursorPos(&point);
    ScreenToClient(hWnd, &point);

    // do hittest and normalize hit
    switch (HitTest(&point))
    {
        case hitBegin:
            ::SetCursor( LoadCursor(NULL, IDC_SIZEWE ) );
            break;

        case hitEnd:
            ::SetCursor( LoadCursor(NULL, IDC_SIZEWE ) );
            break;

        case hitMiddle:
            ::SetCursor( LoadCursor(NULL, IDC_SIZEALL ) );
            break;

        default:
            return FALSE;
    };

    return TRUE;
};        

//------------------------------------------------------    
BOOL CRectTracker::Track( HWND hWnd, const LPPOINT point )
{
    return TrackHandle( hWnd, point, HitTest(point) );
};

//------------------------------------------------------    
BOOL CRectTracker::TrackRubberBand( HWND hWnd, const LPPOINT point )
{
	ATLASSERT(point);

    memcpy(&CurPos.Begin, point, sizeof(POINT));
    memcpy(&CurPos.End, point, sizeof(POINT));
    return TrackHandle( hWnd, point, hitEnd );
};

//------------------------------------------------------    
BOOL CRectTracker::TrackHandle( HWND hWnd, const LPPOINT point, int HitTest )
{
	ATLASSERT(point);

    if ((HitTest != hitBegin) && (HitTest != hitEnd) && (HitTest != hitMiddle))
        return FALSE;

    if (!IsValidPos())
        return FALSE;

    // set mouse cursor parameters
    RECT CursorRect;

    if (IsRectEmpty(&ValidRect))
    {
        GetClientRect(hWnd, &CursorRect);
    }
    else
    {
        // valid rectangle is not empty
        if (HitTest == hitMiddle)
        {
            RECT BeginRect = ValidRect;
			OffsetRect(&BeginRect, point->x, point->y);
			OffsetRect(&BeginRect,  -CurPos.Begin.x, -CurPos.Begin.y);
            NormalizeRect(&BeginRect);

            RECT EndRect = ValidRect;
			OffsetRect(&EndRect, point->x, point->y);
			OffsetRect(&EndRect,  -CurPos.End.x, -CurPos.End.y);
            NormalizeRect(&EndRect);

            CursorRect = ValidRect;
            IntersectRect(&CursorRect, &CursorRect, &BeginRect);
            IntersectRect(&CursorRect, &CursorRect, &EndRect);
        }
        else
            CursorRect = ValidRect;
    };

    if (IsRectEmpty(&CursorRect))
        return FALSE;

    ClientToScreen(hWnd, (LPPOINT)&CursorRect );
    ClientToScreen(hWnd, ((LPPOINT)&CursorRect)+1 );
    ClipCursor(&CursorRect);
    SetCapture(hWnd);

	// get DC for drawing
	HDC dc = GetDC(hWnd);

    // set dc parameters
    HPEN LinePen = CreatePen( PS_SOLID, LineWidth, CLR_WHITE );
    HPEN hOldPen = (HPEN)SelectObject(dc, LinePen);
    SetROP2(dc, R2_XORPEN );

    KLinePos OriginalPos = CurPos;
    BOOL bCanceled = FALSE;

    // draw the rectangle for the first time
	DrawRect(dc, &CurPos.Begin, &CurPos.End);

	// get messages until capture lost or cancelled/accepted
    BOOL bExit = FALSE;
    KLinePos NewPos;

	while (!bExit)
	{
		MSG msg;
		BOOL b = ::GetMessage(&msg, NULL, 0, 0);
		ATLASSERT(b);

		if (GetCapture() != hWnd)
			break; // exit loop

		switch (msg.message)
		{
		    // handle movement/accept messages
		    case WM_LBUTTONUP:
		    case WM_MOUSEMOVE:
                {
			        NewPos = CurPos;
                    POINT MousePoint = { (int)(short)LOWORD(msg.lParam), (int)(short)HIWORD(msg.lParam) };

                    switch (HitTest)
                    {
                        case hitBegin:
                            NewPos.Begin = MousePoint;
                            break;

                        case hitEnd:
                            NewPos.End = MousePoint;
                            break;

                        case hitMiddle:
                             NewPos.Begin.x = OriginalPos.Begin.x + (MousePoint.x-point->x);
                             NewPos.Begin.y = OriginalPos.Begin.y + (MousePoint.y-point->y);

                             NewPos.End.x = OriginalPos.End.x + (MousePoint.x-point->x);
                             NewPos.End.y = OriginalPos.End.y + (MousePoint.y-point->y);
                             break;
                    };

                    // redraw the line
                    if ((NewPos.Begin.x != CurPos.Begin.x) || (NewPos.Begin.y != CurPos.Begin.y) ||
						(NewPos.End.x != CurPos.End.x) || (NewPos.End.y != CurPos.End.y))
                    {
                        // draw new rectangle
						DrawRect(dc, &NewPos.Begin, &NewPos.End);

                        // erase old rectangle
						DrawRect(dc, &CurPos.Begin, &CurPos.End);
                    };

                    if (IsValidPos(NewPos))
                        CurPos = NewPos;

                    if (msg.message == WM_LBUTTONUP)
                        bExit = TRUE;
                };
    	        break;

		    // handle cancel messages
		    case WM_KEYDOWN:
			    if (msg.wParam == VK_ESCAPE)
                {
                    bCanceled = TRUE;
                    bExit = TRUE;
                };
			    break;

		    case WM_RBUTTONDOWN:
                bCanceled = TRUE;
                bExit = TRUE;
                break;

		    // just dispatch rest of the messages
		    default:
			    DispatchMessage(&msg);
			    break;
	    }
    };

    ClipCursor(NULL);
    ReleaseCapture();

    if (bCanceled)
        CurPos = OriginalPos;

    SelectObject(dc, hOldPen);
	DeleteObject(LinePen);
	ReleaseDC(hWnd, dc);
    return !bCanceled;
};


////////////////////////////

void WINAPI NormalizeRect(LPRECT prc)
{
	int nTemp;
	if (prc->left > prc->right)
	{
		nTemp = prc->left;
		prc->left = prc->right;
		prc->right = nTemp;
	}
	if (prc->top > prc->bottom)
	{
		nTemp = prc->top;
		prc->top = prc->bottom;
		prc->bottom = nTemp;
	}
}

static void WINAPI DrawRect(HDC hDC, const LPPOINT pTopLeft, const LPPOINT pBottomRight)
{
	POINT pts[5];
	pts[0].x = pTopLeft->x;
	pts[0].y = pTopLeft->y;
	pts[1].x = pTopLeft->x;
	pts[1].y = pBottomRight->y;
	pts[2].x = pBottomRight->x;
	pts[2].y = pBottomRight->y;
	pts[3].x = pBottomRight->x;
	pts[3].y = pTopLeft->y;
	pts[4].x = pTopLeft->x;
	pts[4].y = pTopLeft->y;
	Polyline(hDC, pts, 5);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada

Comments and Discussions