アプリ版:「スタンプのみでお礼する」機能のリリースについて

どうして、サンプル2じゃダメなのか教えていただけますか?

-------------------------------------------------------------
Sub サンプル1()
'フォームがひとつも開いてない時に実行してもエラーにならない。
Dim intCnt As Integer

'全ての開いているフォームのループ
For intCnt = Forms.Count - 1 To 0 Step -1

'コレクションの intCnt番目のフォームを閉じる
DoCmd.Close acForm, Forms(intCnt).Name

Next intCnt

End Sub

Sub サンプル2()
'フォームがひとつも開いてない時に実行してもエラーにならない。
Dim intCnt As Integer

For intCnt = 1 To Forms.Count
DoCmd.Close acForm, Forms(intCnt).Name
Next

End Sub
-------------------------------------------------------------

サンプル1を実行すると、問題なく全てのフォームを閉じる事が出来ますが、
サンプル2だと、二つ開いているフォームのうち、1つ目は閉じる事が出来るのですが
2つ目のフォームを閉じようとすると、
実行時エラー 2456 "フォームを参照するときに使っている番号が正しくありません"
となってしまいます。

私としては、サンプル1もサンプル2も同じ動きをするものだと思ってるのですが
どうやら違うようです。

どうして、サンプル2じゃダメなのか教えていただけますか?

「現在開いている全てのフォームを閉じるVB」の質問画像

A 回答 (7件)

#7です



Index の最小値は 0 で、途中閉じた場合は 0 スタートの連番で作り直される。
ここまでは良いでしょうか。
復習で、サンプル2の記述を 0 スタートに変更して、フォーム4つの時の動きを追ってみます。

Sub サンプル2()
  Dim intCnt As Integer

  For intCnt = 0 To Forms.Count - 1
    DoCmd.Close acForm, Forms(intCnt).Name
  Next
End Sub

フォームが4つなので For 文は 0 ~ 3 までループします。
(左側 Index、右側フォーム名)
0 フォームA
1 フォームB
2 フォームC
3 フォームD

intCnt = 0 で閉じると
0 フォームB
1 フォームC
2 フォームD

intCnt = 1 で閉じると
0 フォームB
1 フォームD

intCnt = 2 で閉じようとすると、対象の Index が無いのでエラー
これで、何故エラーとなるのか、ダメなのかの説明は終了です。


標題「現在開いている全てのフォームを閉じるVBA」について、

For intCnt = 1 To Forms.Count

を使って、正常時だけを考えれば、以下の様な書き方もできますね。

  For intCnt = 1 To Forms.Count
    DoCmd.Close acForm, Forms(Forms.Count - 1).Name
  Next

どの回答も、標題から脱線したものはないと私は思います。
Index の小さい方から削除すると、こういう事が考えられますよって言ってただけで・・・
#1さんはチョッとした間違いはありましたが・・・・同じ記述でしたからね。
(たぶん、その時 Collection での最小値 1 が一瞬頭に浮かんだ・・・とか)
(あまり記述しない 1 スタート For 文の 1 をそのまま使ってしまった・・・とか)

Index の小さい方から実現しようとすると、かなり面倒です。


質問者さん
これから作り込んでいかれるのなら、推奨というものがあるようですので参考にしてください。
また、ループを使って閉じる時の条件もあるようですので、注意して作り込んでください。

以前ある方に教えてもらったんです・・・・
・処理等を提示する際には、前提条件を記述しないといけない
意味のないものだったんですね・・・・
その方に連絡できれば良いのですが、急激に目が悪くなったようで・・・


中身がどうなっているのか分からないものを相手にしているのであれば、
Index は、 0 ~ の連番を維持するように作り直される・・・を理解され、
素直に Step -1 の方法に、エラーを無視する On Error Resume Next 等を組み込んだ上で、
使われた方が良いと私は思います。
・閉じれなかったものは、そのままに・・・
・一気に複数消えたら、一時的に Index 参照エラーになることも・・・
でも、Index = 0 まで処理を進めるので、動きは完璧に近くなると思います。
(大半の動作では問題ないような気がします)
    • good
    • 0
この回答へのお礼

ありがとうございました。

お礼日時:2012/10/25 20:02

#5です



なぜエラーとなるかは、#1さんが2段目で説明されていたので、

質問者さんの置かれている状況
・新規で1から作っている
・既存のものを手直ししようとしている
がわからなかったもので、削除する際の他の方法についてをメインに回答してみました。

私の提示した方法でも、Step -1 での方法でも対処できないものもあります。
例えば、
・フォームAは自分では閉じれない状況で表示されている
・フォームAがフォームBを起動
・フォームAがフォームCを起動
・フォームCを閉じた時、フォームAを閉じる
簡単に言えば、起動されたフォームが閉じられる時、起動元も閉じる。

あまりこういう構成は取らないですかね。
起動された方は、自分の処理が終わったら情報を起動元に返すとかして、自分だけを閉じる・・
この動きが前提でありました。

Forms 内の情報ですが、私は以下の様に考えてます。
「フォームA」「フォームB」を順に表示した時は、
(左側がIndexで、右側がフォーム名)
0 フォームA
1 フォームB

ここで、「フォームC」を表示した時は
0 フォームA
1 フォームB
2 フォームC
となり
0 フォームC
1 フォームA
2 フォームB
とか
0 フォームA
1 フォームC
2 フォームB
ではないと思ってました。

また、サンプル2で何故エラーになるかの説明になると思いますが
0 フォームA
1 フォームB
2 フォームC
の状況下で、フォームAを閉じたら
0 フォームB
1 フォームC
になり
0 フォームC
1 フォームB
にはならないと思ってました。
そこで、For のループを進んで Index = 2 を参照したところでエラーになる・・・・
Step - 1 でIndex の大きい方から閉じるのであれば、その Index 部分が消えるだけで、
前にある Index 部分に影響はないので、全部処理することが出来ます。

フォームを表示していくと
・現在の Index + 1 した所に情報が置かれる
・途中がなくなれば、0 スタートで順に繰り上がる
と思ってます。なので、
インデックスが変わる可能性については、閉じた時の話であると私は解釈しています。

これを前提とするならば、
Index の小さい方に「親」と呼ばれるフォームが存在することになります。

「親」から処理するか「子」から処理するか、大きな違いがあるように私は思います。
(#5で紹介したように親が子を管理している場合もあるので)

前述した
> 起動された方は、自分の処理が終わったら情報を起動元に返すとかして、自分だけを閉じる・・
を前提としたら、Step - 1 の方法であった方が少しは安全かと私は思います。
「親」も一緒に閉じる様な事をしていたら、On Error Resume Next でエラーを無視すれば・・・

以上 私が思っていた事を前提とした内容になっているので、真偽は確かめてください。
    • good
    • 0
この回答へのお礼

ありがとうございました。

お礼日時:2012/10/25 20:02

No.2のDexMachinaです。



まず、前掲のURL
http://technet.microsoft.com/ja-jp/subscriptions …
からの再引用になりますが、

> コレクションで付けられたインデックスは変わる可能性がある

とあり、この際に順序が保持されるとの公式見解をみたことも
私はありませんので、1つのフォームを閉じるのに連動して
閉じられるフォームがある場合、For Next文によるループを
加算で回すか減算で回すかは、(発生頻度の差はあれ)
本質的な違いはなく、「0」からのスタートを危険とするならば、
「Step -1」も当然危険と考えるべきかと思います。

また、DoCmdのOpenFormメソッドではなく「Set New」による
インスタンス生成によってフォームを表示する場合、メモリの
解放を明示的に行うべきであること(→特に外部MDBで定義
のフォームの場合)、及び変数の解放によってフォームが閉じる
ことから、変数の解放によって閉じる(=DoCmd.OpenFormで
表示したフォームと処理を混同しない)、とするべきと考えます。


ということで、
 ・すべてのフォームをFor Nextなどのループで閉じるのは、
  展開中のフォームで、別のフォームが閉じる処理を行って
  いない場合に限る
 ・オブジェクト変数(配列を含む)に「Set New」を使用して
  インスタンスを生成しているフォームは、その変数の定義
  元に変数解放用のコードを記述し、それによってフォーム
  を閉じる
というのが、普段私がとっている手法であり、推奨する方法とも
なります。


・・・以上、「どうして、サンプル2じゃダメなのか」とのご質問の
趣旨から脱線してしまって申し訳ありませんが(汗)、参考まで。
    • good
    • 0
この回答へのお礼

ありがとうございました。

お礼日時:2012/10/25 20:02

#3です



追記)以下の様な状況があるのなら検討に値すると私は思います。

Access 初心者、中級、上級・・・・このレベルはわかりませんが、
1つのフォームを条件を変えながら2つ3つ同時に表示したい・・・・

こういう要求は出てきてもおかしくはないと思います。

同じフォームを複数表示する その2
http://hatenachips.blog34.fc2.com/blog-entry-5.h …

ここで、実際の動きを感じれるサンプルの入手もできます。

そのサンプルのメニューフォームで、「支社別社員一覧」をクリックすると、
同じフォームで表示内容が異なる3つのフォームが新しく表示されます。

この時、Forms.Count は 4 (1つのメニューフォームと3つの支社フォーム)

Public Sub test1()
  Dim i As Long

  For i = 1 To Forms.Count
    Debug.Print "Forms.Count = " & Forms.Count
    DoCmd.Close acForm, Forms(0).Name, acSaveNo
  Next
End Sub

上記の処理を記述し、フォームを閉じていこうとしたら、
i = 1 の Forms(0).Name でメニューフォームを削除したとたん、
全ての(他の3つも)フォームが閉じられてします。
For は i = 4 までループしますが、i = 2 の時には、
開いているフォームは 0 でエラーになっていきます。

Public Sub test2()
  Dim i As Long

  For i = Forms.Count - 1 To 0 Step -1
    Debug.Print "Forms.Count = " & Forms.Count
    DoCmd.Close acForm, Forms(i).Name, acSaveNo
  Next
End Sub

とか

Public Sub test3()
  While (Forms.Count > 0)
    Debug.Print "Forms.Count = " & Forms.Count
    DoCmd.Close acForm, Forms(0).Name, acSaveNo
  Wend
End Sub

であれば、エラーになることもなく処理が終了します。

1つのフォームを閉じたら、閉じられるのは1つ・・・・考えものだと思います。
現状、1つ閉じたら1つ・・・・なら、余計なお世話になりますが・・・・

提示された Sub サンプル1() の、最後から処理するか、
常に Forms.Count を確認しながら 前の方で処理するか、
どちらかになるような気がします。

For 文で 0 ~ Forms.Count -1 や、1 ~ Forms.Count でのループは危険だと私は思います。
    • good
    • 0
この回答へのお礼

ありがとうございました。

お礼日時:2012/10/25 20:02

Access で良かったでしょうか。



#1さんと被る部分があると思いますが、説明の練習と思って頂ければと・・・

全フォームを閉じたいのですよね。
以下を実行してみて、動きはどうなりますか

Public Sub Sample3()
  Dim iCnt As Integer

  On Error GoTo ERR_HND
  iCnt = 0
  While (Forms.Count > iCnt)
    DoCmd.Close acForm, Forms(iCnt).Name, acSaveNo
  Wend
  Exit Sub

ERR_HND:
  iCnt = iCnt + 1
  Resume Next
End Sub

上記はチョッとわかりにくいですか。エラー処理を省いた以下ではどうでしょう。

Public Sub Sample3Kai()
  While (Forms.Count > 0)
    DoCmd.Close acForm, Forms(0).Name, acSaveNo
  Wend
End Sub

これは、Forms.Count が 0 になるまで、Forms(0).Name で閉じていくものになります。
Forms(0) が閉じられたと同時に、Forms(0) で見えていたものが消えます。
ですが、すぐにまた 0 ~ 参照できるように作り直されます。
開いているフォームの個数は、Forms.Count で知ることが出来ます。

For i = 0 to Forms.Count - 1 と記述すると、記述した時点の Forms.Count 分ループします。
なので、ループ途中で閉じて、Forms.Count が減っても、その値は参照されません。

フォームを開いていない時に
For i = Forms.Count - 1 to 0 Step -1
とした場合、
For i = -1 to 0 Step -1 と記述したことと同じになり、1度もループ内を処理することはありません。
また、For i = 1 to 0 の記述でもループ内を処理することはありません。

Forms.Count の変化を見ながら処理したい場合は、冒頭で示した Sample3 の様に
都度判別するような記述が必要になります。

また、Sample3 の処理では、フォームを閉じようとしたけど閉じれなかった・・・
・何らかのエラー
・「読み込み解除時」 Form_Unload(Cancel As Integer) で Cancel = True していた
これらの時は、閉じるのはあきらめて次を閉じましょう・・・
というものになっています。
    • good
    • 0
この回答へのお礼

ありがとうございました。

お礼日時:2012/10/25 20:02

【要旨】


Formsコレクションの最小Indexは「0」なので、ループ内での
最小値も「0」とする必要があります。


【詳細】

> どうして、サンプル2じゃダメなのか教えていただけますか?

FormsコレクションのIndexは、
 最小値は「0」
 最大値は「開いているフォームの数-1」
です。
http://technet.microsoft.com/ja-jp/subscriptions …

なので、2つのフォームが開いているときに存在するのは
 Forms(0)、Forms(1)
になります。

ここで、サンプル1・サンプル2でのintCntを確認すると、
 サンプル1: Forms.Count-1~0 → 1~0
 サンプル2: 1~Forms.Count → 1~2
となり、サンプル2では、存在しない「Forms(2)」に対して
Nameプロパティを参照してしまいます。
これが、エラーの原因です。
(なお、DoCmd.Close自体は、現在開いていないフォーム
 を指定しても、エラーにはなりません。今回ご質問の
 エラーは、あくまで「Forms(2)」を参照しようとした
 ところで発生します)

では、サンプル2のintCntの範囲が「0~1」となるように

Sub サンプル2()
  Dim intCnt As Integer

  For intCnt = 1 To Forms.Count
    DoCmd.Close acForm, Forms(intCnt).Name
  Next

End Sub

とすればOkかというと、これもエラーとなります。
これは、(既にnicotinismさんからも説明がありますが)
1つ目のフォームが閉じられたところで、Formsコレクションの
Indexは連番になるように割り当て直されてしまうためです。
(この再割当をせず、閉じた番号分が欠番のままとされて
 しまうと、今度はForms.CountでIndexの最大値を取得
 できない、という問題が発生するため、再割当は必須、と)

ですので、サンプル2のパターンで正常動作をさせるには、
ループ内でのIndexを「0」で固定(=Formsコレクションで
管理されている先頭のフォームを常に対象)としてやれば
Ok、となります。

Sub サンプル2()
  Dim intCnt As Integer

  For intCnt = 1 To Forms.Count
    DoCmd.Close acForm, Forms(0).Name
  Next

End Sub
    • good
    • 0
この回答へのお礼

ありがとうございました。

お礼日時:2012/10/25 20:02

サンプル1はフォームのインデックス数の大きい順に閉じてます。


一方、サンプル2は最小値1から閉じようとしています。
この時に何が起きてるかというと
仮にフォームをニケ開いてるとします
For intCnt = 1 To Forms.Count ’------1
DoCmd.Close acForm, Forms(intCnt).Name’--2
Next

ループはForms.Countの値(=2)まで繰り返されます
フォームを閉じた場合にはForms.Count は一つ減りますが
For Next のループ回数は変わることはありません。固定です。
なので二回目のループに入ります。
で、Forms(intCnt) でForms(2) のフォームを閉じようとした時には
Forms のIndex(数)は1しかありませんのでエラーとなります。

Sub サンプル2()
'フォームがひとつも開いてない時に実行してもエラーにならない。
Dim intCnt As Integer

For intCnt = 1 To Forms.Count
DoCmd.Close acForm, Forms(intCnt).Name
msgbox forms.count
Next

End Sub


Sub サンプル3()
Dim i As Integer
Dim toN As Integer
toN = 5
For i = 1 To toN
MsgBox "i= " & i & " toN= " & toN
toN = 3
Next
End Sub

でtoN は3に変化しても、5回繰り返されるのを確認してみてください。

Sub サンプル4()
'これならエラーにはなりません。
Dim intCnt As Integer

For intCnt = 1 To Forms.Count
DoCmd.Close acForm, Forms(1).Name
Next
End Sub

分かってもらえただろうか?説明へたくそ。
    • good
    • 0
この回答へのお礼

ありがとうございました。

お礼日時:2012/10/25 20:02

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています


このQ&Aを見た人がよく見るQ&A