Как программно манипулировать DLGTEMPLATE?
-
03-07-2019 - |
Вопрос
Что?
У меня есть DLGTEMPLATE, загруженный из DLL ресурса. Как я могу программно изменить строки, назначенные элементам управления во время выполнения?
Я хочу иметь возможность сделать это до создания диалогового окна, чтобы я мог сказать, что отображаемые строки получены из DLL ресурса, а не из вызовов SetWindowText при инициализации диалогового окна.
Google нашел примеры создания DLGTEMPLATE в коде или изменения простых элементов стиля, но ничего не нашел по редактированию строк в памяти.
Как?
Я делаю это, подключая API создания диалога/листа свойств.Это дает мне доступ к DLGTEMPLATE до того, как будет создан реальный диалог и до того, как он получит HWND.
Почему?
Я хочу иметь возможность выполнять локализацию во время выполнения и тестирование локализации.У меня уже реализовано это для загрузки строки (включая оболочку MFC 7.0), меню и таблиц сочетаний клавиш, но я изо всех сил пытаюсь справиться с созданием диалога/листа свойств.
Примеры кода были бы идеальным ответом, в идеале — классом, охватывающим DLGTEMPLATE. Если я разработаю собственное решение, я его опубликую.
Решение
Вы не можете редактировать строки в памяти.Структура DLGTEMPLATE представляет собой прямое сопоставление файлов соответствующих байтов библиотеки ресурсов.Это только для чтения.
Вам нужно будет обработать всю структуру DLGTEMPLATE и записать новую со строками измененной длины.
Честно говоря, будет проще просто подключить WM_INITDIALOG и изменить строки, взаимодействуя с элементами управления, чем создавать модуль записи DLGTEMPLATE.Потому что таких вокруг не так уж и много.Если у вас нет дополнительных требований к фактическому сохранению измененных ресурсов диалога на диске в виде необработанных файлов .res (или попытки изменить .dll на месте), я действительно рекомендую вам избегать этого подхода.
Вы говорите, что уже делаете это для таблиц ускорений и строк меню - если вы можете гарантировать, что исправленные строки будут короче, просто создайте двоичную копию структуры DLGTEMPLATE и напишите нетривиальный код сканирования, необходимый для поиска каждую строку, чтобы вы могли исправить копию на месте.
Другие советы
Где-то есть файл (думаю, созданный Microsoft, но я не совсем уверен) под названием RESFMT.ZIP, который объясняет это с помощью некоторых примеров кода.Рэймонд Чен также дает несколько отличных объяснений по этому поводу в своем блоге.Обратите внимание, что формат элементов управления DIALOGEX и DIALOG различен.
Как отмечалось в некоторых других ответах, вам нужно будет создать структуру заново с самого начала.Это не так уж и плохо, поскольку у вас уже есть основная информация.Добавление элементов управления — вот что становится сложнее.
По сути, выделите большой блок памяти для WORD *lpIn.Затем добавьте структуру поверх этого.добавление основной информации для ДИАЛОГА (см. DLGTEMPLATE) и элементов управления довольно очевидно, поскольку информация есть в MSDN.
Две самые большие проблемы, с которыми вы столкнетесь:Убедитесь, что различные части начинаются на границе выравнивания, и интерпретируйте значения элементов управления DIALOG, особенно когда нужно добавить просто строку, строку или порядковый номер.Каждый элемент управления должен начинаться с ровной границы.
Для первого (думаю, где-то позаимствован RESFMT.ZIP):
WORD *AlignDwordPtr (WORD *lpIn) { ULONG ul; ul = (ULONG) lpIn; ul +=3; ul >>=2; ulЧто я сделал, так это создал ряд функций, подобных этой, которые позволили мне собирать ДИАЛОГЫ в памяти.(Мне нужно было иметь некоторый общий код, который не нуждался в связанном RC-файле для некоторых очень простых сообщений).
Вот пример...
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 ); }Заголовочный файл полного модуля включал следующие функции:
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);
Это позволило мне легко собирать целые диалоги из кода.
См. функцию 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 );
}
}
}
Вам нужно будет найти строку, которую вы хотите изменить, в буфере памяти, представляющем шаблон.Единственный способ сделать это — пройти по всему шаблону.Это непросто.Как только вы это сделаете, либо вставьте байты в буфер, если ваша новая строка длиннее исходной.Или сократите буфер, если новая строка короче.
Как писал Крис, было бы гораздо проще изменить текст в WM_INITDIALOG и попытаться перефразировать ваше требование, в котором говорится, что вы не можете вызывать SetWindowText().
Спасибо всем, на самом деле у меня было 24 часа на решение проблемы, а затем я применил глобальную фильтрацию перехватчиков Windows WM_INITDIALOG, которая была гораздо более простым методом, сработала отлично, не требовалось перехвата API, 2 страницы кода сократились до нескольких строк.
Спасибо за ответы на все вопросы.