Excel VBA質問箱 IV

当質問箱は、有志のボランティア精神のおかげで成り立っています。
問題が解決したら、必ずお礼をしましょうね。
本サイトの基本方針をまとめました。こちら をご一読ください。

投稿種別の選択が必要です。ご注意ください。
迷惑投稿防止のため、URLの入力を制限しています。ご了承ください。


1839 / 13645 ツリー ←次へ | 前へ→

【71549】空白列の削除 はる 12/3/16(金) 10:54 質問[未読]
【71553】Re:空白列の削除 UO3 12/3/16(金) 19:53 回答[未読]
【71587】Re:空白列の削除 はる 12/3/21(水) 9:13 お礼[未読]
【71556】Re:空白列の削除 UO3 12/3/16(金) 20:50 発言[未読]
【71592】Re:空白列の削除 はる 12/3/21(水) 17:00 質問[未読]
【71599】Re:空白列の削除 UO3 12/3/22(木) 11:56 発言[未読]
【71600】Re:空白列の削除 はる 12/3/22(木) 13:07 お礼[未読]
【71572】Re:空白列の削除 12/3/18(日) 18:37 回答[未読]
【71588】Re:空白列の削除 はる 12/3/21(水) 9:21 お礼[未読]
【71591】Re:空白列の削除 はる 12/3/21(水) 16:59 質問[未読]

【71549】空白列の削除
質問  はる  - 12/3/16(金) 10:54 -

引用なし
パスワード
   以下のように2の行に項目があり,その下の行からデータが入っています。
そのデータが入っていない列(C列,E列)を列ごと削除したいのですが巧くいきません。
その時によってデータが入っていない列が変わります。
どなたか分かる方がいらっしゃいましたらご教授ください。

  A B C D E F

2 あ い う え お か
3 12         26
4 18     16   20
5 26 20   10   26
6 13 17   15   13
7 10 22   11   22

以下のようにマクロを組みました。

Sub ppp()
Application.ScreenUpdating = False
Dim yyy As Range
  On Error Resume Next
  Range("A2").Select
  Do
  If ActiveCell.Value = Empty Then
  Range("A1").Select
  Exit Do
  Else
  ActiveCell.Offset(1, 0).Resize(200, 1).Select
  Set bbb = Application.Union(Selection, Selection)
  Set yyy = bbb.Address
  End If
  Dim r1 As Range
  Set r1 = Application.ActiveSheet.Range("yyy")
  If Application.WorksheetFunction.CountA(r1) = 0 Then
  Columns(r1.Column).Delete Shift:=xlToLeft
  End If
  ActiveCell.Offset(-1, 1).Select
  Loop
End Sub

アドレスを取得できるところまでは確認できたのですが,巧く作動しませんでした。
初心者なのでできれば詳しく教えていただければ助かります。
よろしくお願いします。

【71553】Re:空白列の削除
回答  UO3  - 12/3/16(金) 19:53 -

引用なし
パスワード
   ▼はる さん:
こんばんは

とりあえずサンプルを2つ。
Sample1は、わかりやすいのではと思います。
Sample2は高速版です。コートにコメントをつけてありますが、わからなかったら質問してください。

なお、そちらでアップされたコードについては、今からみてみますね。

Sub Sample1()
  Dim j As Long
  Dim x As Long
  
  Application.ScreenUpdating = False
    
  With Sheets("Sheet1")
  
    x = .Cells(2, .Columns.Count).End(xlToLeft).Column 'リストのタイトル行の最終列番号
    
    For j = x To 1 Step -1 '最終列から逆に、各列をチェック
      'その列のセルで値があるセルがタイトル行のみなら列削除
      If WorksheetFunction.CountA(.Columns(j)) = 1 Then Columns(j).Delete
    Next
    
  End With
  
  Application.ScreenUpdating = True
  
End Sub

Sub Sample2()
  Dim w() As Variant
  Dim k As Long
  Dim i As Long
  Dim j As Long
  Dim x As Long
  Dim y As Long
  Dim wk As Variant
  
  Application.ScreenUpdating = False
    
  With Sheets("Sheet1")
  
    x = .Cells(2, .Columns.Count).End(xlToLeft).Column 'リストのタイトル行の最終列番号
    
    With .UsedRange
      y = .Cells(.Count).Row   'リストの最終行番号
    End With

    ReDim w(1 To y, 1 To x)     '現在のシートイメージが収まる縦横の配列
    
    For j = 1 To x
      If WorksheetFunction.CountA(.Columns(j)) > 1 Then
        'もし、タイトル行以外にも値があればその列を配列に左詰でセット
        k = k + 1
        For i = 1 To y
          w(i, k) = .Cells(i, j)
        Next
      End If
    Next
    .Range("A1").Resize(y, x).Value = w   'できあがった配列を一挙にシートに落とし込む
  End With
  
  Application.ScreenUpdating = True
  
End Sub

【71556】Re:空白列の削除
発言  UO3  - 12/3/16(金) 20:50 -

引用なし
パスワード
   ▼はる さん:

こんばんは

コードを拝見しました。

まず、コードの先頭のほうに記述されている、On Error Resume Next。
これは、記述してはいけません。
かなり、エキスパートの方の投稿で、このように記述しておられるのを見かけることがあります。
その人が言うには、「これはお約束です」。実際に、SIベンダーにいるプロのプログラマーにも
(古い人に多いのですが)顧客に納めるプログラムに、このような記述をしている人もいます。
昔々、「何らかの事情」で、こう欠かざるを得なかったようですが、今はそんなことはありません。
明確に、エラーが発生する可能性があることを認識した上で、そこを、あえて回避させる場合にのみ
記述すべきですし、その場合でも、用が終われば、すみやかにリセット(On Error GoTo 0)すべきです。

次に、変数は、コードの最中に記述してもいいのですが、可読性がそこなわれます。
プロシジャの先頭にまとめましょう。また、「すべてを」記述しましょう。
アップされた例では変数BBBの記述がありません。
モジュールの先頭に、Option Explict と記述しましょう。そうすると変数宣言が必須になり、
宣言がないとコンパイラーが教えてくれます。いちいち書くのは面倒でしょうから、VBE画面の
ツール->オプション の編集タブで、変数の宣言を必須にするにチェックしておきますと、自動的に
セットされます。

細かなことですが、 If ActiveCell.Value = Empty Then
数値の0もeMPTYとみなされます。認識した上でお使いなら、それでいいのですが。
空白かどうかということなら、If Len(ActiveCell.value) = 0 Then が安全ですね。

その、ActiveCell ですが、特定セルをSelect したうえで、ActiveCellとして処理するのは
実は、あまり感心しません。なぜかということを話し出すと、長くなりますので割愛しますが。
本件の場合は、処理するセルの場所を変化させていく、そのためにループの最後で
Offsetを使ってSelectしている、その工夫は、多としますが、ActiveCellを使わず、セルを特定する
別の方法がありますので、それを、早めに身につけられたらよろしいかと。
(ところで ActiveCell.Offset(-1, 1).Select これは 最初A2だったアクティブセルを、左下、左下と
 動かしているんですが、その認識ですか?)

Set yyy = bbb.Address
yyy というオブジェクト変数に、格納するのは「オブジェクト」です。
BBB.Address は、BBBというオブジェクト(Range)のアドレスを表す文字列ですので、エラーになります。
yyy に bbb をいれるなら Set yyy = bbb です。

そのyyyですが、  Set r1 = Application.ActiveSheet.Range("yyy") このような記述があります。
まず、Application.は、通常つけませんん。まぁ、それはいいとして
この意味は、名前定義で "yyy" と名付けられたセル(またはセル領域)ということなんですよ。
本当は、オブジェクト変数 yyy を指定したかったのではありませんか?
であれば Set r1 = yyy です。

で、実は、アップされたコードの一番の問題点は「インデントがつけられていない」ということです。
すべてのコードが同じ桁から記述開始されていますね。これは、中で、どんな単位の処理がされているか、
パットみただけでは把握しにくい記述方式です。インデントについては、たとえば私がアップしたコードを
参照願います。

さて、私のコードにもコメントをつけましたが、行を削除したり、列を削除する場合の「鉄則」は
最後から逆に処理していくということです。
(「最終列から左に向かって処理、あるいは最終行から上に向かって)

以上のことを参考にして、そちらのコードをブラッシュアップしてみませんか。

【71572】Re:空白列の削除
回答    - 12/3/18(日) 18:37 -

引用なし
パスワード
   こんにちは。
できるだけ元のコードを生かして書いてみました.
ホントはselect,activateしないコードの方が「偉い」のですけどね ^^

Option Explicit               '(1)
Sub ppp()
Dim yyy As Range

'  On Error Resume Next          '(2)
'  Application.ScreenUpdating = False   '(3)
  Range("A2").Select
  Do
    If IsEmpty(ActiveCell) Then
      Range("A1").Select
      Exit Do
    Else
      Set yyy = ActiveCell.EntireColumn  '(4)
      If Application.WorksheetFunction.CountA(yyy) = 1 Then  '(5)
        yyy.Delete
      Else
        ActiveCell.Offset(0, 1).Select
      End If
    End If
  Loop
'  Application.ScreenUpdating = True   '(6)
  
  On Error Resume Next          '(7)
  Set yyy = Nothing            '(8)
  On Error GoTo 0             '(9)
End Sub

■以下,解説です. クイズもあります.
(1) は必須です.モジュール冒頭に書きます.
(2)(3) コード作成中はコメントにしておきます.
(1)〜(3)のようにしておくと,エラーや不具合がどんどんおもてに出ます.
  出たはしから対応していけば,不具合のない良いコードが書けます.
  コード作成中に不具合が見えない設定にしていると , 不具合がそのまま残るため
  エラーはでないけど期待通りに動かないコードが出来上がります.
(2)を書いたら必ず(9)を書きます.
(3)を書いたら必ず(6)を書きます.
(4)を書いたら必ず(8)を書きます.
  開けたら閉める。出したらしまう.それと同じです.そういうものと思って下さい.
(4) この表の下に別の表やデータが無いという前提ですが
  リサイズ200とか面倒なので 列全体を参照するようにしました.
  これは好みです.
(5) 見出しの分、右辺を 1 に変更しました.
(7) この行には意味があります.(2)は削除しても構いませんが
  これは削除してはいけません.理由が分かりますか?
  
※ 行や列を削除するコードは, 後ろから前へループするのが定石です.
  そのほうが簡単だからです.
  ですが,ここでは「元のコードを生かして」前から後ろへループしています.
  そのため,特別な工夫を凝らしてあります. どんな工夫か,なぜそれが必要か見いだして下さい.

【71587】Re:空白列の削除
お礼  はる  - 12/3/21(水) 9:13 -

引用なし
パスワード
   UO3 さん

非常にご丁寧な返信ありがとうございます。
また,御礼が遅くなったことをお詫び申し上げます。

まだ返答頂いた内容をきちんと確認していない(時間が掛かりそうでした)ので,とりあえずお礼をと思い返答させていただいました。

理解できないところがあった場合に,また質問させてください。
ありがとうございました。

【71588】Re:空白列の削除
お礼  はる  - 12/3/21(水) 9:21 -

引用なし
パスワード
   佳 さん

非常にご丁寧な返信ありがとうございます。
また,御礼が遅くなったことをお詫び申し上げます。

理解するのに時間が掛かりそうでしたので,とりあえずお礼をと思い返答させていただいました。

宿題に関しては少し時間が掛かってしまうかもしれませんが,きちんと確認したいと思います。。
理解できないところがあった場合に,また質問させてください。
ありがとうございました。

【71591】Re:空白列の削除
質問  はる  - 12/3/21(水) 16:59 -

引用なし
パスワード
   佳さん

>開けたら閉める。出したらしまう.それと同じです.そういうものと思って下さい.

理解しました。今後はきちんと閉めるようにします。

>(7) この行には意味があります.(2)は削除しても構いませんが
>  これは削除してはいけません.理由が分かりますか?

デバック等のエラーも無視してしまうからですか?

>そのため,特別な工夫を凝らしてあります. どんな工夫か,なぜそれが必要か見いだして下さい.

If IsEmpty(ActiveCell) Then
 Range("A1").Select
Exit Do

の所ですか?
もしこれを入れていないと最後の列(XFD)まで作業を行い、非常に時間が掛かってしまうからかと思います。

以上、宿題の答えになっていますでしょうか?
ありがとうございました。

【71592】Re:空白列の削除
質問  はる  - 12/3/21(水) 17:00 -

引用なし
パスワード
   UO3さん

書いてくださったコードを理解しました。
ActiveやSelectを使わないほうが良いということも分かりました。
(でも、使わないでコードを書くというのには勉強不足なので、もっと思考を変えたりして勉強していきます。一番最初に自動記録で覚えたということもあるのか、Active等を使った思考になってしまいます。。。何かポイントはありますか?)

>(ところで ActiveCell.Offset(-1, 1).Select これは 最初A2だったアクティブセルを、左下、左下と動かしているんですが、その認識ですか?)

これは、少し前のコードで「ActiveCell.Offset(1, 0).Resize(200, 1).Select
」としていたので、そこから右上(次項目)に移動させたかったのです。

>行を削除したり、列を削除する場合の「鉄則」

知らないわけではなかったのですが、コードの組み方が分からず最初(列)から削除していって最後に「If ActiveCell.Value = Empty Then」で逃げようとしていました(このような組み方しかできないのは勉強不足ですね・・・)。


というわけで、UO3さんのコメントを基にブラッシュアップしてみました。
(最終的にはUO3さんが作ってくださったSampleを引用させて頂こうと思っています:勝手にすいません)


Sub ppp()

  Application.ScreenUpdating = False

  Dim bbb As Range

  Range("C5").Select
  
    Do
      If Len(ActiveCell.Value) = 0 Then
        Range("A1").Select
        Exit Do
      Else
        ActiveCell.Offset(1, 0).Resize(200, 1).Select
        Set bbb = Union(Selection, Selection)
      End If

      If Application.WorksheetFunction.CountA(bbb) = 0 Then
        Columns(bbb.Column).Delete Shift:=xlToLeft
        ActiveCell.Offset(-1, 0).Select
      Else
        ActiveCell.Offset(-1, 1).Select
      End If
    Loop
  
  Application.ScreenUpdating = True
  
End Sub


一応、意図していたことをしてくれるようにはなりました。
今後はもっと勉強してActive等を使わないで、後ろから列等を削除できるようになりたいと思います。
ありがとうございました。

【71599】Re:空白列の削除
発言  UO3  - 12/3/22(木) 11:56 -

引用なし
パスワード
   ▼はる さん:

こんにちは

がんばっておられますね!
はるさんの取り組み姿勢はとてもすばらしいと思います。
すぐにレベルがあがって、近い将来、この板の回答者として登場されることを期待しています。

>(でも、使わないでコードを書くというのには勉強不足なので、もっと思考を変えたりして勉強していきます。一番最初に自動記録で覚えたということもあるのか、Active等を使った思考になってしまいます。。。何かポイントはありますか?)

そうですよね。
マクロ記録は人間の操作をそのまま記録しますので、どうしてもSelect/Selectionになってしまいます。

ポイントはと聞かれると、悩むんですが、
「VBAではSelect や Selection というものは無い。またActiveCellという用語も無い」
このように考えてみてはいかがでしょう。
そうすると、「無い」わけですから、なんとかして、処理するセル(セル領域)を規定しなきゃいけない。
で、どういった書き方にしようかと、そう考えるといいかもしれません。
セル領域の書き方については「VBA セル範囲」といったもので検索すると参考情報がいろいろあります。
homepage2.nifty.com/kasayan/vba/excel3.htm
officetanaka.net/excel/vba/cell/cell10.htm
excelvba.pc-users.net/fol2/2_2.html

ただ、結構メジャーなサイトでも、中には
www.eurus.dti.ne.jp/yoneyama/Excel/vba/vba_cell.html
のように、Select を全面にだしているものもあるので、要注意ですが。

ActiveCell ではないのですが、たとえばマクロ記録で

セル領域.Select
Selection.何か
こんなコードが生成されますよね。

これらの、おそらく99.5%ぐらいは
セル領域.何か と、このように書くことができます。

【71600】Re:空白列の削除
お礼  はる  - 12/3/22(木) 13:07 -

引用なし
パスワード
   UO3さん

返信ありがとうございます。
また色々な分かりやすいサイトを教えてくださり重ねて御礼申し上げます。
まだまだ未熟者ですので頑張って勉強していきたいと思います。
この板の回答者側になるのはなかなか難しいですけど・・・。

また躓いた際にはお世話になるかもしれませんが,そのときにはよろしくお願いいたします。

色々とお世話になりまして、ありがとうございました。

1839 / 13645 ツリー ←次へ | 前へ→
ページ:  ┃  記事番号:
2610219
(SS)C-BOARD v3.8 is Free