Excel (VBA) |
![]() ![]() |
(Windows 11 Pro : Excel 2021)
二重ループのコードの書き方について
投稿日時: 25/02/19 19:59:23
投稿者: シナモンジンジャー
|
---|---|
定型封筒の宛名の連続印刷のリストを下のように作りました。宛名のデータベースは1件ずつ横1行になっており、それをVLOOKUPで検索して封筒には差出先住所と宛先を横書き7行に印字されるようになっています。
|
![]() |
投稿日時: 25/02/19 23:37:52
投稿者: simple
|
---|---|
提示されたコードを内容は変えずに、インデントをつけてみました。
Sub 長3封筒宛名選択印刷() Dim Pz, StNo, EndNo As Integer StNo = Range("N4") EndNo = Range("V4") Dim i As Long Dim LastRow As Long Sheets("長3封筒").Select ActiveSheet.PageSetup.PrintArea = "$B$3:$L$33" For Pz = StNo To EndNo '第1ループ開始 Range("V1") = Pz If Pz = StNo Then LastRow Cells(Rows.Count, 32).End(xlUp).Row '基準列32列(AF) '行をループ 最終から13行目まで For i = LastRow To 13 Step -1 '第2ループ開始(空行非表示) If Cells(i, 32) = "" Then Rows(i).Hidden = True '行を非表示 End If Next i '第2ループ終了 Application.Dialogs(xlDialogPrint).Show Else ActiveSheet.PrintOut End If Rows("13:19").Select Selection.RowHeight = 23.25 Next Pz '第1ループ終了 Rows("13:19").Select '印刷後すべての行を表示する Selection.RowHeight = 23.25 ActiveSheet.PageSetup.PrintArea = False Range("C12").Select End Sub 細かい点についてコメントすることはありますが、 それはさておき、取り急ぎコメントしておきます。 |
![]() |
投稿日時: 25/02/20 07:59:40
投稿者: シナモンジンジャー
|
---|---|
ありがとうございます。
|
![]() |
投稿日時: 25/02/20 08:57:43
投稿者: simple
|
---|---|
いえいえ申し訳ないことはありません。
|
![]() |
投稿日時: 25/02/20 09:15:15
投稿者: MMYS
|
---|---|
シナモンジンジャー さんの引用: Pz 処理する行 StNo 最初の行 ですよね。で、コードを確認すると、下記のように書かれております。第1ループ直後に、1件目と2件目以降で処理の分けてますので、当然の動きです。 For Pz = StNo To EndNo '第1ループ開始 Range("V1") = Pz If Pz = StNo Then <略> Else ActiveSheet.PrintOut End If なお、デバックですが、いきなり、2重ループのコードを書いてはいけません。まず、Pz を指定したら、そのPzで指定した1件が確実に動作するコードを作成します。Pzを書き換えて、指定した行確実に動作するコードを完成させます。ループ化して複数対応は、その後です。 指定した1件のみ印刷が確実に動作するコードを完成したら、この時点でPzさえ指定すればよいので。あとは、第1ループを追加。Pzの値をループで変化させ複数印刷機能を追加します。 ステップ1。PZの値で、1件が確実に動作するコードを作成・デバックする。 ' For Pz = StNo To EndNo '第1ループ開始 Pz = 18 '←Pzを直接指定してデバック Range("V1") = Pz '< Pzで指定した1件を印刷コードを作成する> ' Next Pz '第1ループ終了 ステップ2。PZの値をループに変更する。 For Pz = StNo To EndNo '第1ループ開始 ' Pz = 18 '←Pzを直接指定してデバック Range("V1") = Pz '< Pzで指定した1件を印刷コードを作成する> Next Pz '第1ループ終了 |
![]() |
投稿日時: 25/02/20 16:27:42
投稿者: シナモンジンジャー
|
---|---|
なぜ、直接、値を当てはめた後わざわざデバッグするのでしょうか。
引用: この行の役割を教えてください。これまで、こういうふうに作ってこなくて、空行非表示の作業をしない場合はそれぞれの宛名をVLOOKUPで検索代入するだけで普通に動作していたのでわからないので教えてください。 |
![]() |
投稿日時: 25/02/20 16:51:46
投稿者: 半平太
|
---|---|
ちょっと教えてください。
|
![]() |
投稿日時: 25/02/20 20:02:35
投稿者: シナモンジンジャー
|
---|---|
封筒の各行にVLOOKUPで番号検索された値が代入されていますが、AF列(封筒印刷範囲のそと)には、その値(つまりデータベースで空白になっているセル値)は""を返すようにしてあります。その空白セルを検索してその行を非表示にしています。それで、それぞれの宛名データが代入されるたびにAF13からAF17までは""を返されたセルが変わってきます。 |
![]() |
投稿日時: 25/02/20 22:36:49
投稿者: 半平太
|
---|---|
>AF13からAF17までは""を返されたセルが変わってきます。
|
![]() |
投稿日時: 25/02/21 07:45:29
投稿者: シナモンジンジャー
|
---|---|
Pz は $V$1の宛先の印刷番号を代入する変数で
|
![]() |
投稿日時: 25/02/21 13:03:27
投稿者: simple
|
---|---|
初回と二回目以降で区別するのは、出力の仕方だけではないんですか?
If Pz = StNo Then Application.Dialogs(xlDialogPrint).Show Else ActiveSheet.PrintOut End Ifそのほかの共通の処理は、この中には入れる必要がない、入れてはダメということです。 具体的には、その5行のコードの前で、空白処理対応をすればよいと思います。 なお、論理的な内容にするには正確なインデントをつけることが助けになるはずです。 多重ループや、条件判断が入れ子になっているような場合は、特に重要です。 そういう時だからこそ、インデントをしっかりつけることが大切だと思います。 それを曖昧にしているのは、ご自分にハンディキャップを課しているようなものだと思います。 ちなみに、"リスト"じゃなく"コード"と呼ぶのが普通では? |
![]() |
投稿日時: 25/02/21 20:52:21
投稿者: シナモンジンジャー
|
---|---|
私のイメージしている二重ループは、第1のループで宛名一覧表(データベース)の中から、印刷開始番号をPzに代入して印刷開始番号1の宛名(宛名1〜会社名又は担当者名まで)を封筒の各行のVLOOKUPで表示させます。
|
![]() |
投稿日時: 25/02/21 21:26:15
投稿者: simple
|
---|---|
引用: 以下の抜粋は、あなたのコードそのものです。 If Pz = StNo Then LastRow Cells(Rows.Count, 32).End(xlUp).Row '基準列32列(AF) '行をループ 最終から13行目まで For i = LastRow To 13 Step -1 '第2ループ開始(空行非表示) If Cells(i, 32) = "" Then Rows(i).Hidden = True '行を非表示 End If Next i '第2ループ終了 Application.Dialogs(xlDialogPrint).Show Else ActiveSheet.PrintOut End IfPz = StNoのときは、空行非表示の処理をしていますが、 2つめ以降のデータのときは、Pz = StNo ではありませんから、Else以下が実行され ActiveSheet.PrintOut という1行が実行するだけですから、空行非表示の処理はされませんよね。 ご自分でステップ実行してどの行が実行されていくか、目で具体的に確認されたらどうですか? ===================== どのようにしたらよいかも既に 投稿日時: 25/02/21 13:03:27 で書いていますよ。 例えば、こんな風に書いたらどうですか? Sub 長3封筒宛名選択印刷() Dim Pz As Long, StNo As Long, EndNo As Long '変数の型の宣言はひとつひとつに必要 StNo = Range("N4") '"長3封筒"と違うシートなら、シート名を特定する必要? EndNo = Range("V4") Dim i As Long Dim LastRow As Long Sheets("長3封筒").Select ActiveSheet.PageSetup.PrintArea = "$B$3:$L$33" LastRow = Cells(Rows.Count, "AF").End(xlUp).Row '基準列 For Pz = StNo To EndNo Range("V1") = Pz '基準データセルの更新。 'これによって他のセルが再計算される For i = LastRow To 13 Step -1 '空行非表示処理 If Cells(i, "AF") = "" Then Rows(i).Hidden = True End If Next i '印刷処理 If Pz = StNo Then Application.Dialogs(xlDialogPrint).Show Else ActiveSheet.PrintOut End If Rows("13:19").RowHeight = 23.25 '全行表示に Next Pz ActiveSheet.PageSetup.PrintArea = False Range("C12").Select End Sub 実際のデータもこちらには無いので、空行非表示の処理は正確に動作するかは保証の限りではありませんが、質問には応えているはずです。 |
![]() |
投稿日時: 25/02/21 23:21:21
投稿者: MMYS
|
---|---|
シナモンジンジャー さんの引用: まず、 どこを直せばよいか分かりません。とのことですが、 シナモンジンジャーさん自身は、なぜ、そのような動きになるか、どのように調べてましたか。 頭の中の処理手順を実際のコードにするのが、コーディングですが、バグ原因は、 ・頭の中の処理手順が実際のコードに反映されてない。 ・そもそもの頭の中の処理手順が間違っている。 のどちらかが大半です。 プログラムが動かないときは ・頭の中では、○○ と動くはず。 ・実際の動作は、△△ と動く。 ですよね。 さて、シナモンジンジャーさんが頭の中で考えた処理手順通りに、コードは実行されてますか。また、それをどのように確認されましたか。 シナモンジンジャー さんの引用: 結論から言いましょう。第1ループの直後に判定してます。 コードを、アルゴリズムに直すと、次の通り。 初期設定 第1ループ開始 判定 第2ループ開始 行非表示 第2ループ終了 行再表示 第1ループ終了 後処理 第2ループに入る前に、判定しており、しかも、開始行の時のみ、第2ループに入るように、判定コードを書いてます。1件目は処理されるが、2件目以降は処理されません そのように動くように、シナモンジンジャー さんがコードを記述したのだから。 シナモンジンジャー さんの引用: その説明をする前に、シナモンジンジャーさん自身は、どのようにデバックされてますか。 たとえば、今回のケースだと、第1ループの回数が、10回ループだとすると、第2ループも10回実行される。これがシナモンジンジャーさんが考えたアルゴリズムですよね。 で、第2ループが10回実行されることを確認されましたか。 たとえば、 LastRow Cells(Rows.Count,32).End(xlUp).Row '基準列32列(AF) '行をループ 最終から13行目まで Msgbox Pz For i =LastRow To 13 Step -1 ’第2ループ開始(空行非表示) と第2ループ開始直前に 確認コード(Msgbox)を記述。これで、シナモンジンジャーさんの考えでは、Msgboxにより、 Pz値 が10回表示されるはずです。 もし、1回しか実行されてないと気づけば、何故だ。となり、第2ループ直前に、誤った判定コードがあるのでは。と推測。そのあたりをバク原因として探すわけです。 なお、ベテランプログラマでも、一発で動くことは、ありません。動かないのが当たり前。つまり、デバック方法を習得していなとコードを書くことはできません。 |
![]() |
投稿日時: 25/02/22 09:02:43
投稿者: MMYS
|
---|---|
補足しますが、シナモンジンジャー さんは、ネストを理解・意識してますか。ネストを理解してないと、
|
![]() |
投稿日時: 25/02/22 13:05:49
投稿者: シナモンジンジャー
|
---|---|
何回も丁寧なご説明ありがとうございます。教えていただいたコードを見てみると$V$1の値をpZに代入してすぐに空白処理に入り、そのあと連続印刷作業の第1ループに入っているように見えました。
引用: という順番にコードを並べたつもりでしたが、第1ループが開始してまず空白行判定・処理からなんですね。正直ネストの受発のことはよくわかりませんでした。もし、うまく稼働したら、もう1枚の角2封筒連続印刷のシートのコードにも空白処理の第2ループを追加したいと思っています。 教えていただいたコードを職場のパソコンで試してみます。私の職場は、USB使用ができない(ほぼ禁止)、業務用の登録されたメアド以外へのメール送受信も不接続なので、自宅パソコンでいただいたコードを試すことができません。質問に出したコードは職場でコードをプリントアウトして持ち帰ってきてmougのフォーラムに再現したところです。 simple様には本当に丁寧に教えてくださったので、多分大丈夫なような気がするのですが、試していないのですぐにお返事ができなくて申し訳ありません。結果をしばらくお待ちいただけますでしょうか。 |
![]() |
投稿日時: 25/02/22 13:59:18
投稿者: Suzu
|
---|---|
自分で考え書いたコードを動かしてみたけど、思い通りの結果が得られない。
|
![]() |
投稿日時: 25/02/22 18:29:48
投稿者: シナモンジンジャー
|
---|---|
Suzuさまには、基本的なコード進行の方法を指摘していただきありがとうございます。私は、VBAの標準モジュールの画面を開いて、F8キークリックで1行ずつ確かめながらコード進行を見ているのですが、第1ループから第2ループに入って1通目を印刷したので、今度は次の宛名番号が代入されたところからF8キークリックで進行していくと第2ループを飛ばしてしまう状態はなぜそうなるかが分からなくてsimpleさまに教えていただいたところです。 |
![]() |
投稿日時: 25/02/23 10:10:00
投稿者: simple
|
---|---|
シナモンジンジャー さんの引用: 教えてもらったというか、普通はステップ実行すれば間違いに気づくはずなんですが。 二重ループがどうこうというよりも、 If 条件 Then 処理1 Else 処理2 End Ifという条件分岐がわかっていらっしゃらない ということですかね。 条件を満たしたときは、処理1が実行され、満たさないときは、処理2が実行されるということが 理解されていなかったということでしょうか。 念のため、違いがわかりますか? (1)当初のコード For Pz = StNo To EndNo Range("V1") = Pz '基準データセルの更新。 'これによって他のセルが再計算される '印刷処理 If Pz = StNo Then For i = LastRow To 13 Step -1 '空行非表示処理 If Cells(i, "AF") = "" Then Rows(i).Hidden = True End If Next i Application.Dialogs(xlDialogPrint).Show Else ActiveSheet.PrintOut End If Rows("13:19").RowHeight = 23.25 '全行表示に Next Pz (2)修正案 For Pz = StNo To EndNo Range("V1") = Pz '基準データセルの更新。 'これによって他のセルが再計算される For i = LastRow To 13 Step -1 '空行非表示処理 If Cells(i, "AF") = "" Then Rows(i).Hidden = True End If Next i '印刷処理 If Pz = StNo Then Application.Dialogs(xlDialogPrint).Show Else ActiveSheet.PrintOut End If Rows("13:19").RowHeight = 23.25 '全行表示に Next Pz |
![]() |
投稿日時: 25/02/23 13:19:29
投稿者: シナモンジンジャー
|
---|---|
オレンジと青の文字色で分けてくださって、私の書いたコードでは最初のIF〜End IFの条件文の中に違う作業のループが挟まっているのが分かりました。
|