I would like to have line numbers in the left margin of my TRichEdit, on the
side or on the control, like many code editors offer, but without being part
of the Text / Lines properties. All lines have the same height. Any
idea/pointer would be much appreciated.
Thanks,
Guillaume
Ok, the first thing that you need to do is add a permanent margin to the
RichEdit control. Specifically, you need to adjust the "working area" a.k.a.
formatting rectangle of the RichEdit. This can be accomplished via the
EM_GETRECT and EM_SETRECT messages. First, use the EM_GETRECT message to
retrieve the current formatting rectangle, then offset it, and finally use the
EM_SETRECT message, specifying this offset rectangle. So for example...
RECT R;
SNDMSG(RichEdit1->Handle, EM_GETRECT, 0, reinterpret_cast<LPARAM>(&R));
R.left = R.left + 35;
SNDMSG(RichEdit1->Handle, EM_SETRECT, 0, reinterpret_cast<LPARAM>(&R));
This much is straightforward, however, there are some caveats. First, the
RichEdit must be visible for this to work. Next, the TRichEdit class recreates
the underlying window according to several property specifications, effectively
undoing the previous formatting rectangle changes. To work around the first
problem, simply set the formatting rectangle after the control has painted
itself. The second hurdle can be overcome by create a TRichEdit descendant and
augmenting the CreateWnd() member function (in which you set the formatting
rectangle).
To actually render the line numbers, you need to handle the WM_PAINT message
directly. Within the message handler, use the GetDC() API function to grab a
handle to the RichEdit's display device context, then perform any rendering to
this DC. Usually, iterative rendering approaches lead to flicker, so your best
bet is to use a double-buffering technique. That is, render all the line
numbers to a bitmap, then finally render the bitmap to the RichEdit's device
context in one shot. Also, to speed things up, you aonly want to render the
line numbers of the lines that are visible. You can determine the indices of
the first and last visible lines via a combination of the EM_CHARFROMPOS and
EM_EXLINEFROMCHAR messages. Here's an example...
// in header...
class TMyRichEdit : public TRichEdit
{
private:
Graphics::TBitmap *m_FMemBitmap;
RECT m_FRGutter;
bool m_FFirstPaint;
MESSAGE void __fastcall WMPaint(TMessage &Msg);
protected:
virtual void __fastcall CreateWnd();
virtual void __fastcall DrawGutter(TCanvas *ACanvas);
virtual void __fastcall DrawLineNumbers(TCanvas *ACanvas);
public:
__fastcall TMyRichEdit(TComponent *AOwner);
__fastcall ~TMyRichEdit();
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_PAINT, TMessage, WMPaint)
END_MESSAGE_MAP(TRichEdit)
};
// in source...
__fastcall TMyRichEdit::TMyRichEdit(TComponent *AOwner)
: TRichEdit(AOwner), m_FFirstPaint(false)
{
// create a back-buffer bitmap
m_FMemBitmap = new Graphics::TBitmap();
m_FMemBitmap->Canvas->Brush->Color = clBtnFace;
}
__fastcall TMyRichEdit::~TMyRichEdit()
{
// destroy the back-buffer bitmap
delete m_FMemBitmap;
}
void __fastcall TMyRichEdit::CreateWnd()
{
TRichEdit::CreateWnd();
// implicitly reset the working rectangle
m_FFirstPaint = true;
}
void __fastcall TMyRichEdit::WMPaint(TMessage &Msg)
{
TRichEdit::Dispatch(&Msg);
if (m_FFirstPaint)
{
// once-only flag
m_FFirstPaint = false;
// set the working area
RECT R;
SNDMSG(Handle, EM_GETRECT, 0, reinterpret_cast<LPARAM>(&R));
R.left = R.left + 35; // set the width of the gutter
SNDMSG(Handle, EM_SETRECT, 0, reinterpret_cast<LPARAM>(&R));
// calculate the gutter rectangle
m_FRGutter = static_cast<RECT>(ClientRect);
m_FRGutter.right = R.left - 3;
// specify the back-buffer bitmap dimensions
m_FMemBitmap->Width = m_FRGutter.right - m_FRGutter.left;
m_FMemBitmap->Height = m_FRGutter.bottom - m_FRGutter.top;
}
// grab a handle to the device context
HDC Hdc = GetDC(Handle);
try
{
// render the gutter to the bitmap
DrawGutter(m_FMemBitmap->Canvas);
// render the line numbers to the bitmap
DrawLineNumbers(m_FMemBitmap->Canvas);
// block transfer the bitmap's bits to the display
// device context of the RichEdit control
BitBlt(Hdc, 0, 0,
m_FMemBitmap->Width, m_FMemBitmap->Height,
m_FMemBitmap->Canvas->Handle, 0, 0,
SRCCOPY);
}
catch (...)
{
// release the device context
ReleaseDC(Handle, Hdc);
}
// release the device context
ReleaseDC(Handle, Hdc);
}
void __fastcall TMyRichEdit::DrawGutter(TCanvas *ACanvas)
{
// render the gutter
ACanvas->Brush->Color = clBtnFace;
ACanvas->FillRect(ACanvas->ClipRect);
DrawEdge(ACanvas->Handle, &m_FRGutter, EDGE_BUMP, BF_RIGHT);
}
void __fastcall TMyRichEdit::DrawLineNumbers(TCanvas *ACanvas)
{
// change the font and ROP3 mode
ACanvas->Font = Font;
ACanvas->Brush->Style = bsClear;
// calculate the postion to render to
RECT RNumber = m_FRGutter;
InflateRect(&RNumber, -5, 0);
const int letter_height = ACanvas->TextHeight("A");
OffsetRect(&RNumber, 0, -letter_height + 2);
// find the index of the top visible line
POINTL POrg = {0, 0};
long char_index =
SNDMSG(Handle, EM_CHARFROMPOS, 0, reinterpret_cast<LPARAM>(&POrg));
long start_line = SNDMSG(Handle, EM_EXLINEFROMCHAR, 0, char_index);
// find the index of the last visible line
POINTL PEnd = {0, ClientHeight};
char_index =
SNDMSG(Handle, EM_CHARFROMPOS, 0, reinterpret_cast<LPARAM>(&PEnd));
long stop_line = SNDMSG(Handle, EM_EXLINEFROMCHAR, 0, char_index);
// render the line numbers
for (int index = start_line; index <= stop_line; ++index)
{
RNumber.top = RNumber.top + letter_height;
RNumber.bottom = RNumber.top + letter_height;
AnsiString line_number(index);
DrawText(ACanvas->Handle, line_number.c_str(), line_number.Length(),
&RNumber, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
}
}
HTH.
--
Damon Chandler
http://bcbcaq.freeservers.com
Damon Chandler wrote:
>
> Ok, the first thing that you need to do is add a permanent margin to the
> RichEdit control. Specifically, you need to adjust the "working area" a.k.a.
<snip>
Wow, Damon. Very good. I would have wimped out and suggested using
more than one RE and trying to keep them synchronized. This is much
better (assuming that it works, of course <g>). Thanks.
robert
Thanks! I was thinking of using two controls at first as well, but I
decided to give the presented approach a shot.
Robert Dunn wrote:
> Wow Damon. Very good. I would have wimped out and suggested using
zombacity wrote:
>
> But it would be much easier just to have another RE bordering the first at its LHS
> and put the line count into each line of the second. Wouldn't it?? Question: do you
> prefer easy practice or interesting theory?
>
> Robert Dunn wrote:
<snip>
Perhaps I am confused, Z.... Damon offered code that I thought was
both interesting (in theory) and a better solution than two RE controls
(LHS or RHS). Perhaps I missed your point?
robert
zombacity wrote:
>
> Damon's code is great...but IMHO needs quite a lot of work to understand and quite a lot
> of code lines to implement.
Perhaps Damon's solution is "a lot of code," but probably not much more
(maybe less) than synchronizing two RE controls. (That is much harder
than you would expect for pre-RE 3.0 controls.) And certainly his code
is far more straight-forward, IMHO.
robert
Seems an easy way to synchronise the two REs.
OK, it needs some refinement to keep the fonts in line and make sure that RE2 ( the index RE)
has enough lines filled in with line numbers to keep up with RepRE (the data RE).
//-------
No. What's happens when the user scrolls with the scroll bar, but the caret
position doesn't change. The line numbers don't update when the user enter's a
line break, or the text wraps to form a new line. What about OLE drag and
drop? By tapping into the WM_PAINT message, all of these cases are handled.
Robert is right, this is not a trivial task, and, FWIW, we are both familiar
with the relevant RE messages (Robert much more than I <g>).
--
Damon Chandler (TeamB)
http://bcbcaq.freeservers.com