Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Calculating a Rich Edit Control Minimum Size

0.00/5 (No votes)
1 Dec 1999 1  

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 is a CRichEditCtrl object


   // Set the ENM_REQUESTRESIZE event flag

   m_Ctrl .SetEventMask( ENM_REQUESTRESIZE );

   // Force the control to issue a EN_REQUESTRESIZE notification

   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.

   // Calculating the CRichEditCtrl m_Ctrl minimum size


   m_Ctrl.SetEventMask( ENM_REQUESTRESIZE );

   // m_dimRtf is a CSize object that stores m_Ctrl required size


   m_dimRtf.cx = 0;
   m_dimRtf.cy = 0;

   // Performing the binary search for the best dimension


   int cxFirst = 0;
   int cxLast = ::GetSystemMetrics( SM_CXFULLSCREEN ) / 2;
   int cyMin = 0;

   cxLast *= 2;

   do
   {
      // Taking a guess

      int cx = ( cxFirst + cxLast ) / 2;

      // Testing this guess

      CRect rc( 0, 0, cx, 1 );
      m_Ctrl.MoveWindow( rc );
      m_Ctrl.RequestResize();

      // If it's the first time, take the result anyway.

      // This is the minimum height the control needs

      if( cyMin == 0 )
         cyMin = m_dimRtf.cy;

      // Iterating

      if( m_dimRtf.cy > cyMin )
      {
         // If the control required a larger height, then

         // it's too narrow.

         cxFirst = cx + 1;
      }
      else
      {
         // If the control didn't required a larger height,

         // then it's too wide.

         cxLast = cx - 1;
      }
   }
   while( cxFirst < cxLast );

   // Giving it a few pixels extra width for safety

   m_dimRtf.cx += 2;

   // Moving the control

   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 );

      // Storing the requested sized to be used in the binary search


      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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here