SHARE
Facebook X Pinterest WhatsApp

Crosshairs and Supporting Text That Moves

Environment: [VC6, MFC,  SP4, NT4/2000] Skill Level: Beginner, Intermediate   Introduction Not long ago, I was asked to implement a crosshair functionality in an OCX control that displays graphical data. Recalling that I saw something on CodeGuru, I found Paul Shaffers article ROP Codes, Rubber Bands, Clip Regions & Coordinate Transforms, which shows how to […]

Written By
thumbnail
CodeGuru Staff
CodeGuru Staff
Jun 4, 2001
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

Environment: [VC6, MFC,  SP4, NT4/2000]

Skill Level:
Beginner, Intermediate

 

Introduction

Not long ago, I was asked to implement a crosshair functionality in an OCX
control that displays graphical data. Recalling that I saw something on
CodeGuru, I found Paul Shaffers article ROP
Codes, Rubber Bands, Clip Regions & Coordinate Transforms
, which shows
how to draw Rubberbands and which explores GDI map modes and coordinate transforms.
I wanted a functionality as shown in the above picture that is: when users move
the cursor, the X and Y coordinate values should change as the cursor is
moved. Easy I thought, just extend the technique of drawing with an R2_NOTXORPEN
ROP2 code. It turns out that things were not quite that simple.

Problems with TextOut and R2_NOTXORPEN

Drawing the crosshair per-se turned out to be fairly easy. The problems
started appearing when I tried to erase text by simply writing it twice using
the TextOut function as in the code snippet below:

pdc->SetROP2(R2_NOTXORPEN);
pdc->SetMapMode(MM_TEXT);
pdc->SetTextAlign(TA_TOP | TA_LEFT);
pdc->SetBkMode(TRANSPARENT);
pdc->SetTextColor(RGB(255,255,255));
pdc->SetBkColor(0);
pdc->TextOut(10,10, _T("Test"));
// erasing does not work, it just gets overwritten
pdc->TextOut(10,10, _T("Test"));

It turns out that writing the text twice using TextOut() does not work. Then how do you


move text interactively?

Saving the background

My solution to the problem is to save the area of the DC to be
overwritten to a bitmap; only after saving is the text drawn. When it
comes time to erase the text, the area occupied by the text is restored by
copying it back from the previously saved bitmap. This technique will also work if you
need to drag text (or any other graphics) and show the text while it is being dragged.
The two functions below save and restore an area of screen to/from the
display. Note that the bitmaps need to be saved between messages, so I use
a CBitmap* member variable.

void CDrawTextCtrl::SaveAreaRectangleToBitmap(CDC* pdc, CRect& rc,
               CBitmap** ppBitmap)
{
   CDC   dcMem;      // memory dc
   // initialize other memory dc
   dcMem.CreateCompatibleDC(pdc);
   // Create a compatible bitmap.
   *ppBitmap = new CBitmap;
   (*ppBitmap)->CreateBitmap(rc.Width(),
                rc.Height(),
                pdc->GetDeviceCaps(PLANES),
                pdc->GetDeviceCaps(BITSPIXEL),
                NULL) ;
   // Select the bitmap into the memory DC.
   CBitmap* pOldBitmap = dcMem.SelectObject(*ppBitmap);
   // Blt the memory device context to the screen.
   dcMem.BitBlt( 0,            // dst X
                 0,            // dst Y
                 rc.Width(),   // dst width
                 rc.Height(),  // dst height
                 pdc,          // source DC
                 rc.left,      // source starting X
                 rc.top,       // source starting Y
                 SRCCOPY) ;
   // cleanup
   dcMem.SelectObject(pOldBitmap);
}
void CDrawTextCtrl::CopySavedAreaRectangleBack(CDC *pdc,
            CBitmap* pBitmap, CRect& rcOld)
{
   CDC   dcMem;    // memory dc
   // at first, we need to copy the background from
   // our saved bitmap back to the screen DC
   // initialize other memory dc
   dcMem.CreateCompatibleDC(pdc);
   // Select the bitmap into the memory DC.
   CBitmap* pOldBitmap = dcMem.SelectObject(pBitmap);
   // Blt the bitmap to screen
   pdc->BitBlt( rcOld.left,
                   rcOld.top,
                   rcOld.Width(),
                   rcOld.Height(),
                   &dcMem,
                   0,
                   0,
                   SRCCOPY);
   // cleanup
   dcMem.SelectObject(pOldBitmap);
}

Junk crosshair left behind when resizing, losing focus and other small
problems

A number of details gave problems while writing the code. At first
I did not erase the crosshair before copying the background to the bitmap.
This yielded junk lines when moving the mouse quickly. If
a users resized the control, usually an ‘old crosshair’ would be left and shown
with the new crosshair after resizing. The solution to this problem was to
erase the crosshair when starting to size the control. This was done by simply
forcing a redraw of the control and by deleting the saved
bitmaps. While trying to take screenshots of the completed
control, I switched between running applications and found another problem. Two crosshairs
were appearing after switching back to the test control container and moving the
mouse. Also, I noticed that the crosshair was still being drawn in the
ActiveX test control container even when another program had the focus. Playing
with the program, other focus and redrawing-related problems that needed
solving. Finally when moving to the bottom right of the control, labels
would run into each other, and in certain other boundary cases, the
crosshairs would be drawn through one of the labels. Below the code that
calculates the rectangle where the label for the X crosshair is drawn

// calculate the rectangle where the text will appear,
// leave 2 pixels for space. Check for enough space on right
// and make sure we don’t run through anything
if (point.x + sizeTextX.cx + 2 > m_rcCtl.right )
{
   // not enough space, text will run into right side
   // so paint to the left
   rcX.SetRect(point.x2 – sizeTextX.cx,
               m_rcCtl.bottom – sizeTextX.cy,
               point.x2,
               m_rcCtl.bottom);
}
else // enough space
{
   rcX.SetRect(point.x + 2,
               m_rcCtl.bottom – sizeTextX.cy,
               point.x + sizeTextX.cx + 2,
               m_rcCtl.bottom);
   // does the horizontal crossline go through the X label, if so offset
   if (point.y > rcX.top)
      rcX.OffsetRect(0, -sizeTextX.cy);
}

Conclusion

This project is a perfectly good example why estimating software projects is
so difficult – I had initially thought I could provide the functionality in one
hour or so whereas the final debugged project took about eight times longer.
This article shows how to draw crosshairs, but more importantly how to draw text
and other GDI objects that need to be moved (and redrawn) interactively by the
user. Below the code that determines the rectangle where to place the label for
the X crosshair position.

Possible Improvements

  • Hit testing of the labels could be improved, in some cases the labels
    still run into each other.

 

Downloads


Download source – 16 Kb

 

Recommended for you...

Drawing 3D OpenGL Graphics on Google Maps
Mandelbrot Using C++ AMP
CodeGuru Staff
Jan 27, 2012
Simple C++ MP3 Player Class
CodeGuru Staff
Aug 22, 2011
Library for Raw Video Processing
CodeGuru Staff
Jun 14, 2011
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.