如何以编程方式操作DLGTEMPLATE?

时间:2008-10-15 11:05:25

标签: winapi mfc localization hook

什么吗

我从资源DLL加载了DLGTEMPLATE,如何在运行时以编程方式更改分配给控件的字符串?

我希望能够在创建对话框之前执行此操作,以便我可以告诉显示的字符串来自资源DLL,而不是在初始化对话框时调用SetWindowText。

Google已经找到了在代码中创建DLGTEMPLATE的示例,或者在编辑内存中的字符串时没有编辑简单样式位的示例。

如何吗

我是通过挂钩Dialog / Property Sheet创建API来做到这一点的。这使我可以在创建实际对话框之前和具有HWND之前访问DLGTEMPLATE。

为什么吗

我希望能够进行运行时本地化和本地化测试。我已经实现了加载字符串(包括MFC 7.0包装器),菜单和加速器表,但我正在努力处理对话框/属性表的创建。

代码示例将是完美的答案,理想情况下是一个环绕DLGTEMPLATE的类,如果我找到自己的解决方案,我会发布它。

5 个答案:

答案 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;
    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);

这让我可以轻松地从代码中组装整个对话框。

答案 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页代码只需要一个几行。

感谢所有答案。