什么吗
我从资源DLL加载了DLGTEMPLATE,如何在运行时以编程方式更改分配给控件的字符串?
我希望能够在创建对话框之前执行此操作,以便我可以告诉显示的字符串来自资源DLL,而不是在初始化对话框时调用SetWindowText。
Google已经找到了在代码中创建DLGTEMPLATE的示例,或者在编辑内存中的字符串时没有编辑简单样式位的示例。
如何吗
我是通过挂钩Dialog / Property Sheet创建API来做到这一点的。这使我可以在创建实际对话框之前和具有HWND之前访问DLGTEMPLATE。
为什么吗
我希望能够进行运行时本地化和本地化测试。我已经实现了加载字符串(包括MFC 7.0包装器),菜单和加速器表,但我正在努力处理对话框/属性表的创建。
代码示例将是完美的答案,理想情况下是一个环绕DLGTEMPLATE的类,如果我找到自己的解决方案,我会发布它。
答案 0 :(得分:5)
您无法编辑内存中的字符串。 DLGTEMPLATE结构是资源dll的相关字节的直接文件映射。多数民众赞成只读。
您将需要处理整个DLGTEMPLATE结构并写出一个带有更改长度字符串的新结构。
坦率地说,通过与控件交互来挂钩WM_INITDIALOG并更改字符串比构建DLGTEMPLATE编写器更容易。因为周围没有很多人。除非您有额外的要求将更改的对话框资源实际保存到磁盘作为原始.res文件(或尝试修改.dll inplace),否则我建议您避免使用此方法。
你说你已经为加速器表和菜单字符串做了这个 - 如果你可以保证修补的字符串会更短,那么只需制作DLGTEMPLATE结构的二进制副本,然后编写非平凡的扫描代码找到每个字符串以便你可以修复副本。
答案 1 :(得分:4)
有一个文件在那里(我认为起源于微软,但我不完全确定)称为RESFMT.ZIP,它用一些代码示例解释了这一点。 Raymond Chen也在他的博客上做了一些很好的解释。请注意,DIALOGEX和DIALOG控件的格式不同。
如其他一些答案中所述,您需要从头开始重新创建结构。这并非全是坏事,因为您已经掌握了基本信息。添加控件是变得困难的地方。
基本上,将一个较大的内存块分配给WORD * lpIn。然后在其上添加结构。添加DIALOG的基本信息(参见DLGTEMPLATE),控件非常明显,因为MSDN中有相关信息。
您将遇到的两个最大问题是:确保各个部分从对齐边界开始,并解释DIALOG控件的值,尤其是在添加一个字符串或字符串或序号时。每个控件都需要在均匀的边界上开始。
对于第一个(从某个地方借来的,我认为RESFMT.ZIP):
WORD *AlignDwordPtr (WORD *lpIn) { ULONG ul; ul = (ULONG) lpIn; ul +=3; ul >>=2; ulWhat 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);
这让我可以轻松地从代码中组装整个对话框。
答案 2 :(得分:1)
参见API函数:: EnumChildWindows(HWND,WNDENUMPROC,LPARAM)
你可以在CFormView :: Create或CDialog :: OnInitDialog中调用它,让自己有机会替换控制字幕。不要担心,旧字符串在更换之前不会闪烁。
在对话框资源中,将控件标题设置为某种字典中的键。如果您正在编译/ clr,则可以使用托管字符串表资源。在回调中,在字典中查找已翻译的字符串,并将控件的标题设置为翻译。 / clr和托管字符串表的另一个好处是,您可以通过Windows(或您)已经设置System :: Threading :: Thread :: CurrentThread-> CurrentUICulture自动查找正确的语言。
像这样的东西
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 );
}
}
}
答案 3 :(得分:1)
您必须在代表模板的mem缓冲区中找到要修改的字符串。唯一的方法是遍历整个模板。哪个不容易。 完成后,如果新字符串比原始字符串长,则在缓冲区中插入字节。如果新字符串较短,请缩小缓冲区。
正如克里斯写的那样,修改WM_INITDIALOG中的文本并尝试重新表达你可能不会调用SetWindowText()的要求会更容易。
答案 4 :(得分:0)
谢谢大家,我实际上已经24小时休息了问题,然后使用全局Windows钩子过滤WM_INITDIALOG,这是一个更简单的方法,工作得很好,不需要API挂钩,2页代码只需要一个几行。
感谢所有答案。