none
ダイアログのサイズを出来るだけ指定ピクセルサイズに近づけるには? RRS feed

  • 質問

  • たびたびお世話になります。

    NONCLIENTMETRICS と同じフォントをダイアログに設定するには?

    から続く VC++ 6.0 MFC での CDialog::InitModalIndirect 関数に関する質問です。

    一昨日、Atsushi777 さんの回答を頂いて、NONCLIENTMETRICS と同じフォントをダイアログに設定出来ました。

    ですが、今度はダイアログのサイズ設定が上手く行きません。
    GetDialogBaseUnits 関数のドキュメントから、

    DWORD dwUnits = ::GetDialogBaseUnits();
    CSize    sizUnit(LOWORD(dwUnits), HIWORD(dwUnits));

    DLGTEMPLATE.cx = (320 * 4) / sizUnit.cx;
    DLGTEMPLATE.cy = (320 * 8) / sizUnit.cy;

    としてダイアログを表示してみたのですが、明らかに小さく、spy++ で確認するとクライアントのサイズは 240 × 231 となってしまいました。

    色々調べ、考えた結果、GetDialogBaseUnits 関数は ::GetStockObject(SYSTEM_FONT) で取得したフォントを用いた際にのみ機能するのではないかと思い至りました。ダイアログユニットは、ダイアログに設定されたフォントが基本になっているはずですから。

    そこで、NONCLIENTMETRIC.lfMenuFont を使おうと思ったのですが、lfMenuFont メンバ ( LOGFONT ) のlfWidth が 0 となっていました。LOGFONT 構造体のドキュメントを見ると、lfWidth が 0 の場合、アスペクト比率を用いて、デバイスが利用できるフォント比率から一番近い幅が選ばれる、みたいです。かなりの意訳なので間違っているかも知れませんが。

    ともかく、幅が 0 である以上、ベースユニットサイズとして用いるわけには行かないので、NONCLIENTMETRIC.lfMenuFont を使って、CreateFontIndirect 関数を呼び出して実際にフォントを作成。GetTextMetrics 関数で取得した TEXTMETRIC を使い、次のように変更しました。

    DLGTEMPLATE.cx = (320 * 4) / TEXTMETRIC.tmAveCharWidth;
    DLGTEMPLATE.cy = (320 * 8) / TEXTMETRIC.tmHeight;

    そして実行し、再び spy++ でクライアントサイズを覗いた所、384 × 319 となりました。

    ダイアログベース↔ピクセル間での変換なので、高さの 319 はピクセル指定の 320 と大差のない、許容範囲内だと思いましたが、幅の 384 はピクセル指定の 320 と離れすぎています。

    また派手な勘違いをしているかも知れませんが、何処がどう間違っているのか、そして、マイナス高さのフォントをダイアログに設定出来たように、幅 0 の LOGFONT のメンバ、lfWidth と lfHeight を変換して設定できるのか、ご存知の方はご教授下さい。

    前述しましたが、英語の翻訳能力は自分が思っていた以上に散々なようですので、間違った訳をして、違う方向へと進んでいる気がしないでもありませんが、ともかくお願いします。
    • 編集済み ミッヒー 2009年6月17日 19:11 誤字訂正
    2009年6月17日 19:10

回答

  • この事前にダイアログを作成する場合ですが、作成したダイアログで MapDialogRect を呼び、CRect(0, 0, 1, 1) を渡してダイアログユニット値を取得して・・・以後、どうすればいいのでしょうか?ここで得られる値は 1 ダイアログユニット値をピクセル単位に変換した値ですよね?
    あれ? この値が分かれば「ダイアログのサイズを出来るだけ指定ピクセルサイズに近づけるには?」を実現できると思うのですが、やりたいことはこれじゃないんですかね?

    LPDLGTEMPLATE lpDlgTmpl = &dlgTmpl; // Dialog Template

    double dlgBaseUnitX = 0.0;
    double dlgBaseUnitY = 0.0;
    {
        CDialog dlg;
        dlg.CreateIndirect(lpDlgTmpl, this); // Temporary Dialog
        CRect rect(0, 0, 1000, 1000);
        ::MapDialogRect(dlg.m_hWnd, &rect);
        dlgBaseUnitX = (rect.right * 4.0) / 1000;
        dlgBaseUnitY = (rect.bottom * 8.0) / 1000;
    }

    // 320 x 320 (client size)
    lpDlgTmpl->cx = static_cast<short>((320 * 4) / dlgBaseUnitX);
    lpDlgTmpl->cy = static_cast<short>((320 * 8) / dlgBaseUnitY);

    CDialog dlg;
    dlg.InitModalIndirect(lpDlgTmpl, this);
    dlg.DoModal();

    • 回答としてマーク ミッヒー 2009年6月19日 8:16
    2009年6月19日 4:22

すべての返信

  • 色々調べ、考えた結果、GetDialogBaseUnits 関数は ::GetStockObject(SYSTEM_FONT) で取得したフォントを用いた際にのみ機能するのではないかと思い至りました。
    その通りです。GetDialogBaseUnits には引数がありません。つまり、ダイアログの情報を与えることなく base units を返すという神業を成し遂げている…わけではなく、単にシステムのデフォルトフォントから計算された値を返すだけです。

    GetDialogBaseUnits のヘルプ を見ると、次のように「MapDialogRect を使うと簡単だよ」と書いてあります。
    # 日本語版のヘルプで抜けている関数名は英語版 から持ってきました。

    システムフォントを使わないダイアログボックスでは、ベース単位は、そのダイアログで使われているフォントの各文字の幅と高さの各平均値を表すピクセル数に相当します。GetTextMetrics と GetTextExtentPoint32 の各関数を使って、特定のフォントに関するこれらの値を計算できます。ただし、自分で計算した結果と、システムが計算した結果が異なる可能性がありますが、MapDialogRect 関数を使うと、そのような違いに起因するエラーを回避できます。
    やりたいこととは逆のことをしてくれる関数だと思いますが、逆算するだけなのでそれほど大変ではないはずです。
    また、MapDialogRect は引数にダイアログのハンドルが必要ですが、無いなら無いで、適当な大きさのダイアログを作った後でリサイズすれば良いでしょう。
    2009年6月18日 2:04
  • zakio さん、回答ありがとうございます。

    とりあえず、GetDialogBaseUnits 関数に関する憶測はあっていたという事ですね。わざわざ日本語訳までして下さり、ありがとうございます。

    ただ、MapDialogRect 関数を使う場合、CDialog::InitModalIndirect 呼出し後、OnCreate か OnInitDialog ハンドラでダイアログ本体とダイアログ内の全アイテム (DLGTEMPLATE 内の DLGITEMTEMPLATE) に対して、SetWindowPos 等の呼び出しを行う必要がある、という事ですよね・・・。
    やりたいこととは逆のことをしてくれる関数だと思いますが、逆算するだけなのでそれほど大変ではないはずです。
    逆算する、とは具体的にどうすればいいのでしょうか?

    DLGTEMPLATE 作成時点で既に GetTextMetrics 関数で取得したフォントサイズを使っていますが、この計算で幅がずれてるわけですから・・・。
    2009年6月18日 7:36
  • わざわざ日本語訳までして下さり、ありがとうございます。
    あ、いや、リンク先を見ていただければ分かりますが、MSDN ライブラリの日本語版からの引用です。

    ただ、MapDialogRect 関数を使う場合、CDialog::InitModalIndirect 呼出し後、OnCreate か OnInitDialog ハンドラでダイアログ本体とダイアログ内の全アイテム (DLGTEMPLATE 内の DLGITEMTEMPLATE) に対して、SetWindowPos 等の呼び出しを行う必要がある、という事ですよね・・・。
    そういう事になります?
    試していないので間違ってたら申し訳ないのですが、今回の問題は「ダイアログ内の全アイテムはダイアログベース単位でレイアウトされていて、それは思ったとおりの位置にくるんだけれども、ダイアログそのもののサイズがうまく調節できないのでなんとかしたい」という風にとらえていました。
    それなら、一回適当なサイズで作ってからリサイズすれば良いのでは?と思ったのですが、なんだか話がズレているかもしれませんね。
    逆算する、とは具体的にどうすればいいのでしょうか?
    MapDialogRect はダイアログベース単位からピクセル単位への変換をしてくれる関数です。
    今回やりたいことは、ピクセル単位からダイアログベース単位への変換(あるいはダイアログベース単位の取得)だと思ったので「逆算する」と書きました。
    たとえば、left の値は left_pixel = (left_dlgunit * baseunitX) / 4 という風に変換されるので、baseunitX が知りたいなら、baseunitX = (left_pixel * 4) / left_dlgunit といった具合に逆算できます。
    ただ、InitModalIndirect 後にレイアウトをするなら、逆算なんかせずに MapDialogRect をそのまま使えば良いだけですね。

    あと、ダイアログベース単位を取得するためだけにダミーの CDialog を作るという手も考えられます。
    これなら事前にダイアログベース単位を取得できるので、今回の流れでいくとこっちの方がしっくりくるかも知れません。

    2009年6月18日 9:12
  • zaiko さん、また回答下さり、ありがとうございます。
    試していないので間違ってたら申し訳ないのですが、今回の問題は「ダイアログ内の全アイテムはダイアログベース単位でレイアウトされていて、それは思ったとおりの位置にくるんだけれども、ダイアログそのもののサイズがうまく調節できないのでなんとかしたい」という風にとらえていました。
    説明不足でした。ダイアログ内のアイテムもダイアログベース単位で配置しており、ダイアログユニット単位の高さはあっているが、幅が大きい値になる、という現象です。

    上手く説明できませんが、ダイアログには 1 つのアイコンと、2 つのスタティックテキスト、そして 3 つのボタンが配置してあり、アイコンのサイズはピクセル単位で 32 × 32、スタティックテキストはピクセル単位でのダイアログ幅 320 から 10 引いた 310 ピクセル単位の幅で、2 行のテキストを表示するので、高さはダイアログユニット高さ × 2 となっています。ボタンは内部に 1 行のテキストを持つ為、高さはダイアログユニット高さ × 2 としています。

    高さはあっているが、幅が異なる、という現象は、ダイアログに高さがマイナス、幅が 0 の NONCLIENTMETRIC.lfMenuFont を指定しているが、ダイアログユニットは NONCLIENTMETRIC.lfMenuFont より作成したフォントの TEXTMETRIC を使用している事から別のフォントサイズとして計算される為に起こるのではないかと思っています。つまり、ダイアログに指定したフォントと、ダイアログユニット計算に用いるフォントは別のものとシステムが認識し、たまたま高さがほぼ一致している為に高さに関してのずれが起こっていないのではないかと。

    ダイアログに設定したフォントを DLGTEMPLATE のピクセル値からダイアログユニット値への変換計算に使えればいいのですが、前述の通り、高さはマイナス、幅が 0 の LOGFONT の為、上手く行かないという状態です。
    あと、ダイアログベース単位を取得するためだけにダミーの CDialog を作るという手も考えられます。
    これなら事前にダイアログベース単位を取得できるので、今回の流れでいくとこっちの方がしっくりくるかも知れません。
    この事前にダイアログを作成する場合ですが、作成したダイアログで MapDialogRect を呼び、CRect(0, 0, 1, 1) を渡してダイアログユニット値を取得して・・・以後、どうすればいいのでしょうか?ここで得られる値は 1 ダイアログユニット値をピクセル単位に変換した値ですよね?
    2009年6月18日 10:14
  • この事前にダイアログを作成する場合ですが、作成したダイアログで MapDialogRect を呼び、CRect(0, 0, 1, 1) を渡してダイアログユニット値を取得して・・・以後、どうすればいいのでしょうか?ここで得られる値は 1 ダイアログユニット値をピクセル単位に変換した値ですよね?
    あれ? この値が分かれば「ダイアログのサイズを出来るだけ指定ピクセルサイズに近づけるには?」を実現できると思うのですが、やりたいことはこれじゃないんですかね?

    LPDLGTEMPLATE lpDlgTmpl = &dlgTmpl; // Dialog Template

    double dlgBaseUnitX = 0.0;
    double dlgBaseUnitY = 0.0;
    {
        CDialog dlg;
        dlg.CreateIndirect(lpDlgTmpl, this); // Temporary Dialog
        CRect rect(0, 0, 1000, 1000);
        ::MapDialogRect(dlg.m_hWnd, &rect);
        dlgBaseUnitX = (rect.right * 4.0) / 1000;
        dlgBaseUnitY = (rect.bottom * 8.0) / 1000;
    }

    // 320 x 320 (client size)
    lpDlgTmpl->cx = static_cast<short>((320 * 4) / dlgBaseUnitX);
    lpDlgTmpl->cy = static_cast<short>((320 * 8) / dlgBaseUnitY);

    CDialog dlg;
    dlg.InitModalIndirect(lpDlgTmpl, this);
    dlg.DoModal();

    • 回答としてマーク ミッヒー 2009年6月19日 8:16
    2009年6月19日 4:22
  • zakio さん、回答ありがとうございます。

    凄まじい勘違い、というか、何かパニくっていたようです。ダイアログベースユニットって何の値だっけ・・・ピクセル値じゃないよな・・・とドツボにはまっていました。

    zakio さんのおかげで、クライアントサイズ 320 × 319 のダイアログが作成できました。
        dlgBaseUnitX = (rect.right * 4.0) / 1000;
        dlgBaseUnitY = (rect.bottom * 8.0) / 1000;
    GetDialogBaseUnits 関数にピクセル値からダイアログベース値への変換式があり、zakio さんのコードそのものなんですが、ドツボにはまっていて、思いつく事が出来ませんでした。

    どうも、一度パニくると、わけの判らない状態になるようで、zakio さんには非常にご迷惑をお掛けしました。

    zakio さんのおかげでようやくドツボから抜け出せました。
    長い間お付き合い頂き、ありがとうございました。
    • 編集済み ミッヒー 2009年6月19日 8:20 誤字訂正
    2009年6月19日 8:16