none
Loadイベントハンドラ内でPropertyGrid付きのフォームをShowDialogで表示させた後MessageBox.Showを呼ぶと、正常に表示されない場合がある RRS feed

  • 質問

  • はじめまして。kome211と申します。
    Visual C# 2008 Express Edition (.NET Framework 3.5 SP1)を使用しまして、
    Windows Formアプリケーションを作成中、不可解な現象が発生しました。
    私が探した限り、同様の情報がありませんでしたので投稿させていただきます。
    .NET Framework側の問題?と想像しておりますが、もし当方の使い方が悪いなどでしたら、
    ぜひご指摘いただけると幸いです。

    まず、Visual C# Express Edition 2008にて、新規Windows Formアプリケーションを作成します。
    そのメインウインドウ(Form1)のLoadイベントハンドラを、以下のように記述します。

        private void Form1_Load(object sender, EventArgs e)
        {
          Form2 form2 = new Form2();
    
          // PropertyGrid付きのFormをモーダル表示する
          form2.ShowDialog();
    
          // メッセージボックスを表示する
          DialogResult result = MessageBox.Show("Hoge");
        }
    

    Form2は、PropertyGridだけを配置したフォームです。
    以下のような、単純なクラスになります。

      public partial class Form2 : Form
      {
        class PropertyGridData
        {
          public bool BoolTest1 { get; set; }
          public bool BoolTest2 { get; set; }
    
          public PropertyGridData()
          {
            BoolTest1 = false;
            BoolTest2 = false;
          }
        }
    
        public Form2()
        {
          InitializeComponent();
    
          this.propertyGrid1.SelectedObject = new PropertyGridData();
        }
      }
    

    これを実行しますと、Form1のLoadイベントハンドラ内で
    Form2が表示され、その後メッセージボックスが表示され、最後にForm1が表示されるという流れになることを
    期待しているのですが、
    Form2が表示される際、プロパティグリッドをいじった時だけ、なぜかForm2を閉じた後、
    メッセージボックスが表示されず、Form1がいきなり表示されてしまいます
    Form2のプロパティグリッドを触らないままForm2を閉じた場合は、正常にメッセージボックスが表示されます。

    デバッガで追いかけてみると、Form2のプロパティグリッドをいじって、メッセージボックスが表示されない時には、
    MessageBox.Show(String)関数はすぐに終了し、DialogResult.Noを返していました。

    これは、.NET Framework側の問題でしょうか?それとも、当方の使い方の問題なのでしょうか?
    ご意見いただけると幸いです。

    2010年12月21日 4:30

回答

  • WinXP SP3 (.NET 3.5 SP1 - 2.0.50727.3615) では再現しませんでした。

    標準の MessageBox クラスは、OS からエラーが返った場合に DialogResult.No を返すという、ちょっと困った挙動があるので、問題の場合に Marshal.GetLastWin32Error() か throw new Win32Exception() してみると、問題解決の糸口が見つかるかもしれません。

    予想では、特定のパターンで PropertyGrid のエディタウィンドウや常駐しているアプリケーションの都合で取得されるアクティブウィンドウが不正なハンドルになることがあるのかもしれません。もし、そうであれば、ShowDialog や Show に owner を省略していることが原因ですので、owner を明示することで治るかと思います。

    個人的には、親ウィンドウを指定しないでモーダルウィンドウを表示するのは、設計としてありえないと思います。
    # ただ、Form1 がアプリケーションの最初のウィンドウの場合、Form1 の Load イベントではまだ親となるべき Form1 が存在しないため、HWND_DESKTOP を保持する IWin32Window を明示してあげないとダメかもしれませんね

    • 回答としてマーク kome211 2010年12月21日 9:08
    2010年12月21日 8:15
  • すでに回答ずみになってますが、試して再現したので。

    • Vista 32bit SP2 + .NET 2.0.50727.4200
    • XP 32bit SP2 + .NET 2.0.50727.1433 (Virtual PC ほとんど素の状態)

    で試しましたが、両方とも100%再現しました。

    フレームワークのソースを追ってみたところ、MessageBox.Showでオーナー指定していないときにはGetActiveWindowが呼ばれて設定されていました。
    PropertyGridで選択をしなかった場合には0でしたが、選択したときはForm2のハンドルが取得されていました。
    このハンドルを指定してWin32APIのMessageBoxが呼ばれているのですが、エラーが発生してもGetLastWin32Errorでは取れません。
    かわりにWin32APIのMessageBoxをForm2のハンドルを指定して直接呼んで試したところ、エラー内容は「ウィンドウ ハンドルが無効です。」となってました。

    PropertyGridでDropDownを操作するとどうしてGetActiveWindowがForm2のハンドルになるかまではわかりませんでした。

    • 回答としてマーク kome211 2010年12月21日 10:52
    2010年12月21日 10:29
  • 私も昨日は複数の環境(OS)で再現できなかったのですが、もう一度試してみたら、再現させることができました。

    再現しない人 → ダブルクリックで値を変更した人
    再現する人  → プルダウンから値を選択した人
    じゃないでしょうか。

    kome211 さんが書かれた回避方法以外に、以下のように DefaultDesktopOnly を指定する方法もあります。
    この場合はハンドルを補う処理は行われず、Windows の MessageBox は、オーナーウィンドウのハンドルにはゼロが指定されて呼び出されます。
    Option が指定したいだけなのに長くなっちゃいますが。

    DialogResult result =
        MessageBox.Show(
            "Hoge",
            string.Empty, MessageBoxButtons.OK,
            MessageBoxIcon.None, MessageBoxDefaultButton.Button1,
            MessageBoxOptions.DefaultDesktopOnly);

    ただし、表示される Form1 がアクティブにならないという弊害があるようです。
    kome211 さんの方法では、その点、大丈夫でしょうか?(たぶん同じと想像してます。)

    試しに Form2 上に ComboBox や DateTimePicker や DataGridView(選択肢項目あり)を配置してプルダウンさせても問題は発生しませんので、PropertyGrid の処理に問題を誘発させるコードがあるのかなと思いました。
    GetActiveWindow は、同じスレッドでのメッセージループ(メッセージキュー?)上からアクティブなものを取得するようですので、プルダウン表示の処理でそのあたりが変わってしまっているのかなと想像しました。

    • 回答としてマーク kome211 2010年12月22日 6:21
    2010年12月22日 1:37

すべての返信

  • こちらで試したところ、再現しませんでした。こちらの環境は、

    Windows 7 x64
    Microsoft Visual Studio 2008
    Version 9.0.30729.1 SP
    Microsoft .NET Framework
    Version 3.5 SP1

    です。
    プロパティグリッドをいじるというのは、falseをtrueに変えているだけですよね?

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年12月21日 4:59
    モデレータ
  • trapemiyaさま

    早速のご返信、まことにありがとうございます。

    > プロパティグリッドをいじるというのは、falseをtrueに変えているだけですよね?

    はい、その通りです。

    書き忘れていましたが、当方のPCはWindows XP(32bit)になります。
    この違いがもしかしたら有るのでしょうか…

    Visual C# Express 2008の詳細なバージョンは、9.0.30729.1 SPです。

    2010年12月21日 5:06
  • 只今手元にあるVista (32bit)マシンで、全く同じexeファイルを実行して確認しましたが、
    こちらでも再現いたしました。
    Windows 7はいますぐ試すことは出来ないのですが…
    2010年12月21日 5:41
  • WinXP SP3 (.NET 3.5 SP1 - 2.0.50727.3615) では再現しませんでした。

    標準の MessageBox クラスは、OS からエラーが返った場合に DialogResult.No を返すという、ちょっと困った挙動があるので、問題の場合に Marshal.GetLastWin32Error() か throw new Win32Exception() してみると、問題解決の糸口が見つかるかもしれません。

    予想では、特定のパターンで PropertyGrid のエディタウィンドウや常駐しているアプリケーションの都合で取得されるアクティブウィンドウが不正なハンドルになることがあるのかもしれません。もし、そうであれば、ShowDialog や Show に owner を省略していることが原因ですので、owner を明示することで治るかと思います。

    個人的には、親ウィンドウを指定しないでモーダルウィンドウを表示するのは、設計としてありえないと思います。
    # ただ、Form1 がアプリケーションの最初のウィンドウの場合、Form1 の Load イベントではまだ親となるべき Form1 が存在しないため、HWND_DESKTOP を保持する IWin32Window を明示してあげないとダメかもしれませんね

    • 回答としてマーク kome211 2010年12月21日 9:08
    2010年12月21日 8:15
  • K.Kataokaさま

    ご返信ありがとうございます。

    なるほど…MFCの時代にも同じような話があったような気がします。
    ありがとうございます。

    最適な方法かは分からないのですが、親ウインドウのHWNDを
    Win32APIのGetDesktopWindowを使って取得し、
    MessageBox.Showに渡すようにしたところ、PropertyGridを触っても
    MessageBoxが表示されないような変な動きはしなくなりました。

      public partial class Form1 : Form
      {
        [DllImport("user32.dll")]
        static extern IntPtr GetDesktopWindow();
    
        class WindowWrapper : System.Windows.Forms.IWin32Window
        {
          public WindowWrapper(IntPtr handle)
          {
            _hwnd = handle;
          }
    
          public IntPtr Handle
          {
            get { return _hwnd; }
          }
    
          private IntPtr _hwnd;
        }
    
        public Form1()
        {
          InitializeComponent();
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
          Form2 form2 = new Form2();
    
          // PropertyGrid付きのFormをモーダル表示する
          form2.ShowDialog();
    
          // 親ウインドウをDesktopに指定
          DialogResult result = MessageBox.Show(new WindowWrapper(GetDesktopWindow()), "Hoge");
        }
      }
    
    Loadイベント内で、メッセージボックスを表示するようなケースは、
    これからもかなりありそうな気はしているので、十分注意したいと思います。

    ご回答頂いた皆様、ありがとうございました。

    2010年12月21日 9:07
  • すでに回答ずみになってますが、試して再現したので。

    • Vista 32bit SP2 + .NET 2.0.50727.4200
    • XP 32bit SP2 + .NET 2.0.50727.1433 (Virtual PC ほとんど素の状態)

    で試しましたが、両方とも100%再現しました。

    フレームワークのソースを追ってみたところ、MessageBox.Showでオーナー指定していないときにはGetActiveWindowが呼ばれて設定されていました。
    PropertyGridで選択をしなかった場合には0でしたが、選択したときはForm2のハンドルが取得されていました。
    このハンドルを指定してWin32APIのMessageBoxが呼ばれているのですが、エラーが発生してもGetLastWin32Errorでは取れません。
    かわりにWin32APIのMessageBoxをForm2のハンドルを指定して直接呼んで試したところ、エラー内容は「ウィンドウ ハンドルが無効です。」となってました。

    PropertyGridでDropDownを操作するとどうしてGetActiveWindowがForm2のハンドルになるかまではわかりませんでした。

    • 回答としてマーク kome211 2010年12月21日 10:52
    2010年12月21日 10:29
  • gettaさま

    詳細な実験をしていただき、本当にありがとうございます。
    どのようなことが起こっていたのか理解できて、大変すっきりしました。

    こちらにも、回答としてマークさせていただきたいと思います。

    2010年12月21日 10:52
  • 私も昨日は複数の環境(OS)で再現できなかったのですが、もう一度試してみたら、再現させることができました。

    再現しない人 → ダブルクリックで値を変更した人
    再現する人  → プルダウンから値を選択した人
    じゃないでしょうか。

    kome211 さんが書かれた回避方法以外に、以下のように DefaultDesktopOnly を指定する方法もあります。
    この場合はハンドルを補う処理は行われず、Windows の MessageBox は、オーナーウィンドウのハンドルにはゼロが指定されて呼び出されます。
    Option が指定したいだけなのに長くなっちゃいますが。

    DialogResult result =
        MessageBox.Show(
            "Hoge",
            string.Empty, MessageBoxButtons.OK,
            MessageBoxIcon.None, MessageBoxDefaultButton.Button1,
            MessageBoxOptions.DefaultDesktopOnly);

    ただし、表示される Form1 がアクティブにならないという弊害があるようです。
    kome211 さんの方法では、その点、大丈夫でしょうか?(たぶん同じと想像してます。)

    試しに Form2 上に ComboBox や DateTimePicker や DataGridView(選択肢項目あり)を配置してプルダウンさせても問題は発生しませんので、PropertyGrid の処理に問題を誘発させるコードがあるのかなと思いました。
    GetActiveWindow は、同じスレッドでのメッセージループ(メッセージキュー?)上からアクティブなものを取得するようですので、プルダウン表示の処理でそのあたりが変わってしまっているのかなと想像しました。

    • 回答としてマーク kome211 2010年12月22日 6:21
    2010年12月22日 1:37
  • TH01さま

    ご返信まことにありがとうございます。

    再現しない人 → ダブルクリックで値を変更した人
    > 再現する人  → プルダウンから値を選択した人

    というのはまさにおっしゃるとおりでした。気づきませんでした…
    確かにこちらでも、PropertyGridのプルダウンから選択した場合だけ、問題が起こりました。
    おっしゃるとおり、PropertyGridのプルダウン表示の処理の中に、何らかの原因があるのかもしれません。

    DefaultDesktopOnlyを指定する方法も、こちらで試してみました。
    私が昨日の投稿で書いた、IWin32Windowを指定する方法と、教えていただいた、DefaultDesktopOnlyを
    指定する方法で、Form2のPropertyGridで、プルダウンから値を選んでからForm2を閉じるようにした場合の
    動作比較してみました。

    当方で試せる、XP(32bit)、Vista(32bit)共に、両方の方法で、メッセージボックスは正常に表示されました。
    TH01さんの懸念の、Form1が後ろに隠れるかどうか、ですが、
    DefaultDesktopOnlyの方法だと、Form1がアクティブにならず、他のウインドウの後ろに隠れてしまいますが、
    IWin32Windowを明示する方法ではそのようなことは無く、Form1は正常に前面に表示されました。

    もっと別の環境では、動作が異なるのかもしれませんが、とりあえずご報告まで。

    2010年12月22日 6:20