代码之家  ›  专栏  ›  技术社区  ›  titanae

如何以编程方式操作dlgtemplate?

  •  3
  • titanae  · 技术社区  · 17 年前

    什么?

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

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

    Google已经找到了用代码创建dlgtemplate或使用简单样式位的例子,但在编辑内存中的字符串时却一无所获。

    怎么用?

    我通过挂接对话框/属性表创建API来完成此操作。这使我可以在创建实际对话框之前和创建HWND之前访问dlgtemplate。

    为什么?

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

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

    5 回复  |  直到 17 年前
        1
  •  5
  •   Chris Becke    17 年前

    您不能编辑内存中的字符串。dlgtemplate结构是资源dll的相关字节的直接文件映射。这是只读的。

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

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

    你说你已经在为加速器表和菜单字符串做这个了-如果你能保证修补后的字符串会变短,那么就做一个dlgtemplate结构的二进制副本,并编写查找每个字符串所需的非普通扫描代码,这样你就可以在适当的地方修补副本。

        2
  •  4
  •   David L Morris    17 年前

    有一个名为resfmt.zip的文件(我认为它起源于Microsoft,但我不完全确定),它用一些代码示例解释了这一点。陈瑞蒙在他的博客中也做了一些很好的解释。请注意,DialogEx和Dialog控件的格式不同。

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

    基本上,在word*lpin中分配一个较大的内存块。然后把结构加起来。添加对话框的基本信息(请参阅dlgtemplate)和控件非常明显,因为这些信息在msdn中。

    您将遇到的两个最大问题是:确保各个部分从路线边界开始,并解释对话框控件的值,尤其是在只添加字符串、字符串或序号时。每个控件都需要从一个偶数边界开始。

    对于第一个(从我认为是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);

    这使得我可以很容易地从代码中组装整个对话框。

        3
  •  1
  •   Aidan Ryan    17 年前

    见api函数: EnumChildWindows (HWND、Wndunmproc、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 );
            }
        }
    }
    
        4
  •  1
  •   Serge Wautier    17 年前

    您必须在表示模板的mem缓冲区中找到要修改的字符串。唯一的方法是遍历整个模板。这不容易。 完成后,如果新字符串比原始字符串长,可以在缓冲区中插入字节。或者,如果新字符串较短,则收缩缓冲区。

    正如Chris所写,在wm_InitDialog中修改文本并尝试重新表述您的要求,即您可能不会调用setWindowText(),这会更容易。

        5
  •  0
  •   titanae    17 年前

    多谢大家,我确实在这个问题上休息了24个小时,然后使用了一个全局Windows钩子过滤wm-initdialog,这是一个非常简单的方法,解决得很好,不需要API钩住,只需要2页代码到几行。

    谢谢你的回答。

    推荐文章