3

什么?

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

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

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

如何?

我通过挂钩对话框/属性表创建 API 来做到这一点。这使我可以在创建实际对话框之前以及在它具有 HWND 之前访问 DLGTEMPLATE。

为什么?

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

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

4

5 回答 5

6

您无法编辑内存中的字符串。DLGTEMPLATE 结构是资源 dll 的相关字节的直接文件映射。那是只读的。

您将需要处理整个 DLGTEMPLATE 结构并用改变的长度字符串写出一个新结构。

坦率地说,仅挂钩 WM_INITDIALOG 并通过与控件交互来更改字符串比构建 DLGTEMPLATE 编写器更容易。因为周围的人不多。除非您有额外的要求将更改的对话框资源作为原始 .res 文件实际保存到磁盘(或尝试就地修改 .dll),否则我真的建议您避免这种方法。

您说您已经在为加速器表和菜单字符串执行此操作 - 如果您可以保证修补的字符串会更短,那么只需制作 DLGTEMPLATE 结构的二进制副本,并编写查找所需的非平凡扫描代码每个字符串,以便您可以将副本修补到位。

于 2008-10-15T13:30:50.730 回答
4

有一个名为 RESFMT.ZIP 的文件(我认为它起源于 Microsoft,但我不完全确定),它通过一些代码示例解释了这一点。Raymond Chen 在他的博客上也做了一些很好的解释。注意 DIALOGEX 和 DIALOG 控件的格式是不同的。

正如在其他一些答案中所指出的,您需要从一开始就再次创建结构。这还不错,因为您已经掌握了基本信息。添加控件是很难的地方。

基本上,将较大的内存块分配到 WORD *lpIn 中。然后在上面添加结构。添加 DIALOG 的基本信息(请参阅 DLGTEMPLATE)和控件非常明显,因为信息在 MSDN 中。

您将遇到的两个最大问题是:确保各个部分从对齐边界开始,以及解释 DIALOG 控件的值,尤其是在仅添加字符串或字符串或序数时。每个控件都需要从一个均匀的边界开始。

对于第一个(从我认为 RESFMT.ZIP 的某个地方借来的):

WORD *AlignDwordPtr (WORD *lpIn)
    {
    乌龙 ul;

    ul = (ULONG) lpIn;
    ul +=3;
    ul >>=2;
    ul

我所做的是构建了一系列函数,如下所示,它允许我在内存中组装 DIALOGS。(我的需要是我可以拥有一些不需要关联 RC 文件来获取一些非常基本的消息的通用代码)。

这是一个例子......

WORD *AddStringOrOrdinalToWordMem( WORD *lpw, char *sz_Or_Ord )
    {
    LPWSTR lpwsz;
    整数缓冲区大小;

    如果(sz_Or_Ord == NULL)
        {
        *lpw++ = 0;
        }
    别的
        {
        if (HIWORD(sz_Or_Ord) == 0) //MAKEINTRESOURCE 宏
            {
            *lpw++ = 0xFFFF;
            *lpw++ = LOWORD(sz_Or_Ord);
            }
        别的
            {
            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 + 缓冲区大小;
                }
            别的
                {
                *lpw++ = 0;
                }
            }
        }
    返回(lpw);
    }

完整模块的头文件包括以下功能:

WORD *AddControlToDialogTemplateEx(MTDialogTemplateType *dlgtmp, 字符 *标题, 字号, 字符 *WinClass, 双字样式, 短 x, 短 y, 短cx, 短cy, DWORD ExStyle, 诠释帮助ID); int DestroyDlgTemplateEx(MTDialogTemplateType *dlgtmp); MTDialogTemplateType *CreateDlgTemplateEx( char *Name, // 我们使用 name 仅供参考,所以可以为 NULL 短 x, 短 y, 短cx, 短cy, DWORD 扩展样式, 双字样式, 字符 *菜单, 字符 *WinClass, 字符 *标题, 字符 *FontTypeFace, 整数字体大小, 整数字体重量, int FontItalic, 整数字符集, 整数帮助ID, int NumberOfControls);

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

于 2008-10-16T00:00:12.723 回答
1

请参阅 API 函数 :: EnumChildWindows ( HWND, WNDENUMPROC, LPARAM )

您可以在 CFormView::Create 或 CDialog::OnInitDialog 中调用它,让自己有机会替换控件标题。不用担心,旧琴弦在更换之前不会闪烁。

在您的对话框资源中,将控件标题设置为某种字典中的键。如果您正在编译 /clr,您可以使用托管字符串表资源。在您的回调中,在您的字典中查找已翻译的字符串并将控件的标题设置为翻译。/clr 和托管字符串表的另一个好处是,您可以通过已设置 System::Threading::Thread::CurrentThread->CurrentUICulture 的 Windows(或您)自动查找正确的语言。

像这样的东西

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 );
        }
    }
}
于 2008-10-15T18:51:11.163 回答
1

您必须在代表模板的内存缓冲区中找到要修改的字符串。唯一的方法是遍历整个模板。这并不容易。完成此操作后,如果新字符串比原始字符串长,则在缓冲区中插入字节。如果新字符串较短,则缩小缓冲区。

正如 Chris 所写,修改 WM_INITDIALOG 中的文本并尝试重新表述您可能不会调用 SetWindowText() 的要求会容易得多。

于 2008-10-15T20:43:46.240 回答
0

谢谢大家,我实际上有 24 小时的时间来解决这个问题,然后使用全局 Windows 挂钩过滤 WM_INITDIALOG,这是一种更简单的方法,效果很好,不需要 API 挂钩,2 页代码只需要几行。

感谢所有的答案。

于 2008-10-16T09:52:19.797 回答