none
VB2005 ボタン二度押し防止方法 RRS feed

  • 質問

  • Windows Formのボタンの二度押し防止方法ですが、別フォーラムでも同トピック、議論はあるものの

    正解らしい回答を見たことがありません。変数でENABLE/DISABLEにするという案はありま

    すが通常はどのような処理がよいのでしょうか?

    宜しくお願いします。

    2007年11月8日 8:52

回答

  •  よしろう さんからの引用

    おっと、NewメソッドでLOADイベントよりも先に処理を記述することもできるの

    ですね。ちょっとまずは書いてみます。(VB2005 Windows Formアプリケーション)


    New メソッドではなくてコンストラクタと言います。
    そこではなくて、やはりエントリ ポイントでの記述が望ましいです。


    エントリ ポイントを書くには、プロジェクトのプロパティから 「スタートアップ」 を Sub Main にしなくてはなりません。

    なお VB2005 の場合は、アプリケーション フレームワークで二重起動の防止がノンコーディングでできます。

    VB2005 をお使いのようですので、アプリケーション フレームワークに頼られた方がよろしかろうと思います。

    2007年11月14日 4:54

すべての返信

  • 正解といっても、一人の万能な神がいて決めるわけではありませんからね。(^^;

     

    http://www.microsoft.com/japan/msdn/community/gdn/ShowPost-38117.htm

     

    たとえばここにいろいろな方法がありますが、この方法はここがいや、とかよしろうさんが言うことから

    始めるしかないかと。

    2007年11月8日 9:40
  •  よしろう さんからの引用

    Windows Formのボタンの二度押し防止方法ですが、別フォーラムでも同トピック、議論はあるものの

    正解らしい回答を見たことがありません。変数でENABLE/DISABLEにするという案はありますが通常はどのような処理がよいのでしょうか?


    見た目どうするのか、という話もありますよね。
    非活性表示するだけで良いのか、モーダルで何らかの情報を表示するのかなど。


    Windows Form だとよほどのことがない限りは非活性にして即更新して終了って感じですね。
    現実問題確認用の MessageBox を 1 つ挟むだけでも連続で押されてしまうという誤動作は防げます。


    重要なものであればフラグに頼るが確実だと思います。

    2007年11月8日 9:41
  • 以下なんかが参考になるんじゃないでしょうか? 結構、目から鱗でした。

     

    無効中のボタンをマウスクリックイベントを受けてしまう
    http://forums.microsoft.com/msdn-ja/ShowPost.aspx?PostID=717271&SiteID=7

    2007年11月8日 10:03
    モデレータ
  • コメント色々どうもありがとうございます。Application.DoEvents() メソッドを
    使って試してみます。

    2007年11月9日 5:01
  •  

    次のように書いて動作致しました。どうもありがとうございました。

     

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Button1.Enabled = False

        Threading.Thread.Sleep(5000)  ' wait for 5secs

        Application.DoEvents()

        Button1.Enabled = True

    End Sub

    2007年11月9日 6:30
  • いまさらですが。

     

    Application.DoEventsを使う手法は、私はよくないと思います。

    イベントドリブン方式のプログラミングの基本を考えれば分かると思うのですが、

    イベント中にメッセージループを回すのは危険がたくさんあります。

    内部で何がおこっているのか、きちんと理解して使うなら問題ないのですが、

    Windowsのコードは公開されていませんので、

    中で何が起こっているのか調べようがありませんし、

    ある程度検証してもバージョンが変わったら変わる可能性もあります。

     

    本来、Application.DoEventsは無くてもプログラムできるはずのものですから、

    私はテストなどの一時使用を除き、一切使いません。

    2度押し防止には違う方法を用いています。

     

    「2度押しを防ぐ」という文面の解釈を文字通りとるのではなく、

    「アプリケーションが処理中の場合は押しても効果が無い」と解釈し、

     

    2度押し防止コード

        Private _IsEventProcessing As Boolean

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            AddHandler Application.Idle, New EventHandler(AddressOf Application_Idle)
        End Sub

        Private Sub Application_Idle(ByVal sender As Object, ByVal e As System.EventArgs)
            _IsEventProcessing = False
        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            If _IsEventProcessing Then Exit Sub
            _IsEventProcessing = True
            Threading.Thread.Sleep(5000)  ' wait for 5secs
        End Sub

     

     

    のようにしています。

     

    こちらのほうが、イベントドリブン式のプログラミングとしてはよりよい方法であろうと思っています。

    パフォーマンス的には劣るかもしれませんが。

     

    もちろん、

    時間のかかる処理は他スレッドでやるとか、

    細切れにしてTimerで行うとか、

    そういったように処理できれば一番です。

    2007年11月9日 8:55
  • なるほど。Application.Idleイベントをうまく使うわけですね。勉強になりました。

     

     れい さんからの引用

    イベントドリブン方式のプログラミングの基本を考えれば分かると思うのですが、

    イベント中にメッセージループを回すのは危険がたくさんあります。

     

    私もDoEventsの実装を知っているわけではありません。ただ、おっしゃられるようにDoEventsはイベント中に他のイベントを処理するために存在します。MSDNにも堂々とそのような趣旨が書いてあります。そこまで書いてあるので私は今のところDoEventsを信用しています。

     

    すっきりしていて確かに堅いのはApplication.Idleイベントを使う方法だと思うのですが、だからと言ってDoEventsを使うなと言い切るだけの材料を私は持っていません。特に今回のケースですと、Button1のClickイベントプロシージャでButton1のEnabledをtrueに戻すだけの処理を残してでのDoEventsですから、DoEventsによってそれ以降の処理が一時的にブロックされてもあまり危険がないように思えます。違うかなぁ・・・?

    2007年11月9日 10:48
    モデレータ
  • ながーい投稿になってしまいました。

     

    私もDoEventsの実装を知っているわけではありません。ただ、おっしゃられるようにDoEventsはイベント中に他のイベントを処理するために存在します。MSDNにも堂々とそのような趣旨が書いてあります。そこまで書いてあるので私は今のところDoEventsを信用しています。

     

    VB6の時はこれがないとできないこともありましたし、

    今でも無いと困る人たちもたくさんいるでしょう。

    メッセージループを回す、という意味ではきちんと動作するのも知っています。

     

    ですが、私はあらゆる場合でDoEventsは使わないほうが良い、と思っています。

     

    すっきりしていて確かに堅いのはApplication.Idleイベントを使う方法だと思うのですが、だからと言ってDoEventsを使うなと言い切るだけの材料を私は持っていません。特に今回のケースですと、Button1のClickイベントプロシージャでButton1のEnabledをtrueに戻すだけの処理を残してでのDoEventsですから、DoEventsによってそれ以降の処理が一時的にブロックされてもあまり危険がないように思えます。違うかなぁ・・・?

     

    今回はたしかにEnabled=trueにするだけなのでよさそうですが、

    本当は、廃棄されたコントロールに対して、Enable=trueを行っても大丈夫かどうか、

    そのコントロールを含む親コントロールが廃棄中だったり、EnableやVisibleが変更されてる最中だったら大丈夫かどうか、

    そのコントロールを含むWindowが廃棄中だったり、アプリケーションが終了中だったら大丈夫か、

    ということまで考慮しなければならないと、私は思います。

     

    DoEventsを一回呼んだら、どんなメッセージが、いくつ処理されるのかわかりません。

    そこまで考慮してるでしょうか?

     

    私がDoEventsを嫌いな理由、他人に使わないほうがいいという理由を、

    いま思いついたまま適当に列挙してみます。

     

    ○ 普段気にしなくてもいい、Windows Formなどの実装、振舞いを詳しく知らないといけない。

     

    Windowsメッセージもいろいろあり、System.Windows.Forms.Controlにもいろいろなイベントがありますが、

    それらはイベントがイベントを呼ぶ形で、連鎖的にイベントが呼ばれようになっています。

    自分でイベントハンドラを記述し、その内部でメッセージループを回すと、その順番や内包関係が崩れます。

    異常な振舞いを起こす場合もあり、対応するのが困難です。

    一見動いているようにみえても、ある条件でだめになったり、デバッグも大変です。

     

    DoEventsを使ったら、そのDoEvents内で、いろいろなイベントがいくつも起きるわけです。

    他のClickイベント、Focusイベント、MouseDownイベント、

    FormClosingイベント、FormCloseイベント、Disposeイベント…。

    それら全てで問題がないことを確認するのは大変困難です。

     

    例えば、

    KeyDownイベント内でDoEventsを使ったら、

    KeyUpイベントはいつどこで起こるのでしょうか?

    KeyDownイベント処理中、KeyDownイベント内のDoEventsで起こるのでしょうか?

    それともKeyDownイベントハンドラを抜けたあとに起きるのでしょうか?

    KeyDownイベント処理中に他のボタンをクリックしたら、

    そのKeyDownイベント、Clickイベント、KeyUpイベントはいつどこで起こるのでしょうか?

    それらのイベントにTimerイベントが重なったりした場合、どうなるのでしょう?

    KeyDownとUpの間にTimer.Tickが入ったりするのでしょうか?

    Timer.Tickでフォーカスを移してたら、KeyUpイベントはどうなるのでしょう?

     

    MFCのころがんばって調べたりしたんですが、複雑すぎて忘れてしまいましたし、

    いまはそれに.Netの皮がついていますので、さらに複雑です。

    私はそれらをとても覚えられません。調べきれません。

     

    ○DoEventsの方が綺麗にできる、という事例を見たことがない。

     

    綺麗とは、見た目とか、保守性とか、そういったもの全てを含めてます。

    とくに、DoEventsを利用したコードは保守性が激しく悪くなります。

    理由は前述のように、イベント間の順序関係、依存関係のせいです。

    なにかイベントを追加するたび、変更するたびに、

    そのコントロールのすべてのDoEvents内から呼ばれる可能性を考慮しなければいけません。

     

    ○動作の保証ができない。

     

    上記二つに関連して。

    あるイベント中にDoEventsした場合、イベント処理中に他のイベントが発生するわけですが、

    これがきちんと動作するのかわかりません。

    Clickイベント処理中にClickイベントが起きても、経験上大丈夫ですが、

    SelectedIndexChangedイベント中にFormClosedが起きたらたぶん例外を吐きます。

    これらの動作がきちんと動くべきであるのか、動かなくても普通なのか、MSDNなどに明記されていません。

    動かないイベントが何であるか分かれば対応もできますが、

    どこにも書いてないので、実際やってみてエラーがでないことを確認するしかありませんが、

    全てのイベントを試すのは困難です。

     

    ○スタックをたくさん使う。

     

    そのまんま。スタックがたくさん消費されて嫌です。

     

    ○ 代替の方法がある。

     

    説明がめんどくさいですが、Idleイベントやフラグを組み合わせれば、

    DoEventsがなくても理論上大丈夫です。

     

○ 乱用を生む

 

DoEventsを使い始めると、乱用しはじめる人がいます。

本来Timerやマルチスレッドで解決すべき問題をDoEventsでごまかし始め、

そのうち、イベントドリブンとは全くかけ離れたコードになってしまうことが多々あります。

 

そんなとこです。

ずいぶん長くなりました。

 

強制する権限もないですし、

私は発言力の大きい有名人というわけでもないですし、

そう思ってる人もいるという、参考程度にしていただければ。

2007年11月9日 11:53
  • こんばんはtrapemiyaさん、れいさん。

    DoEventsはこの場合は処理の最後なので大丈夫のように思いますが、別の状況では動作しない例があります。

    少し前ですが私の質問から始まって、まどかさんにクリティカルなイベント処理の途中にモーダルウィンドウはだめよと教えていただいて、追試でDoEventsでも誤動作する例に遭遇してしまいました。モーダルウィンドウやDoEventsを全面的に使うのは注意が必要と考えた方が良さそうです。

    http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=2250677&SiteID=7

     メッセージが途中で流れても誤動作しない状態管理をきっちり行うコードを書くのは難しそうですし、実際DataTableでさえ誤動作するのですから、プログラマとしては避けるべきと考えるもの現実解の一つだと思います。

     2度押し防止だけでもいろんな方法があるのですね。勉強になります。イベントハンドラ毎に何か書くのではなくて、おまじないをしておけばアプリケーション全体で有効になるようなうまい方法はないですかね。れいさんのIdleイベントで処理する方法はそれに近いですね。

    2007年11月9日 12:55
  •  れい さんからの引用

    ながーい投稿になってしまいました。

     

    私がある意味書かせてしまったようで、すみません。でもとても参考になりました。

     

     れい さんからの引用

    本当は、廃棄されたコントロールに対して、Enable=trueを行っても大丈夫かどうか、

    そのコントロールを含む親コントロールが廃棄中だったり、EnableやVisibleが変更されてる最中だったら大丈夫かどうか、

    そのコントロールを含むWindowが廃棄中だったり、アプリケーションが終了中だったら大丈夫か、

    ということまで考慮しなければならないと、私は思います。

     

    廃棄されたコントロールに対する動作がどうなるのかは確かによくわらないですね。ひょっとしてDoEventsはうまく実装されているかもしれないし、そうでないかもしれないし。わからない以上は使わないというのはありだと思いました。

    実際、三輪の牛さんが言われているようにDoEventsで不具合が発生する場合があるようですので、DoEventsはそれほどインテリジェントに実装されているわけではなさそうですね。あまり信用すると落とし穴がありそうです。

     

     れい さんからの引用

    私がDoEventsを嫌いな理由、他人に使わないほうがいいという理由を、

    いま思いついたまま適当に列挙してみます。

     

    読ませていただきました。しっかりとしたポリシーを持っていらっしゃると思いますし、言われるとおりだと思います。

    使い方を間違わなければDoEventsは使えないわけではないとは思いますが、使い方を間違えないことがとても大変であれば、そこまでして使う必要はないわけですね。使わなくとも示されたようにApplication.Idleを使った方法などがあるわけですから。

     

     れい さんからの引用

    強制する権限もないですし、

    私は発言力の大きい有名人というわけでもないですし、

    そう思ってる人もいるという、参考程度にしていただければ。

     

    この場では誰も強制する権限はありませんが、権限はなくともきちんと論理が通っていれば、それは多くの人に受け入れられると思います。だから有名人だろうがなんだろうが関係なく、その発言の背景にしっかりしたものがあるかどうかだと思います。

    少なくとも私にとってこのスレは大変有意義なものになりましたし、勉強にもなりました。大変感謝しております。

    2007年11月9日 15:54
    モデレータ
  •  三輪の牛 さんからの引用

    DoEventsはこの場合は処理の最後なので大丈夫のように思いますが、別の状況では動作しない例があります。

    少し前ですが私の質問から始まって、まどかさんにクリティカルなイベント処理の途中にモーダルウィンドウはだめよと教えていただ

     

    具体例を挙げていただき、ありがとうございました。確かにDoEventsは使うのが難しそうですね。

     

     三輪の牛 さんからの引用

     メッセージが途中で流れても誤動作しない状態管理をきっちり行うコードを書くのは難しそうですし、実際DataTableでさえ誤動作するのですから、プログラマとしては避けるべきと考えるもの現実解の一つだと思います。

     

    結局、私もそこに行き着きました。実際、私はDoEventsを使ったことがありません。イベント中に他のイベントが処理されることが気持ち悪かったからです。DoEventsは魔法の呪文のように困難を解決してくれますが、その代償は決して小さくないことを知る必要がありますね。

    2007年11月9日 16:07
    モデレータ
  • またながーーい投稿になってしまいました。

    DoEventsに関してはこれで言いたいことは全部です。

     

     三輪の牛 さんからの引用

     2度押し防止だけでもいろんな方法があるのですね。勉強になります。イベントハンドラ毎に何か書くのではなくて、おまじないをしておけばアプリケーション全体で有効になるようなうまい方法はないですかね。れいさんのIdleイベントで処理する方法はそれに近いですね。

     

    2度押しが起こる理由は、メッセージがキューに入るからです。

    キューをなくすことはできませんが、

    マウスイベント・キーボードイベントのみキューに入らないようにすれば

    2度押しは起こりません。

    これはフックなどで実現できます。

     

    昔、やってみたことがあります。

    ものすごく不快でした。

     

    遅いコンピューターを使っていると、

    イベントがキューに入ること前提で作業するようになってしまいます。

    それを禁止されたので、

    作業が終わったか、ジーっと画面を眺めなくてはならず、

    目も心も大変疲れました。

     

    一律に2度押し禁止は、不快に思う場合もありますので

    2度押されたらまずいもの、処理に少し時間がかかるとわかってるもののみ、

    個別に対応するのがよいと思います。

     

    私の方法は2度押し防止ではなく、イベント処理中は無効化するという方法なので、

    あるイベントを処理をしている間、特定のボタンを無効化したい時などはかなり楽です。

    各Clickイベントの先頭にIfを一個、代入を一個置けばいいだけです。

     

    DoEventsはこの場合は処理の最後なので大丈夫のように思いますが、別の状況では動作しない例があります。

     

    前の投稿で書いたように、処理の最後でもダメな場合もあります。

    モーダルダイアログもClickなどの「決まりきったイベント」で使わなければ危険です

    この「決まりきったイベント」が何であるか、資料はあまりありません。

    ~のイベントの時は安全である、といったような資料は今のMSDNではみつかりませんでした。

    (昔はあったような…。)

    私はClickとIdle、Timer.Tickでしか使いません。

    ClickとIdle、TickならOSが変わってもComCtl32が変わっても、安全に使えると考えていて、

    (これにはいくつか理由がありますが、割愛します。)

    Win16時代から使っていますが、不具合はありません。

     

     trapemiya さんからの引用

    廃棄されたコントロールに対する動作がどうなるのかは確かによくわらないですね。ひょっとしてDoEventsはうまく実装されているかもしれないし、そうでないかもしれないし。わからない以上は使わないというのはありだと思いました。

    実際、三輪の牛さんが言われているようにDoEventsで不具合が発生する場合があるようですので、DoEventsはそれほどインテリジェントに実装されているわけではなさそうですね。あまり信用すると落とし穴がありそうです。

     

    DoEventsに何か期待しているようですが、

    DoEventsは普通にメッセージループを回すだけです。

    知る限り、特別なことは何もしません。インテリジェンスはありません。

    これ以上の複雑さを避けるためにも、互換性のためにも、そうあるべきだと思います。

    #そして、静かに消えていって欲しいと思ってます。

     

    DoEventsを呼んだ場合、きちんと動作するよう組むのは当然プログラマの責任です。

    ですが、DoEventsを呼んだ後は、ControlやApplicationがどんな状態になっているのか、

    現実的には知りようがありません。

    その状態でもきちんと動作するよう、

    DoEvents呼び出し元・他のイベントがきちんとコーディングされているのでしたら、

    DoEventsを使おうとなんら問題ありません。

     

    これは現実には難しく、

    大丈夫であることが保証できるコード、保証されてるコードを私は知りません。

    テストを厳密に行うと、合格できるコードは殆どありません。

     

    結局、私もそこに行き着きました。実際、私はDoEventsを使ったことがありません。イベント中に他のイベントが処理されることが気持ち悪かったからです。DoEventsは魔法の呪文のように困難を解決してくれますが、その代償は決して小さくないことを知る必要がありますね。

     

    イベントドリブンの価値は操作・作業の分割にあります。

    ある作業中にその作業に関連したイベントが次々呼ばれるのは正しいのですが、

    関係のない他の作業に手を出す=メッセージループを回すのはそもそもおかしいのです。

     

    DoEventsはたくさんのイベントを呼び出す、ある意味分岐に相当するわけですから、

    網羅率100%のホワイトボックステストを要求されたとき、

    DoEventsは一個でもあるなら、テストは現実的には不可能です。

     

    #もともとはそういう困難を解決するためのイベントドリブンなのです。

     

    私が知っている私視点のDoEventsの歴史的経緯を書いておきます。

    調べたわけではないですし、当時はまだお子様だったので、

    曲解してる可能性がありますが、何かの参考にしてください。

     

    DoEventsやモーダルダイアログ、イベントドリブンの問題は

    Windows3.0/3.1の頃に大きな話題になっていたと記憶しています。

    おそらく日本では、そのころからWindowsのイベントドリブンプログラミングが

    普及してきたのだと思います。

    当時たくさん書籍もでましたし、NiftyやPC-VANの掲示板、ニュースグループなどでも

    如何にプログラミングするかで盛り上がっていたと思います。

     

    いままでの逐次実行型の概念が抜けず、

    生産性が落ちて苦労している人がたくさんいました。

    とくに既存のコードをどうやったら簡単に移植できるか、それが重要でした。

     

    DOSの時代は画面を自分で書いて、メニューも自分で書いて、

    処理を開始したら終わるまで処理し続ける、という感じなプログラムで、

    中に自前のメッセージループのようなものが(複数)あるのが普通でしたから、

    移植の際にはそのメッセージループをGetMessage、DispatchMessageに変更し、

    自分で行っていたボタンや文字列表示をコントロールに置き換えるのが

    一般的な方法でした。

     

    これと同じことをVBでやるときに、DoEventsがありました。

    本当の所は知りませんがあちこちで、それをやるためのメソッドである、

    という論調で言われてましたし、私もそうだと思ってました。

    昔のVBのヘルプには、似たようなことが書かれていたと思います。

     

    私が指摘したようなDoEventsの問題は当時からあったので、

    Windowsのプログラムを最初から組むなら、DoEventsは使うべきでなく、

    きちんとイベントドリブンなプログラムを組めと言われていました。

     

    まぁそんな感じで、

    DoEvents=「過去の遺産を生かすための一時的メソッド」と認識していたので、

    .Net1.0を使い始めて、未だにApplication.DoEventsがあるのを知った時は驚きました。

    ネット上にもあちこちにDoEventsを多用したコードがあります。

    何か画期的方法で問題点が解決したのかと調べましたが、

    いまだに当時と同じ問題がありますし、

    それに加え.Net固有のイベントもありますから、さらに複雑な状況になっています。

     

    今のプログラマは初めからWindowsのイベントドリブンなので、

    DoEventsを知らなくても十分プログラミングできそうなのですが、

    2度押し防止のためのキュークリアや、処理時間がかかる時のストレス緩和のために、

    DoEventsを使うという、応急処置的方法で使うことが多いようです。

     

    DoEventsを使わないで済まそうと思うと、

    イベントハンドラをDoEventsの所で二つに分け、状態を何処かに保存しなくてはいけません。

    その手間を考えると、DoEventsは便利なのでしょう。

     

    ネットに転がってるコードはサンプル程度がほとんどですから、

    あまり問題は起こってないようですが、

    DoEventsやモーダルダイアログの問題をきちんと認識して使ってる例は見当たりません。

    というか、問題を正しく認識すると、対処がかなり難しいのもわかるので、

    本当に簡単な場合しか使えないはずです。

     

    昔からの経験、いやな思い出などのせいで、私の中ではDoEvents=ダメと直結してます。

    ほんとはフィードバックに、「DoEventsは削除して欲しい」と投稿したいくらいですが、

    便利だと言う人がたくさんいると思いますし、

    「私は使わないし、使われると困るし、見てると不快」

    という理由では削除してくれないと思いますから、我慢してます。

     

    違う意見をお持ちの方は是非聞かせてください。

     

    私の説明で納得できた方は

    使ってる人にやめたほうがいいと勧めていただければと思います。

    そのうちObsoleteになってくれるかな、と期待してます。

     

    #問題が発生したといわれても、DoEventsを使ったコードなんてとてもデバッグできないのですよ。

    #そんなコードは捨てて、きちんと作り直すべきなのですよ。

     

     

    以下、関係ない雑談です。

    この場では誰も強制する権限はありませんが、権限はなくともきちんと論理が通っていれば、それは多くの人に受け入れられると思います。だから有名人だろうがなんだろうが関係なく、その発言の背景にしっかりしたものがあるかどうかだと思います。

    少なくとも私にとってこのスレは大変有意義なものになりましたし、勉強にもなりました。大変感謝しております。

    本来はそうあるべきなのですが、人間の思考力・時間は有限です。

    思考がめんどくさい時はブランドに頼るのが常です。

    ここは匿名でなく、しかも発言者の同一性が保証されてますから、

    今日現在☆4つでMVPのtrapemiyaさんの発言は「☆4つでMVP」として

    認識されると思いますよ。

    私は流し読みをするときは、有益な情報の多いであろう☆の多い人、

    名前をよく見かける人の話から読みます。

     

    つまり、☆は勲章であると同時に他者への大きさを表す、責任でもあるわけです。

    Slashdotも似てますが、貢献度というのはうまい仕組みですよね。

    無料で投稿させているのに、貢献度を表示させることで責任を課すこともできるのです。

    だから全ての人によく見えるよう、投稿者名の所についてるんでしょうね。

    投稿した人に感謝を示すだけなら、プロファイルだけに表示すればいいはずで。

     

    いわゆる社会的地位より厄介ですね。リターンが殆どないですから…。

    2007年11月9日 19:16
  •  れい さんからの引用

    DoEventsに何か期待しているようですが、

    DoEventsは普通にメッセージループを回すだけです。

     

    期待している根拠は以下が元になっています。

     

    (1) My.Application.DoEvents メソッド
      http://msdn2.microsoft.com/ja-jp/library/bd65th41(vs.80).aspx

     

    に以下のように書いてあります。

    ----- 引用開始 ---------------------------------------------
    My.Application.DoEvents メソッドがイベントを処理する方法は、フォームとすべて同じではありません。Use multithreading to make the form directly handle the events.詳細については、「Visual Basic におけるマルチスレッド」を参照してください。
    ----- 引用終了 ---------------------------------------------

     

    (2) なぜC#にDoEventsを加えたのか?

     

    VB.NETはVB6からの移行者を想定し、あえてDoEventsを残したのであれば、なぜ、C#にDoEventsを設けたのかにも疑問が残ります。DoEventsの問題は過去からあったわけですから、そのような問題を完全に解決することなく、なぜC#という新しい言語に組み込んだのか・・・。そんなにDoEventsが必要なのか?

     

    (3) 海外の掲示板でMSFTな方がDoEventsを使ったコードを載せている。

     

    focus bug on listview ???
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=56327&SiteID=1

     

    Windows Form Blank Issue
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1776715&SiteID=1

    など。

     

    しかし、私の期待が裏切られていることはいろいろな情報から事実だと判断しましたですので、れいさんの言われるように現時点において私もDoEventsの使用に関しては否定的です。


     れい さんからの引用

    本来はそうあるべきなのですが、人間の思考力・時間は有限です。

    思考がめんどくさい時はブランドに頼るのが常です。

     

    れいさんの言われようとしていることは理解しているつもりです。
    私も海外のMSDN掲示板ではMVPの発言に注目します。しかし、私の場合はまず順番が逆です。海外のMSDNであればまず回答済みを見ます。そこで解決しなければそのスレッドの全発言を読み始めます。最後に回答を見つけることができればそこで終わりです。かつ、そこにMSFTや開発チームの方であることがシグニチャで示されていると大安心です。MVPマークが付いていればまぁ安心です。何のマークが付いていなくても書かれていることはそれなりに信じます。なぜならその人の回答は掲示板という公の場で、しかも活発な掲示板であるため、多くの人の目に触れているからです。もし、間違っていたら、そうではないという回答が付く可能性が高いと思っているからです。

    私の基本的な考え方はここにあって、多くの人の目にふれて、多くの回答者がいる掲示板では、有名人であろうがそうでなかろうが、誤った回答が掲載されたままになる可能性は少ないと思うのです。だから、このMSDN掲示板にもっと活発になってほしいと思うのです。

    事実、このスレッドでれいさんの発言により、DoEventsで解決済みのままになることが無かったわけです。有名人による発言がえらいわけではなく、その発言がえらいのかを判断するのは掲示板を見ている多くの方々であることを言いたかったのです。

     

    だからといって

     

     れい さんからの引用

    今日現在☆4つでMVPのtrapemiyaさんの発言は「☆4つでMVP」として

    認識されると思いますよ。

     

    こうやって認識されることの重みをないがしろにするものではありません。

     

    #私が海外のMSDN掲示板を読む例を出したのは、日本のMSDN掲示板だと基本的に最初から全てに目を通すからです。

    2007年11月10日 4:16
    モデレータ
  •  trapemiya さんからの引用

    期待している根拠は以下が元になっています。

    (1) My.Application.DoEvents メソッド
      http://msdn2.microsoft.com/ja-jp/library/bd65th41(vs.80).aspx

     

    に以下のように書いてあります。

    ----- 引用開始 ---------------------------------------------
    My.Application.DoEvents メソッドがイベントを処理する方法は、フォームとすべて同じではありません。Use multithreading to make the form directly handle the events.詳細については、「Visual Basic におけるマルチスレッド」を参照してください。
    ----- 引用終了 ---------------------------------------------

     

    確かにApplication.RunとDoEventsとForm.ShowDialogでは、

    呼ばれるメッセージは微妙に異なります。

     

    Win16時代は呼ばれるメッセージ、呼ばれないメッセージは直感的に理解できるように思いましたが、

    Win32になってからはメッセージの数もふえ、把握し切れなくなりました。

    動的になにか考慮してるのではなく、

    呼ばれるメッセージ、呼ばれないメッセージが決まってるだけだと思います。

     

    (2) なぜC#にDoEventsを加えたのか?

     

    VBはPeekMessageやDispatchMessageが無かったのでDoEventsがありました。

    System.Windows.Forms.Formの設計はMFCとVB6の間みたいなもので、

    PeekMessageやDispatchMessageはありません。それで加えたのではないかと思います。

    私としてはせめてMicrosoft.VisualBasic内に入れて欲しかったですが、

    別アセンブリだから入りませんし。

     

    #ならいっそのこと無くしてしまえ!

     

    ほんと、そんなにDoEventsが必要なんでしょうか。

     

    (3) 海外の掲示板でMSFTな方がDoEventsを使ったコードを載せている。

     

    当たり前ですが、サンプル程度の短いコードや

    イベントハンドラの最後にDoEventsだと問題になることは少ないです。

     

    よくよく考えて検証した上で使ってる…のではない思います。

    2007年11月11日 3:49
  •  れい さんからの引用

    ほんと、そんなにDoEventsが必要なんでしょうか。

    安定しているかどうかは別として、便利ですからなかなかやめられませんね。

     

    参考までに、 http://www.codeplex.com/metabuildersweb/ に二度押し防止機能付き Web Control ボタンがあります。

     

    2007年11月11日 10:06
  •  れい さんからの引用

    動的になにか考慮してるのではなく、

    呼ばれるメッセージ、呼ばれないメッセージが決まってるだけだと思います。


    そうかもしれませんね。少なくともDoEventsは不具合を起こすという事実があるのですから。

     

     れい さんからの引用

    #ならいっそのこと無くしてしまえ!

    そこなんです。なぜ言語設計でそうならなかったか?なんです。DoEventsが無くても逃げ手はあるでしょうし、最悪P/Invokeもあります。
    その答えは、問題が発生する確率と利便性を天秤にかけたってところでしょうか・・・。最初は何か.NET Frameworkで動的に考慮しているのではないかと思ったのですが、少なくとも不具合を起こしますし、完璧ではないようですね。(または全く動的に考慮していないのかもしれませんし)

    2007年11月11日 16:29
    モデレータ
  •  れいさん、trapemiyaさんありがとうございます。

     すべての処理に2度押し防止をすることはできるけれども使いにくくて仕方がないということですね。遅い処理のみ対処であればクリックイベントの先頭に1行追加するだけなので十分簡素化されていますね。私も目と心をいたわりたいと思います。

     メッセージが流れても問題が起こらない決まりきったイベントが何であるか書いてある資料を私も知りたいです。

    2007年11月11日 23:21
  • 色々、アドバイス、コメントどうもありがとうございます。

    話を伺っていると、結局、ClickOnceのような機能が、WebアプリケーションにあるならWindowsアプリケーションでも

    あってもいいのになと思いました。また、実際には注意すべき(または、習慣で使われているけど実は問題があり得る

    使い方)ことがあるのですね。いやいや難しいです。。

     

    一つ、私のコメントとしては今回の要求事項としてはサンプルにあるように次の2点です。

    1)ボタン二度押し防止: 目的は、2回以上(間違って)ボタンをクリックされたイベントをキャンセルすること。

      Button1.Enabled = Falseでボタン表示をDISABLEにしているのにその間ボタンをクリックするとキューに

      押されたことが溜まり、1回目の実行後に二度目の実行が勝手に行われてしまうという問題でした。

    2)現在ボタンの実行中の状態をボタン表示をDISABLEにすることでオペレーターに意思表示したい。

      Button1.Enabled = Trueのままだとボタンが押されたのかよくわからないので、オペレーターの二度押し

      を誘うので。

     

    ということで次のコードにしました。前回も、Application.DoEvents()で問題解決と思っていたので、念の為。

     

    Private _IsEventProcessing As Boolean

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        AddHandler Application.Idle, New EventHandler(AddressOf Application_Idle)
    End Sub

    Private Sub Application_Idle(ByVal sender As Object, ByVal e As System.EventArgs)
        _IsEventProcessing = False
    End Sub

     

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        If _IsEventProcessing Then Exit Sub
        Button1.Enabled = False

        _IsEventProcessing = True
        Threading.Thread.Sleep(5000)  ' wait for 5secs
        Button1.Enabled = True

    End Sub

    2007年11月12日 6:51
  • .NET になってからは使わなくなりました。 (Applicationの下にあると気づくのが大変だったというのは秘密)

     

    私も安易にDoEventsは使っちゃいけない、副作用を検証した上で使えと言います。

    一番の動機は、同期なくせにDoEventsのせいでイベントの再入が発生するなんてことです。

    あ、いや、確認はしてませんがありえるだろなみたいな感覚が強いです。

    またイベントの順序が狂ったりしたら大変ですし、逆に発生しなくなるものも大変です。

    #これは、Button.DoEvents ではなく、Application.DoEvents ということ。

     

    私としては使う使わないというよりも、何度実行されても正常な結果になるという実装をするべきだと思っています。

    つまり、ボタン連打大歓迎、と。

    2007年11月12日 7:20
  •  trapemiya さんからの引用

    そこなんです。なぜ言語設計でそうならなかったか?なんです。DoEventsが無くても逃げ手はあるでしょうし、最悪P/Invokeもあります。

     

    それは言語設計ではなくライブラリ設計の問題かと.少なくとも C# チームの人に責任は無いように思います.って Visual Basic か.うーむ.

     

    メッセージハンドラ内でメッセージポンプを起こすことによる再入の問題については,『Windowsプログラミングの極意 歴史から学ぶ実践的Windowsプログラミング!』でも触れられていますね.

    The importance of setting the correct owner for modal UI

    こちらはMessageBox API の owner 引数を NULL にしたときの問題ですが.

    2007年11月12日 9:49
  • エゴの押し付けに成功しましたね。

    だいぶ皆さんに納得いただいたようです。

     

     三輪の牛 さんからの引用

    すべての処理に2度押し防止をすることはできるけれども

     よしろう さんからの引用

    2)現在ボタンの実行中の状態をボタン表示をDISABLEにすることでオペレーターに意思表示したい。

    この辺、コードを読めばわかりますし自由に応用すればいいのですが、

    どのボタンを押したときに、どのコントロールが無効になるのか、

    っていうのをきちんと作るとわかりやすいですよね。

    私は重い処理をしてるときはフォーム自体が無効になって欲しいので、

     

    コード ブロック

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            AddHandler Application.Idle, New EventHandler(AddressOf Application_Idle)
        End Sub

        Private Sub Application_Idle(ByVal sender As Object, ByVal e As System.EventArgs)
            _IsEventProcessing = False
            Me.Enabled = True
        End Sub

        Protected ReadOnly Property IsEventProcessing() As Boolean
            Get
                Return _IsEventProcessing
            End Get
        End Property

        Protected Sub SetEventProcessing()
            _IsEventProcessing = True
            Me.Enabled = False
        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            If IsEventProcessing Then Exit Sub Else SetEventProcessing()
            Threading.Thread.Sleep(5000)  ' wait for 5secs
        End Sub

     

    としてるようです。

    重い処理をするボタンだけ、先頭に1行入れるんですね。

    物によってコンテナだけ無効化したり、自分だけ無効にしたり、

    デザインに合わせて適当にすればいいですね。

     

    ただ、これはめんどくさいときです。

    重い処理はやはり別スレッドでやるべきです。

     

     まどか さんからの引用

    一番の動機は、同期なくせにDoEventsのせいでイベントの再入が発生するなんてことです。

    同期/非同期と再入と全く別の概念ですから、再入自体は仕方ないんですがね。

    その再入が、「自分で作ったコード」なら

    再入が起こりうるメソッド・プロパティなのかは考えて作れますから、

    私としては使う使わないというよりも、何度実行されても正常な結果になるという実装をするべきだと思っています。

    つまり、ボタン連打大歓迎、と。

    という風に作るのも簡単です。

     

    Controlの場合コードが見えませんので再入してよいか調べるのが大変で、皆さん苦労してるはずです。

    「SelectionChangedイベントでもう一回Selectしちゃまずいのかな?」とか。

    DoEventsを使うとそれが全てのイベントに広がってしまうので、

    どのイベントにどの順番で再入するのか調べようがない、というのが問題なわけです。

     

    #ところで、別スレッドで重い処理をするなら、

    #2度押しの問題は自動で解決される場合が多いですよね。

    #やっぱり別スレッドにしたらどうですか?

     

     Nyaruru さんからの引用

    それは言語設計ではなくライブラリ設計の問題かと

    よくわかってない上の人からの圧力とかあって泣く泣く入れたんではないか、とか、

    想像すると楽しいです。

    2007年11月12日 10:47
  •   よしろうさん、老婆心ながら、クリックイベントの中は例外処理してFinallyの中でボタンを有効にした方がよいです。もしFinallyなしで例外が起こればボタンが二度と押せなくなるので。サンプルのなので省略なさっているのかも知れませんが。

    コード ブロック

      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        If _IsEventProcessing Then Exit Sub
        _IsEventProcessing = True
        Button1.Enabled = False
        Try
          Threading.Thread.Sleep(5000)  ' wait for 5secs
        Catch ex As Exception
          '適切な例外処理
        Finally
          Button1.Enabled = True
        End Try
      End Sub

     

    2007年11月12日 13:52
  •  NyaRuRu さんからの引用

    それは言語設計ではなくライブラリ設計の問題かと.少なくとも C# チームの人に責任は無いように思います.って Visual Basic か.うーむ.

     

    確かにライブラリありきですから、切り捨てられなかったのかもしれませんね。(^^;
    ヘルスバーグ氏に聞いてみようがまたあれば、雑談レベルで質問してみようかな。

    2007年11月12日 15:45
    モデレータ
  • 三輪の牛さん、皆様、コメントありがとうございます。

    例外処理は私も入れなきゃなと丁度思ってたところです。

    確かにエラーが途中で起きた場合に、ボタンが二度と押せなくなりますね。

    2007年11月13日 1:48
  • れいさん、

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

    なるほど、思い処理の場合は、フォーム自体を無効にしてかわすわけですね。

    今回は重い別プロセスは裏で走らせて、強制中断ボタンを置きたいのでフォーム

    は有効のままでいこうかと思います。でもアイデアとして参考にさせて頂きます。

    重い処理の場合は、説明のあるように別プロセスかスレッドですよね。

     

    今回はこの件は解決ということで閉めたいと思います。でもとても勉強になりました。

    ありがとうございます。追記はいつでも大歓迎ですので(笑)

    2007年11月13日 3:42
  •  

    1点、問題がある事に気がつきました。追加の質問です。

    処理は解決したのですが、Form1_Load() は二重起動防止(ミューテックス使用)

    で使っていて;

    Form1_Load( ...) Handles MyBase.Load

     

    この場合、今回の二度押し防止用の定義での;

     

    Form1_Load( ...) Handles MyBase.Load

      AddHandler Application.Idle, New EventHandler(AddressOf Application_Idle)

    の指定が重複してしまいます。

    単純にForm1_loadの名前をForm2_loadなどに変更するだけでよいのでしょうか?

    Form1_loadは、My PorjectでStartup form:として指定してあります。

     

    宜しくお願いします。

    2007年11月14日 2:09
  • こんにちは。

    二重起動防止は無駄のないように、もっと前の段階で実施しておくべきではないでしょうか。

    アプリケーション単位の話ならば、エントリ ポイントに実装するでしょうし、Form 単位での話なら、
    その Form のインスタンスが作られる前に判断すべきことだと思います。

     

    なので Load イベントに実装することにはならない...

    2007年11月14日 2:53
  • じゃんぬねっとさん、

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

    じゃんぬねっとさんのサイトの「プロセス」「二重起動を確実に禁止する」項が

    お勧めの方法ということですね。

    http://jeanne.wankuma.com/

     

    実際に書いて実行してみたのですが、スミマセンMain()の実装場所がわかりません。

    VB2005で、Public Class Form1以外使ってなかったので。。

     

    VB.NET のサンプルを拝見したのですが;

     

    <System.STAThread()> _

    Protected Shared Sub Main()

        Mutexの取り扱い

      ...

    End Sub

     

    Public Class Form1

        Formの取り扱い

      ...

    End Class

     

    と書いてみましたが、

    Statement is not valid in a namespaceエラーになってしまいます。

    Classの中にmain()を入れるとコンパイルは通るけど動作しないですし。

     

    Form起動前に二重起動防止を行えばよいとのことですが、Form1_loadは

    My PorjectでStartup form:としてLOADイベントに指定してあるのでよいかとは思ったのですが。。

    (また、前述の通りForm2_loadと追定義したら動作したのですが、これが正しいかどうかは??)

    宜しくお願いします。

    2007年11月14日 3:37
  • おっと、NewメソッドでLOADイベントよりも先に処理を記述することもできるの

    ですね。ちょっとまずは書いてみます。(VB2005 Windows Formアプリケーション)

    2007年11月14日 4:33
  •  よしろう さんからの引用

    おっと、NewメソッドでLOADイベントよりも先に処理を記述することもできるの

    ですね。ちょっとまずは書いてみます。(VB2005 Windows Formアプリケーション)


    New メソッドではなくてコンストラクタと言います。
    そこではなくて、やはりエントリ ポイントでの記述が望ましいです。


    エントリ ポイントを書くには、プロジェクトのプロパティから 「スタートアップ」 を Sub Main にしなくてはなりません。

    なお VB2005 の場合は、アプリケーション フレームワークで二重起動の防止がノンコーディングでできます。

    VB2005 をお使いのようですので、アプリケーション フレームワークに頼られた方がよろしかろうと思います。

    2007年11月14日 4:54
  • なるほど、Sub Mainをスタートアップで指定するのですね。(汗)

     

     じゃんぬねっと さんからの引用

    エントリ ポイントを書くには、プロジェクトのプロパティから 「スタートアップ」 を Sub Main にしなくてはなりません。

     

    今回はご指摘の方法が簡単だと思うのでアプリケーションフレームワークの設定を使ってみます。

     

     じゃんぬねっと さんからの引用

    なお VB2005 の場合は、アプリケーション フレームワークで二重起動の防止がノンコーディングでできます。

    VB2005 をお使いのようですので、アプリケーション フレームワークに頼られた方がよろしかろうと思います。

     

    これは知りませんでした。今回はVB2005ですので、アプリケーションフレームワークでの二重起動

    設定を使用したら動作しました。

     

    プロジェクト -> Processのプロパティ -> 単一インスタンスのアプリケーションを作成する、にチェックを入れる

     

    どうもありがとうございます。

    2007年11月14日 5:17