Where do I translate MFC message boxes - visual-c++

I'm localizing an MFC app into Japanese, and have the resources working right.
My current problem is that, when I use AfxMessageBox or MessageBox, the dialog title and button messages come up in English, and I haven't found anywhere to change that.
I haven't found anything useful searching MSDN or Google.
So,
Does this work? Does Windows come up with "OK" or "Yes/No" buttons in Japanese, Arabic, and Russian?
If not, what do I specify to change these?
Edit: I went with MessageBoxEx, writing a wrapper class to make it as convenient to use as AfxMessageBox. It does appear that MB_OK comes up as "OK" in both English and Japanese, but other button options like MB_YESNO are translated into something that I assume is correct Japanese. This was a fairly small application to translate, and we might want to use something automated for the larger ones.

Use MessageBoxEx and specify the WORD wLanguageId field.
wLanguageId
[in] Specifies the language for the text displayed in the message box
button(s). Specifying a value of zero
(0) indicates to display the button
text in the default system language.
If this parameter is
MAKELANGID(LANG_NEUTRAL,
SUBLANG_NEUTRAL), the current language
associated with the calling thread is
used.
To specify a language other than the current language, use the
MAKELANGID macro to create this
parameter. For more information, see
MAKELANGID.
More information on MAKELANGID can be found here.
If this does not work well for you, the alternative would be to make your own dialog box.

The title is a string you specify, so you should be able to translate it.
In AfxMessageBox(), the title is the application name (AfxGetAppName() IIRC).
Regarding the buttons, the text is part of the OS and cannot be translated.
MessageBoxEx (mentioned by Brian) never worked well regarding languages support. This MS KB article from over a decade ago says the language id will be correctly supported by Windows 2000. But it obviously never made it through.
So you're pretty much out of luck. Your only solution would be to use a 3rd party implementation such as the excellent XMessageBox and provide translations through your string table.
Note that appTranslator's glossary contains words such as Yes, no, cancel,... in 25 languages and would translate them automatically.

Create your own L10N macros/function and use this code:
static LRESULT __stdcall ChangeCaptions(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HCBT_ACTIVATE) {
SetWindowText(GetDlgItem((HWND) wParam, IDOK), L10N(GUI_OK_MSG));
SetWindowText(GetDlgItem((HWND) wParam, IDCANCEL), L10N(GUI_CANCEL_MSG));
SetWindowText(GetDlgItem((HWND) wParam, IDYES), L10N(GUI_YES_MSG));
SetWindowText(GetDlgItem((HWND) wParam, IDNO), L10N(GUI_NO_MSG));
}
return 0;
}
int addon_gui_messagebox(HWND parentHWnd, HINSTANCE hInstance, void *text, void *caption, int type)
{
int ret;
hook = SetWindowsHookEx(WH_CBT, ChangeCaptions, hInstance, GetCurrentThreadId());
ret = MessageBox(parentHWnd, text, caption, type);
UnhookWindowsHookEx(hook);
return ret;
}
As I wrote previously MessageBoxEx currently ignore lang arg.

Related

Is it safe to use CString::GetString() with CWnd::SendMessage()?

I have this code:
GetParent()->SendMessage(UWM_DELETE_NAME_HISTORY_MSG, (WPARAM)strName.GetBufferSetLength(_MAX_PATH));
strName.ReleaseBuffer();
Is it safe for me to change it like this:
GetParent()->SendMessage(UWM_DELETE_NAME_HISTORY_MSG, (WPARAM)strName.GetString());
Related to this, is it correct to use static_cast<WPARAM>(strName.GetString())?
For completion, this is my custom message handler:
LRESULT CChristianLifeMinistryEditorDlg::OnDeleteNameHistory(WPARAM wParam, LPARAM lParam)
{
auto szName = (LPCTSTR)wParam;
m_History.erase(szName);
for (auto& kv : m_mapWeekendHistData)
kv.second.erase(szName);
return 0;
}
SendMessage is a blocking call. Once it returns, it no longer needs access to its arguments. With that in mind
GetParent()->SendMessage(UWM_DELETE_NAME_HISTORY_MSG, (WPARAM)strName.GetString());
is safe (as far as the SendMessage call is concerned).
You'd still need to be careful about the implementer of the UWM_DELETE_NAME_HISTORY_MSG message (which, presumably, is a custom message). If the implementation stores a pointer and uses it after the handler has run to completion, then that is an issue that needs to be resolved.
In general, if you implement a message handler, you should follow the Windows API's core principles. There are ultimately two implementation:
Store a copy of client-provided data (such as SetWindowTextW).
Return a reference to the previous value in case the API takes ownership of the client-provided data (such as SelectObject).
To answer your question in the title:
Is it safe to use CString::GetString() with CWnd::POstMessage()?
No, it is not - the content of that CString may be reallocated or deleted by the time the message will be processed.

Visual Studio MFC change text in Edit Control while typing/dynamically

I am trying to set up a MFC C++ App in Visual Studio 2019 such that modifies the user's text as they are typing.
Current layout is 2 radio buttons,
ID= rdbOn (set to Group = True, with Value int variable m_isOn = 1)
ID= rdbOff, m_isOn value would be = 0
and 1 Edit Control,
ID= txtInputBox, with Value CString variable m_inputString
Currently, for testing I can see how it would work for a button on click, it would take something like the following and just SetDlgItemText of the result. But that would be after they have typed, not WHILE they are typing.
void Onsomebtnclick()
{
//convert CString to String of m_inputString
//do some string manipulation
//convert back to CString
//SetDlgItemText(txtInputBox, result)
}
Update:
got EN_CHANGE to work
I was able to get EN_CHANGE working with the flag suggestion from user #GoGoWorx. However, now I just have a slight problem that the cursor is back to the beginning of the edit control txtInput.
I'm reading about using a CEdit::SetSel but don't know how to use that directly in my code. I tried
CEdit control MFC, placing cursor to end of string after SetWindowText
someDlg::someFunction()
{
//some logic stuff to get a result string
SetDlgItemText(txtInputBox, result);
//need it to set the cursor to the end
//I tried these, but it didn't recognize (expression must have class type?)
//txtInputBox.SetSel(0, -1);
//txtInputBox.SetSel(-1);
}
It sounds like you need to use the ON_EN_CHANGE message-map notification (called after the control has been updated due to typing or pasting for example)
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_EN_CHANGE(IDC_EDIT_CONTROL, &CMyDialog::OnEnChangeEditControl)
END_MESSAGE_MAP()
void CMyDialog::OnEnChangeEditControl()
{
// Copy or call your Onsomebtnclick() here
}
I'm not sure what you're using for the numeric identifier for the edit control, since these are typically upper case defines - replace IDC_EDIT_CONTROL above with your define (possibly txtInputBox, but again, these are normally upper case, so I'm not sure).
Also change CMyDialog for the name of your dialog class too.
Note that we're using the ON_EN_CHANGE message-map handler here instead of the ON_EN_UPDATE, since the ON_EN_CHANGE message is sent after the control has been updated, whereas ON_EN_UPDATE is called just before it's updated.
The message-map handlers are described in the Remarks section of the CEdit control documentation: https://learn.microsoft.com/en-us/cpp/mfc/reference/cedit-class?view=msvc-160
Regarding your concern about modifying things as the user types - this should be fine, since every change (keystroke or paste from clipboard, etc.) should trigger this handler to be called, where you can change whatever you need. Just be sure that when you're updating the control, you don't trigger the ON_EN_CHANGE again and end up in a recursive 'change' loop.
You might be able to do this with some sort of flag to indicate you're the one updating the control, as opposed to the user, however it's probably better to subclass the CEdit control to do what you're wanting. There are a few examples out there of how to do this (it's not as difficult as it might sound), for example:
https://www.codeproject.com/Articles/27376/Avoiding-EN-CHANGE-notifications

Winapi children of MFC window

It is a simple question that I might delete soon, but I have a problem that I don't seem to find anywhere else (at least google has not helped).
I am trying to write a plugin for a software based completely in MFC, but I refuse to use MFC myself. I thought that, in the end, I could just use the proper handles and modules to create my windows with winapi to work with MFC but it does not seem that easy. I am trying to launch a "Modal" window (that is, it blocks the parent by calling EnableWindow(parentHwnd, FALSE)). For this window I wrote a custom wndProc function:
LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
//Do stuff to get a HWND to the parent window
switch(msg) {
case WM_CLOSE:
EnableWindow(parentHwnd, TRUE);
//DestroyWindow(hwnd);
return DefWindowProc(hwnd, msg, wParam, lParam);
case WM_DESTROY:
EnableWindow(parentHwnd, TRUE);
//PostQuitMessage(0);
return DefWindowProc(hwnd, msg, wParam, lParam);
//... Handling other messages
When the user closes the window, I want to enable the parent and simply close my custom window. You can see that the functions DestroyWindow(hwnd) and PostQuitMessage(0) are commented, and instead I return DefWindowProc. This is because leaving the message uncommented, the whole app crashes with the following exception:
My first guess would be that there is a problem with the messages in windows. I don't know if I am forced to use the "DECLARE_MESSAGE_MAP" macro or something to handle my events in MFC's wndProc. I'm quite a beginner for windows development so, even though I found a solution for this crash, I'm not quite satisfied because I have no idea why it failed in the first place. Thank you!
First, what is a "plugin" exactly? From what I can guess it must be just some additional code, built into the main executable (otherwise you wouldn't be asking about message-maps and that "MFC's wndProc"). As for your question, maybe you don't really need to get into this. I'm just wondering if it would work just calling:
DialogBox(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDD_MYDIALOG), parentHwnd, MyDlgProc);

Converting LPCTSTR to LPWSTR

using MFC and Unicode-Build
i want to change the colum-header-text of a listctrl and to do so i have to convert an LPCTSTR to an LPWSTR. what i do now is
void CSPCListViewCtrl::SetHeaderText(long lCol, LPCTSTR lColText)
{
CListCtrl& ListCtrl = GetListCtrl();
LV_COLUMN lvc;
::ZeroMemory((void *)&lvc, sizeof(LVCOLUMN));
lvc.mask |= LVCF_TEXT;
ListCtrl.GetColumn(lCol, &lvc);
lvc.pszText = const_cast<LPWSTR>(lColText);
ListCtrl.SetColumn(lCol, &lvc);
}
it seems to work but the const_cast looks somewhat strange and wrong to me so i tried out something like
USES_CONVERSION;
lvc.pszText = CT2W(lColText);
this seems to work in release-build but produces garbage in debug-build so i wonder what is the correct way to do it?
TL;DR: Using const_cast<LPTSTR>(lColText) is safe when calling CListCtrl::SetColumn.
But why then is the pszText member of the LVCOLUMN structure declared non-const? The LVCOLUMN structure is used both to set and retrieve information. When retrieving a column's text, you need to pass in a modifiable buffer (and length argument). When setting a columns text, on the other hand, the system uses the pszText member and internally stores a copy. It does not attempt to write to it. This is documented as well, even if very subtly:
cchTextMax
Size in TCHARs of the buffer pointed to by the pszText member. If the structure is not receiving information about a column, this member is ignored.
This is a common pattern in the Windows API, where the same structure is used both as an input and output parameter. The only option to work around this would have been to introduce a const-ified version for those structures. When the Windows API was invented 30 years ago, this wasn't deemed necessary or useful. Besides, it would have made a common pattern (read-update-write) more tedious and error prone, as the data would have to be manually copied between unrelated types.
Now that you know, that using a const_cast is safe in the scenario you described, you may be wondering, if it is a good idea to use it. That depends on a number of factors, e.g.
Can you come up with a helpful comment to concisely explain, why this is ok? Something along the lines of // Totally safe, it's just that M$ sucks probably won't cut it.
Will all members on your team understand the implications, or will this present a red herring in heap corruption debug sessions?
Do you have coding guidelines that allow the use of const_casts?
Are you using static code analysis tools that won't produce false positives for const_casts?
If you find that you cannot satisfactorily answer all those questions, you may consider implementing (technically unnecessary) manual copying (and exchange a const_cast for exception handling):
void CSPCListViewCtrl::SetHeaderText(long lCol, LPCTSTR lColText) {
CListCtrl& ListCtrl = GetListCtrl();
LVCOLUMN lvc = {0};
lvc.mask |= LVCF_TEXT;
// Create modifiable copy of lColText
size_t colTextLength = _tcslen(lColText);
std::vector<TCHAR> buffer(colTextLength + 1);
std::copy(lColText, lColText + colTextLength + 1, buffer.data());
lvc.pszText = buffer.data();
ListCtrl.SetColumn(lCol, &lvc);
}
One more note on your use of character string encodings: You are mixing the use of Generic-Text Mappings with explicit Unicode (UTF-16LE) encoding. To add consistency, you should change the lColText argument to type LPCWSTR, and use an LVCOLUMNW structure (as well as a const_cast<LPWSTR> if you decide to go that route). Unfortunately, when using MFC, you will have to resort to using the generic-text mappings when calling any class members. But at least you will get a compiler error in case of mismatching character encodings.
You can use CString::GetBuffer()
void SetHeaderText(long lCol, LPCTSTR lColText)
{
LV_COLUMN lvc;
::ZeroMemory((void *)&lvc, sizeof(LVCOLUMN));
lvc.mask |= LVCF_TEXT;
list.GetColumn(lCol, &lvc);
CString str = lColText;
lvc.pszText = str.GetBuffer();
list.SetColumn(lCol, &lvc);
str.ReleaseBuffer();
//ReleaseBuffer is option in this case because
//"str" is local variable and is destroyed before being used again*
}
SetHeaderText(0, L"text");
In UNICODE, LPTSTR is just LPWSTR (or simply wchar_t*)
If for some reason you have ANSI text, then you can use CString for conversion
CStringA ansi = "Text";
CStringW wide = CStringW(ansi);
SetHeaderText(0, wide);
See also
CString::GetBuffer()

Visual C++ get string from Cedit

This is probably a pretty basic question, but I can't seem to get it. I am working on a visualC++ project and I basically want to get a string from a GUI and then use that as a filename. I have written the following thus far, where IDC_FILE_NAME is the ID of the edit control box but I'm not sure if that is even the way to accomplish this.
m_pFileName = (CEdit*)GetDlgItem( IDC_FILE_NAME );
CString fName =_T(" ");
GetDlgItemTextA(IDC_FILE_NAME, fName);
but I'm not sure if that is even the way to accomplish this.
The answer is YES and NO.
YES if properly used,
NO, not as you do it.
Do not use UNICLODE/ANSI specific versions of functions unless you want to force UNICODE or ANSI.
Your code should look like:
CString csText;
GetDlgItemText(IDC_FILE_NAME, csText);
NOTE GetDlgItemText
This has been tested with VS2015:
//
// Get string from CEdit m_ceDate;
// where
// DDX_Control(pDX, IDC_EDIT_DATE, m_ceDate);
char cdateBuf[128];
UINT nCountOfCharacters = GetDlgItemText(IDC_EDIT_DATE, cdateBuf, 16);
CString csDate = cdateBuf;

Resources