Related
I am learning Win32 API programming. I read this paragraph, it says:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
SPI_SETMOUSETRAILS
Enables or disables the Mouse Trails feature, which improves the visibility of mouse cursor movements by briefly showing a trail of cursors and quickly erasing them.
To disable the feature, set the uiParam parameter to zero or 1. To enable the feature, set uiParam to a value greater than 1 to indicate the number of cursors drawn in the trail.
Windows 2000: This parameter is not supported.
I tried to write the following code:
#include <windows.h>
int main(int argc, char *argv[], char *envp[])
{
//SystemParametersInfo(SPI_SETMOUSETRAILS, NULL,(PVOID)10, 0);
SystemParametersInfo(SPI_SETMOUSETRAILS, NULL,(PVOID)0, 0);
}
But it doesn't seem to make a difference.
Can anyone tell me how this macro works? I couldn't find an example on the Internet, so I am asking here.
Read the documentation again more carefully:
To disable the feature, set the uiParam parameter to zero or 1. To enable the feature, set uiParam to a value greater than 1 to indicate the number of cursors drawn in the trail.
Look at the declaration of the function:
BOOL SystemParametersInfoA(
UINT uiAction,
UINT uiParam,
PVOID pvParam,
UINT fWinIni
);
Your code is setting the uiParam parameter to 0 (disabling the feature), and setting the pvParam parameter to the cursor count (which is ignored).
Try this instead:
#include <windows.h>
int main(int argc, char *argv[], char *envp[])
{
UINT cursorCount = ...;
SystemParametersInfo(SPI_SETMOUSETRAILS, cursorCount, NULL, 0);
return 0;
}
I couldn't find an example on the Internet
Really? When I enter just SPI_SETMOUSETRAILS by itself into Google, literally the 1st link returned contains an example:
http://ntcoder.com/bab/tag/spi_setmousetrails/
SystemParametersInfo is a powerful function which does and retrieves whole lot of things related to windows general behavior for eg: turning on and off certain features like mouse trails which we normally do via control panel->main.cpl
Look up MSDN for more information on SystemParametersInfo. I’ll just show a demo on how to turn on and off mouse trails…
// Turn on mouse trails, showing 10 cursors drawn in the trail
SystemParametersInfo( SPI_SETMOUSETRAILS, 10, 0, 0 );
// Turn off mouse trails, set trail count to 1 or zero to disable trails
SystemParametersInfo( SPI_SETMOUSETRAILS, 1, 0, 0 );
Starting with October 2018 Update (version 1809) Win10 has support for Dark theme in Windows Explorer.
It can be configured here:
UI: Desktop | Context Menu | Personalize | Colors | Choose your default app mode = Dark
Registry : HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme = DWORD:0
While this setting exists for a while now, it only affected UWP applications. However, with this Windows 10 release, it also affects Windows Explorer, which is a Desktop application. This means that Windows now have internal support for it. Still, Desktop applications other then Windows Explorer are not affected at the moment.
I'd like to use it in my application. How is it implemented under the hood? Is there some way (manifest, WINAPI, etc) to subscribe for new dark theme?
Update 1:
I noticed that Windows Explorer Control Panel is partially light and partially dark, so it should be a per-window setting, rather then per-process setting.
One other example: Open File dialogs become dark in all Desktop applications, while the application itself remains in old light theme.
Update 2:
I tried SetWindowTheme(hwnd, L"Explorer", NULL); for TreeView and ListView. This visibly changes TreeView style (+ expand button becomes V), but the window remains white.
See https://github.com/ysc3839/win32-darkmode
This guy has it all laid out in some nice reusable code (MIT license).
It seems that Dark Mode is still an area of development in Windows 10, but I believe that Microsoft will eventually properly document and expose it to desktop apps.
Until then, we are stuck with undocumented ordinal-only imports, then custom draw and WM_CTLCOLOR* messages to dictate how controls that don't yet have native Dark Mode support are painted.
The most fundamental of the new Windows APIs are SetPreferredAppMode (uxtheme#135), to be called prior to any window creation, and AllowDarkModeForWindow (uxtheme#133), to be called on any Window that intends to use native Windows 10 dark mode support.
Here is the full list ordinal-only imports from that project:
using fnRtlGetNtVersionNumbers = void (WINAPI *)(LPDWORD major, LPDWORD minor, LPDWORD build);
// 1809 17763
using fnShouldAppsUseDarkMode = bool (WINAPI *)(); // ordinal 132
using fnAllowDarkModeForWindow = bool (WINAPI *)(HWND hWnd, bool allow); // ordinal 133
using fnAllowDarkModeForApp = bool (WINAPI *)(bool allow); // ordinal 135, removed since 18334
using fnFlushMenuThemes = void (WINAPI *)(); // ordinal 136
using fnRefreshImmersiveColorPolicyState = void (WINAPI *)(); // ordinal 104
using fnIsDarkModeAllowedForWindow = bool (WINAPI *)(HWND hWnd); // ordinal 137
using fnGetIsImmersiveColorUsingHighContrast = bool (WINAPI *)(IMMERSIVE_HC_CACHE_MODE mode); // ordinal 106
using fnOpenNcThemeData = HTHEME(WINAPI *)(HWND hWnd, LPCWSTR pszClassList); // ordinal 49
// Insider 18290
using fnShouldSystemUseDarkMode = bool (WINAPI *)(); // ordinal 138
// Insider 18334
using fnSetPreferredAppMode = PreferredAppMode (WINAPI *)(PreferredAppMode appMode); // ordinal 135, since 18334
using fnIsDarkModeAllowedForApp = bool (WINAPI *)(); // ordinal 139
InitDarkMode imports and initializes dark mode, in a safe manner, carefully checking for min and max supported Windows 10 builds:
void InitDarkMode()
{
fnRtlGetNtVersionNumbers RtlGetNtVersionNumbers = reinterpret_cast<fnRtlGetNtVersionNumbers>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers"));
if (RtlGetNtVersionNumbers)
{
DWORD major, minor;
RtlGetNtVersionNumbers(&major, &minor, &g_buildNumber);
g_buildNumber &= ~0xF0000000;
if (major == 10 && minor == 0 && 17763 <= g_buildNumber && g_buildNumber <= 18363) // Windows 10 1809 10.0.17763 - 1909 10.0.18363
{
HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (hUxtheme)
{
_OpenNcThemeData = reinterpret_cast<fnOpenNcThemeData>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(49)));
_RefreshImmersiveColorPolicyState = reinterpret_cast<fnRefreshImmersiveColorPolicyState>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(104)));
_GetIsImmersiveColorUsingHighContrast = reinterpret_cast<fnGetIsImmersiveColorUsingHighContrast>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(106)));
_ShouldAppsUseDarkMode = reinterpret_cast<fnShouldAppsUseDarkMode>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(132)));
_AllowDarkModeForWindow = reinterpret_cast<fnAllowDarkModeForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133)));
auto ord135 = GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135));
if (g_buildNumber < 18334)
_AllowDarkModeForApp = reinterpret_cast<fnAllowDarkModeForApp>(ord135);
else
_SetPreferredAppMode = reinterpret_cast<fnSetPreferredAppMode>(ord135);
//_FlushMenuThemes = reinterpret_cast<fnFlushMenuThemes>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(136)));
_IsDarkModeAllowedForWindow = reinterpret_cast<fnIsDarkModeAllowedForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(137)));
if (_OpenNcThemeData &&
_RefreshImmersiveColorPolicyState &&
_ShouldAppsUseDarkMode &&
_AllowDarkModeForWindow &&
(_AllowDarkModeForApp || _SetPreferredAppMode) &&
//_FlushMenuThemes &&
_IsDarkModeAllowedForWindow)
{
g_darkModeSupported = true;
AllowDarkModeForApp(true);
_RefreshImmersiveColorPolicyState();
g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast();
FixDarkScrollBar();
}
}
}
}
}
Elsewhere, he takes advantage of WM_CTLCOLOR* messages and custom draw notifications to paint dark where Windows doesn't (yet) do it for us.
Note the FixDarkScrollBar. That is an IAT hook on OpenNcThemeData to over-ride the scrollbar theme selection by the listview class in comctl32. That is the part that bothers me most and I'm looking to axe it. I'm sure he is too.
I've adapted this code to my own application and it works well. I am, however, uncomfortable using these undocumented ordinal-only APIs (even as safely as possible), and fully expect Microsoft to eventually announce and document dark mode for Win32 apps and make this work redundant.
After some digging, I was able to find these two approaches. Both are undocumented and may change without notice.
1
SetWindowTheme(hwnd, L"DarkMode_Explorer", NULL);
2
using TYPE_AllowDarkModeForWindow = bool (WINAPI *)(HWND a_HWND, bool a_Allow);
static const TYPE_AllowDarkModeForWindow AllowDarkModeForWindow = (TYPE_AllowDarkModeForWindow)GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133));
AllowDarkModeForWindow(a_HWND, true);
SetWindowTheme(hwnd, L"Explorer", NULL);
WARNING: Ordinal 133 may have completely different API behind it on other versions of Windows, including newer/older Win10 builds.
Both approaches apply some effects, but not everything.
For example, TreeView gets dark scrollbars and dark background for selected item, but the rest of background stays default.
Unfortunately, so far it's not like "call a function and that's it". It seems that even with correct theme applied, some background colors need to be handled manually.
In order to add a toolbar to my MFC dialog baced class, I tried all ways of adding resources>toolbars but they didn't work. Finally I came up to the point to create a toolbar dynamically. This is the code that I have used:
Resource.h
#define IDB_PanTbrBtn 139
#define IDB_NewTbrBtn 140
#define IDB_ZoomInTbrBtn 141
#define IDB_ZoomOutTbrBtn 142
#define IDC_FirstToolBar 1011
#define IDC_NEWTBRBTN 1012
#define IDC_ZOOMINTBRBTN 1013
#define IDC_ZOOMOUTTBRBTN 1014
#define IDC_PANTBRBTN 1015
InitialJobProject2Dlg.h : header file for the dialog baced project
#pragma once
#include "WndResizer.h"
#include "afxdlgs.h"
#include "FilesWorkFlow.h"
#include "OpenGLControl.h"
CWndResizer m_resizer;
CMFCToolBar m_FirstToolBar;
FilesWorkFlow *m_files;
COpenGLControl *m_oglWindow;
InitialJobProject2Dlg.cpp : the codes related to the toolbar in the function OnInitDialog()
bool bAnchored = false;
bAnchored = m_resizer.Hook(this);
assert(bAnchored);
bool ToolbarCreated = m_FirstToolBar.CreateEx(this, AFX_DEFAULT_TOOLBAR_STYLE, 100 );
if(ToolbarCreated)
{
m_FirstToolBar.SetDlgCtrlID(IDC_FirstToolBar);
bAnchored = m_resizer.SetAnchor(IDC_FirstToolBar,ANCHOR_LEFT | ANCHOR_TOP);
assert(bAnchored);
m_FirstToolBar.SetPaneStyle(m_FirstToolBar.GetPaneStyle() & ~(CBRS_GRIPPER|CBRS_SIZE_DYNAMIC|CBRS_BORDER_ANY));
VERIFY(m_FirstToolBar.LoadBitmapW(IDB_NewTbrBtn));
VERIFY(m_FirstToolBar.GetImages()->Load(IDB_NewTbrBtn,nullptr,TRUE));
int imageIndex = m_FirstToolBar.GetImages()->GetCount();
m_FirstToolBar.InsertButton(CMFCToolBarButton(IDC_NEWTBRBTN,imageIndex));
VERIFY(m_FirstToolBar.LoadBitmapW(IDB_PanTbrBtn));
VERIFY(m_FirstToolBar.GetImages()->Load(IDB_PanTbrBtn,nullptr,TRUE));
imageIndex = m_FirstToolBar.GetImages()->GetCount();
m_FirstToolBar.InsertButton( CMFCToolBarButton( IDC_PANTBRBTN,imageIndex));
VERIFY(m_FirstToolBar.LoadBitmapW(IDB_ZoomInTbrBtn));
VERIFY(m_FirstToolBar.GetImages()->Load(IDB_ZoomInTbrBtn,nullptr,TRUE));
imageIndex = m_FirstToolBar.GetImages()->GetCount();
m_FirstToolBar.InsertButton( CMFCToolBarButton( IDC_ZOOMINTBRBTN,imageIndex));
VERIFY(m_FirstToolBar.LoadBitmapW(IDB_ZoomOutTbrBtn));
VERIFY(m_FirstToolBar.GetImages()->Load(IDB_ZoomOutTbrBtn,nullptr,TRUE));
imageIndex = m_FirstToolBar.GetImages()->GetCount();
m_FirstToolBar.InsertButton(CMFCToolBarButton(IDC_ZOOMOUTTBRBTN,imageIndex));
CSize size = m_FirstToolBar.CalcFixedLayout( FALSE, TRUE );
m_FirstToolBar.SetWindowPos( NULL, 0, 0, size.cx + 10, size.cy + 10 , SWP_NOACTIVATE | SWP_NOZORDER );
}
this is my project's resource view:
and this one is the res folder of my program:
images that I want to be shown as the icons of toolbar buttons are 48x48 ,32 bit depth bitmap images but I had the same problem with 24x24 ones
The problem is when I run the program:
IT's clear that there is just one button as the toolbar button but I have inserted four buttons dynamically as you see in the code.
and the image is not shown even for this known button.
My code does not have any compiler or run-time error so I don't understand what is happening and whats the problem?
and since I'm new to MFC(this is my first program of MFC) I didn't know that adding a toolbar to a dialog-based application is such a hard task!!!! **
**So I created a dialog-based project at start and now that my program has been developed, I do need a toolbar
please help me. this is the forth question I have posted about adding a toolbar on a dialog based MFC application.
after writing the code to add a toolbar to a dialog-based mfc the dialog doesn't run
My toolbar on a dialog based mfc application is not shown
having trouble with LoadToolBarEx function of the CMFCToolBar class and set ID for the COpenGLControl class
But the problem has not been completely solved yet?
**Please introduce me a good reference that has taught adding toolbars to the MFC dialogs step by step from the scratch and has been useful for yourself
Oh and if there's a need to my project, it is downloadable here
as #Edward Clements suggested I changed the code to this but nothing changed.
VERIFY(m_FirstToolBar.LoadBitmap(IDB_NewTbrBtn));
m_FirstToolBar.InsertButton(CMFCToolBarButton(IDC_NEWTBRBTN,1));
VERIFY(m_FirstToolBar.LoadBitmap(IDB_PanTbrBtn));
m_FirstToolBar.InsertButton( CMFCToolBarButton( IDC_PANTBRBTN,2));
VERIFY(m_FirstToolBar.LoadBitmap(IDB_ZoomInTbrBtn));
m_FirstToolBar.InsertButton( CMFCToolBarButton( IDC_ZOOMINTBRBTN,3));
VERIFY(m_FirstToolBar.LoadBitmap(IDB_ZoomOutTbrBtn));
m_FirstToolBar.InsertButton(CMFCToolBarButton(IDC_ZOOMOUTTBRBTN,4));
Firstly, according to the MFC sources, LoadBitmap() [NOT LoadBitmapW(), that seems to happen because of VS Intellisense picking up a #define from WinUser.h] adds the bitmap to the image list, so calling m_FirstToolBar.GetImages()->Load() will load the image twice.
Secondly, the InsertButton() should specify the index of the image of the button, m_FirstToolBar.GetImages()->GetCount() will always point to an invalid index value.
VERIFY(m_FirstToolBar.LoadBitmap(IDB_NewTbrBtn));
VERIFY(m_FirstToolBar.LoadBitmap(IDB_PanTbrBtn));
VERIFY(m_FirstToolBar.LoadBitmap(IDB_ZoomInTbrBtn));
VERIFY(m_FirstToolBar.LoadBitmap(IDB_ZoomOutTbrBtn));
m_FirstToolBar.InsertButton(CMFCToolBarButton(IDC_NEWTBRBTN, 0));
m_FirstToolBar.InsertButton(CMFCToolBarButton(IDC_PANTBRBTN, 1));
m_FirstToolBar.InsertButton(CMFCToolBarButton(IDC_ZOOMINTBRBTN, 2));
m_FirstToolBar.InsertButton(CMFCToolBarButton(IDC_ZOOMOUTTBRBTN, 3));
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)
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.