This explains a technique to calculate the minimum size that's required by a
Rich Edit Control to fully display all its contents. The Rich Edit Control
already has what's called a "bottomless" behavior, but as we'll see, it's doesn't
solve the problem of calculating the optimal "width".
Your application can resize a Rich Edit Control (CRichEditControl) as
needed, so that the control is always the same height as its contents. This
is what's called a "bottomless" behavior. The CRichEditControl supports it by
sending its parent window a EN_REQUESTRESIZE notification whenever the height of
its contents changes.
When processing the EN_REQUESTRESIZE notification the parent window should resize
the control to the dimensions in the specified REQRESIZE structure. Of course the
parent window should also move any graphical element near the control to open room for the
control's change in height.
In order to activate the EN_REQUESTRESIZE notification, the application must set
the ENM_REQUESTRESIZE event flag of the control's event mask. The application can
also "force" the control to send a EN_REQUESTRESIZE notification by calling the
control's RequestResize member function. For example, the following code does that:
m_Ctrl .SetEventMask( ENM_REQUESTRESIZE );
m_edCtrl.RequestResize( );
In the EN_REQUESTRESIZE handler, the parent window can use CWnd::SetWindowPos
or CWnd::MoveWindow to resize the control.
This mechanism works very well, but for one detail. It works only to adjust the "height"
of the control, not its "width". The point is that the control can always solve problems
of width by word breaking lines, thus turning it again in a "height" problem. Yet, this
is automatic and there's nothing you can do to prevent this behavior. You can change
the right margin (CRichEditCtrl::SetRect), but you can't ask the control what's
the optimal value for the width.
Now, suppose you have very short single line paragraphs that you really want to display
unbroken. Yet, you would like the control to not waste horizontal real state, and to be
as narrow as the wider text line - no one single pixel wider (for an example of a UI that
has such requirements, take a look in my
TCX Message Box class).
Well, with the CRichEditCtrl's normal bottomless behavior you don't get it.
If you use a too narrow width, the control will break the text lines. And if you use a
too wide width, the UI might look wasting space.
The technique I used in the
TCX Message Box class
is a "binary search" for the best width. Basically I start by sizing the control to the
largest width that I can afford in the UI (in the
TCX Message Box
that means 1/2 the of the screen width), then I force a EN_REQUESTRESIZE notification
and take the required height. This is the minimum height the control needs to show
its contents.
All I have to do now is to find the minimum width that still keeps that height. If I
set a too small width, the control will break the lines and require a larger height.
Since the relation is linear, I can optimize the search with a binary search algorithm.
Here're the code.
m_Ctrl.SetEventMask( ENM_REQUESTRESIZE );
m_dimRtf.cx = 0;
m_dimRtf.cy = 0;
int cxFirst = 0;
int cxLast = ::GetSystemMetrics( SM_CXFULLSCREEN ) / 2;
int cyMin = 0;
cxLast *= 2;
do
{
int cx = ( cxFirst + cxLast ) / 2;
CRect rc( 0, 0, cx, 1 );
m_Ctrl.MoveWindow( rc );
m_Ctrl.RequestResize();
if( cyMin == 0 )
cyMin = m_dimRtf.cy;
if( m_dimRtf.cy > cyMin )
{
cxFirst = cx + 1;
}
else
{
cxLast = cx - 1;
}
}
while( cxFirst < cxLast );
m_dimRtf.cx += 2;
m_Ctrl.MoveWindow( xMsg, cyTop, m_dimRtf.cx, m_dimRtf.cy );
And, the parent's window EN_REQUESTRESIZE notification handler member function:
void CParenWindow::OnRequestResize( NMHDR* pNMHDR, LRESULT* pResult )
{
_ASSERT( pNMHDR->code == EN_REQUESTRESIZE );
REQRESIZE* prr = (REQRESIZE*)pNMHDR;
m_dimRtf.cx = prr->rc.right - prr->rc.left;
m_dimRtf.cy = prr->rc.bottom - prr->rc.top;
*pResult = NULL;
}
Final Notes
Do note that, in the
TCX Message Box class, I do
all this calculation and resizing before I actually display the window. Otherwise, it will flick
crazily as the application traverses the binary search loop. Therefore, if you need to recalculate
the Rich Edit Control size when it's already visible, you must first freeze the parent
window redraw, or you should move the Rich Edit Control to outside of the visible area,
do all the calculation and, when it's done, you get it back to the visible area.