0

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)&current_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)&current_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

Usernamed
  • 41
  • 5
  • Why are you sending yourself an `EN_PROTECTED` notification? The control will send that when the user tries to edit protected text, you don't need to send it yourself. – Jonathan Potter Jan 03 '20 at 03:49
  • Thanks for your input. I wasn't sure whether I had to or not. I commented out that block of code now. The effect is that I no longer receive the ```WM_NOTIFY``` message after pressing Enter, and I am still able to modify the text. I am guessing my attempt at protecting the characters was never actually successful...and I was fooling myself by sending the ```WM_NOTIFY``` message forcefully? – Usernamed Jan 03 '20 at 06:21
  • Can you show more code? Maybe your CHARFORMAT structure isn't initialised properly. – Jonathan Potter Jan 03 '20 at 07:20
  • @JonathanPotter - I have added more code. The sample of text from the rich edit control procedure I posted earlier may also be slightly different as I was experimenting. Thanks – Usernamed Jan 03 '20 at 09:41
  • You only need to send `EM_SETEVENTMASK` once (the mask stays in effect once set), not every time a key is pressed (although that wouldn't stop it from working). To be honest I can't see anything obviously really wrong. Can you try changing from setting the protected status to e.g. the text color or something else that would be visible, to see if your logic is actually working? – Jonathan Potter Jan 05 '20 at 20:29
  • Thanks, I just tried changing the text colour by setting `cf.crTextColor` to a random RGB value and it successfully applied the new colour to the selection. I also set the event mask as `SendMessage( GetParent( hwnd ), EM_SETEVENTMASK, 0, (LPARAM)ENM_PROTECTED );` but that made no difference either. Haven't been able to work out what's going wrong. – Usernamed Jan 06 '20 at 10:32
  • So it seems I have issues setting other effects like strikeout, italic, bold etc. those don't work either. – Usernamed Jan 06 '20 at 11:44
  • OK, seems like I have got it working now. The `cf.dwMask = CFM_COLOR;` statement at the top of my MultiLineProc function appears to be what was affecting dwEffects being set properly. I am not sure why. I commented that out and I was able to set other effects like strikeout, italic, etc. as well now. Shall test out a bit more and post the working code later for anyone else who runs into a similar issue later. – Usernamed Jan 06 '20 at 12:13

0 Answers0