VBA100本ノックチャレンジ:1~10本目

ExcelVBAは個人的によく使っていて、エクセルの神髄さんにもよくお世話になっているんですが、そういえば100本ノックはやったことがありませんでした。

コロナのワクチン接種(一回目)で今日明日とお休みなので、身体がつらくなければちょくちょく進めていきたいです。

まずは1本目。

Public Sub VBA100本ノック_001()
   
   With ThisWorkbook.Worksheets("Sheet1").Range("A1:C5")
       .Copy Destination:=ThisWorkbook.Worksheets("Sheet2").Range(.Address)
       Application.CutCopyMode = False
   End With

End Sub

特に言うことはなし。
>Destination:=ThisWorkbook.Worksheets("Sheet2").Range("A1")
でも良かったし、実際サンプルもそうしていましたが、「A1:C5」にコピーしてくださいとあったので何となくアドレス指定に。

2本目。

Public Sub VBA100本ノック_002()

   Dim wsSrc As Worksheet
   Dim wsDst As Worksheet
   
   Set wsSrc = ThisWorkbook.Worksheets("Sheet1")
   Set wsDst = ThisWorkbook.Worksheets("Sheet2")
   
   With wsSrc.Range("A1:C5")
       .Copy
       wsDst.Range(.Address).PasteSpecial Paste:=xlPasteValues
       wsDst.Range(.Address).PasteSpecial Paste:=xlPasteFormats
       Application.CutCopyMode = False
   End With
   
End Sub

これもそのまま。
この短さならSheetオブジェクト宣言しなくてもいいと思うんですが、自動メンバ表示が楽なのとうろ覚え部分のヒントになったりするので使っています。

3本目。

Public Sub VBA100本ノック_003()
   
   Dim ws As Worksheet
   Set ws = ActiveSheet
   
   With ws.Range("A1").CurrentRegion
       .Resize(.Rows.Count - 1, .Columns.Count - 1).Offset(1, 1).ClearContents
   End With
   
End Sub

このやり方よく使います。
データにこの表以外のデータがないなら、
>ws.Range("A1").CurrentRegion.Offset(1, 1).ClearContents
としたほうがすっきり1文で済みますね。

4本目。

Public Sub VBA100本ノック_004()
  
   Dim ws As Worksheet
   Set ws = ActiveSheet
   
   With ws.Range("A1").CurrentRegion.Offset(1, 1)
       On Error Resume Next
       .SpecialCells(xlCellTypeConstants).ClearContents
       On Error GoTo 0
   End With
   
End Sub

<失念ポイント>.SpecialCells(xlCellTypeConstants)に該当するセルがない場合のエラー回避を忘れていました。
上記は答えを見たあとにOn Error Resume Nextを書き足したものです。反省。

5本目。

Public Sub VBA100本ノック_005()
   
   Dim ws As Worksheet
   Set ws = ActiveSheet
   
   With ws.Range("B2").CurrentRegion
       ws.AutoFilterMode = False
       
       .AutoFilter field:=1, Criteria1:="<>"
       .AutoFilter field:=2, Criteria1:="<>"
       
       Dim rngCur As Range
       Set rngCur = .Resize(.Rows.Count - 1, 1).Offset(1, 2)
   End With

   With rngCur
       .ClearContents
       .FormulaR1C1 = "=RC[-2]*RC[-1]"
       .Value = .Value
       .NumberFormatLocal = "\##,#0"
   End With
   
   ws.AutoFilterMode = False
   
End Sub

多分意図してるのはForNextだなと思いつつ、オートフィルタが好きなのでこちらに。
オートフィルタで絞り込んでるときにセル範囲に対して値の代入や数式の設定をおこなうと、表示セルのみに反映されます。

<失念ポイント①>
書式設定のプロパティ(NumberFormatLocal)を忘れていたので、マクロ記録して確認しました。
<失念ポイント②>
オートフィルタの空白セル以外の選択の書き方を忘れていたので、こちらもマクロ記録して確認しました。空白セル以外の選択は「Criteria1:="<>"」、空白セルのみの選択は「Criteria1:="="」

6本目。

Public Sub VBA100本ノック_006()

   Dim ws As Worksheet
   Set ws = ActiveSheet
   
   With ws.Range("A1").CurrentRegion
       ws.AutoFilterMode = False
       
       .AutoFilter field:=1, Criteria1:="<>*-*"
       
       Dim rngCur As Range
       Set rngCur = .Resize(.Rows.Count - 1, 1).Offset(1, 3)
   End With

   rngCur.FormulaR1C1 = "=RC[-2]*RC[-1]"
   
   ws.AutoFilterMode = False
   
End Sub

5本目と同じ構造にしてしまいました。せっかくだから違うやりかたを試せばよかった。

<失念ポイント>
「計算式を入れずそのままにしてください。」を読み足りずに一度rngCur.ClearContentsを入れていました。反省!

7本目。

Public Sub VBA100本ノック_007()

   Dim ws As Worksheet
   Set ws = ActiveSheet
   
   Dim rw As Long
   For rw = 2 To ws.Range("A1").End(xlDown).Row
       Dim myDate As Variant
       myDate = Replace(ws.Cells(rw, 1).Value, ".", "/")
       
       If IsDate(myDate) Then
           myDate = CDate(myDate)
           myDate = Format(DateSerial(Year(myDate), Month(myDate) + 1, 0), "mmdd")
       Else
           myDate = ""
       End If
   
       With ws.Cells(rw, 2)
           .NumberFormatLocal = "@"
           .Value = myDate
       End With
   Next
   
End Sub

シンプルに。IsDateとCDateは便利。
答えを見ると.NumberFormatLocal = "@"は別に要らなかった模様。(mmddの出力とあったので…)

8本目。

Public Sub VBA100本ノック_008()

   Dim ws As Worksheet
   Set ws = ThisWorkbook.Worksheets("成績表")
   
   Dim rw As Long
   For rw = 2 To ws.Range("A1").End(xlDown).Row
       Dim rng As Range
       Set rng = ws.Range(ws.Cells(rw, "B"), ws.Cells(rw, "F"))
       
       Dim ret As String
       ret = ""
       If WorksheetFunction.CountIf(rng, "<50") = 0 Then
           If WorksheetFunction.Sum(rng) >= 350 Then
               ret = "合格"
           End If
       End If
       
       ws.Cells(rw, "G").Value = ret
   Next

End Sub

CellsでRowIndexとColumnIndex指定するときに、ColumnIndexで指定する列番号は数字でもアルファベットでもいい、というのは何気に便利です。
ただし、「文字列の数字("1"とかString型の変数に入っているとか)」だとエラーを起こすので、IsNumericでTrueならCLngしておくと安全。
(特にWinActorでライブラリのスクリプト修正・作成するときに活用してます。)

9本目。

Public Sub VBA100本ノック_009()

   Const sNewName As String = "合格者"

   Dim ws As Worksheet
   Set ws = ThisWorkbook.Worksheets("成績表")
   
   Dim w As Worksheet
   Dim wsNew As Worksheet
   For Each w In ThisWorkbook.Worksheets
       If w.Name = sNewName Then
           Set wsNew = w
           Exit For
       End If
   Next
   
   If wsNew Is Nothing Then
       ThisWorkbook.Worksheets.Add After:=ws
       Set wsNew = ActiveSheet
       wsNew.Name = sNewName
   End If
   
   wsNew.Columns(1).ClearContents
   
   With ws
       .AutoFilterMode = False
       .Range("A1").AutoFilter field:=7, Criteria1:="合格"
       .Range(.Range("A2"), .Range("A2").End(xlDown)).Copy
       wsNew.Range("A1").PasteSpecial Paste:=xlPasteValues
       Application.CutCopyMode = False
       .AutoFilterMode = False
   End With

End Sub

解説にもあったように、On Error Resume Nextで挟んで「合格者」シートを削除してから新規シート追加した方が短いしすっきりします。
合格者の抽出は、またしてもオートフィルタに頼ってしまいました。バリエーションに乏しいだろうか…つい便利で…。

ラスト10本目。

Public Sub VBA100本ノック_010()

  Dim ws As Worksheet
  Set ws = ThisWorkbook.Worksheets("受注")
  
  With ws.Range("A1").CurrentRegion
      ws.AutoFilterMode = False
      .AutoFilter field:=3, Criteria1:="="
      .AutoFilter field:=4, Criteria1:="=*削除*", Operator:=xlOr, Criteria2:="=*不要*"
      .Offset(1, 0).EntireRow.Delete
      ws.AutoFilterMode = False
  End With

End Sub

締めもオートフィルタ!やっぱり頼りすぎかもしれない。
データが他にない想定で単純に「.Offset(1, 0).EntireRow.Delete」とだけしましたが、もしこの表の下にも行を開けて入力があるようなら、Resizeで範囲を変えるとかRange(Range(”A2”),Range("A2").End(xlDown)).EntireRow.Deleteとかにした方が安全だと思います。

■10本目まで終えて

自分、オートフィルタにめちゃくちゃ頼っているなと思いました。
暗記が苦手なのでちょくちょくうろ覚えでマクロ記録からプロパティを確かめたものもありつつ、動作としては自分の中で持っている範囲で出来てとりあえずよかったです。

あとは処理のバリエーションを増やすべき…。

11本目からもまた頑張ってみたいです。