How to manipulate DLGTEMPLATE programmatically? - winapi

What?
I have a DLGTEMPLATE loaded from a resource DLL, how can I change the strings assigned to the controls at runtime programmatically?
I want to be able to do this before the dialog is created, such that I can tell that the strings on display came from the resource DLL, and not from calls to SetWindowText when the dialog is initialized.
Google has found examples of creating DLGTEMPLATE in code, or twiddling simple style bits but nothing on editing the strings in memory.
How?
I am doing this by hooking the Dialog/Property Sheet creation API's. Which gives me access to the DLGTEMPLATE before the actual dialog is created and before it has a HWND.
Why?
I want to be able to do runtime localization, and localization testing. I already have this implemented for loading string (including the MFC 7.0 wrapper), menus and accelerator tables, but I am struggling to handle dialog/property sheet creation.
Code examples would be the perfect answer, ideally a class to wrap around the DLGTEMPLATE, if I work out my own solution I will post it.

You can't edit the strings in memory. The DLGTEMPLATE structure is a direct file mapping of the relevent bytes of the resource dll. Thats read only.
You are going to need to process the entire DLGTEMPLATE structure and write out a new one with the altered length strings.
It will frankly be easier to just hook the WM_INITDIALOG and alter the strings by interacting with the controls than building a DLGTEMPLATE writer. Because there arn't a lot of those around. Unless you have an additional requirement to actually save altered dialog resources to disk as raw .res files (or attempt to modify the .dll inplace) Id really recommend you avoid this approach.
You say you are already doing this for accellerator tables and menu strings - if you can guarantee that the patched in strings are going to be shorter, then just make a binary copy of the DLGTEMPLATE struct, and write the non trivial scanning code necessary to find each string so you can patch the copy in place.

There is a file out there somewhere (which I think originated at Microsoft but I am not completely sure) called RESFMT.ZIP which explains this with some code examples. Raymond Chen also does some excellent explanations of this on his blog. Note that the format of DIALOGEX and DIALOG controls are different.
As noted in some other answers you would need to create the structure again from the start. This isn't all bad as you already have the basic information. Adding the controls is where is gets hard.
Basically, allocate a largish block of memory into a WORD *lpIn. Then add the structure up on top of that. adding the basic information for the DIALOG (see DLGTEMPLATE) and the controls is pretty obvious as the information is there in MSDN.
The two biggest problems you will encounter are: Making sure that the various part start on an alignment boundary, and interpreting the values of DIALOG controls, especially when to add a just a string or, a string or ordinal. Each control needs to start on an even boundary.
For the first (borrowed from somewhere I think RESFMT.ZIP):
WORD *AlignDwordPtr (WORD *lpIn)
{
ULONG ul;
ul = (ULONG) lpIn;
ul +=3;
ul >>=2;
ul
What I did was build a series of functions like this one following that allowed me to assemble DIALOGS in memory. (My need was so I could have some common code that didn't need an associated RC file for some very basic messages).
Here is an example...
WORD *AddStringOrOrdinalToWordMem( WORD *lpw, char *sz_Or_Ord )
{
LPWSTR lpwsz;
int BufferSize;
if (sz_Or_Ord == NULL)
{
*lpw++ = 0;
}
else
{
if (HIWORD(sz_Or_Ord) == 0) //MAKEINTRESOURCE macro
{
*lpw++ = 0xFFFF;
*lpw++ = LOWORD(sz_Or_Ord);
}
else
{
if (strlen(sz_Or_Ord))
{
lpwsz = ( LPWSTR ) lpw;
BufferSize = MultiByteToWideChar( CP_ACP, 0, sz_Or_Ord, -1, lpwsz, 0 );
MultiByteToWideChar( CP_ACP, 0, sz_Or_Ord, -1, lpwsz, BufferSize );
lpw = lpw + BufferSize;
}
else
{
*lpw++ = 0;
}
}
}
return( lpw );
}
The header file to the complete module included these functions:
WORD *AddControlToDialogTemplateEx(MTDialogTemplateType *dlgtmp,
char *Title,
WORD Id,
char *WinClass,
DWORD Style,
short x,
short y,
short cx,
short cy,
DWORD ExStyle,
int HelpID);
int DestroyDlgTemplateEx(MTDialogTemplateType *dlgtmp);
MTDialogTemplateType *CreateDlgTemplateEx( char *Name, // We use name just for reference, so it can be NULL
short x,
short y,
short cx,
short cy,
DWORD ExtendedStyle,
DWORD Style,
char *Menu,
char *WinClass,
char *Caption,
char *FontTypeFace,
int FontSize,
int FontWeigth,
int FontItalic,
int Charset,
int HelpID,
int NumberOfControls);
Which allowed me to assemble whole dialogs easily from code.

See the API function ::EnumChildWindows( HWND, WNDENUMPROC, LPARAM )
You can call this in a CFormView::Create or CDialog::OnInitDialog to give yourself a chance to replace the control captions. Don't worry, the old strings don't flicker up before you replace them.
In your dialog resource, set the control captions to a key in some kind of dictionary. If you're compiling /clr you can use a managed string table resource. In your callback, look up the translated string in your dictionary and set the control's caption to the translation. Another benefit of /clr and managed string table is that you can automatically look up the right language by Windows (or you) having already set System::Threading::Thread::CurrentThread->CurrentUICulture.
Something like this
CMyDialog::OnInitDialog()
{
::EnumChildWindows(
this->GetSafeHwnd(),
CMyDialog::UpdateControlText,
(LPARAM)this )
}
BOOL CALLBACK CMyDialog::UpdateControlText( HWND hWnd, LPARAM lParam )
{
CMyDialog* pDialog = (CMyDialog*)lParam;
CWnd* pChildWnd = CWnd::FromHandle( hWnd );
int ctrlId = pChildWnd->GetDlgCtrlID();
if (ctrlId)
{
CString curWindowText;
pChildWnd->GetWindowText( curWindowText );
if (!curWindowText.IsEmpty())
{
CString newWindowText = // some look up
pChildWnd->SetWindowText( newWindowText );
}
}
}

You'll have to locate the string you want to modify in the mem buffer that represents the template. The only way to do that is to traverse the whole template. Which is not easy.
Once you've done that, either insert bytes in the buffer if your new string is longer than the original one. Or shrink the buffer if the new string is shorter.
As Chris wrote, it would be much easier to modify the text in WM_INITDIALOG and try to re-phrase you requirement that says you may not call SetWindowText().

Thanks all, I actually had 24 hours rest on the problem, then went with a global windows hook filtering WM_INITDIALOG which was a much simpler method, worked out just fine, no API hooking required, 2 pages of code down to just a few lines.
Thanks for all the answers.

Related

Are there any Win32 functions I can use to get the count / itemdata from a CComboBoxEx control?

My parent dialog has a CComboBoxEx control (which is mapped to a derived class called CDatesComboBoxEx).
In one part of the application this dialog displays a popup modal dialog. And, inside the modal dialog, it needs access to information from the dates combo.
What I decided to do (which works fine) is pass the address of my combo in the constructor of the popup dialog. So I can now do things like:
m_pComboDates->GetCount()
m_pComboDates->GetItemDataPtr(i)
I was wondering if there was any way to use native Win32 code here instead?
We can get access to the parents handle (GetParent()->GetSafeHWnd()).
We know the ID of the control on the parent dialog (IDC_COMBOBOXEX_OCLM_WEEK_OF_MEETING).
So is it possible to somehow directly get the count and item data?
I know that there are these macros:
ComboBox_GetCount
ComboBox_GetItemData
But:
Can these be macros be used with CComboBoxEx control? And ...
How do we get the HWND on the combo given the context I previously described?
Actually, I think I missunderstood the purpose of those "macros". I can get the combo handle like this:
HWND hDatesCombo = ::GetDlgItem(
GetParent()->GetSafeHwnd(), IDC_COMBOBOXEX_OCLM_WEEK_OF_MEETING);
But, ComboBox_GetCount does not return a value. Nor the others. So I am somewhat confused.
Based on the answer, this bit is now fine:
HWND hDatesCombo = ::GetDlgItem(GetParent()->GetSafeHwnd(), IDC_COMBOBOXEX_OCLM_WEEK_OF_MEETING);
int iNumDates = static_cast<int>(::SendMessage(hDatesCombo, CB_GETCOUNT, 0, 0));
And inside my for loop I am doing this:
LRESULT itemData = ::SendMessage(hDatesCombo, CB_GETITEMDATA, static_cast<WPARAM>(i), 0);
auto* pEntry = static_cast<CChristianLifeMinistryEntry*>((LPVOID)itemData);
That is the only way I can find to cast it. If I try static_cast<LPVOID> it won't work either.
I was wondering if there was any way to use native Win32 code here instead?
Yes, there is. The SendMessage function (and its returned value) is what you need …
Once you have the HWND of your combo-box, you can send it the CB_GETCOUNT message to ask it how many items it contains:
HWND hDatesCombo = ::GetDlgItem(GetParent()->GetSafeHwnd(), IDC_COMBOBOXEX_OCLM_WEEK_OF_MEETING);
LRESULT nItems = ::SendMessage(hDatesCombo, CB_GETCOUNT, 0, 0);
And, to get the item data associated with a particular entry, send the CB_GETITEMDATA message, with the (zero-based) index of the item in question as the wParam argument:
//...
LRESULT *ItemData = new LRESULT[static_cast<size_t>(nItems)];
for (int i = 0; i < nItems; ++i) {
ItemData[i] = ::SendMessage(hDatesCombo, CB_GETITEMDATA, static_cast<WPARAM>(i), 0);
}
//...
delete[] ItemData; // When you're done with the data list
Of course, if your item data are pointers (such as if you have an owner-drawn combo with1 the CBS_HASSTRINGS style), you would need to modify the second code snippet accordingly, adding relevant the reinterpret_cast operations where necessary. (Note that both the LRESULT and WPARAM types are defined as being suitable for storing pointers.)
1 The linked M/S documentation page is a bit fuzzy on whether this applies to owner-drawn combos with or without the CBS_HASSTRINGS style.

Create a font resource from byte array on Win32

I have a byte array that contains the contents of a read in font file. I'd like WinAPI (No Gdi+) to create a font resource from it, so I could use it for rendering text.
I only know about AddFontResourceExW, that loads in a font resource from file, and AddFontMemResourceEx, which sounded like what I'd need, but it seems to me that it's still some resource-system thing and the data would have to be pre-associated with the program.
Can I somehow convert my loaded in byte-array into a font resource? (Possibly without writing it to a file and then calling AddFontResourceExW)
When you load a font from a resource script into memory, you use code like the following (you didn't add a language tag, so I'm using C/C++ code - let me know if that's a problem):
HANDLE H_myfont = INVALID_HANDLE_VALUE;
HINSTANCE hResInstance = ::GetModuleHandle(nullptr);
HRSRC ares = FindResource(hResInstance, MAKEINTRESOURCE(IDF_MYID), L"BINARY");
if (ares) {
HGLOBAL amem = LoadResource(hResInstance, ares);
if (amem != nullptr) {
void *adata = LockResource(amem);
DWORD nFonts = 0, len = SizeofResource(hResInstance, ares);
H_myfont = AddFontMemResourceEx(adata, len, nullptr, &nFonts);
}
}
The key line here is void *adata = LockResource(amem); - this converts the font resource loaded as an HGLOBAL into 'accessible memory' (documentation). Now, assuming your byte array is in the correct format (see below), you could probably just pass a pointer to it (as void*) in the call to AddFontMemResourceEx. (You can use your known array size in place of calling SizeofResource.)
I would suggest code something like this:
void *my_font_data = (void*)(font_byte_array); // Your byte array data
DWORD nFonts = 0, len = sizeof(font_byte_array);
H_myfont = AddFontMemResourceEx(my_font_data, len, nullptr, &nFonts);
which (hopefully) will give you a loaded and useable font resource.
When you're done with the font (which, once loaded, can be used just like any system-installed font), you can release it with:
RemoveFontMemResourceEx(H_myfont);
As I don't have your byte array, I can't (obviously) test this idea. However, if you do try it, please let us know if it works. (If it doesn't, there may be some other, relatively straightforward, steps that need to be added.)
NOTE: Although I can't say 100% what the exact format expected of a "font resource" is, the fact that code given above works (for me) with a resource defined in the .rc script as a BINARY with a normal, ".ttf" file, suggests that, if your byte array follows the format of a Windows Font File, then it should work. This is how I have included a font as an embedded resource:
IDF_MYFONT BINARY L"..\\Resource\\MyFont.ttf"

Questions regarding GetWindowPlacement return data

I'm a bit unsure of the meaning of some of the return values from a call to the GetWindowPlacement() function, so I'd like your help, please.
I'll be calling this to obtain the normal dimensions of a hidden window.
First, where do the values of the showCmd field come from? In the Microsoft documentation of the return structure (WINDOWPLACEMENT structure, all the descriptions of the possible values use verbs/action words; e.g., "SW_MAXIMIZE: Maximizes the specified window", or "SW_SHOWNOACTIVATE: Displays a window in its most recent size and position."
I want to obtain the dimensions of the hidden window without unhiding/restoring it first, so with the verbs it seems that I would have to call SetWindowPlacement() with showCmd set to SW_SHOWNOACTIVATE before calling GetWindowPlacement. Is that correct?
So do I understand correctly that the primary (and perhaps only) way that field gets its various values is by an explicit call to SetWindowPlacement() somewhere?
My second question relates to the rcNormalPosition return values. Do those data include the window decorations, or are they client values?
Thank you for your time!
The meaning of the showCmd member of the WINDOWPLACEMENT struct is a bit confusing because Win32 is reusing the SW_* commands used by ShowWindow().
Luckily, the meaning is documented on the GetWindowPlacement() function.
If the window identified by the hWnd parameter is maximized, the
showCmd member is SW_SHOWMAXIMIZED. If the window is minimized,
showCmd is SW_SHOWMINIMIZED. Otherwise, it is SW_SHOWNORMAL.
So, based on which of those 3 values is returned, you can tell whether the window is currently maximized, minimized or, normal (restored). And if you'd like to know what the normal placement is, you can just use the rcNormalPosition member. You do not need to call SetWindowPlacement() at all.
However, heed the warning that GetWindowPlacement() returns workspace coordinates rather than screen coordinates, which differ based on taskbar position and size. This is not a problem if you are only using the coordinates returned by GetWindowPlacement() to call SetWindowPlacement(). Otherwise, you might have to find a way to convert from workspace to screen coordinates.
I found these 2 functions to work for me.
void MyDialog::LoadDialogPlacement()
{
static WINDOWPLACEMENT last_wp = {};
// Load last stored DB version
WINDOWPLACEMENT *wp = new WINDOWPLACEMENT;
GetStoredWindowPlacement(&wp);
if (memcmp((void *)&last_wp, (const void *)wp, sizeof(WINDOWPLACEMENT)) == 0) return;
memcpy((void *)&last_wp, (const void *)wp, sizeof(WINDOWPLACEMENT));
SetWindowPlacement(wp);
delete[] wp;
}
void MyDialog::SaveDialogPlacement()
{
static WINDOWPLACEMENT last_wp = {};
if (IsWindowVisible())
{
WINDOWPLACEMENT wp = {};
wp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(&wp);
if (memcmp((void *)&last_wp, (const void *)&wp, wp.length) == 0) return;
memcpy((void *)&last_wp, (const void *)&wp, wp.length);
StoreWindowPlacement(&wp);
}
}

How to compute the actual height of the text in a static control

My simple Win32 DialogBox contains two static text controls (IDC_STATIC_TITLE and IDC_STATIC_SECONDARY), here's what it looks like in the resource editor:
At run time, the text first string is updated dynamically. Also, the font of the that text string is replaced such that it's bigger than the IDC_STATIC_SECONDARY string below it. The resulting text string might span a single line, two lines, or more.
I want the other static control holding the secondary text to be placed directly underneath the title string at run time. However, my resulting attempt to re-position this control in the WM_INITDIALOG callback isn't working very well. The second string is overlapping the first. I thought I could use DrawText with DT_CALCRECT to compute the height of the primary text string and then move the secondary text string based on the result. My code is coming up a bit short as seen here:
DrawText returns a RECT with coordinates {top=42 bottom=74 left=19 right=461} Subtracting bottom from top is "32". That seems a little short. I suspect I'm not invoking the API correctly and/or an issue with the different mappings between logical and pixel units.
Here's the relevant ATL code. The CMainWindow class just inherits from ATL's CDialogImpl class.
CMainWindow::CMainWindow():
_titleFont(NULL),
_secondaryFont(NULL)
{
LOGFONT logfont = {};
logfont.lfHeight = 30;
_titleFont = CreateFontIndirect(&logfont);
}
LRESULT CMainWindow::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CString strTitle;
RECT rectDrawText = {}, rectTitle={}, rectSecondary={};
CWindow wndTitle = GetDlgItem(IDC_STATIC_TITLE);
CWindow wndSecondary = GetDlgItem(IDC_STATIC_SECONDARY);
this->GetDlgItemText(IDC_STATIC_TITLE, strTitle);
wndTitle.SetFont(_titleFont); // font created with Create
wndTitle.GetWindowRect(&rectTitle);
wndSecondary.GetWindowRect(&rectSecondary);
ScreenToClient(&rectTitle);
ScreenToClient(&rectSecondary);
rectDrawText = rectTitle;
DrawText(wndTitle.GetDC(), strTitle, strTitle.GetLength(), &rectDrawText, DT_CALCRECT|DT_WORDBREAK); // compute the actual size of the text
UINT height = rectSecondary.bottom - rectSecondary.top; // get the original height of the secondary text control
rectSecondary.top = rectDrawText.bottom; // position it to be directly below the bottom of the title control
rectSecondary.bottom = rectSecondary.top + height; // add the height back
wndSecondary.MoveWindow(&rectSecondary);
return 0;
}
What am I doing wrong?
Despite what its name may make it sound like, wndTitle.GetDC() doesn't return some pointer/reference that's part of the CWindow and that's the same every call. Instead, it retrieves a brand new device context for the window each time. (It's basically a thin wrapper for the GetDC() Windows API call, right down to returning an HDC instead of the MFC equivalent.)
This device context, despite being associated with the window, is loaded with default parameters, including the default font (which IIRC is that old "System" font from the 16-bit days (most of this screenshot)).
So what you need to do is:
Call wndTitle.GetDC() to get the HDC.
Call SelectObject() to select the correct window font in (you can use WM_GETFONT to get this; not sure if MFC has a wrapper function for it), saving the return value, the previous font, for step 4
Call DrawText()
Call SelectObject() to select the previous font back in
Call wndTitle.ReleaseDC() to state that you are finished using the HDC
More details are on the MSDN page for CWindow::GetDC().

load a ttf font with the Windows API

With CreateFont one can specify font name and a bunch of other properties. However, what if I have a font.ttf file, and I want that particular font to be loaded by windows? How do I specify that specific file to be used?
I'm pretty sure you can't. All requests for fonts go through the font mapper, and it picks out the font file that comes the closest to meeting the specifications you've given. Though I'm not sure it even does in reality, it could at least theoretically use (for example) data from two entirely separate font files to create one logical font.
It's admittedly rather indirect, but you could utilize GDI interop with DWrite when running on Windows 7+.
#include <Windows.h>
#include <WindowsX.h>
#include <DWrite.h>
...
// Make the font file visible to GDI.
AddFontResourceEx(fontFileName, FR_PRIVATE, 0);
if (SUCCEEDED(GetLogFontFromFileName(fontFileName, &logFont)))
{
logFont.lfHeight = -long(desiredPpem);
HFONT hf = CreateFontIndirect(&logFont);
HFONT oldFont = SelectFont(hdc, hf);
...
// Do stuff...
...
SelectFont(hdc, oldFont);
}
RemoveFontResource(fontFileName);
....
HRESULT GetLogFontFromFileName(_In_z_ wchar const* fontFileName, _Out_ LOGFONT* logFont)
{
// DWrite objects
ComPtr<IDWriteFactory> dwriteFactory;
ComPtr<IDWriteFontFace> fontFace;
ComPtr<IDWriteFontFile> fontFile;
ComPtr<IDWriteGdiInterop> gdiInterop;
// Set up our DWrite factory and interop interface.
IFR(DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&dwriteFactory)
);
IFR(g_dwriteFactory->GetGdiInterop(&gdiInterop));
// Open the file and determine the font type.
IFR(g_dwriteFactory->CreateFontFileReference(fontFileName, nullptr, &fontFile));
BOOL isSupportedFontType = false;
DWRITE_FONT_FILE_TYPE fontFileType;
DWRITE_FONT_FACE_TYPE fontFaceType;
UINT32 numberOfFaces = 0;
IFR(fontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces));
if (!isSupportedFontType)
return DWRITE_E_FILEFORMAT;
// Set up a font face from the array of font files (just one)
ComPtr<IDWriteFontFile> fontFileArray[] = {fontFile};
IFR(g_dwriteFactory->CreateFontFace(
fontFaceType,
ARRAYSIZE(fontFileArray), // file count
&fontFileArray[0], // or GetAddressOf if WRL ComPtr
0, // faceIndex
DWRITE_FONT_SIMULATIONS_NONE,
&fontFace
);
// Get the necessary logical font information.
IFR(gdiInterop->ConvertFontFaceToLOGFONT(fontFace, OUT logFont));
return S_OK;
}
Where IFR is just a failure macro that returns on a FAILED HRESULT, and ComPtr is a helper smart pointer class (substitute with your own, or ATL CComPtr, WinRT ComPtr, VS2013 _com_ptr_t...).
One possibility is to EnumFonts(), save the results. Then add your private font with AddFontResourceEx(), and EnumFonts() again, the difference is what you added. Note that TTF and bitmap fonts enumerate differently, but for this test, that shouldn't matter.
If you were using bitmap fonts, they could be easily parsed (.FNT and .FON). TTF you'd likely have to build (or borrow, as another commenter suggested FreeType) a parser to pull the "name" table out of the TTF file.
That seems like a lot of work for a font you're controlling or supplying with your app.
We use AddFontResourceEx() to add a private font, but since we control the font we're adding, we just hardcode the fontname passed to CreateFontIndirect() to match.
If you dont care about installing the font you can do so with AddFontResource then you can fetch the relationship between the physical .TTF and it logical/family name by looking at the mappings in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts.
I mentioned PrivateFontCollection in my comment because I thought you wanted to do this temporarily; you can load a TTF into a PFC with PrivateFontCollection::AddFontFile, fetch back the new FontFamily object from the collection & examime GetFamilyName. (I've done similar with the .net implementation of this but not the raw API)

Resources