DLGTEMPLATEをプログラムで操作する方法は?
-
03-07-2019 - |
質問
何?
リソースDLLからDLGTEMPLATEをロードしていますが、実行時にコントロールに割り当てられた文字列をプログラムで変更するにはどうすればよいですか
ダイアログが作成される前にこれを実行できるようにしたいので、表示されている文字列はダイアログが初期化されたときのSetWindowTextの呼び出しからではなく、リソースDLLから来たことがわかります。
Googleは、コード内でDLGTEMPLATEを作成したり、メモリ内の文字列を編集する際にシンプルなスタイルのビットをいじったりする例を見つけました。
方法?
ダイアログ/プロパティシート作成APIをフックしてこれを行っています。これにより、実際のダイアログが作成され、HWNDが設定される前にDLGTEMPLATEにアクセスできます。
なぜ?
ランタイムのローカライズとローカリゼーションテストを行えるようにしたい。文字列(MFC 7.0ラッパーを含む)、メニュー、およびアクセラレータテーブルの読み込み用にこれを既に実装していますが、ダイアログ/プロパティシートの作成の処理に苦労しています。
コード例は完璧な答えであり、理想的にはDLGTEMPLATEをラップするクラスです。自分で解決策を考え出せば投稿します。
解決
メモリ内の文字列は編集できません。 DLGTEMPLATE構造は、リソースdllの関連バイトの直接ファイルマッピングです。それは読み取り専用です。
DLGTEMPLATE構造体全体を処理し、変更された長さの文字列で新しい構造体を書き出す必要があります。
率直に言って、DLGTEMPLATEライターを作成するよりも、WM_INITDIALOGをフックし、コントロールと対話することで文字列を変更する方が簡単です。周りにたくさんの人がいないからです。変更されたダイアログリソースを実際に生の.resファイルとしてディスクに保存する(または.dllをインプレースで変更する)追加要件がない限り、このアプローチを避けることをお勧めします。
すでにアクセラレータテーブルとメニュー文字列に対してこれを行っていると言います-パッチを適用した文字列が短くなることを保証できる場合は、DLGTEMPLATE構造体のバイナリコピーを作成し、非自明なスキャンコードを記述しますコピーを所定の位置にパッチできるように、各文字列を見つける必要があります。
他のヒント
RESFMT.ZIPというファイルがあり(これはMicrosoftで作成されたと思いますが、完全にはわかりません)、これをいくつかのコード例で説明しています。レイモンド・チェンも彼のブログでこれについて素晴らしい説明をしています。 DIALOGEXとDIALOGコントロールの形式が異なることに注意してください。
他のいくつかの回答で述べたように、最初からもう一度構造を作成する必要があります。すでに基本的な情報を持っているので、これはすべて悪いことではありません。コントロールを追加するのは難しいところです。
基本的に、大量のメモリブロックをWORD * lpInに割り当てます。次に、その上に構造を追加します。 DIALOG(DLGTEMPLATEを参照)とコントロールの基本情報を追加することは、MSDNに情報があるので非常に明白です。
遭遇する2つの最大の問題は、さまざまなパーツがアライメント境界で始まることを確認し、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 ); }完全なモジュールのヘッダーファイルには、次の機能が含まれていました。
WORD * AddControlToDialogTemplateEx(MTDialogTemplateType * dlgtmp、 char *タイトル、 WORD ID、 char * WinClass、 DWORDスタイル、 短いx、 短いy、 短いcx、 短いcy、 DWORD ExStyle、 int HelpID); int DestroyDlgTemplateEx(MTDialogTemplateType * dlgtmp); MTDialogTemplateType * CreateDlgTemplateEx(char * Name、//参照用に名前を使用しているため、NULLにすることができます 短いx、 短いy 短いcx、 短いcy、 DWORD ExtendedStyle、 DWORDスタイル、 char *メニュー、 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とマネージ文字列テーブルのもう1つの利点は、System :: Threading :: Thread :: CurrentThread-:
このようなもの
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 );
}
}
}
テンプレートを表すmemバッファーで、変更する文字列を見つける必要があります。それを行う唯一の方法は、テンプレート全体をトラバースすることです。簡単ではありません。 それが完了したら、新しい文字列が元の文字列よりも長い場合は、バッファにバイトを挿入します。または、新しい文字列が短い場合はバッファーを縮小します。
Chrisが書いたように、WM_INITDIALOGのテキストを変更し、SetWindowText()を呼び出さないようにという要件を書き直そうとする方がはるかに簡単です。
すべてに感謝します。実際に問題に24時間かかってから、WM_INITDIALOGをフィルタリングするグローバルウィンドウフックを使用しました。数行。
すべての回答に感謝します。