I am trying to create Console-like behaviour in my Rich Edit control using the WinAPI through C, and would like previously entered information to be protected from editing by the user.
The control seems to be working fine mostly for my purposes, except I would like to restrict editing on existing text in the control.
Globals
WNDPROC OldMultiLineIO;
HFONT font_all = NULL;
int nWindowCharsX, nWindowCharsY;
int nWindowX, nWindowY, nCharX, nCharY;
LRESULT CALLBACK WndProc ( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK MultiLineProc( HWND, UINT, WPARAM, LPARAM );
Rich Edit Control creation and setting desired background colour/font:
HWND wMultiLine( HWND hwnd, HFONT font_all )
{
LoadLibrary( TEXT( "Riched20.dll" ) );
HWND multiline_io = CreateWindowEx(
WS_EX_CLIENTEDGE, RICHEDIT_CLASS, 0,
WS_CHILD | WS_VISIBLE |
ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
0, 0, 0, 0, hwnd, //set in WM_SIZE
(HMENU)IDC_MULTILINEIO,
(HINSTANCE)GetWindowLong( hwnd, GWL_HINSTANCE ),
NULL );
SendMessage( multiline_io, EM_SETBKGNDCOLOR, 0, BKGD_GLOBAL );
SendMessage( multiline_io, WM_SETFONT, (WPARAM)font_all, 0 );
return multiline_io;
}
Window procedure for multiline_io control (child window)
LRESULT CALLBACK MultiLineProc( HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam )
{
static CHARFORMAT2W cf = { 0 };
static wchar_t * retstring;
static LONG last_char_posn;
static CHARRANGE current_text;
cf.cbSize = sizeof( CHARFORMAT2W );
cf.dwMask = CFM_COLOR;
switch( msg )
{
case WM_CHAR:
{
switch( wParam )
{
case 0x08:
//backspace
break;
case 0x09:
//tab
break;
case 0x0D:
//carriage return
//retstring = NULL;
//retstring = getInput( hwnd, last_char_posn, 0 );
//get range of existing text in rich edit control
last_char_posn = lastCharIndex( hwnd, false );
current_text.cpMin = 0;
current_text.cpMax = LOWORD( last_char_posn );
//as per MSDN to receive EN_PROTECTED notifications, set the mask
SendMessage( hwnd, EM_SETEVENTMASK, 0, (LPARAM)ENM_PROTECTED );
//set parameters to enable protection
cf.dwEffects = CFE_PROTECTED;
cf.dwMask = CFM_PROTECTED | CFM_COLOR;
//apply protection to the existing text
SendMessage( hwnd, EM_EXSETSEL, 0, (LPARAM)¤t_text );
SendMessage( hwnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&cf );
//reset colour back to white for new input
cf.dwMask = CFM_COLOR;
cf.crTextColor = FRGD_GENERAL;
SendMessage( hwnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf );
break;
case 0x0A:
//line feed
break;
case 0x1B:
//escape
break;
default:
last_char_posn = lastCharIndex( hwnd, false );
printf( "Def: %d %ld\n", LOWORD( last_char_posn ),
current_text.cpMax );
break;
}
}
break;
}
}
return CallWindowProc( OldMultiLineIO, hwnd, msg, wParam, lParam);
}
Parent Window Procedure
I must admit that I am not entirely sure how the interaction between the parent and multiline window procedures work as this is my first foray into GUI programming. In this parent procedure, I create a few child windows to display some data, logo, and the multiline rich edit control.
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam )
{
static HBITMAP logo_hBitmap;
static HBRUSH txt_hBrush = NULL;
static HWND txt_acrloaded, multiline_io;
static RECT hwnd_rect;
static POINT point_diff_acrloaded;
static CHARFORMAT2W cf = { 0 };
point_diff_acrloaded.x = 230;
point_diff_acrloaded.y = 25;
if ( NULL == font_all ) font_all =
CreateFont( 26, 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT( "Segoe UI" ) );
//don't use GetWindowRect, we need the client area, which is different
GetClientRect( hwnd, &hwnd_rect );
switch( msg )
{
case WM_CREATE:
{
cf.cbSize = sizeof( CHARFORMAT2W );
cf.dwMask = CFM_COLOR;
cf.crTextColor = FRGD_GENERAL;
/*CHILD WINDOW LOAD INFO*/
txt_acrloaded = wTextLoaded( hwnd, hwnd_rect,
point_diff_acrloaded,
font_all, lParam );
/* LOGO WINDOW */
logo_hBitmap = wLogo( hwnd, hwnd_rect,
point_diff_acrloaded );
/* MULTILINE USER I/O WINDOW */
multiline_io = wMultiLine( hwnd, font_all );
SendMessage( multiline_io, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf );
TEXTMETRIC tm;
HDC hdc = GetDC( multiline_io );
SelectObject( hdc, (HGDIOBJ)font_all );
GetTextMetrics( hdc, &tm );
ReleaseDC( multiline_io, hdc );
nCharX = tm.tmAveCharWidth; //10 avg, 49 max
nCharY = tm.tmHeight; //25
SetFocus( multiline_io );
OldMultiLineIO = (WNDPROC) SetWindowLong ( multiline_io,
GWL_WNDPROC, (LONG) MultiLineProc );
break;
}
case WM_SIZE: //resize the child windows based on an update of WM_SIZE
{
MoveWindow( txt_acrloaded, ( hwnd_rect.right - hwnd_rect.left ) - point_diff_acrloaded.x, 0,
point_diff_acrloaded.x, point_diff_acrloaded.y, TRUE ); //move child window as parent is resized
MoveWindow( multiline_io, 0, 70,
LOWORD(lParam), HIWORD(lParam), TRUE ); //70 pixels y-offset
nWindowX = LOWORD( lParam );
nWindowCharsX = max( 1, nWindowX/nCharX );
nWindowY = HIWORD( lParam );
nWindowCharsY = max( 1, nWindowY/nCharY );
SetFocus( multiline_io );
break;
}
case WM_CTLCOLORSTATIC:
{
HDC hdc_acrload = (HDC)wParam;
SetTextColor( hdc_acrload, FRGD_ACRLOADED );
SetBkColor ( hdc_acrload, BKGD_GLOBAL );
if( NULL == txt_hBrush ) txt_hBrush = CreateSolidBrush( BKGD_GLOBAL );
return (INT_PTR)txt_hBrush;
}
case WM_PAINT:
{
BITMAP bitmap;
HGDIOBJ old_bitmap;
HDC logo_hdc, logo_hdcmem;
PAINTSTRUCT ps;
logo_hdc = BeginPaint( hwnd, &ps );
logo_hdcmem = CreateCompatibleDC( logo_hdc );
old_bitmap = SelectObject( logo_hdcmem, logo_hBitmap );
GetObject( logo_hBitmap, sizeof( bitmap ), &bitmap );
BitBlt( logo_hdc, 5, 5, bitmap.bmWidth, bitmap.bmHeight,
logo_hdcmem, 0, 0, SRCCOPY );
SelectObject( logo_hdcmem, old_bitmap );
DeleteDC( logo_hdcmem );
EndPaint( hwnd, &ps );
DeleteDC( logo_hdc );
DeleteObject( old_bitmap );
SetFocus( multiline_io );
break;
}
case WM_NOTIFY:
if ( EN_PROTECTED == ( (LPNMHDR)lParam )->code )
{
printf("@ EN_PROTECTED CASE\n");
return 1;
}
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
default:
return DefWindowProcW( hwnd, msg, wParam, lParam );
}
return 0;
}
The lastCharIndex function, either sends back the pixel position of the last character if true is passed to the function, else it sends back the index
extern LONG lastCharIndex( HWND hwnd, BOOL want_coord )
{
RECT r;
POINT pt;
GetClientRect( hwnd, &r );
pt.x = ( r.right - r.left ) -1;
pt.y = ( r.bottom - r.top ) -1;
//get index of last char within window
//then retrieve coordinates of that char
LONG n = SendMessage( hwnd, EM_CHARFROMPOS, 0, (LPARAM)&pt );
LONG x_last = SendMessage( hwnd, EM_POSFROMCHAR, n, 0 );
//last char co-ordinates
if( true == want_coord ) return x_last;
return n;
}
The WM_NOTIFY case back in the parent window procedure. The printf statement is for debugging and is shown in a separate console that Code Blocks controls. As per the MSDN, we are told to return a non-zero value to prevent the edit operation.
case WM_NOTIFY:
if ( EN_PROTECTED == ( (LPNMHDR)lParam )->code )
{
printf("@ EN_PROTECTED CASE\n");
return 1;
}
break;
Observation:
After putting in some text and pressing Enter. The text that was entered is selected in the Rich Edit control - highlighted like when someone selects text with the mouse, so the SendMessage( hwnd, EM_EXSETSEL, 0, (LPARAM)¤t_text );
command is probably working.
My debug window also outputs the printf statement in WM_NOTIFY, so it seems that the parent window has received the EN_PROTECTED notification code.
However, the text is not protected and I am able to delete/overwrite etc.
Can someone explain what is going wrong? I would also like to know how exactly this return value from WM_NOTIFY is being handled by the caller? Am I meant to to explicitly handle it back in the multiline window procedure myself?
MSDN referenced: EN_PROTECTED PARAMETER, WM_NOTIFY, ENPROTECTED STRUCT, NMHDR STRUCT