windows_programming_notes.nbk: Home | Index | Next Page: Using Edit Controls in a Window | Previous Page: Using DDE


 Using Edit Controls

Abstract

This article is a complete reference for edit controls. It describes all edit control styles, messages, and notifications in more detail than those in the Microsoft® Windows™ versions 3.0 and 3.1 Software Development Kit (SDK) documentation. Techniques for solving common problems with edit controls are also described in this article.

Introduction

Edit controls are a way for an application to receive input from a user. They also provide a way for the application to display information, especially when the information requires more than one line. There are two types of edit controls:

Edit controls will not accept virtual-key code less than 0x20. The only exception is that multiple-line edit controls will accept TAB and ENTER key codes. Single-line edit controls do not accept TAB and ENTER keys.

Limits of Edit Controls

Edit controls were designed to enter, display, and edit small amounts of text. They were not meant to be the basis for large-scale text editors. Edit controls in Microsoft® Windows™ have the following limits:

Single-line edit controls are limited to a maximum of 32K (32,767 bytes) of text and, of course, to a single line. By default, single-line edit controls are limited to 30,000 characters. An application can change this limit to a maximum of 32,767 characters with the EM_LIMITTEXT message described in "Edit Control Messages," later in this article.

Multiple-line edit controls are limited to a maximum of 64K (65,535 bytes) of text. Whether a multiple-line edit control is actually able to approach this limit is based on how the edit control uses memory. Techniques to control the way edit controls use memory are described in the next section, "Edit Controls and Memory." Multiple-line edit controls are also limited to the following:

Edit Controls and Memory

An edit control allocates its control structure, tab stop settings, and text buffer from a local heap by calling LocalAlloc or LocalRealloc. A multiple-line edit control allocates its character-width buffer and line-break array from the local heap as well. The Undo buffer is allocated in the global heap. Because a local heap has a maximum size of 64K bytes, and the text buffer for an edit control plus the edit control's support information are all allocated from one local heap, the actual amount of text that an edit control can hold is always less than 64K.

Table 1 shows some guidelines on the amount of local heap an edit control uses. These rules are not likely to be accurate for versions of Windows later than version 3.1.

Edit controls that are not in a dialog box use the data segment (DS) of their parent for the edit control's local heap. The amount of text an edit control can store is affected by the amount of memory that has already been used in this heap. To maximize the amount of text that an edit control can contain, the application should supply a heap for the edit control that has not been used for any other allocations. To do this, an application should perform the following steps:

Allocate a global memory block 256 bytes in length. This block becomes the local heap for the edit control. In real mode, this handle must be less than 0x2000. If the handle returned is greater than 0x2000 and Windows is running in real mode, you cannot use this global memory block as a local heap.

Pass the handle to the newly allocated global memory block to CreateWindow (or CreateWindowEx) in the hInstance parameter.

Send the edit control an EM_LIMITTEXT message to set the maximum number of characters in the edit control. A zero value passed with EM_LIMITTEXT allows for the maximum number of characters as shown in the following sample code:

      // Assuming PROTECTED MODE ONLY
      //
      // Real mode would need to check for 0x2000 returned.
      //
      hEditDS = GlobalAlloc(GMEM_MOVEABLE |
                            GMEM_ZEROINIT | GMEM_SHARE, 256L);
      if (hEditDS == NULL) {
         // Allocation failed; use default.
         hEditDS = hInst;
      }
      //
      // Create the edit control.
      // Pass the local heap handle in hInstance.
      hwndEdit = CreateWindow("edit", NULL,
         WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL |
         WS_VSCROLL | ES_MULTILINE | ES_AUTOHSCROLL |
         ES_AUTOVSCROLL,
             10, 10, 250, 200, hWnd, IDEDITCTL, hEditDS, NULL);
      // Now limit the text to the maximum possible amount.
      //
      SendMessage(hwndEdit, EM_LIMITTEXT, 0, 0L);

When an edit control is in a dialog box, the dialog manager controls which local heap the dialog box will use. By default, Windows allocates a local heap for each dialog box that contains an edit control. All the edit controls in a dialog box share the same local heap. If the dialog box is created with the DS_LOCALEDIT style, the edit controls use the data segment of the parent window for their local heap. It is not possible for edit controls within a dialog box to each use a different local heap.

In real mode, a local heap must have a handle of less than 0x2000. If, when the dialog manager attempts to allocate a local heap for a dialog box, a handle greater than or equal to 0x2000 is returned, it uses one of its own local heap handles. Windows keeps 10 handles to use as local heaps. If all ten of these handles have been used by other dialog boxes, and if Windows cannot allocate a handle less than 0x2000, Windows is unable to create the dialog box. In protected mode, the problem will not occur.

Edit Control Styles

Edit controls implement the styles in the sections that follow.

Important

Windows uses the Undo buffer to ensure that any displayable text is accepted. If an edit control does not have ES_AUTOVSCROLL specified, using the undo command will not work correctly in that only the last character entered can be undone. Edit controls with ES_AUTOVSCROLL can undo all the characters entered since the last cut, paste, or replace.

ES_AUTOVSCROLL is automatically applied to an edit control that has a WS_VSCROLL style. In other words, any edit control that has a vertical scroll bar automatically scrolls vertically.

The Knowledge Base has article Q66942, which describes the details of these techniques. EDALIGN, a sample application, demonstrates the code required.

Edit Control Messages

Edit controls implement the messages in the sections that follow.

For example:

    RECT rcl;
    SendMessage(hWnd, EM_GETRECT, 0, (DWORD) ((LPRECT) &rcl));

To deselect a selection:

    SendMessage(hEditWnd, EM_SETSEL, 0, MAKELONG(0xffff, 0) );

To set the caret at the end of the edit control:

    SendMessage(hEditWnd, EM_SETSEL, 0, MAKELONG(0xffff, 0xffff) )

To set the caret after the position of the Nth character:

    SendMessage(hEditWnd, EM_SETSEL, 0, MAKELONG(N, N) );

The order of the start and end positions is not important. The edit control always places the caret at the high end of the selection. If the selection is too large to be displayed within the edit control, the text is scrolled as far to the left as necessary to display the caret.

In Windows version 3.1, an application can set the wParam to 1 to cause the edit control not to scroll the caret into view.

In Windows version 3.0, edit controls calculate tab-stop positions on the size of the default system font. This can cause incompatibilities between tab stops set for edit controls and list boxes that base tab-stop calculations on the size of the font used in the list box. When an array of tab-stop positions is used to set the tab stops for an edit control and a list box in Windows version 3.0, the tab positions may not line up the same for both the edit control and the list box. Windows version 3.1 calculates the tab-stop positions using the control's font in both list boxes and edit controls.

Word-break function

A word-break function scans a text buffer, which contains text to be sent to the display, looking for the first word that will not fit on the current display line. The word-break function places this word at the beginning of the next line on the display. A word-break function defines at what point Windows should break a line of text for multiple-line edit controls, usually at a blank character that separates two words. Either a multiple-line or a single-line edit control might call this function when the end user presses arrow keys in combination with the CTRL key to move the cursor to the next word or previous word. The default word-break function breaks a line of text at a blank character. The application-defined function may define a word break to be a hyphen or a character other than the blank character.

There are three action codes that a word-break function must respond to:

Using a Multiple-Line Edit Control's Text Buffer Handle

Windows stores the contents of a multiple-line edit control in a buffer that Windows allocates by calling LocalAlloc. An application can use the return value of LocalAlloc to read the contents of an edit control without the overhead of having the multiple-line edit control copy the text (using GetWindowText, for example) into another buffer supplied by the application. It is also possible for an application to change the contents of the multiple-line edit control in place or to quickly change the contents of a multiple-line edit control by setting the text buffer handle.

The return value of LocalAlloc is the handle to a memory block allocated in the local heap used by the multiple-line edit control. For a multiple-line edit control outside of a dialog box, this local heap will be in the application's data segment; thus, the text buffer is allocated from the application's local heap, and the application can use the local heap functions LocalLock and LocalUnlock to establish a pointer by which the application can access the text.

The dialog manager sets up a local heap for each dialog box that has an edit control. Text buffers for edit controls in dialog boxes are not in the application's local heap; therefore, the local heap functions cannot be used directly to access the text. Edit controls that are created as child windows and are given an alternate local heap to use also have their text buffers outside of the application's local heap and cannot use the local heap functions. See the "Edit Controls and Memory" section, earlier in this article.

There are two ways for an application to make use of a multiple-line edit control's text buffer handle:

If you use the DS_LOCALEDIT style to create a dialog box with multiple-line edit controls, the dialog manager uses the application's local heap for the edit controls in the dialog box. This means that all text buffer handles are from the application's local heap. One drawback of this method is that the amount of memory available for the edit controls to store their data is less than when the edit controls have a local heap of their own.

An application can adjust the "current local heap" to the local heap used by the multiple-line edit control. Once the switch is made, the local heap functions can be used. After accessing the data, the application can then switch the local heap back to the application's local heap. To do this, some code must be written in assembly language. The following code demonstrates how to switch to the multiple-line edit control's local heap:

   WORD wEditSelector, wEditCtl;
   HANDLE hMem;
   //
   // Get the handle.
   //
   hMem =(HANDLE) SendMessage(hWndEdit, EM_GETHANDLE, 0, 0L);
   // Switch local heaps.
   //
   // Edit controls keep the handle to their local heap in the
   // hInstance member of their window information structure.
   //
   wEditCtl = (WORD) GetWindowWord( hWndEdit, GWW_HINSTANCE);
   // GlobalHandle always returns the handle to a global object
   // in the LOWORD and the selector in HIWORD.
   //
   wEditSelector = HIWORD(GlobalHandle(wEditCtl));
   //
   // Save the Data Segment; then set the Data Segment
   // to the local heap's selector.
   //
   //
   _asm {
      push DS
      mov  AX,wEditSelector
      mov  DS,AX
        }
   //
   // Do something with the text.
   //           
     {
      int i, length, NumChars;
     // ***************************************
     //  IMPORTANT...IMPORTANT...IMPORTANT
     // ***************************************
     // Only use stack variables and the pointer
     // to text. At this moment SS != DS and all
     // the application's DS-based variables are
     // unavailable.
     // ********************************************
              pBufText = LocalLock(hNewMem);
               length = strlen(pBufText);
               for ( i = 0, NumChars = 0; i < length; i++ )
                   if ( isalpha(pBufText[i]) )
                      NumChars++;
               pBufText = LocalUnlock(hNewMem);
            }
     //
     // Switch DS back.
     //
     _asm {
            pop  DS
          }

Notification Messages

The edit control's parent window receives notification messages through WM_COMMAND messages.

The wParam parameter specifies the identifier of the edit control, and the lParam parameter specifies the handle to the edit control in the low-order word and the notification code in the high-order word.

Common Techniques

The following techniques help to solve some common problems with edit controls.

Finding the Caret Position

An application can perform the following steps to determine the current position of the caret:

{
   WORD  wLineNumber;
   WORD  wLineIndex;
   WORD  dwGetSel;
   WORD  wStart;
   WORD  wEnd;
   WORD  wColNumber;
   char  szBuffer[128];

   wLineNumber = (WORD)SendMessage(GetDlgItem(hWndDlg,IDEDIT),
                    EM_LINEFROMCHAR, -1, 0L);

   // Send the EM_GETSEL message to the edit control.
   // The low-order word of the return value is the character
   // position of the caret relative to the first character in the
   // edit control.
   dwGetSel = (WORD)SendMessage(GetDlgItem(hWndDlg,101), EM_GETSEL,
               0, 0L);
   wStart = LOWORD(dwGetSel);

   // Send the EM_LINEINDEX message with the value of -1 in wParam.
   // The return value is the absolute number of characters
   // that precede the first character in the line containing
   // the caret.
   wLineIndex = (WORD)SendMessage(GetDlgItem(hWndDlg,101),
                 EM_LINEINDEX, -1, 0L);

   // Subtract the LineIndex from the start of the selection,
   // and then add 1 (since the column is zero-based).
   // This result is the column number of the caret position.
   wColNumber = wStart - wLineIndex;

   sprintf(szBuffer,"Caret at Line:%d Column:%d", wLineNumber+1,
             wColNumber+1);
   MessageBox(hWndDlg, szBuffer, "Current Caret Position",
          MB_APPLMODAL);
}

Finding the First Visible Line

Windows versions 3.1 and later supply the edit-control message EM_GETFIRSTVISIBLELINE, which an application can use to obtain the topmost visible line. Windows version 3.0 did not supply this edit-control message. An application can perform the following steps to determine the topmost visible line in Windows version 3.0:

    {
    TEXTMETRIC   tm;
    HDC          hDC;
    HFONT        hFont;
    WORD         iLine;
    WORD         iSel;
    WORD         iTopLine;
    POINT        pt;

   iSel=(WORD)SendMessage(hWndEdit, EM_GETSEL, NULL, 0L);
   // Get the line number; add 1 to correct for zero-based.
   iLine=(WORD)SendMessage(hWndEdit, EM_LINEFROMCHAR, iSel, 0L)+1;

   // Call GetCaretPos() to fill a POINT structure with the caret
   // coordinates relative to the client area of the edit control.
   // (The client area is inside the border.)
   GetCaretPos(&pt);

   // Call GetDC using the handle to the edit control. The return
   // value is the handle to a display context for subsequent
   // operations.
   hDC=GetDC(hWndEdit);

   // Send the WM_GETFONT message to the edit control. The return
   // value is a handle to the font currently used by the edit
   // control.
   hFont=SendMessage(hWndEdit, WM_GETFONT, 0, 0L);

   // If the font used is not the system font, select it.
   if (hFont != NULL)
       SelectObject(hDC, hFont);

   // Call GetTextMetrics using hDC to fill a TEXTMETRIC structure
   // with information about the font used by the edit control,
   // which is selected into hDC. The field of interest is
   // tmHeight.
   GetTextMetrics(hDC, &tm);
   ReleaseDC(hWndEdit, hDC);

   cyLine=tm.tmHeight;

   // Calculate the first visible line.
   // While the vertical coordinate of the caret is greater than
   // tmHeight, subtract tmHeight from the vertical coordinate and
   // subtract 1 from the line number of the caret.
   // The value remaining in the line number variable is the line
   // number of the first visible line in the edit control.
   iTopLine=iLine;
   while (pt.y > cyLine) {
      pt.y -=cyLine;
      iTopLine--;
   }
}

Coloring an Edit Control

Each time an edit control is about to be painted to the screen, Windows sends the parent window a WM_CTLCOLOR message. The wParam parameter contains the handle to the display context of the edit control, the low-order word of the lParam parameter identifies the edit control, and the high-order word of lParam contains CTLCOLOR_EDIT. The application can set the color or pattern for the edit control by returning a handle to a brush. Brush handles can be obtained by selecting a stock object or by building a brush from a pattern bitmap. The application can also set the text foreground and background colors.

The following code sets the colors for an edit control to white text on a gray background. The colors for other controls use the defaults.

   case WM_CTLCOLOR:
      if ( HIWORD(lParam) == CTLCOLOR_EDIT ) {
         // Set the text background color.
            SetBkColor(wParam, RGB(128,128,128));
         // Set the text foreground color.
            SetTextColor(wParam, RGB(255, 255, 255) );
         // Return the control background brush.
            return GetStockObject(LTGRAY_BRUSH);
                  }
      else
         return GetStockObject(WHITE_BRUSH);
.
.
.

In Windows version 3.0, there is a bug that caused a single-line edit control to be colored incorrectly. Windows would draw the control with the colors and brush from a WM_CTLCOLOR message and then again with the standard colors. Here are two workarounds to this problem:

With a 256-color driver the first statement creates a gray/green background, and the second statement produces a gray background.

Using an Edit Control for Clipboard I/O

Edit controls have built-in functionality to perform cut, copy, and paste operations between the control and the Clipboard. Using these built-in functions instead of writing the Clipboard I/O from scratch can be convenient, especially during the prototype and early development phases of a project or when time is extremely short. To take advantage of this built-in functionality, applications can create an edit control just for the purpose of Clipboard I/O. This technique results in an application that is slower and takes more memory than if you had developed the Clipboard I/O, but it is a quick way to program Clipboard I/O. Details on implementing this are available in the Knowledge Base under article Q35100.

TAB and ENTER Keys for Multiple-Line Edit Controls in Dialog Boxes

When a multiple-line edit control is used in a dialog box, Windows assigns a default behavior for the ENTER and TAB keys. The ENTER key is used to perform the default function of the dialog box. Usually this is to accept any changes made in the dialog box and then dismiss the dialog box. When a user is editing text in a multiple-line edit control, it may be preferred to have the ENTER key advance to the next line of the edit control. By default, multiple-line edit controls accept CTRL+ENTER to advance to the next line.

Similarly, by default the TAB key moves the input focus to the next control in the tabbing sequence. However, it might be useful for the user to be able to enter TAB characters in an edit control. By default, multiple-line edit controls accept CTRL+TAB to enter TAB characters in an edit control.

The following sections describe four methods that can be used to implement alternative behavior for the ENTER and TAB keys. It should be noted that some users may object if the ENTER and TAB keys no longer act in the default fashion.

Use [ES_WANTRETURN] in Windows version 3.1

Windows version 3.1 supplies a new edit control style, [ES_WANTRETURN], which causes an ENTER key to advance to a new line and not be passed to the dialog manager. ES_WANTRETURN does not affect TAB keys.

Subclass the edit control

An edit control subclass procedure could respond to the WM_GETDLGCODE message with DLGC_WANTALLKEYS to receive all keyboard input. This causes the edit control to be sent all key input, including ENTER and TAB keys. The following subclass procedure could be used:

LONG FAR PASCAL AllKeyFunc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
   //
   // This subclass function makes pressing an ENTER key
   // create a new line when the focus is in an edit control.
   //

   if ( Message == WM_GETDLGCODE )
      return DLGC_WANTALLKEYS;

   return CallWindowProc(lpfnOldClassProc, hWnd, Message, wParam,
      lParam);
}

= Process the DM_GETDEFID message =
Windows sends a [DM_GETDEFID] message to a dialog box procedure when the
user presses ENTER in a dialog box. By intercepting this message and
keeping track of whether the focus is in an edit control, an application
can change the behavior of the ENTER key. This does not work for TAB
keys. This technique takes these three steps: 

Process [WM_COMMAND] messages with the HIWORD(lParam) set to [EN_SETFOCUS]
and [EN_KILLFOCUS] to determine if a multiple-line edit control has the
focus. When [EN_SETFOCUS] signals that a multiple-line edit control has the
focus, set a static flag to TRUE. When [EN_KILLFOCUS] signals that a
multiple-line edit control has lost the focus, reset the flag to FALSE. 

When the dialog procedure receives a [DM_GETDEFID] message, an edit
control has the focus, and if the ENTER key is down, then post a [WM_CHAR]
message with wParam set to 0x0a to the edit control with the focus. 

If an edit control has the focus, the dialog function should return TRUE;
otherwise, the function must return FALSE. Failing to do so keeps the
ENTER key from behaving properly when an edit control does not have the
focus. 

The following code fragment demonstrates this procedure:

#pre
     static fEditFocus;
     switch (msg)
         {
         case WM_COMMAND:
             // ID_EDIT is a multiline edit control.
             if (ID_EDIT == wParam)
                 {
                 if (EN_KILLFOCUS == HIWORD(lParam))
                     fEditFocus = FALSE;
                 if (EN_SETFOCUS == HIWORD(lParam))
                     fEditFocus = TRUE;
                 }
             else
                 ...
             break;
         case DM_GETDEFID:
             /*
              * Check to see that an edit control has the focus
              * and that the ENTER key is down. DM_GETDEFID may be
              * sent in other situations when the user did not press
              * the ENTER key.
              */
             if (fEditFocus && (0x8000 & GetKeyState(VK_RETURN)))
                 {
                 PostMessage(hEdit, WM_CHAR, 0x0A, 0L);
                 return TRUE;
                 }
             break;
         }
     return FALSE;

Intercept messages before calling IsDialogMessage

Applications that use modeless dialog boxes are required to filter messages through the IsDialogMessage function. IsDialogMessage modifies certain messages to implement dialog box behavior. For example, the ENTER key message is modified to generate a WM_COMMAND message with wParam set to IDOK.

An application is, however, free to modify the message before passing it to IsDialogMessage. The code example below modifies the WM_KEYDOWN message containing a VK_RETURN to be an [EM_REPLACESEL] message with a carriage return (CR) and linefeed (LF) combination.

The disadvantage to this method is that it places additional code in the main message loop for the application, slowing the processing of every message. In addition, code in the message loop is far removed from the dialog procedure and is therefore harder to maintain.

The following code fragment demonstrates this method:

// hWndEditControl is the handle to the multiline edit control.
// hWndModeless is the handle to the modeless dialog box.
while (GetMessage(&msg, NULL, 0, 0)) {
   if (msg.hWnd == hWndEditControl
           && msg.message == WM_KEYDOWN
           && msg.wParam == VK_RETURN) {
      // Normally, Windows will translate this to IDOK.
      // Perform a custom translation to something more
      // useful (replace selection with carriage-return,
      // line feed).
      msg.message = EM_REPLACESEL;
       msg.wParam  = 0;
        msg.lParam  = (long)(LPSTR)"\015\012";
       }
   if (!IsDialogMessage(hWndModeless, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
}

If the functionality of a modal dialog box is desired in an application but you still want to use this interception method, it can be simulated by using a modeless dialog box that disables its parent window when the dialog box is created.

Read-Only Edit Control

Windows versions 3.1 and later supply an [ES_READONLY] edit control style and the EM_SETREADONLY edit control message to make an edit control read-only. Windows version 3.0 did not supply these features. The easiest way to make an edit control read-only in Windows version 3.0 is to subclass the edit control and have the subclass function absorb any messages that would cause the edit control to change its contents.

Here is how to make an edit control read-only in Windows version 3.0:

// Declare the subclass function.
long FAR PASCAL SubEditWndProc(HWND hWnd, unsigned wMessage,
                WORD wParam, LONG lParam);

//
// Create an edit control.
//
hWndEdit=CreateWindow("EDIT",
                  "Edit Control Line Numbers",
                  WS_OVERLAPPEDWINDOW,
                  35, 35, 400, 150,
                  NULL, NULL, hInstance, NULL);
//
// Subclass the edit control.
//
static FARPROC     lpSubClassProc;

lpSubClassProc=MakeProcInstance((FARPROC)SubEditWndProc,hInstance);
     SetWindowLong(hWndEdit, GWL_WNDPROC, (LONG)lpSubClassProc);
.
.
.

//
// Read-only subclass function for an edit control.
//
long FAR PASCAL SubEditWndProc(HWND hWnd, unsigned wMessage, WORD
   wParam, LONG lParam)
{
   switch ( wMessage ) {
      case WM_CUT:
      case WM_KEYDOWN:
      case WM_PASTE:
      case WM_CHAR:
         // Do nothing here.
      break;

      default:
         CallWindowProc (lpPrevWndFunc, hWnd, wMessage, wParam,
                         lParam );
   }
}

Final Warnings

In working with edit controls, you need to be aware of some situations to avoid.

Don't Use getc with Edit Controls

When an application uses getc in a Windows edit control, data corruption can occur. When an application uses an edit control and needs getc functionality, the fgetc function should be used because it does not produce the data corruption.

Single-Line Edit Controls and the BACKSPACE Key

In Windows version 3.0, single-line edit controls that have a font larger than the default system font do not correctly implement the BACKSPACE key. When the user uses the BACKSPACE key to delete characters, the edit control does not remove the lower half of each character from the screen. This problem can be avoided by creating the edit control with the ES_MULTILINE attribute. If you do not specify the ES_AUTOVSCROLL attribute, the edit control will be a multiple-line edit control that behaves like a single-line edit control but will not exhibit this problem.


windows_programming_notes.nbk: Home | Index | Next Page: Using Edit Controls in a Window | Previous Page: Using DDE


Notebook exported on Monday, 7 July 2008, 18:56:50 PM Eastern Daylight Time