none
フォームを並べて動かしています。Windows10では幅が変わってフォームの間隔が空いてしまいます。 RRS feed

  • 質問

  • VS2013で、C#のフォームアプリケーションを作っています。

    Windows7や8.1では、2つ目のフォームの左は1つ目のものの Left + Widthとして隙間なく並べています。
    Windows10では隙間ができてしまいますので、Left + Width - 14 ということで、隙間を無くすことができます。

    どのOSでも同じように隙間なく表示したいのですが、C++ の GetVersionEx() で得られるOSの情報、
    dwPlatformId、dwMajorVersion、dwMinorVersion、dwBuildNumber はWindows8.1 と Windows10 はまったく同じものです。
    (Windows8.1 と Windows10 は低レベルでは全く同じものということのようですね)

    Window7以降のOSで、2つのフォームを隙間なく並べるための何らかの情報を得る方法をお願いします。
    2015年8月28日 1:12

回答

  • 解決しました。

    API DwmGetWindowAttribute を DWMWA_EXTENDED_FRAME_BOUNDS と一緒に使ってウィンドウサイズを取得すると正しくウィンドウサイズを取得できました。

    Win32 で GetClientRect、DwmGetWindowAttribute、の2つのAPIで比較した結果が下図です。

    調べてみると、Vista 以降から GetWindowRect では正しくウィンドウサイズを取得できない、という問題が続いており、Widows10 についても世界中でいろいろとレポートされていました。
    いずれも、DwmGetWindowAttribute を使うことで解決、という結果になっていましたので問題ないと思います。
    もちろん私自身もC#+Formで実験済みです。

    注意事項:
    ・私が実験した限り フォームの Load イベント中では DwmGetWindowAttribute も誤ったサイズを返しました。インターバルタイマー中で取得すると期待する値になったので、フォーム表示から若干時間をおけば良いように見えます。
    ・Vista/7 ではエアロをON/OFFできます。エアロのON/OFFをチェックして、エアロがONのときのみ DwmGetWindowAttribute を使用する必要があります。OFFの時は従来のウィンドウサイズ取得を使用します。これをしないとまた誤差を生じます。
    ・DwmGetWindowAttribute は Vista 以降のみサポートのAPIです。

    参考URLの一例です。
    http://stackoverflow.com/questions/31920709/retrieving-size-of-windows-10-shadowing-border
    http://m.srad.jp/~shuichi/journal/594973

    今回はずいぶん勉強になりました。ありがとうございました。





    • 回答としてマーク mnicksashimisan 2015年9月1日 4:52
    • 編集済み HIDE0707 2018年10月7日 10:53 URLにリンクを追加
    2015年8月29日 0:13

すべての返信

  • どのOSでも同じように隙間なく表示したいのですが、C++ の GetVersionEx() で得られるOSの情報、
    dwPlatformId、dwMajorVersion、dwMinorVersion、dwBuildNumber はWindows8.1 と Windows10 はまったく同じものです。
    (Windows8.1 と Windows10 は低レベルでは全く同じものということのようですね)

    1.まずは GetVersionEx() について。

    Microsoft の GetVersionEx のページを参照ください。
    https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms724451(v=vs.85).aspx

    「Applications not manifested for Windows 8.1 or Windows 10 will return the Windows 8 OS version value (6.2).  」
    ⇒ 「Windows8.1 または Windows10 用のマニュフェストが記載されていないアプリケーションは、Windows8 の OSバージョンを返す」ということですね。

    skumakurodes さんが確認したバージョン番号はおそらく 6.2 だったはずです。

    というわけで、アプリケーションのマニュフェストにおまじないのコードを挿入する必要があります。

    以下のような記載です。
    ======================
    <assembly>
      <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
          <!-- Windows 7 -->
          <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
          <!-- Windows 8 -->
          <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
          <!-- Windows 8.1 -->
          <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
          <!-- Windows 10 -->
          <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
        </application>
      </compatibility>
    </assembly>
    ======================

    これで Windows8.1 の場合は 6.3を、Window10 の場合は 10.0 をそれぞれ返すようになります。

    2.ずれてしまう、という件について
    再現実験してみました。結果から先に書くと skumakurodes さん記載の問題を確認できませんでした。
    内容や前提条件が違いますかね?

    Form を2つ作成します。
    ・左のフォームは起動時の Location を(100,100)固定で作成。
    ・同、System.Diagnotics.Trace.WriteLine( ・・・ ); で Left と Width の値を確認。
    ⇒ Left = 100, Width = 300 を確認。
    ・右側のフォームを Location (100+300, 100) で起動
    この結果が下図の通りです。ぴったり、隙間なく、表示できていますよね?

    環境:
    ・Windows 10 home
    ・アプリ作成: VS2015 Pro.


    • 編集済み HIDE0707 2015年8月28日 6:49
    2015年8月28日 6:11
  • Windows7 と 8.1 では元々エアロの時に大きくなった部分を含め幅を定義しています。

    Windows10での実行は、左右7画素分ぐらいのその部分の表示が実際になされていません。
    Widthはその部分も含めてのもので、その分隙間になっているようです。

    Snipping  Tools でのウインドウの領域の切り取りでは、Widthの幅ではなく、
    表示されている部分だけが期待通りに得ることができています。

    ちなみに、VS2013のデザイナーでは、Windows7などの実行と同じような表示になっています。

    とりあえずは、最初の質問での解決を図りたいとは思いますが、
    Windows10というか、Visual Studio での本質的な解決が望まれるのではないでしょうか。


    2015年8月28日 6:23
  • Windows7 と 8.1 では元々エアロの時に大きくなった部分を含め幅を定義しています。

    Windows10での実行は、左右7画素分ぐらいのその部分の表示が実際になされていません。
    Widthはその部分も含めてのもので、その分隙間になっているようです。

    質問の記載に詳細な情報やコード記載が無いので推測ですが、ウィンドウサイズ と クライアントサイズ、の違いについてはご理解されていますか。

    最初の質問に記載の -14 という補正の値がありますが、
    私の手元のテストプログラムの場合、ウィンドウサイズとクライアントサイズの Width で値が16異なっており酷似しており気になります。

    ウィンドウサイズの情報を扱うべきところをクライアントサイズの情報を使っている、または逆、などが考えられますがいかがでしょうか。


    • 編集済み HIDE0707 2015年8月28日 7:24
    2015年8月28日 7:23
  • 最初の画像はVS2013のC#のデザイナーで見えているものです。次の画像が実行時のものです。
    実行のものは周りがなくなっています。

    右にある2番目のフォームの左の位置は
    左の1番目のフォームの左の位置+1番目のフォームの幅としていますが、
    消えてなくなった部分も含めて幅となっているようです。

    3番目のものはメモ帖ですが、同じように周りがなくなっています。

    私のパソコンの状態が違うのでしょうか?
    ちなみに、Windows7 32bitからアップグレードしたものです。

    GetVersionEx() に関して、

    dwPlatformId = 2
    dwMajorVersion = 6
    dwMinorVersion = 2
    dwBuildNumber = 9200
    です。
    <assembly> から </assembly>まで、マニュフェストツール/コマンドライン/追加のオプション
    にコピーしましたが、何の変化もありませんでした。


        
    2015年8月28日 8:07
  • 1.GetVersionEx について

    マニュフェストの記載方法について、手順を含む参考URLは下記をどうぞ。

    http://d.hatena.ne.jp/chi-bd/20130915/1379207754

    他、新しいAPIで実現する、という方法もあります。
    http://d.hatena.ne.jp/chi-bd/20130916/1379304240

    私は業務ではWMIによるバージョン番号取得を使用しています。

    2.Windows10 について

    少なくとも skumakurodes さんの Windows10 環境と私のWindows10環境には差異がありそうです。
    詳細わかりませんが、ウィンドウマネージャ―(DWM)が正常に動作しているか疑問です。
    申し訳ありませんがこちらの件について私から追加コメントできることはなさそうです。

    当方の環境と同じ状況になれば、少なくともウィンドウ位置を14補正するという処理は不要になると思います。
    そういう意味で前述のWindowsバージョン毎の処理分岐を実装するべきか、Windows10 のインストールをやり直すべきか、をご検討される方が良いのではと考えます。

    Windows10 のインストールですが、アップグレードの場合もクリーンインストールする手段が提供されているようです。
    下記URLの ”アップグレードした後で、Windows10を再インストールすることはできますか” を参照ください。
    http://www.microsoft.com/ja-jp/windows/windows-10-faq


    • 編集済み HIDE0707 2015年8月28日 10:04
    2015年8月28日 9:24
  • 私の環境でも質問者さんと同じ現象が発生しています。そもそもWindows 10においてウインドウ枠の幅は1pixel程度で、HIDE0707さんのスクリーンショットのような太い枠にはなりません。
    2015年8月28日 9:30
  • 私の環境でも質問者さんと同じ現象が発生しています。そもそもWindows 10においてウインドウ枠の幅は1pixel程度で、HIDE0707さんのスクリーンショットのような太い枠にはなりません。

    佐祐理さん、コメントありがとうございます。

    特に不便や違和感なく使っていたので気づきませんでした。動作がおかしいのは私の方なのですね。
    Upgradeしくじりましたかね?
    困ってはいないので当面このまま使いたいと思いますが、引き続き原因を調べるなどはしたいと思います。

    追伸:
    「個人設定」->「テーマ」->「テーマの設定」からWindows10のテーマを選んだところ、
    さしあたりウィンドウ枠の太さについてはお二人と同じ状況になりました。
    前述の私のテストについても下図のようになったので、完全再現したと思います。
    ということはさしあたりの問題は、Widows10環境でウィンドウサイズを正しく得られない、ということになりますね。もう少し調べてみます。

    • 編集済み HIDE0707 2015年8月28日 11:30
    2015年8月28日 10:21
  • 解決しました。

    API DwmGetWindowAttribute を DWMWA_EXTENDED_FRAME_BOUNDS と一緒に使ってウィンドウサイズを取得すると正しくウィンドウサイズを取得できました。

    Win32 で GetClientRect、DwmGetWindowAttribute、の2つのAPIで比較した結果が下図です。

    調べてみると、Vista 以降から GetWindowRect では正しくウィンドウサイズを取得できない、という問題が続いており、Widows10 についても世界中でいろいろとレポートされていました。
    いずれも、DwmGetWindowAttribute を使うことで解決、という結果になっていましたので問題ないと思います。
    もちろん私自身もC#+Formで実験済みです。

    注意事項:
    ・私が実験した限り フォームの Load イベント中では DwmGetWindowAttribute も誤ったサイズを返しました。インターバルタイマー中で取得すると期待する値になったので、フォーム表示から若干時間をおけば良いように見えます。
    ・Vista/7 ではエアロをON/OFFできます。エアロのON/OFFをチェックして、エアロがONのときのみ DwmGetWindowAttribute を使用する必要があります。OFFの時は従来のウィンドウサイズ取得を使用します。これをしないとまた誤差を生じます。
    ・DwmGetWindowAttribute は Vista 以降のみサポートのAPIです。

    参考URLの一例です。
    http://stackoverflow.com/questions/31920709/retrieving-size-of-windows-10-shadowing-border
    http://m.srad.jp/~shuichi/journal/594973

    今回はずいぶん勉強になりました。ありがとうございました。





    • 回答としてマーク mnicksashimisan 2015年9月1日 4:52
    • 編集済み HIDE0707 2018年10月7日 10:53 URLにリンクを追加
    2015年8月29日 0:13
  • Windows10 の表示はエアロかそうでないのか?

    Windows10の表示は透明に透き通る部分は無く、また、境界の幅も1でエアロではないようです。
    DWM関数の DwmIsCompositionEnabled( &Enabled ) はWindows10をエアロと判断しています。
    そのせいで、見えていない周辺部分もフォームの範囲としているようです。

    バグなのかVS2013が対応できていないのでしょうか?


    > インターバルタイマー中で取得すると期待する値になったので、フォーム表示から
    > 若干時間をおけば良いように見えます。
    インターバルタイマーとは一定時間ごとに繰り返し行うという意味に思え、理解ができません。
    フォーム表示が完了した時点とはどこなのでしょう?
    よく理解できていませんので、具体的に説明して頂けたら助かります。

    DwmGetWindowAttributeなどのWin32を C#で使う場合、
    自力でC#のラッパーを作っていますが、一々作るのは面倒です。
    既にラッパーは用意されていると思うのですが、どうでしょうか?
    有りましたら、使い方をお願いします。

    2015年8月31日 8:38
  • Windows10 の表示はエアロかそうでないのか?

    Windows10の表示は透明に透き通る部分は無く、また、境界の幅も1でエアロではないようです。
    DWM関数の DwmIsCompositionEnabled( &Enabled ) はWindows10をエアロと判断しています。
    そのせいで、見えていない周辺部分もフォームの範囲としているようです。

    バグなのかVS2013が対応できていないのでしょうか?


    > インターバルタイマー中で取得すると期待する値になったので、フォーム表示から
    > 若干時間をおけば良いように見えます。
    インターバルタイマーとは一定時間ごとに繰り返し行うという意味に思え、理解ができません。
    フォーム表示が完了した時点とはどこなのでしょう?
    よく理解できていませんので、具体的に説明して頂けたら助かります。

    DwmGetWindowAttributeなどのWin32を C#で使う場合、
    自力でC#のラッパーを作っていますが、一々作るのは面倒です。
    既にラッパーは用意されていると思うのですが、どうでしょうか?
    有りましたら、使い方をお願いします。

    タイトル記載の質問内容から外れてきており、他の方へも有効な参考情報になりにくくなっているように感じます。
    またここに記載の内容だけで3つの質問があります。
    他の方からのコメントも付きやすくなると思いますので、新しい質問として投稿されることを勧めさせていただきます。

    とはいえ、せっかくなので簡単にコメントさせていただきます。

    1.Windows10 の表示はエアロかそうでないのか?
    API DwmIsCompositionEnabled は DWM 環境であるかを判定するAPIです。
    Windows8、8.1、10 はDMW固定の環境です。
    Windows Vista, 7 はテーマにエアロを選択するなど特定の条件下で DWM が使用されます。
    そして エアロ=DWM ではありません。
    つまり、skumakurodesさん記載の動作で正しいです。
    詳細は下記Wikiなどを参照されてはいかがでしょうか。

    https://ja.wikipedia.org/wiki/Desktop_Window_Manager

    2.フォーム表示が完了した時点とはどこなのでしょう?
    通常は Load イベントを受けた時点でフォーム表示を完了したと判断してよいと考えます。
    しかしながら、先に記載した通り Load イベント内での確認では正確な情報を取れませんでした。
    私から提供できる情報は以上の事実までです。
    何かしらの工夫が必要と思われます。

    3. 既にラッパーは用意されていると思うのですが、どうでしょうか?
    Microsoft から提供されている情報は無いと思います。
    .NET プログラマーの多くの人が PInvoke.net の情報やツールを利用しているであろう、と考えます。
    私は VS 拡張機能として PInvoke.net のツールを主に使っています。

    http://pinvoke.net/





    • 編集済み HIDE0707 2018年10月7日 10:55 URLにリンクを追加
    2015年8月31日 12:31
  • Load イベントのどこでやっても情報が取れませんでしたが、
    DwmGetWindowAttributeをActivated イベントでやり取ることができました。
    色々教えて頂きどうもありがとうございました。


    2015年9月1日 4:51