3

I am deriving from CEdit, to make a custom control. It would be nice, if like the MFC Feature Pack controls (Mask, Browsable) that I could change GetWindowText to actually report back not what is normally displayed on the control (for example, convert the data between hex and decimal, then return back that string).

Is it this possible in a derived CEdit?

Mary Ellen Bench
  • 589
  • 1
  • 8
  • 28
  • 1
    It may be possible, but it's really not a good idea. Simply provide an extra function to perform the conversion. Also, I have to ask why you are writing MFC code in 2017? –  Oct 26 '17 at 21:46
  • 1
    Legacy code. I have a big code base, and I'd like to drop in and replace some of my edit controls, with another one, where I don't need change any controlling code and just plop in derived one, which lets a user edit as decimal or hex but still return original way. How would it be done? – Mary Ellen Bench Oct 26 '17 at 21:57
  • You can steal code from `CMFCMaskedEdit` or you can drive a class from `CMFCMaskedEdit` instead. `CMFCMaskedEdit` is just derived from `CEdit` – Barmak Shemirani Oct 26 '17 at 22:17
  • 2
    @NeilButterworth: You appear to know of another supported framework for native desktop application development on Windows. Which one is it? – IInspectable Oct 26 '17 at 22:18
  • @IInspectable Qt? .Net? Delphi? Many others. –  Oct 26 '17 at 22:21
  • @NeilButterworth: .NET isn't exactly native. Delphi isn't exactly C++. And Qt, come on, seriously, I wasn't asking for some broken ghetto toolkit, that cannot be made to work on Windows. Anything else? – IInspectable Oct 26 '17 at 22:28
  • @IInspectable Obviously, if you don't think .Net is native, there is no point in further communication, even if I wanted to communicate further with you, which I don't. –  Oct 26 '17 at 22:31
  • @NeilButterworth: UWP is native. .NET is not. While there is .NET Native, this is still a very different programming model and runtime environment from truly native code. And that's not exactly C++ either. – IInspectable Oct 26 '17 at 22:34
  • @IInspectable ATL+WTL? It is also pretty much a dinosaur, but it has less overhead (we are speaking mostly binary size here), and you can upgrade from MFC more or less incrementally. – ivanmoskalev Oct 26 '17 at 22:45
  • @Barmak Shemirani How did they manage it in mask control in feature pack. Must be possible. But that is true maybe could derive from that. – Mary Ellen Bench Oct 26 '17 at 22:48
  • There's some MVC frameworks that sit on the Win32 API, which is also better than using MFC. @MaryEllenBench I will burn a candle for you. – zzxyz Oct 26 '17 at 22:54
  • 5
    Source code for MFC classes should be available in `"VS-path\VC\atlmfc\src\mfc"`, location depends on VS version. `CMFCMaskedEdit` intercepts input and checks against the mask. – Barmak Shemirani Oct 26 '17 at 23:05
  • 1
    Barmak is correct. Do what CMFCMaskedEdit does (uses the message map to intercept WM_GETTEXT) and you'll catch all cases – Joe Oct 26 '17 at 23:35
  • 4
    @ivanmoskalev: WTL doesn't come with official support. ATL is fine for COM, but it doesn't suffice as a general-purpose application framework. Regardless, this question is asking about MFC. Suggesting to not use MFC is neither an answer, nor a helpful comment. – IInspectable Oct 27 '17 at 01:19
  • @Mary Ellen Bench. Overwritting WM_GETTEXT will be a bit tricky. There is no own OnPaint/WM_PAINT handler. So the control uses WM_GETTEXT itself to get the text to paint. Maybe it would be easier to to create your own edit template/interface for edit control, that uses a virtual function GetValue, that returns the interpreted value. That's the way we do it. – xMRi Oct 27 '17 at 08:18
  • @xMRi The control doesn't call `WM_GETTEXT` internally. Instead it reads directly from its internal string buffer. Otherwise `CMFCMaskedEdit` wouldn't work. You can also verify that by setting a breakpoint in an overridden `OnGetText()` handler. It will never fire unless you call `GetWindowText()` or send `WM_GETTEXT` explicitly. – zett42 Oct 27 '17 at 10:51
  • @zett42. This is true, but the OnPaint routine will do this too. That is what I want to tell. – xMRi Oct 27 '17 at 10:53

2 Answers2

3

Add message map entries for WM_GETTEXT and WM_GETTEXTLENGTH to your derived CEdit class:

BEGIN_MESSAGE_MAP( CMyEdit, CEdit )
    ON_WM_GETTEXT()
    ON_WM_GETTEXTLENGTH()
END_MESSAGE_MAP()

As we are overriding these messages we need a method of getting the original text of the edit control without going into endless recursion. For this we can directly call the default window procedure which is named DefWindowProc:

CStringW CMyEdit::GetTextInternal()
{
    CStringW text;
    LRESULT len = DefWindowProcW( WM_GETTEXTLENGTH, 0, 0 );
    if( len > 0 )
    {
        // WPARAM = len + 1 because the length must include the null terminator.
        len = DefWindowProcW( WM_GETTEXT, len + 1, reinterpret_cast<LPARAM>( text.GetBuffer( len ) ) );
        text.ReleaseBuffer( len );
    }
    return text;
}

The following method gets the original window text and transforms it. Anything would be possible here, including the example of converting between hex and dec. For simplicity I just enclose the text in dashes.

CStringW CMyEdit::GetTransformedText()
{
    CStringW text = GetTextInternal();
    return L"--" + text + L"--";
}

Now comes the actual handler for WM_GETTEXT which copies the transformed text to the output buffer.

int CMyEdit::OnGetText( int cchDest, LPWSTR pDest )
{
    // Sanity checks
    if( cchDest <= 0 || ! pDest )
        return 0;

    CStringW text = GetTransformedText();

    // Using StringCchCopyExW() to make sure that we don't write outside of the bounds of the pDest buffer.
    // cchDest defines the maximum number of characters to be copied, including the terminating null character. 
    LPWSTR pDestEnd = nullptr;
    HRESULT hr = StringCchCopyExW( pDest, cchDest, text.GetString(), &pDestEnd, nullptr, 0 );
    // If our text is greater in length than cchDest - 1, the function will truncate the text and
    // return STRSAFE_E_INSUFFICIENT_BUFFER.
    if( SUCCEEDED( hr ) || hr == STRSAFE_E_INSUFFICIENT_BUFFER )
    {
        // The return value is the number of characters copied, not including the terminating null character. 
        return pDestEnd - pDest;
    }
    return 0;
}

The handler for WM_GETTEXTLENGTH is self-explanatory:

UINT CMyEdit::OnGetTextLength()
{
    return GetTransformedText().GetLength();
}
zett42
  • 25,437
  • 3
  • 35
  • 72
  • This will change the contents of the edit control in the display too! So it changes any output... this will not work. – xMRi Oct 27 '17 at 10:54
  • OK. Interesting. So OnPaint in the edit control directly uses the internal buffer. Strange enough that in the CMFCMaskedEdit the internal overwritten buffer (m_str) is displayed... but this seams OT. – xMRi Oct 27 '17 at 10:57
0

Thanks to everyone for pointing me in the right direction. I tried OnGetText, but the problem seemed to be I couldn't get the underlying string or it would crash when calling GetWindowText (or just called OnGetText again...and couldn't find the underlying string).

After seeing what they did on masked control, I did a simpler answer like this. Are there any drawbacks? It seemed to not cause any issues or side effects...

Derive directly from GetWindowText

void CConvertibleEdit::GetWindowText(CString& strString) const
{
    CEdit::GetWindowText(strString);

    ConvertibleDataType targetDataType;
    if (currentDataType == inputType)
    {

    }
    else
    {
        strString = ConvertEditType(strString, currentDataType, inputType);
    }
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
Mary Ellen Bench
  • 589
  • 1
  • 8
  • 28
  • 1
    _I couldn't get the underlying string_ - call `DefWindowProc` with parameter `WM_GETTEXTLENGTH` to get string length, then again with `WM_GETTEXT` to get the underlying string. This way you won't call your own handler again. If this is still unclear, I may add some code to my answer. – zett42 Oct 27 '17 at 13:31
  • 1
    BTW, what you did here, is called masking `CWnd::GetWindowText()`, because the method is not virtual. The drawback is that your method won't get called if someone uses the base class to get the window text, which is very common when using MFC. Imagine someone enumerating all child windows, they will receive a `CWnd` pointer for each window. When they call `CWnd::GetWindowText()`, your method won't get called. – zett42 Oct 27 '17 at 13:38
  • For the record, that masking was done in the original masked control then? If you can add more to your example, to get the underlying string. That was the main part that seemed lacking. – Mary Ellen Bench Oct 27 '17 at 13:43
  • I've added a method `GetTextInternal()` which does that. – zett42 Oct 27 '17 at 15:51
  • So I guess your question had nothing to do with `CMFCMaskedEdit` which can be used, for example, to force the user to type in integers only. – Barmak Shemirani Oct 27 '17 at 16:24
  • Not quite, yeah. It would've been nice if somehow Microsoft compiled all that functionality into one edit control with options - so you could allow for masking, buttons, etc, together. – Mary Ellen Bench Oct 27 '17 at 17:16