[[20150512163427]] 『Userformから脱出』(まー) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『Userformから脱出』(まー)

 初歩的な質問で申し訳ありませんが教えてください。
 標準モジュールからユーザーフォームを呼び出しコマンドボタン2(キャンセル)をクリックするとユーザーフォームを閉じるようにしています。
 標準モジュールではUserform1.showの構文あとにユーザーフォームで処理したあとの処理がつづきます。

 sub test ()
 Userform1.show
 '後処理
 end sub

 Private Sub CommandButton2_Click()
 Unload Me
 End Sub

 ユーザーフォームでキャンセル処理後は標準モジュールの後処理も行わず終了したいのですがどのようにしたらよいですか? 

< 使用 Excel:Excel2002、使用 OS:WindowsXP >


 End つっこむのが一番ラクです
 でもできれば、キャンセルしない場合に最終的に押下するボタンの処理のあとに後処理を続けて書いてください
 その方が後々見やすい……と思っているのは私だけかもしれないですね…

(もあ) 2015/05/12(火) 17:11


 脱出というか、ボタンを押されたら処理を呼び出す、という形がベターだと思います。
 標準モジュールにユーザーフォームを呼び出すだけの処理と、ボタン1を押したときの処理
    Option Explicit
    Sub test1()
        UserForm1.Show
    End Sub
    Sub btn1()
        'ここにボタン1が押された時の処理を書く
    End Sub

 ユーザーフォームはボタン1のときに、標準モジュールのbtn1を呼び、
 ボタン2のときはただunloadするだけ
    Private Sub CommandButton1_Click()
        Call btn1
        Unload Me
    End Sub
    Private Sub CommandButton2_Click()
        Unload Me
    End Sub

 が定番の処理ではないでしょうか?
(稲葉) 2015/05/12(火) 17:27

 説明はしないけど、ユーザーフォームでプライベートスコープの変数を監視して
 標準モジュールに返すやり方・・・は煩雑すぎるよね

 標準モジュール
    Option Explicit
    Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    Sub test2()
        If UserForm1.ShowForm Then
            MsgBox "ボタン1が押されました"
        Else
            MsgBox "キャンセルされました"
        End If
    End Sub

 UserForm1
    Option Explicit
    Private IsClick  As Boolean
    Private IsCancel As Boolean
    Public Function ShowForm() As Boolean
        IsClick = False
        Me.Show vbModeless
        Do Until IsClick
            Sleep 10
            DoEvents
        Loop
        ShowForm = Not IsCancel
        Unload Me
    End Function
    Private Sub CommandButton1_Click()
        IsClick = True
        IsCancel = False
    End Sub
    Private Sub CommandButton2_Click()
        IsClick = True
        IsCancel = True
    End Sub
(稲葉) 2015/05/12(火) 17:31

 私見ですけど、(もあ)さんの 「End」をつっこむ方式は、あとあと、保守フェーズで苦労しそうです。
 コードの可読性、保守性の観点から、私個人としては【絶対に End は書かない】主義です。

 (稲葉)さんの最初の提言、処理は(そのコードが標準モジュールに書かれていたとしても)ユーザーフォームが閉じられる前に
 実行しておくというのが素直な方法ではないでしょうか。

 その他には、このユーザーフォームはモーダル表示ですから、

 With UserForm1
   .Show
   ここで、UserForm1内のなんらかの参照できるもの(Property仕立てにしたものとか、ユーザーフォーム内に規定したPublic変数とか、等々)を取得
 End With

 取得した値に基づいて分岐処理。

 といったことも考えられます。

(β) 2015/05/12(火) 17:51


 βさん
 なるほど!
 わざわざ監視しなくても、いいわけですね。
 Hideで次の処理に行けるの、実験してて初めて知りました(汗

 UserForm2
    Option Explicit
    Public IsCancel As Boolean
    Private Sub CommandButton1_Click()
        IsCancel = False
        Me.Hide
    End Sub
    Private Sub CommandButton2_Click()
        IsCancel = True
        Me.Hide
    End Sub
    Public Sub UnloadMe()
        Unload Me
    End Sub

 標準モジュール
    Sub test3()
        With UserForm2
            .Show
            If .IsCancel Then
                MsgBox "キャンセルされました"
            Else
                '通常の処理
            End If
            .UnloadMe
        End With
    End Sub
(稲葉) 2015/05/12(火) 18:36

 ユーザーフォームのxボタンを押された時の対処のために
 Query_Close イヴェントを私は よく使いますが、基本的には、βさんや稲葉さんの仕様に賛成です。

 ユーザーフォームを表示させてどのボタンが押されたかを呼び出し元のプロシジャーに返すという仕様ですよね

 ユーザーフォームがどのボタンを押されようが その情報は、呼び出し側に返すという仕様にすることを
 考えてください。 返されたボタンによって分岐する処理は、よびだし側に任せてしまえばよいのです。

 ユーザーフォーム側で判断する必要はありません。

 何度か引用する言葉ですが、「卵下さい と言われたら 卵を返せばよいのです ゆで卵にして返してはいけません」 
 私が 自身の戒めになっている かつての上司に言われた言葉です。
こんな考え方を片隅に置いて仕様を考えると、汎用的な 結合度の低い オブジェクトを作る 指針になると思います。

 
(ichinose) 2015/05/12(火) 18:53


 横から失礼します。

 もしかしてこういうことですか?

 Sub test()
  Dim f As String
  Dim YN As Integer
   YN = MsgBox("ユーザーフォームを表示しますか?", vbYesNoCancel, "どっちでっか?")
   Select Case YN
    Case vbYes
     UserForm1.Show 0
    Case vbNo
     AppActivate Application.Caption
     MsgBox "いいえ"
    Case vbCancel
     AppActivate Application.Caption
     MsgBox "キャンセル"
   End Select
 End Sub

 思いっきり外してそう・・・。
(カリーニン) 2015/05/12(火) 19:05

 To 稲葉さん

 Hide じゃなく Unload でもできますよ。

 Public IsCancel As Boolean

 Private Sub CommandButton1_Click()
    IsCancel = False
    Unload Me
 End Sub

 Private Sub CommandButton2_Click()
    IsCancel = True
    Unload Me
 End Sub

 で、標準モジュールに

 Sub Test()
    With UserForm1
        .Show
        MsgBox .IsCancel
    End With
 End Sub

(β) 2015/05/12(火) 19:25


 >Hide じゃなく Unload でもできますよ。
 この記述、このスレッドで私も知りましたが、
[[20120522093647]]

 出来ようが出来まいが 理屈として Hideですよね

 リンクスレッドでも記述しましたが、 

 ユーザーフォーム上に

 Public aaa as string

 の場合は、値が保持されません。

 hideにしておくのが無難だと思いますよ

 

(ichinose) 2015/05/12(火) 20:11


 To ichinoseさん

 リンクスレッド、ざらっっと拝見。(流し読みですが)

 Hide なら、そもそもが議論の対象にはなりませんよね。
 オブジェクトがなくなるわけではなく、存在しているわけですから。
 ブックやシートを非表示にしても、その参照が(あたりまえですが)できるのと同じですから。

 素人ですので、本職のichinose師匠に、どうこういう立場ではありませんが、その素人的な理解からいえば
 With で、そのフォームオブジェクトを参照している。その参照がおわらないうちは、オブジェクトは消滅しないんだろうと。

 まとはずれかもしれませんが、以下のコードで生成されたDictionaryインスタンス。
 ★でやっているのは、あくまで DicA という変数をクリアといいますか、そこからの参照をやめただけで
 インスタンスそのものは DicB という変数で参照され続けていますので、当然、消滅せず、参照可能。
 これと、おなじことなんじゃないかなぁと。

 Sub Test()
    Dim dicA As Object
    Dim dicB As Object

    Set dicA = CreateObject("Scripting.Dictionary")
    dicA("AAA") = "AAA_DATA"

    Set dicB = dicA

    dicB("BBB") = "BBB_DATA"

    MsgBox dicA("BBB")

    MsgBox dicB("AAA")

    Set dicA = Nothing  '★

    MsgBox dicB("AAA")
    MsgBox dicB("BBB")

    Set dicB = Nothing

    '以下は当然エラー
    MsgBox dicB("AAA")

 End Sub

(β) 2015/05/12(火) 21:54


 もう1つ、逆の経験談で、Unload の場合、End With の前でも参照ができないケースがあります。

 MsgBox .ComboBox1.Tag なんてのはOKなんですが、MsgBox .Tag とやるとエラーになりますね。

(β) 2015/05/12(火) 22:02


 > Hide なら、そもそもが議論の対象にはなりませんよね。

 そうですよ!!議論というより、HideのコードにUnload Meを出す意味がわかりません。

 現象として 面白い内容であることは リンク先にも記述した通りです。

ユーザーフォーム(UserForm1)にコマンドボタン(CommandButton1)を一つ配置してください。

 userform1のモジュール

 Option Explicit
 Private dat As Long
 Private Sub CommandButton1_Click()
    dat = 1
    Unload Me
 End Sub
 Property Get ans() As Long
   ans = dat
 End Property

 標準モジュール

 Option Explicit

 Sub test1()
    With UserForm1
      .Show
      MsgBox .ans
    End With
 End Sub

 Test1の実行で userform1 が表示 コマンドボタンクリックでエラーになります。

 こういう結果を含んでいるので このような場合に Unloadではなく、Hideにした方が
 良いですと 申し上げたいのです。

 >MsgBox .ComboBox1.Tag なんてのはOKなんですが、MsgBox .Tag とやるとエラーになりますね。

 ご自身でもわかっていると思いますが、このようにエラーになるケースがあるのなら尚更です。

 面白い現象として記述するならよいですが、 

 >>Hide じゃなく Unload でもできますよ。

 と記述すると UnloadでもHideと同じ効果がある と誤解します。

 この例の場合 、 色々なことを想定すれば 記述するなら Hideです。

 私が申し上げたいのは、この一点です。

(ichinose ) 2015/05/12(火) 22:28


 目を離している間に話が・・・

 Unloadでも、Hideと全く同じことはできないが、Unloadで処理する方法もある、ってことでいいんですかね?

 話は変わってしまうのですが
 Withの話の時に、End Withでオブジェクトが解放される・されないの話を見たときに
 じゃあHideの時は? と考えてあえてEnd Withの前にUnloadさせているのですが
 この場合、変数にセットして、使い終わったらNothingのほうがいいのでしょうか?
 いずれにしても、明確に消したほうがいいのかなと思っていたのですが、必要ない・・・?
(稲葉) 2015/05/13(水) 00:48

 >>面白い現象として記述するならよいですが、 
 >>>Hide じゃなく Unload でもできますよ。
 >>と記述すると UnloadでもHideと同じ効果がある と誤解します。
 >>この例の場合 、 色々なことを想定すれば 記述するなら Hideです。

 まぁ、いろいろな考え方、あるいは受け取り方があると思いますので。
 β的には、極端に Unload した後でも Public変数の参照ができるという事象を述べただけす。
 すべてのことができるというようには申し上げていないつもりです。

 また、つっこまれそうですが、↑の稲葉さんの問題提起、明確に Nothing にしたほうがいいかどうかについても
 β的には、そのまま、うっちゃっておきます。
 もしかしたら、インスタンスが、ちゃんと解放されているかもしれなし、あるいはされていないかもしれない。
 「おそらく」当該プロシジャが終了すれば解放されると思っているんですが、それが解放されなかったとしても
 「それが、なんぼのもんじゃ」というのがβの基本スタンスです。

 たとえばミッションクリティカルな処理、かつ、24時間356日連続で稼働保証が必要なものであれば別でしょうが
 しょせん、我々がエクセル上で、VBAで、ちゃちゃちゃ と処理する程度のものであれば、エクセル区画で
 動いているはずの膨大な数のオブジェクトの中で、たとえ1つ、そのまま浮遊していたとしても、めくじらたてるまでのこともないと。

 ですから、βの書くコード、プロシジャの中でSet したObject変数に対する、終了前の Nothing化は、原則なしです。

(β) 2015/05/13(水) 06:12


 > Unloadでも、Hideと全く同じことはできないが、Unloadで処理する方法もある、ってことでいいんですかね?

 私は、今回のUnload Meの使い方、非常に危ういコードだと思っています。
 これをあたかも実用できるというような「Unloadで処理する方法もある」という記述さえ

 大反対です。大^100反対です。

 現象としては、大変興味深いし この現象を解明する 又は、予測する という話なら
 是非やりましょう という事になりますけどね!!

 先のβさんのDictionaryを使ったオブジェクトの参照から、

 ユーザーフォームを Withで修飾しているのと 同じという仮説は、Propertyプロシジャーがエラーなったり、他にも 直下のプロパティがエラーになっている結果から 明らかに違う現象です。

 前回の私の投稿で

 Sub test1()
    With UserForm1
      .Show
      msgbox userforms.count
      MsgBox .ans
    End With
 End Sub

 msgbox userforms.countは、 Unload では、0を返します。

 あやういでしょう?

 本当に単なる想像にすぎませんが、

 With Userform1 という記述で生まれてしまった メモリ上の残骸データが起こした奇跡

 尾崎亜美風に言えば、「疲れ切ったあなたUserformの幻をみていたの♪」(音あまり)だと思っています。

 ちょっと気になったのは、・・・・。

 ユーザーフォームの表示の記述って いくつか方法がありますよね?

 普通に

 userform1.showに始まって、

 dim frm as userform1

 set frm=userfom1

 frm.show

 他にもありますよね!! これらで今度の現象を確認してみたりすると発見もあるかもしれません。

 >じゃあHideの時は?

 With userform1
    .show 'me.hide で戻す
 '返り値チェック
 end with
 unload userform1

 としているコードが殆どです。

 >使い終わったらNothingのほうがいい

 UserForm_Terminateイベントの発生タイミングをこれを先の記述のように
 (ユーザーフォームの表示の記述って いくつか方法)のいくつかの方法で試してみるのもよいかもしれません。Unloadステートメントを改めて見つめなおすのも良い機会だと思います。

(ichinose ) 2015/05/13(水) 06:19


 本件は、是非論からいえば、絶対に ichinoseさんがおっしゃるとおりです。
 βのいってることは、たとえば、甘いものの食べすぎはだめだ ということに対して、たま〜に、ちょっとぐらい
 いいじゃないか というようなもので、どうしても分が悪いです。

 なので、本件からは、これで撤退しますが、

 たとえば、シートの1行目で一番最後に値があるセルを求めたい なんてことがあれば
 βは 平気で MsgBox Cells(1, Columns.Count).End(xlToLeft).Address こんなコードを書くでしょうし
 そういう質問があれば、そう答えます。

 そのことに関して、「いや、それはあやうい。もし、最終列までずらっとデータが埋まっていると正しいセルが取得できない」と
 お叱りを受けたことはありません。

 おそらく、皆さんも、この処理のために、最終列まで埋まっているかという制御をいれるコードは、書かないだろうと思います。

 なぜ、こと、オブジェクトに関しては、「絶対にダメだ!」という反応になるのかが、β的にはよくわからないところです。

(β) 2015/05/13(水) 08:00


 Unloadでも出来てしまう事実があるとして
 私が気になったのは、デストラクタの発生タイミングでした。
 UnLoadとhide、QueryClose、Terminameのタイミングが異なりますので
 気をつけようと思います。

 お二人の意見を聞いて、UnloadやNothingといった1行を「入れないこだわり」を
 捨てるということで、今後も入れていきたいと考えています。

  >ここで、UserForm1内のなんらかの参照できるもの(Property仕立てにしたものとか、
                          ~~~~~~~~↑~~~~~
                  ここでhideのことを指していると思ってました

 ユーザーフォーム
    Option Explicit

    Public IsCancel As Boolean

    Private Sub CommandButton1_Click()
        IsCancel = False
        Debug.Print "1クリック"
        Unload Me
        Debug.Print "Unload"
    End Sub

    Private Sub CommandButton2_Click()
        IsCancel = False
        Debug.Print "2クリック"
        Me.Hide
        Debug.Print "Hide"
    End Sub

    Private Sub UserForm_Initialize()
        Debug.Print "初期化"
    End Sub

    Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
        If CloseMode = vbFormControlMenu Then
            IsCancel = True
            Cancel = True
            Me.Hide
        End If
        Debug.Print "クエリクローズ"
    End Sub

    Private Sub UserForm_Terminate()
        Debug.Print "解放"
    End Sub

 標準モジュール
    Option Explicit

    Sub Test()
       With UserForm1
           .Show
           Debug.Print .IsCancel
           Unload UserForm1
       End With
    End Sub

 ボタン1の順序
    初期化
    1クリック
    クエリクローズ
    解放
    Unload
    False
    (初期化)
    (クエリクローズ)
    (解放)

 ボタン2の順序
    初期化
    2クリック
    Hide
    False
    クエリクローズ
    解放

 ×ボタンの順序
    初期化
    クエリクローズ
    True
    クエリクローズ
    解放

 最終的に質問者さんのやりたいことをほとんどそのままかなえるなら
 このように修正すると良いのでは、という結論になりました。
 ユーザーフォーム
    Option Explicit

    Public IsCancel As Boolean

    Private Sub CommandButton1_Click()
        IsCancel = False
        Me.Hide
    End Sub

    Private Sub CommandButton2_Click()
        IsCancel = True
        Me.Hide
    End Sub

    Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
        If CloseMode = vbFormControlMenu Then
            IsCancel = True
            Cancel = True
            Me.Hide
        End If
    End Sub
 標準モジュール
    Option Explicit

    Sub Test()
        With UserForm1
            .Show
            If .IsCancel = False Then
                '処理
            End If
        End With
        Unload UserForm1
    End Sub
(稲葉) 2015/05/13(水) 10:20


 撤退宣言したのにのこのこと と思いましたので書きこんだレスを消しましたが
 やっぱりアップしておきます。

 Hideなら何も考えることはないとコメントしているとおり、以下でよろしいのでは。
 機能テストですから、Xボタンで閉じられたらどうするんだということは無視しています。
 からっぽのユーザーフォームを表示してフォームクリックで Hide しています。

 Public IsCancel As Boolean
 Private Sub UserForm_Click()
    IsCancel = True
    Me.Hide
 End Sub

 標準モジュール

 Sub test()
    UserForm1.Show
    MsgBox UserForm1.IsCancel
 End Sub

(β) 2015/05/13(水) 15:06


 稲葉さんが記述された
 >この場合、変数にセットして、使い終わったらNothingのほうがいいのでしょうか?

 ユーザーフォームをオブジェクト変数に入れて 試してみました。
 新規ブックにて ユーザーフォーム(UserForm1)を作成し、コマンドボタン(CommandButton1)を配置したものを用意してください。

 UserForm1のモジュールに

 '=============================================
 Option Explicit
 Public aaa As Long
 Private Sub CommandButton1_Click()
    aaa = 2
    Me.Hide
 End Sub
 Private Sub UserForm_Terminate()
    Debug.Print "term"
 End Sub

 標準モジュールには
 '================================================================
 Option Explicit
 Sub test1()
    Dim frm As UserForm1
    Set frm = UserForm1
    With frm
       .Show
       MsgBox .aaa
    End With
    Set frm = Nothing
 End Sub
 test1を実行すると
 Set frm = Nothing
 で、Terminateイベントは発生しません。

 ところが

 Sub test2()
    Dim frm As UserForm1
    Set frm = New UserForm1
    With frm
       .Show
       MsgBox .aaa
    End With
    Set frm = Nothing
    Debug.Print "end"
 End Sub
 test2では、きちんと Terminateイベントが発生します。
 興味深いですよね!!

 これらの事から 私は Unloadステートメントを使う事が殆どです。

(ichinose ) 2015/05/14(木) 05:35


 >なぜ、こと、オブジェクトに関しては、「絶対にダメだ!」という反応になるのか

 今回のUserForm内での Unload Meというコードで呼び出し側に戻り、UserForm内のPublic変数が参照できてしまう現象 そのメカニズムは 今までの投稿から きちんと分かっていませんよね!

 もしかしたら バグの逆効果で参照できているのかもしれないですよね? そうだとしたら、いずれこのロジックの変更を余儀なくされます。 他に方法がないのなら、当面の施しとしてはわからないではありません。

 が、Me.Hideにすれば、問題がないのに敢えて 根拠のないロジックを選択することを反対しています。

 検証が難しい題材なのに何故 と私の方が大きい疑問です。

 >With Userform1 という記述で生まれてしまった メモリ上の残骸データが起こした奇跡

 こんな想像があたっているとしたら 危険コードです。

 Unloadによって、本来なら 削除されクリアされるべきメモリ空間を何らかの原因でデータが残っていて
 参照できてしまう、言わば ライセンスを失ったデータを参照する こんなデータを参照したら
 どんな結果になるか分かりません。 

 UserFormが出力する情報をプロパティ(Publicデータ)として 取得するという仕様する。

 オブジェクトの独立性を高めるための施しです。使い方は、呼び出し側委ねられてもオブジェクトは淡々と
 仕様通りの情報を提供する これを実現するために このような仕様にしているのですよね!!

 私もこの事が重要だと感じているので 色んなスレッドで Public変数の乱用等には、意見を投稿してきたつもりです。

 今回のUnloadステートメント使用について

  新規ブックにて ユーザーフォーム(UserForm1)を作成し、コマンドボタン(CommandButton1)を配置したものを用意してください。

 UserForm1のモジュールに
 '=============================================
 Option Explicit
 Public aaa As Long
 Private Sub CommandButton1_Click()
    aaa = 2
    Unload Me
 End Sub

 標準モジュールには
 '================================================================
 Option Explicit
 Sub test1()
    With UserForm1
       .Show
       MsgBox .aaa
    End With
 End Sub

 test1では、確かに aaaが取得できていますが、

 Sub test2()
    With UserForms.Add("userform1")
       .Show
       MsgBox .aaa
    End With
 End Sub

 Sub test3()
    Dim frm As Object
    Set frm = UserForm1
    With frm
       .Show
       MsgBox .aaa
    End With
 End Sub

 では、エラーが発生します。

 これでは、独立性を高めても意味が薄れてしまいます。 これらのことが 反対した理由です。

[[20120522093647]]

↑ ここでも今回とほとんど同じ話をしています。もう3年前なんです。

 このスレッドでも

 >これは、あるいみあたりまえですね

 Me.Hideで行うロジックに対して 言われた言葉です。

 私は、ごくごく当たり前のことしか言っていないのです。難しい話を展開しているつもりは
 まったくありません。

 これは、テーマが違うのでまた別の場面で意見交換させていただくとして

 >ですから、βの書くコード、プロシジャの中でSet したObject変数に対する、終了前の Nothing化は、原則なしです。

 この件も私は、別意見です。 もっとも私は、よく忘れます。コードレビューでよくツッコまれます。
 自分でNothing入れろ って言っておきながら・・・ と。

( ichinose) 2015/05/14(木) 05:56


 ichinoseさんの2015/05/14(木) 05:35投稿で、Terminateイベントの発生有無確認致しました。
 その後、Set frm = NothingをUnload frmに置き換え、Terminateイベントが発生することも確認できました。
 残った変数をローカルウィンドウで確認すると、Userform1の形で残っていたので、
 Unload後にNothingをSetするのが筋かなぁと思いました。

 当たり前のことですが
 Unload UserForm1 '1
 Unload UserForm1 '2
 とすると、2でIniとTermが走り

 Unload frm '3
 Unload frm '4
 では、4でIniとTermが走りませんでした

 言いたいことが自分でもわかってないですが、自分の中で新しい発見でした。

(稲葉) 2015/05/14(木) 08:50


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.