[[20181121003859]] 『(マクロ) Exit Subについて』(マイン) ページの最後に飛ぶ

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

 

『(マクロ) Exit Subについて』(マイン)

お世話になっております。

質問は

マクロAで、Exit Subで抜けた場合
マクロBも含めてマクロを終了したい

UserFormを用いて処理をしています。
ユーザーが正しい操作をしない場合はExit Subで処理中止し
メッセージを出すと言ったものです

しかし、呼び出し先のマクロ内で、Exit Subが出ても、移行の処理は続けられます。

試しに マクロA の  If a < 1 Then End
とすると、全てのマクロは停止(メッセージ、フォームも)してしまいます。

みなさまどうかアドバイスの程よろしくお願い致します。

Sub 呼び出し先マクロ()

 Call マクロA

 Call マクロB

End sub

↓呼び出し元↓

sub マクロA()
 If a < 1 Then

   MsgBox "処理を中止します"
   Exit Sub
  End If
end sub

sub マクロB()
 (処理)
end sub

< 使用 Excel:Excel2010、使用 OS:Windows7 >


 マクロBもマクロAと同じ条件でExit Subする。のは
どうでしょうか。
0.パラメータでBに渡す(参照渡し?)
1.変数aを作業用セルに変え共有する
2.パブリック変数にする(不安要素ありかも ^^;)
3.ファイルに書込Bから読み込む
4.レジストリ、使う

 0.、1.がとても安全、確実 ^^ 3.めんど〜
4.はとてもむつかしいし、危険がいつぱい  (私は解かりません。)^^;
かな〜〜〜〜と想像します。 m(_ _)m

(隠居じーさん) 2018/11/21(水) 07:25


編集かぶりましたが、とりあえずそのまま。

ステップ実行で動きを追ってみるとわかりますが、呼び出し先のプロシージャが終われば呼び出し元に戻る仕様です
<参考;ステップ実行>
https://www.239-programing.com/excel-vba/basic/basic023.html

また、Endステートメントはすべてのプログラムを終了させる命令です。
<参考;Endステートメント>
http://officetanaka.net/excel/vba/statement/End.htm

なので、そのような構成にしたいのであればば呼出"元"で条件分岐させてください。

    Sub 呼出元マクロ()
        If a < 1 Then
            Call マクロA
        Else
            Call マクロB
        End If
    End Sub

(もこな2) 2018/11/21(水) 07:26


隠居じーさんさんの投稿を拝見して。

参照渡しでフラグを渡してしまうのもありですね。

    Sub 呼出元マクロ()
        Dim 終了フラグ As Boolean

        Call マクロA(終了フラグ)

        If 終了フラグ Then Exit Sub

        Call マクロB

    End Sub

    Sub マクロA(ByRef フラグ As Boolean)
        Dim a As Long

        If a < 1 Then
            フラグ = True
            MsgBox "処理を中止します"
            Exit Sub
         End If
    End Sub

【参照渡し】
http://officetanaka.net/excel/vba/tips/tips94.htm

また、複数のプロシージャから同じ変数を使いたいのであれば、グローバル変数にするという手もあります。
【グローバル変数(モジュールレベル変数)】
http://officetanaka.net/excel/vba/variable/05.htm

(もこな2) 2018/11/21(水) 07:41


みなさま 

朝早くからありがとうございます。

説明不足でしたので追加させてください。

マクロAですが、処理内容はエラーチェック用に作成しました。

条件によって、Exit Subで抜ける
If a < 1 Then
のようなものが5つほどあります。

あれから、ネットで探していたところ

まさに、やりたいことのコードをを見つけました。
しかし、x.text>0 then でオブジェクトが見つかりませんと弾かれました。

https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11117180951

Private sub test()
dim chck As Boolean
Hantei(chck)
If chck then
Msgbox("Exit で抜けました")
Exit Sub
Else
Msgbox("通常に抜けました")
End If
End sub

Private sub hantei(chck As Boolean)
dim x as Variant
If x.text>0 then ←エラー
chck = True
Exit sub
Else
chck = False
End if
End sub
(マイン) 2018/11/21(水) 09:04


 おはようございます。

 前回は不発だったので、、スミマセン(笑)
 私のほうからは、まだ誰からも提案のないやり方を一つ。。

 Subから処理の成否を返せるFunctionに変更してはどうでしょうか?
 この方法はかなり一般的なやり方だと思います(^^)

    Option Explicit

    Sub 呼出元マクロ()

        If Not マクロA() Then ''マクロA() = Falseでも可
            MsgBox "処理を中止します"
            Exit Sub
        End If

        Call マクロB

    End Sub

    ''Subから値を返せるFunctionに変更します
    Function マクロA() As Boolean

        Dim a As Long

        ''この行はなくてもマクロAの初期値はFalseだが年の為に(^^)
        マクロA = False

        If a < 1 Then Exit Function

        ''色々な処理
        ''・・・

        ''処理が正常終了したら
        マクロA = True

    End Function

    Sub マクロB()
      ''(処理)
    End Sub

 判定箇所が何個もあるとのことですが、
 正常終了したと思われるところにマクロA = True を書いておけば
 途中で抜けた場合は、マクロAの値は全てFalseになり処理を中断出来ますね!

(みそじのおじさん) 2018/11/21(水) 09:11


 たぶんActiveXのテキストボックスがあってその名前がxなのだろう。
 そしてxが0より大きければExit subするのだろう。
 なので、
 >If x.text>0 then 
 部分が処理をマインさんの判断基準に置き換て見てくれ。
(ねむねむ) 2018/11/21(水) 09:17

むねむね さん

投稿から10分足らずでアドバイスありがとうございます。

皆さんのを参考に挑戦して結果をご報告いたします。
(マイン) 2018/11/21(水) 09:21


実際のコードで確認しました。

はじめてのことでよく分からずにアドバイスコードを記述しまいたが

「配列には割り当てられません」とでます。

     '★初期値を念のためにFalseにする
    リストボックス_エラーチェック = False 

    '★処理完了後にTrueにすれば、読み込みマクロ先で本マクロでエラー抜け時に中断可能
    リストボックス_エラーチェック = True 

すみません、アドバイスをお願いできますでしょうか

'【重要】ファンクション化
'リスト選択内容と実在するシートを選択したうえで所定の処理を実施

Function リストボックス_エラーチェック() As Boolean()

    Dim List選択 As Long, List数 As Long  'リスト選択有無チェック
    Dim j As Long    'ListBox内の値をReDim Preserveで追記
    Dim i As Long    'ListBox用カウンター
    Dim strSelect() As String    'ListBox内の値を格納
    Dim ii As Long    'Sheetエラー用カウンター
    Dim jj As Long  'Sheetエラー値をReDim Preserveで追記
    Dim msg As String  'Sheetエラー用
    Dim strError() As String  'Sheetエラー値を格納

     '★初期値を念のためにFalseにする
    リストボックス_エラーチェック = False ←配列には割り当てられません。とでます。

    With ListBox1
        '------------------------------------------------
        '★リストを選択しているかチェック
        For List選択 = 0 To ListBox1.ListCount - 1
            If ListBox1.Selected(List選択) = True Then
                List数 = List数 + 1
            End If
        Next
        If List数 < 1 Then      '格納値があれば
            MsgBox "リストを選択してください", vbOKOnly, "リスト未選択"
            Call 全てのフォーム色を通常に戻す
            Call Label_wの制限解除
            Exit Function
        End If
        '------------------------------------------------
        '★Listが選択状態であれば下記処理
        j = -1    'ListBox内で選択された項目
        jj = -1    'ListBox内の存在しないSheet抽出
        For i = 0 To .ListCount - 1    'List内のリスト数分ループ
            If .Selected(i) Then    '選択行ならば
                j = j + 1    'リスト項目の取得と格納
                ReDim Preserve strSelect(j)
                strSelect(j) = .List(i, 1)    'List値(行,列)=シート名取得
                '------------------------------------------------
                '★シート存在チェック 【重要】Function shCheck(name As String)と連携
                If Not (shCheck(strSelect(j))) Then
                    jj = jj + 1    'エラーSheet名取得と格納
                    ReDim Preserve strError(jj)
                    strError(jj) = strSelect(j)
                End If
                '-------------------------------------------------
            End If
        Next i
        '-------------------------------------------------
        '★シート存在チェック(存在なし:メッセージ表示とExit Sub)
        If jj > -1 Then
            For ii = LBound(strError) To UBound(strError)
                msg = msg & strError(ii) & vbCrLf    'msg に配列の中身を連結しながら格納
            Next ii
            MsgBox "【選択された記録が見つかりません】" & vbCrLf & _
                   vbCrLf & msg & vbCrLf & "※リストとシート名を確認してください", vbOKOnly, "Sheetエラー"
            Call 全てのフォーム色を通常に戻す
            Call Label_wの制限解除
            Exit Function
        End If
        '-------------------------------------------------
        '★格納したシート名を選択し形成処理
        If j > -1 Then    '格納値があれば
            Worksheets(strSelect).Select    'リスト名と同じシート名を選択する
        End If
    End With

    '★処理完了後にTrueにすれば、読み込みマクロ先で本マクロでエラー抜け時に中断可能
    リストボックス_エラーチェック = True

End Function

(マイン) 2018/11/21(水) 10:55


 >Function リストボックス_エラーチェック() As Boolean() 
 を
 Function リストボックス_エラーチェック() As Boolean
 に。 
(ねむねむ) 2018/11/21(水) 11:10

むねむね さん

取り急ぎ報告です。
動きました。

このコードを使い回しすれば、不要なコードの削減に繋がります。
すごいです。

もうしばらく、弄りたいと思います。
(マイン) 2018/11/21(水) 11:34


 参考にしてください
 インプットボックスで1と入力された時は意図的にエラーを発生させて
 Macroの最後の行まで飛びます。
 それ以外の場合は正常に動作します。

 条件分岐の部分を直せばそのまま使えると思います。
 考えてみてください。

 ------------------------------------------------------
    Sub Macro()
    On Error GoTo LINE
        Call A
        Call B
    LINE:
    End Sub

 -------------------------------------------------------    

    Sub A()
        If InputBox("入力") = "1" Then Err.Raise 1
    End Sub

 -------------------------------------------------------    

    Sub B()
        MsgBox "Hallo"
    End Sub

 -------------------------------------------------------
(TAKA) 2018/11/21(水) 11:41

ちょっと訂正

 ------------------------------------------------------
    Sub Macro()
    On Error GoTo LINE
        Call A
        Call B
    LINE: On Error GoTo 0
    End Sub

 -------------------------------------------------------    

    Sub A()
        If InputBox("入力") = "1" Then Err.Raise 1
    End Sub

 -------------------------------------------------------    

    Sub B()
        MsgBox "Hallo"
    End Sub

 -------------------------------------------------------
(TAKA) 2018/11/21(水) 11:43

TAKA さん

ありがとうございます。
超シンプルです。

Err.Raise 1を加えるだけで!!

しかも、正常に動きました。

        If List数 < 1 Then      '格納値があれば
            MsgBox "リストを選択してください", vbOKOnly, "リスト未選択"
            Call 全てのフォーム色を通常に戻す
            Call Label_wの制限解除

            Err.Raise 1 'Exit Sub

        End If

(マイン) 2018/11/21(水) 12:05


追加で

Err.Raise 1

とは、IF文で何らかのエラーが発生したら、あえて

「1」のエラーを発生させる

→アプリケーション定義またはオブジェクト定義のエラーです。”のエラーが発生

それを、取得して On Error GoTo LINE につなげるという認識でよろしいでしょうか?

(マイン) 2018/11/21(水) 12:13


 はいその通りです。
 お役に立ててよかったです。
(TAKA) 2018/11/21(水) 18:07

隠居じーさん
ねむねむ さん
もこな2 さん
TAKA さん

皆様ありがとうございました。
無事に完動しました。

おかげさまで、コード全体の縮小化できました。

解決です。
(マイン) 2018/11/21(水) 20:55


 私は、みそじのおじさんの案がまっとうだと思っていたのですがねぇ・・

(半平太) 2018/11/21(水) 22:01


 皆様、こんばんは。

 解決したとのことで不要かとは思いますが。。
 私も、もちろんErr.Raiseさせる時もあるのですが、一つだけ注意点があります。
 マインさんのこのファイルは御自身一人でで使う前提ですか?それとも配布予定ですか?

 エラートラップには各PC毎に(個人個人のExcel毎に)設定が異なっていて
 その設定が違えば意図しないエラートラップになり期待した効果が得られない場合があるので注意が必要ですね!

 VBEのエディタに
 ツール
 オプション
 全般タブに
 エラートラップ という項目があり
 ・エラー発生時に中断
 ・クラスモジュールで中断
 ・エラー処理対象外のエラーで中断

 この3つがあり、デフォルトでは確か3番目のエラー処理対象外のエラーで中断になっているはずですが
 ここを他の二つに設定していると、場合によってはマインさんが設定したエラートラップに飛ばなくなります。エラーの旨を示すメッセージボックスが現れ終了ボタンを押してしまうとそこで処理が終了します。

 このエラートラップの設定はファイルを配布しても同じ設定にはなりません。
 これは各Excelアプリケーション独自の設定なのです。

 私の経験ですが、配布先でこのエラートラップの設定が私と異なり、エラー発生後の重要な後処理を行えずに
 処理が終了してしまってデータベースに甚大な被害を与えてしまったという苦い思い出があります^^;

 もし配布するということであれば、配布先のエラートラップの設定を確認する事をお薦めしますね(^^)

 ・エラー発生時に中断
 これになっていると問答無用でエラーのトラップは出来ません!!
 ただし、エラーの発生場所を突き止めるには一番良い設定です。これはデバック用の設定ですね。

 ・クラスモジュールで中断
 ユーザーフォームやクラスモジュールを使用している場合にデバック用に設定する事がありますが
 メインの呼び出し側が標準モジュール側で、On Errorを仕掛けても実際の処理がユーザーフォームモジュール内になっていたりするとエラーのトラップが出来ません!要注意です。

 ・エラー処理対象外のエラーで中断
 On Error を使っていない箇所だと中断してしまいますが、マインさんのコードですと
 この設定になっていないと駄目だと思います(^^)

 あーだこーだ書きましたが
 私もエラートラップの設定を確認してErr.Raise "は”"使っています!
 設定さえ合っていれば便利なものですね(^^)

 ちなみにErr.Raiseはマインさん独自の番号を割り当てが出来て、メッセージも適切なものに変更できますよ!
 参考のURL貼っておきますね。
 https://qiita.com/ririn_yume/items/13d3b7aad7bc49411eb7

(みそじのおじさん) 2018/11/21(水) 23:18


みそじのおじさん

お世話になっています。
エラーについてのアドバイス感謝します。

・エラー処理対象外のエラーで中断

の部分ですが、当初のアドバイスコードでは問題なかった処理が、
Err.Raise導入で、予期せぬ部分で処理がストップする事態に陥っていました。

関係するコード全てに対しOn Errorを加えてとりあえず回避することができました。

当初のコードとErr.Raiseはよく考えて使わければと後々気づきました。

全体をもう一度見直し、どちらの処理が適しているか考えたいと思います。

(マイン) 2018/11/22(木) 07:59


 >関係するコード全てに対しOn Errorを加えて

 これもピンとこないですねぇ・・

 旨くいったならこれ以上言うのは野暮ですが、一般論として言わせていただくと、
 ここで質問している人のレベルでOn Errorを書きまくるなんて、
 とてもまともなものに仕上がるとは思えないです。 

 老婆心からです、悪しからず m(__)m

(半平太) 2018/11/22(木) 09:01


おはようございます。

そうですね、後付でErr.Raiseするとそういう状態に陥ることはありますね^^;

私は"一般的に"とやわらかく表現しましたが、本当は"Functionにするべき!"
と書きたかったのが本音です(笑)

超私見ですが、色々なやり方はありますがほとんどの方がFunction派だと私は
勝手に思っています(^^)

 前回ご紹介したAPIのSetTimer関数ですが
 Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, _ 
                                        ByVal nIDEvent As Long, _
                                        ByVal uElapse As Long, _
                                        ByVal lpTimerFunc As Long) As Long

タイマーのセットに成功したらLong型のタイマーID(0以外)、失敗すれば0が返されるという
仕様になっています。ですのでユーザーは0が返ってくれば失敗した!という認識で
後処理をすれば良いとなります。
これはWindowsがプログラマに対して公開しているAPIですので
やはりFunction化は”一般的”と言えるのではないでしょうか?

#私はVBAをやられている方とメールのやり取りをする事がありますが、
#この件については、「やはりFunction化だね!」とか「テスト環境に限ってErr.Raiseを使用する」
#なんて話も聞きましたね(^^)

(みそじのおじさん) 2018/11/22(木) 09:07


命令が正常に(最後まで実行されたか)という結果を返したいのですから、みそじのおじさんの仰るように、Functionにするのが一般的です。

VBAはSubが標準なのでFunction使えない方が多いのですが、関数型言語では常識中の常識です。
C言語なんかでは真っ先に覚える話です。
int main(){return 0;} は正常終了
int main(){return 1;} は異常終了(であることを呼び出し元に通知する)

Err.Raiseはクラスなんかで様々なパターンのエラーを呼び出し元に通知する時に使うくらいで、初心者が使うようなものではないと私は考えます。
(名無し) 2018/11/22(木) 09:46


 >Err.Raise導入で、予期せぬ部分で処理がストップする事態に陥っていました。 

 予期せぬ部分でストップ(GoTo LINE)してしまうということは、
 Err.Raiseを書く場所が間違っているか、条件分岐が正しくない、
 もしくはErr.Raise以外の部分で別のエラーが発生しています。
 ステップ実行でマクロの動きを確認した方がいいです。
 コードを見直してください。

 >関係するコード全てに対しOn Errorを加えてとりあえず回避することができました。 

 On Error はエラーになる箇所や原因を理解している上で、想定内のエラーに対して使うものです。
 よく分からないけどとりあえず回避する、というように安易に使うべきではありません。
 修正しないといけない重大なエラーに気づかず、取り返しの付かないことになる可能性があります。  

 ステップ実行などで、何故予期せぬ部分でストップしてしまうのか分からなければ
 この方法を無理矢理使うのはオススメできません。
 こんなやり方もあるんだな、と参考程度にして、一般的な方法でやってみてください。

 ちなみに、配列には割り当てられませんと出るのは
 Function リストボックス_エラーチェック() As Boolean() ←この最後の括弧が原因です。
(TAKA) 2018/11/22(木) 10:16

コメント返信:

[ 一覧(最新更新順) ]


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