Click here to Skip to main content
15,892,697 members
Articles / Desktop Programming / MFC
Article

Hilite Edit View

Rate me:
Please Sign up or sign in to vote.
4.79/5 (28 votes)
5 Jun 2005CPOL3 min read 126.3K   3.6K   29   19
A CEditView derived class that highlights the caret line.

Sample Image - hiliteedit.gif

Introduction

I'm a little nervous to introduce my first code here. CHiliteEditView is a small reusable class derived from CEditView that highlights the current caret line, something like the famous editor program UltraEdit does. I wrote this class just for fun, and I like MFC very much.

How to use?

Using CHiliteEditView in your project is fairly easy, just add HiliteEditView.cpp and HiliteEditView.h to your project, and add the following include line:

C++
#include "HiliteEditView.h"

to your stdafx.h, then replace CEditView with CHiliteEditView in yourview.cpp and yourview.h files. Then compile and run.

If your project doesn't use CEditView as the base class, but another class, and you want to try this funny stuff, all you have to do is change the view's base class to CHiliteEditView, in the class declaration and implementation and message map declarations.

Details

I think it is easier for me to make it than to explain how I made it. First problem is when to highlight the caret line? The answer is: after the control's normal paint action. When we enter some text, or click the mouse, or scroll the view, the Edit control will repaint itself. If we want to paint some extra stuff, it is a great chance to do so at that time, just after the control's normal repaint actions.

Obviously, we must handle the following messages:

  • WM_PAINT - main place when the control needs repainting.
  • WM_LBUTTONDOWN - when we click in the control's area, the caret may have moved to another line, so we have to highlight the new line and repaint the previous highlighted line to its normal state.
  • WM_KEYDOWN - when we move the caret by arrow keys, or PgDn/PgUp etc...
  • WM_MOUSEMOVE - when we drag to select, it also needs to update the hilite bar.
  • WM_CHAR - why is it needed? Isn't WM_KEYDOWN enough? Actually I first wrote the class in CRichEditView without handling WM_CHAR and it worked just fine. But when I tried to rewrite it in CEditView, it didn't repaint when I entered a char. Finally I found the reason: when entering a char in RichEdit control, it updates the screen by sending a WM_PAINT; this doesn't happen with the Edit control - it updates the screen by drawing on the screen directly in the WM_CHAR message handler (or others?) and does not send a WM_PAINT message.

Methods

  • int GetCaretLine() const - returns the line number which contains the caret.
  • void GetLineRect(int nLine, LPRECT lpRect) const - gets the rectangle bound of the specified line in client coordinates.
  • virtual void DrawCaretLine(BOOL bInPaint = FALSE) - highlights the line containing the caret. Can be overridden to implement other kinds of hilite.

I would say some more words on DrawCaretLine().

C++
void CHiliteEditView::DrawCaretLine(BOOL bInPaint)
{
    int nLine = GetCaretLine();
    // for effective we need not redraw when we
    // just move caret in the same line using arrow keys, simply return.
    if (nLine == m_nCaretLine && !bInPaint)
    return;

    CRect rectClip;
    GetEditCtrl().GetRect(rectClip);
    CClientDC dc(this);
    dc.IntersectClipRect(rectClip);

    int nLineFirst = GetEditCtrl().GetFirstVisibleLine();
    int nLineLast = GetEditCtrl().LineFromChar(
                            GetEditCtrl().CharFromPos(
                                rectClip.BottomRight()
                            )
                        );

    // hide caret, else it will be ugly.
    HideCaret();

    if (m_nCaretLine >= nLineFirst && m_nCaretLine <= nLineLast)
    {
        // in this section we must not make WM_PAINT a loop
        // so don't let OnPaint() call our DrawCaretLine()
        m_bCanPaint = FALSE;
        CRect rect;
        GetLineRect(m_nCaretLine, rect);
        InvalidateRect(rect, FALSE);
        // update immediately
        UpdateWindow();
        m_bCanPaint = TRUE;
    }

    // we change the caret line color by ROP
    if (nLine >= nLineFirst && nLine <= nLineLast)
    {
        CRect rect;
        GetLineRect(nLine, rect);
        CDC dcMem;
        dcMem.CreateCompatibleDC(&dc);
        CBitmap bmp;
        bmp.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());
        CBitmap* pSaveBmp = dcMem.SelectObject(&bmp);
        CBrush br(RGB(0, 0, 255));
        dcMem.FillRect(CRect(0, 0, rect.Width(), rect.Height()), &br);

        // "capture" the line into our memory dc, and "INVERT" it
        dcMem.BitBlt(0, 0, rect.Width(), rect.Height(), 
                    &dc, rect.left, rect.top, SRCINVERT
                );

        // blt it back to origin place, but change colors
        dc.BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), 
                    &dcMem, 0, 0, SRCCOPY
                );
        dcMem.SelectObject(pSaveBmp);
    }

    ShowCaret();
    m_nCaretLine = nLine;
}

First, we compare the previous and current caret lines to decide if we need to paint. Normally we needn't repaint twice in the same line, but when handling WM_PAINT, it always needs to repaint because of scrolling. This is the purpose of the BOOL parameter bInPaint. In the WM_PAINT handler, we call DrawCaretLine() with bInPaint set to TRUE, and in other cases, we just forget the parameter, which is default to FALSE.

When the caret position changes, before we paint a hilite bar in the new place, we have to erase the old one. We simply invalidate the previous line rectangle and force WM_PAINT to handle it. There is another important thing here to indicate that the WM_PAINT handler must not call DrawCaretLine() in this case, otherwise it will go into a loop. The OnPaint() handler looks like this:

C++
void CHiliteEditView::OnPaint() 
{
    Default();

    if (m_bCanPaint)
        DrawCaretLine(TRUE);
}

So we can set m_bCanPaint to TRUE to disable this loop, and restore m_bCanPaint to FALSE immediately after repainting.

Finally, I appreciate your enduring my angularity English. Thank you!

License

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


Written By
Web Developer
China China
I love tech!

Comments and Discussions

 
QuestionLicense question Pin
tsimer8-Apr-12 19:30
tsimer8-Apr-12 19:30 
AnswerRe: License question Pin
Yi Yang16-Nov-12 4:01
Yi Yang16-Nov-12 4:01 
GeneralThanks, and a note Pin
Dan Bloomquist2-Feb-06 15:42
Dan Bloomquist2-Feb-06 15:42 
First, the note. The method for the last line will return the last line in the document. I don't know if this is because something has changed since VC6. As I may have thousands of lines I thought this could make the app sluggish if you are doing multiple lines. It should be:
long lastLine= HIWORD( GetEditCtrl( ).CharFromPos( rectClip.BottomRight( ) ) );

You have saved me some time, Thanks! (I'm not strong on DCs)

Here is what I'm up to for visual feedback to my client before they import text data. I have a stack of line objects that tell me what to do with the lines. I want to color all the lines appropriately in the doc. First is to push back RECTs that are visible:


bCanRedraw= false;
std::vector << RECT >> rects;
for( int i= firstLine; i < lastLine && i < (long)lines.size( ); ++i )
if( lines[i].bInclude )
{
CRect rect;
GetLineRect( i, rect );
InvalidateRect( rect, FALSE );
rects.push_back( rect );
UpdateWindow( );
}
bCanRedraw= true;

//Don't need to continue if no lines to color
if( !rects.size( ) )
return;

// As this is an edit control, the height and width are always the same.
long width= rects.begin( )->right - rects.begin( )->left;
long height = rects.begin( )->bottom - rects.begin( )->top;

// Do the stuff that doesn't need to be done in the loop
CBrush br( RGB( 0, 0, 255 ) );
CDC dcMem;
CBitmap bmp;
bmp.CreateCompatibleBitmap( &dc, width, height);
dcMem.CreateCompatibleDC( &dc );
CBitmap* pSaveBmp= dcMem.SelectObject( &bmp );
std::vector<< RECT >> ::iterator itRects;
for( itRects= rects.begin( ); itRects != rects.end( ); ++itRects )
{
//reset the dcMem
dcMem.FillRect( CRect( 0, 0, width, height), &br );
// "capture" the line into our memory dc, and "INVERT" it
dcMem.BitBlt( 0, 0, width, hight, &dc, itRects->left, itRects->top, SRCINVERT );
// blt it back to origin place, but change colors
dc.BitBlt( itRects->left, itRects->top, width, height, &dcMem, 0, 0, SRCCOPY );
}
dcMem.SelectObject( pSaveBmp );

I'll want to color lines differently. I'll probably extend the RECT objects with an index into brushes for that.

Best, Dan. (vote of 5!)



-- modified at 22:18 Thursday 2nd February, 2006
GeneralRe: Thanks, and a note Pin
Yi Yang19-Feb-06 23:03
Yi Yang19-Feb-06 23:03 
GeneralThanks a million Pin
k7771-Dec-05 3:28
k7771-Dec-05 3:28 
GeneralHi,GOOD JOB! Pin
YangTze1-Jul-05 22:33
YangTze1-Jul-05 22:33 
GeneralRe: Hi,GOOD JOB! Pin
Yi Yang4-Jul-05 2:16
Yi Yang4-Jul-05 2:16 
GeneralBUG Pin
benjamin2320-May-05 0:28
benjamin2320-May-05 0:28 
GeneralRe: BUG Pin
Yi Yang3-Jun-05 1:31
Yi Yang3-Jun-05 1:31 
GeneralRe: BUG Pin
Yi Yang3-Jun-05 6:12
Yi Yang3-Jun-05 6:12 
GeneralDont' mess with SRCINVERT Pin
Kochise25-Jan-05 22:30
Kochise25-Jan-05 22:30 
GeneralRe: Dont' mess with SRCINVERT Pin
Yi Yang26-Jan-05 3:07
Yi Yang26-Jan-05 3:07 
Generalyou may wanna start out with little stuffs Pin
mystro_AKA_kokie28-Aug-02 20:12
mystro_AKA_kokie28-Aug-02 20:12 
GeneralRe: you may wanna start out with little stuffs Pin
2sky29-Aug-02 0:13
2sky29-Aug-02 0:13 
GeneralRe: you may wanna start out with little stuffs Pin
Paul A. Howes29-Aug-02 2:26
Paul A. Howes29-Aug-02 2:26 
GeneralRe: you may wanna start out with little stuffs Pin
Yi Yang29-Aug-02 19:09
Yi Yang29-Aug-02 19:09 
GeneralRe: you may wanna start out with little stuffs Pin
Yi Yang29-Aug-02 18:46
Yi Yang29-Aug-02 18:46 
GeneralRe: you may wanna start out with little stuffs Pin
mystro_AKA_kokie30-Aug-02 10:18
mystro_AKA_kokie30-Aug-02 10:18 
GeneralUseful for my source editor ;) Pin
Kochise26-Sep-03 2:33
Kochise26-Sep-03 2:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.