none
非同期スレッドからのShowDialogについて RRS feed

  • 質問

  • こんばんわ。shchと申します。

    WindowsXP SP2にてWindowsFormアプリケーションを作成しております。
    現象について、対処方法が見当たらなかったので質問させてください。


    現在作成中のアプリケーションには下記の登場人物がおります。
    ・親フォーム
    ・子フォーム
    ・ユーザ通知フォーム

    親フォームはしばしば子フォームをShowDialogし、また、ときどき非同期スレッドからユーザ通知フォームをShowDialogします。


    以下にサンプルコードを記載します。

    Code Snippet


            private void button1_Click( object sender, EventArgs e )
            {
                MethodInvoker showThread = ShowThread;

                showThread.BeginInvoke( null, null );


                Form2 f2 = new Form2( );
                f2.ShowDialog( this );
            }

            private void ShowThread( )
            {
                Thread.Sleep( 3 * 1000 );

                this.Invoke( ( MethodInvoker )delegate( )
                {
                    Form3 f3 = new Form3( );
                    f3.ShowDialog( this );
                } );
            }




    上記コード中の「Thread.Sleep( 3 * 1000 );」の間に、他アプリケーション(ブラウザなど)をアクティブにしておき「f3.ShowDialog( this );」が行われた後にf3(ユーザ通知フォーム)を閉じます。
    すると、f2(子フォーム)にフォーカスが移るものと思ったのですが、親フォームに移ってしまいます。
    その為、エンターキー押下等でボタンを押せてしまい、対処方法に悩んでいます。


    そもそも、同じフォーム(親)からShowDialogが複数呼ばれる作りがまずいのでしょうか?
    ユーザ通知フォームは監視スレッドからエラーなどをユーザに通知するものなので、最前面に表示したいと考えております。




    2009年1月14日 11:14

回答

  • モーダルダイアログは階層化はできますが、並列に表示することはできないという認識がよいと思います。
     
    .NET Framework のクラスライブラリの内部でも、そのことが前提になった実装があったように思います。(再確認はしていません)
    .NET Framework だけでなく、他社の Win32 のクラスライブラリやフレームワーク類でも、それが前提になっているものがいくつもあります。
     
     
    状態管理としてどうあるべきなのかにもよりますが、Form2 に対してモーダルに通知ウィンドウが表示されてほしいのであれば、owner を指定しない ShowDialog のオーバーロードを呼び出すという手がつかえたように思います。
     
    また、ここままでは通知ウィンドウが表示中に、さらに通知ウィンドウが表示されたりすると思いますが、そういったことも含めてアプリケーションの状態管理をきっちり考えて調整する必要があるでしょう。
    2009年1月14日 14:19
  • こんばんは!(^^)!ふ~です。

     

    Code Snippet

    using System;
    using System.Windows.Forms;
    using System.Threading;

    namespace InvokeTest1
    {
        public partial class Form1 : Form
        {
           public Form2 f2;

           public Form1()
           {
                InitializeComponent();
           }
      
            private void button1_Click( object sender, EventArgs e )
            {
                MethodInvoker showThread = ShowThread;
                showThread.BeginInvoke( null, null );

                f2 = new Form2( );
                f2.ShowDialog(this);
            }

            private void ShowThread( )
            {
                Thread.Sleep( 3 * 1000 );

                this.Invoke( ( MethodInvoker )delegate( )
                {
                    Form3 f3 = new Form3( );
                    f3.ShowDialog( f2 );
                } );
            }
        }
    }

     

     

    親の関係を Form1 の上に Form2 その上に Form3と修正しました。

     

    2009年1月14日 14:58
  •  !(^^)!ふ~ さんからの引用

     

    Code Snippet

    using System;
    using System.Windows.Forms;
    using System.Threading;

    namespace InvokeTest1
    {
        public partial class Form1 : Form
        {
           public Form2 f2;

           public Form1()
           {
                InitializeComponent();
           }
      
            private void button1_Click( object sender, EventArgs e )
            {
                MethodInvoker showThread = ShowThread;
                showThread.BeginInvoke( null, null );

                f2 = new Form2( );
                f2.ShowDialog(this);
            }

            private void ShowThread( )
            {
                Thread.Sleep( 3 * 1000 );

                this.Invoke( ( MethodInvoker )delegate( )
                {
                    Form3 f3 = new Form3( );
                    f3.ShowDialog( f2 );
                } );
            }
        }
    }

     

     

    Sleepで擬似的に表現している処理が、実際には時間のかかるものであった場合、f2のフォームはユーザ操作によって閉じられているかもしれません。

    そんな状態で、f3の表示にf2を使用するのはリスクの高い行為です。

     

    処理中にはf2が消えることはないという論理的な保障を別途担保するか、f3を表示する時点でf2が有効かどうか同期的にチェックすることが必要でしょう。

     

     

    # サンプルとはいえ、その可能性に触れないのは怖いことでしたので、突っ込みを入れる形としました。ご了承下さい。

    2009年1月14日 15:54
    モデレータ
  • おはようございます!(^^)!ふ~です。

     

    >そんな状態で、f3の表示にf2を使用するのはリスクの高い行為です。

    もし、f2がクローズされた場合でも、f3は正しく表示されます。f1がまだ残っています。

    f1とf2をクローズするとさすがにf3は開けません。この仕組みがポイントと思います。

     

    2009年1月14日 16:18
  • K.Takaoka様、!(^^)!ふ~様、Azulean様ご回答ありがとうございます。


    >モーダルダイアログは階層化はできますが、並列に表示することはできないという認識がよいと思います。
    やはり、K.Takaoka様の仰るとおり、モーダルダイアログを並列に複数表示している事がまずいのですね。
    ご教示いただいた通り、引数なしのShowDialogを使用することでForm1がアクティブになってしまう現象は解消されました。ありがとうございます。

    ただ、Form3を閉じた後にタスクバーからForm1を選択すると、Form1がアクティブになってしまい、同様の現象が発生してしまいます。
    なぜ、マウスからForm1を選択した場合はForm2がアクティブになるのに、タスクバーから選択した場合はForm1がアクティブになってしまうのでしょう。。
    MSDNによると、引数なしのShowDialogは「アプリケーションの現在アクティブなウィンドウによって自動的に所有されます。」とあり、ブラウザなどを使用中の場合(アクティブなウィンドウがない場合?)はうまく動作しないのかもしれません。


    !(^^)!ふ~様のサンプルのように、ShowDialogに直接Form2を指定すると、Form3を閉じたタイミングでForm2がアクティブになるので、上記の現象も再現しませんでした。

    しかし、サンプルコードからは読み取れない情報で恐縮なのですが、Form2にあたる子フォームは実は数種類あります。さらに何も開いていない場合もあり、どれが開かれているかもわかりません。
    そこで、Form1のOwnedFormsを再帰処理などで探査し、一番階層の深いFormを指定してShowDialogしたところ、うまくいっているように見えます。
    はたして、通知ウィンドウを表示したいというだけで、こんな方法が必要なのでしょうか。


    本題である、ShowDialogの件については、並列表示がまずいという認識で理解しましたので、回答済みとしたいと思います。
    ウィンドウの配置構成については、いまのところ、下記2パターンの対処方法を考えております。
    もし、問題点などについてご指摘いただけるのであれば、引き続きお願いいたします。

     ・通知ウィンドウをモードレスで表示するように変更する
     ・OwnedFormsを探査する


    >また、ここままでは通知ウィンドウが表示中に、さらに通知ウィンドウが表示されたりすると思いますが、そういったことも含めてアプリケーションの状態管理をきっちり考えて調整する必要があるでしょう。
    この件に関しては対処しております。ご忠告、ありがとうございました。
    通知ウィンドウが複数表示されることはありません。
    2009年1月15日 2:51
  •  shch さんからの引用
    ウィンドウの配置構成については、いまのところ、下記2パターンの対処方法を考えております。
    もし、問題点などについてご指摘いただけるのであれば、引き続きお願いいたします。
     ・通知ウィンドウをモードレスで表示するように変更する
     ・OwnedFormsを探査する


    それを考えるのはアプリケーションとしての要件次第ですよね?
    通知ウィンドウがどのような要求で、どのような内容が表示されるかによって、モードレスにできるのかモーダルでなければならないのかは違いますよね? 

    特に、元の外部要因でダイアログが表示され入力を奪うような構造は、非常に特殊だと思います。ユーザが何らかの入力を行う時、意図しないタイミングで通知ウィンドウが表示された場合に、多くの場合で事故の発生が想定されるためです。(たとえば、文章の入力中「や」「ゆ」「よ」を入力しようとして Y キーを押す瞬間に、Yes/No を確認する通知ウィンドウが出現して、目で追えない速度で Yes が選択されてしまうなど)

    2009年1月15日 4:00
  • ご回答ありがとうございます。

     K.Takaoka さんからの引用

    それを考えるのはアプリケーションとしての要件次第ですよね?
    通知ウィンドウがどのような要求で、どのような内容が表示されるかによって、モードレスにできるのかモーダルでなければならないのかは違いますよね? 


    説明不足で申し訳ございません。その通りだと思います。
    そのあたりは自分で考えるべきと思い、回答済みといたしました。

    モードレスにできるのか、モーダルでなければならないのか等については、のちに上司と考えたいと思っております。
    現段階では、対処方法としていくつかのパターンを用意したいので、もしお付き合い頂けるのであれば「モードレスにしなくてもこういうのはいかが?」ですとか、「OwnedFormsを探査する」という対処方法に対する問題点などをご指摘いただければと期待して書き込みました。

     K.Takaoka さんからの引用

    特に、元の外部要因でダイアログが表示され入力を奪うような構造は、非常に特殊だと思います。ユーザが何らかの入力を行う時、意図しないタイミングで通知ウィンドウが表示された場合に、多くの場合で事故の発生が想定されるためです。(たとえば、文章の入力中「や」「ゆ」「よ」を入力しようとして Y キーを押す瞬間に、Yes/No を確認する通知ウィンドウが出現して、目で追えない速度で Yes が選択されてしまうなど)


    確かに、ご指摘の通りだと思います。検討不足でした。
    非常に特殊なケースだという意見は、上司と戦う際に参考にさせていただきます。

    2009年1月15日 5:18
  • 本筋ではありませんが、意図を伝え切れていなかったようですので、追記しておきます。

     

     !(^^)!ふ~ さんからの引用

    >そんな状態で、f3の表示にf2を使用するのはリスクの高い行為です。

    もし、f2がクローズされた場合でも、f3は正しく表示されます。f1がまだ残っています。

    f1とf2をクローズするとさすがにf3は開けません。この仕組みがポイントと思います。

    f2をDisposeしていないのであれば、確かにf3は無事開きます。

    f2.ShowDialog()の直後に、f2.Dispose()を呼んでしまうと、NGです。

    (理由はShowDialogの引数にf2を渡していること、ShowDialogの中でHandleの取得を行う際にObjectDisposedExceptionがスローされること)

     

    f1が生きているかだけではなく、f2の寿命をどのように管理するかもポイントだと思います。

    また、f2を生かしておくことが問題ないかどうかも関係してきます。

     

     

    ただ、既にスレッドの流れにもある通り、ShowDialogを並列させるのは良くないため、この手法を使うこと自体は避けるべきですが…。

     

     

     shch さんからの引用

    非常に特殊なケースだという意見は、上司と戦う際に参考にさせていただきます。

    戦うというと打ちのめすようなイメージを持ちました。

    上司とは戦うと言うよりは、よりよいアプリケーションを作るために議論しましょう。

    この方が効果的である、安全である等、良い方向に引っ張っていけるのであれば、上司も納得してくれると思います。

    2009年1月15日 14:03
    モデレータ

すべての返信

  • モーダルダイアログは階層化はできますが、並列に表示することはできないという認識がよいと思います。
     
    .NET Framework のクラスライブラリの内部でも、そのことが前提になった実装があったように思います。(再確認はしていません)
    .NET Framework だけでなく、他社の Win32 のクラスライブラリやフレームワーク類でも、それが前提になっているものがいくつもあります。
     
     
    状態管理としてどうあるべきなのかにもよりますが、Form2 に対してモーダルに通知ウィンドウが表示されてほしいのであれば、owner を指定しない ShowDialog のオーバーロードを呼び出すという手がつかえたように思います。
     
    また、ここままでは通知ウィンドウが表示中に、さらに通知ウィンドウが表示されたりすると思いますが、そういったことも含めてアプリケーションの状態管理をきっちり考えて調整する必要があるでしょう。
    2009年1月14日 14:19
  • こんばんは!(^^)!ふ~です。

     

    Code Snippet

    using System;
    using System.Windows.Forms;
    using System.Threading;

    namespace InvokeTest1
    {
        public partial class Form1 : Form
        {
           public Form2 f2;

           public Form1()
           {
                InitializeComponent();
           }
      
            private void button1_Click( object sender, EventArgs e )
            {
                MethodInvoker showThread = ShowThread;
                showThread.BeginInvoke( null, null );

                f2 = new Form2( );
                f2.ShowDialog(this);
            }

            private void ShowThread( )
            {
                Thread.Sleep( 3 * 1000 );

                this.Invoke( ( MethodInvoker )delegate( )
                {
                    Form3 f3 = new Form3( );
                    f3.ShowDialog( f2 );
                } );
            }
        }
    }

     

     

    親の関係を Form1 の上に Form2 その上に Form3と修正しました。

     

    2009年1月14日 14:58
  •  !(^^)!ふ~ さんからの引用

     

    Code Snippet

    using System;
    using System.Windows.Forms;
    using System.Threading;

    namespace InvokeTest1
    {
        public partial class Form1 : Form
        {
           public Form2 f2;

           public Form1()
           {
                InitializeComponent();
           }
      
            private void button1_Click( object sender, EventArgs e )
            {
                MethodInvoker showThread = ShowThread;
                showThread.BeginInvoke( null, null );

                f2 = new Form2( );
                f2.ShowDialog(this);
            }

            private void ShowThread( )
            {
                Thread.Sleep( 3 * 1000 );

                this.Invoke( ( MethodInvoker )delegate( )
                {
                    Form3 f3 = new Form3( );
                    f3.ShowDialog( f2 );
                } );
            }
        }
    }

     

     

    Sleepで擬似的に表現している処理が、実際には時間のかかるものであった場合、f2のフォームはユーザ操作によって閉じられているかもしれません。

    そんな状態で、f3の表示にf2を使用するのはリスクの高い行為です。

     

    処理中にはf2が消えることはないという論理的な保障を別途担保するか、f3を表示する時点でf2が有効かどうか同期的にチェックすることが必要でしょう。

     

     

    # サンプルとはいえ、その可能性に触れないのは怖いことでしたので、突っ込みを入れる形としました。ご了承下さい。

    2009年1月14日 15:54
    モデレータ
  • おはようございます!(^^)!ふ~です。

     

    >そんな状態で、f3の表示にf2を使用するのはリスクの高い行為です。

    もし、f2がクローズされた場合でも、f3は正しく表示されます。f1がまだ残っています。

    f1とf2をクローズするとさすがにf3は開けません。この仕組みがポイントと思います。

     

    2009年1月14日 16:18
  • K.Takaoka様、!(^^)!ふ~様、Azulean様ご回答ありがとうございます。


    >モーダルダイアログは階層化はできますが、並列に表示することはできないという認識がよいと思います。
    やはり、K.Takaoka様の仰るとおり、モーダルダイアログを並列に複数表示している事がまずいのですね。
    ご教示いただいた通り、引数なしのShowDialogを使用することでForm1がアクティブになってしまう現象は解消されました。ありがとうございます。

    ただ、Form3を閉じた後にタスクバーからForm1を選択すると、Form1がアクティブになってしまい、同様の現象が発生してしまいます。
    なぜ、マウスからForm1を選択した場合はForm2がアクティブになるのに、タスクバーから選択した場合はForm1がアクティブになってしまうのでしょう。。
    MSDNによると、引数なしのShowDialogは「アプリケーションの現在アクティブなウィンドウによって自動的に所有されます。」とあり、ブラウザなどを使用中の場合(アクティブなウィンドウがない場合?)はうまく動作しないのかもしれません。


    !(^^)!ふ~様のサンプルのように、ShowDialogに直接Form2を指定すると、Form3を閉じたタイミングでForm2がアクティブになるので、上記の現象も再現しませんでした。

    しかし、サンプルコードからは読み取れない情報で恐縮なのですが、Form2にあたる子フォームは実は数種類あります。さらに何も開いていない場合もあり、どれが開かれているかもわかりません。
    そこで、Form1のOwnedFormsを再帰処理などで探査し、一番階層の深いFormを指定してShowDialogしたところ、うまくいっているように見えます。
    はたして、通知ウィンドウを表示したいというだけで、こんな方法が必要なのでしょうか。


    本題である、ShowDialogの件については、並列表示がまずいという認識で理解しましたので、回答済みとしたいと思います。
    ウィンドウの配置構成については、いまのところ、下記2パターンの対処方法を考えております。
    もし、問題点などについてご指摘いただけるのであれば、引き続きお願いいたします。

     ・通知ウィンドウをモードレスで表示するように変更する
     ・OwnedFormsを探査する


    >また、ここままでは通知ウィンドウが表示中に、さらに通知ウィンドウが表示されたりすると思いますが、そういったことも含めてアプリケーションの状態管理をきっちり考えて調整する必要があるでしょう。
    この件に関しては対処しております。ご忠告、ありがとうございました。
    通知ウィンドウが複数表示されることはありません。
    2009年1月15日 2:51
  •  shch さんからの引用
    ウィンドウの配置構成については、いまのところ、下記2パターンの対処方法を考えております。
    もし、問題点などについてご指摘いただけるのであれば、引き続きお願いいたします。
     ・通知ウィンドウをモードレスで表示するように変更する
     ・OwnedFormsを探査する


    それを考えるのはアプリケーションとしての要件次第ですよね?
    通知ウィンドウがどのような要求で、どのような内容が表示されるかによって、モードレスにできるのかモーダルでなければならないのかは違いますよね? 

    特に、元の外部要因でダイアログが表示され入力を奪うような構造は、非常に特殊だと思います。ユーザが何らかの入力を行う時、意図しないタイミングで通知ウィンドウが表示された場合に、多くの場合で事故の発生が想定されるためです。(たとえば、文章の入力中「や」「ゆ」「よ」を入力しようとして Y キーを押す瞬間に、Yes/No を確認する通知ウィンドウが出現して、目で追えない速度で Yes が選択されてしまうなど)

    2009年1月15日 4:00
  • ご回答ありがとうございます。

     K.Takaoka さんからの引用

    それを考えるのはアプリケーションとしての要件次第ですよね?
    通知ウィンドウがどのような要求で、どのような内容が表示されるかによって、モードレスにできるのかモーダルでなければならないのかは違いますよね? 


    説明不足で申し訳ございません。その通りだと思います。
    そのあたりは自分で考えるべきと思い、回答済みといたしました。

    モードレスにできるのか、モーダルでなければならないのか等については、のちに上司と考えたいと思っております。
    現段階では、対処方法としていくつかのパターンを用意したいので、もしお付き合い頂けるのであれば「モードレスにしなくてもこういうのはいかが?」ですとか、「OwnedFormsを探査する」という対処方法に対する問題点などをご指摘いただければと期待して書き込みました。

     K.Takaoka さんからの引用

    特に、元の外部要因でダイアログが表示され入力を奪うような構造は、非常に特殊だと思います。ユーザが何らかの入力を行う時、意図しないタイミングで通知ウィンドウが表示された場合に、多くの場合で事故の発生が想定されるためです。(たとえば、文章の入力中「や」「ゆ」「よ」を入力しようとして Y キーを押す瞬間に、Yes/No を確認する通知ウィンドウが出現して、目で追えない速度で Yes が選択されてしまうなど)


    確かに、ご指摘の通りだと思います。検討不足でした。
    非常に特殊なケースだという意見は、上司と戦う際に参考にさせていただきます。

    2009年1月15日 5:18
  • 本筋ではありませんが、意図を伝え切れていなかったようですので、追記しておきます。

     

     !(^^)!ふ~ さんからの引用

    >そんな状態で、f3の表示にf2を使用するのはリスクの高い行為です。

    もし、f2がクローズされた場合でも、f3は正しく表示されます。f1がまだ残っています。

    f1とf2をクローズするとさすがにf3は開けません。この仕組みがポイントと思います。

    f2をDisposeしていないのであれば、確かにf3は無事開きます。

    f2.ShowDialog()の直後に、f2.Dispose()を呼んでしまうと、NGです。

    (理由はShowDialogの引数にf2を渡していること、ShowDialogの中でHandleの取得を行う際にObjectDisposedExceptionがスローされること)

     

    f1が生きているかだけではなく、f2の寿命をどのように管理するかもポイントだと思います。

    また、f2を生かしておくことが問題ないかどうかも関係してきます。

     

     

    ただ、既にスレッドの流れにもある通り、ShowDialogを並列させるのは良くないため、この手法を使うこと自体は避けるべきですが…。

     

     

     shch さんからの引用

    非常に特殊なケースだという意見は、上司と戦う際に参考にさせていただきます。

    戦うというと打ちのめすようなイメージを持ちました。

    上司とは戦うと言うよりは、よりよいアプリケーションを作るために議論しましょう。

    この方が効果的である、安全である等、良い方向に引っ張っていけるのであれば、上司も納得してくれると思います。

    2009年1月15日 14:03
    モデレータ