📑

Excelで上の行から値が変わるときに罫線を引く

2024/12/07に公開

この記事はBrainPad Advent Calender 20247日目の記事です。

はじめに:やりたいこと


データ分析では、生データを目検して特徴やバグを探す作業がよくあります。その際、私はExcelにデータを貼り付けて確認しますが、IDや日付の切れ目が見づらく、無駄な脳のリソースを割いてしまっておりました。

そこで、上の行から値が変わるときに罫線を引くマクロを組むことで、この問題を解決しました。

マクロの使い方

最初にマクロの使い方をご紹介します。

  1. (最初だけ)マクロを登録する
  2. 罫線を引きたい表を選択してマクロを実行
  3. 罫線を引く基準となる列を入力
  4. 罫線の太さを選択
  5. 終わりです!

1. (最初だけ)マクロを登録する

マクロを個人用マクロブック(PERSONAL.XLSB)に登録するのが良いと思います。マクロは次節に貼っているのをコピペしてください。
個人用マクロを利用したことがない方は以下をご参照ください。[1]
https://qiita.com/sumikko_tester/items/f0d1e9ed3abf297a272a

2. 罫線を引きたい表を選択してマクロを実行


罫線は選択範囲のみに引かれます。
列ごと選択しても、Ctrl+Aで表を選択しても構いません。表はA1セルから始まることを前提としていますが、そうでなくとも大体のケースで動きます。

3. 罫線を引く基準となる列を入力


どの列の値が変われば、罫線が引かれるかを指定します。アルファベットのカラム名で指定してください。

  • 例えば、「A列」を基準列に指定した場合、A列の値が変わるところで罫線が引かれます。
  • 「A列(クッキーID)」と「B列(セッションID)」を基準にすると、どちらかの列で値が変わるタイミングで罫線が引かれます。
    複数列を指定する際は、カラム名をカンマ(,)区切りで指定してください。

4. 罫線の太さを選択


罫線の太さを1~4で数値で選んでください。
こだわりがなければ、そのままOKを押してください。

5. 終わりです!

マクロの内容

マクロはここを展開してください
' This code is licensed under the MIT License.  
' Copyright © 2024 Naoaki-H
' For more details, please see: https://opensource.org/licenses/MIT

Sub 値変更時に罫線()
    Dim ws As Worksheet
    Dim selectedRange As Range
    Dim i As Long
    Dim keyColumnNames() As String
    Dim keyColumnIndices() As Long
    Dim firstCol As Long
    Dim lastCol As Long
    Dim firstRow As Long
    Dim lastRow As Long
    Dim borderWeight As XlBorderWeight
    Dim isCancell As Boolean
    
    ' アクティブシートを設定
    Set ws = ActiveSheet
        
    ' 例外処理:セルを選択していなければ、退出
    If TypeName(Selection) <> "Range" Then
        MsgBox "セル範囲を選択してから実行してください", vbExclamation
        Exit Sub
    End If
    Set selectedRange = Selection
    
    ' 基準列をinputBoxで取得。
    ' 出力は、"A"、"B"、"A, B"、"A,B"を期待している
    inputKeyColumnsNames = GetKeyColumnsNames(selectedRange, isCancell)
    If isCancell = True Then
        Exit Sub
    End If
    
    ' 入力された基準列をカンマで分割
    keyColumnNames = Split(inputKeyColumnsNames, ",")
        
    ' 入力値を列番号に変換
    ReDim keyColumnIndices(UBound(keyColumnNames))
    For i = 0 To UBound(keyColumnIndices)
        keyColumnIndices(i) = convertColNameToColIndex(Trim(keyColumnNames(i))) ' 空白を削除して列番号に変換
    Next i
    
    ' 罫線の太さをinputBoxで指定
    borderWeight = GetBorderWeight(isCancell)
    If isCancell = True Then
        Exit Sub
    End If
    
    ' 選択範囲の最初と最後の行番号と列番号を取得
    firstCol = selectedRange.columns(1).Column
    lastCol = selectedRange.columns(Selection.columns.Count).Column
    firstRow = selectedRange.Rows(1).Row
    If selectedRange.Rows.Count = 1048576 Then
        lastRow = selectedRange.Cells(selectedRange.Rows.Count, firstCol).End(xlUp).Row ' 列範囲での指定が前提
    Else
        lastRow = firstRow + selectedRange.Rows.Count - 1
    End If

    ' 行ごとにループ
    For i = firstRow To lastRow
        Dim addBorder As Boolean
        addBorder = False
        
        ' 各基準列のうち、一つでも値が異なれば罫線を追加
        For Each colIndex In keyColumnIndices
            If ws.Cells(i, colIndex).Value <> ws.Cells(i + 1, colIndex).Value Then
                addBorder = True
                Exit For
            End If
        Next colIndex
        
        ' 下罫線を引く
        If addBorder Then
            With ws.Range(ws.Cells(i, firstCol), ws.Cells(i, lastCol)).Borders(xlEdgeBottom)
                .LineStyle = xlContinuous
                .Weight = borderWeight
            End With
        End If
    Next i
    
    MsgBox "罫線の追加が完了しました!"
End Sub
' inputBoxを利用して、罫線を引く基準の列を取得する
Function GetKeyColumnsNames(ByVal selectedRange As Range, ByRef isCancell As Boolean) As String
    Dim msgContent As String
    Dim tmpColName As String
    Dim tmpColValue As String
    Dim firstColName As String
    Dim col As Range
    
    ' 選択範囲内の列情報をリスト化(列名 + ヘッダーの値)
    msgContent = "以下の列から基準とする列を選んでください:" & vbCrLf & vbCrLf _
        & "列名 | ヘッダー行の値" & vbCrLf _
        & String(30, "-") & vbCrLf ' 区切り線
    
    firstColName = "" ' 選択範囲の1列目の列名
    For Each col In selectedRange.columns
        tmpColName = convertColIndexToColName(col.Column)
        If firstColName = "" Then
            firstColName = tmpColName
        End If
        tmpColValue = col.Cells(1, 1).Value
        msgContent = msgContent & tmpColName & "   |   " & tmpColValue & vbCrLf ' ヘッダー値
    Next col
    
    ' InputBoxメソッドの文字数制限が厳しすぎるので、InputBox関数を利用
    GetKeyColumnsNames = InputBox( _
        msgContent & vbCrLf & "基準列(複数ある場合は、カンマ区切りでA.Bのように入力)を入力してください:", _
        title:="基準列の選択", _
        Default:=firstColName _
    )
    If GetKeyColumnsNames = "" Then
        isCancell = True
    End If

End Function


' inputBoxを利用して太さを取得する関数
Function GetBorderWeight(ByRef isCancell As Boolean) As XlBorderWeight
    Dim weightInput As String
    Dim borderWeight As XlBorderWeight
    
    ' 太さの選択肢を提示
    weightInput = Application.InputBox( _
        "罫線の太さを指定してください(番号を入力):" & vbCrLf & vbCrLf & _
        "1. 極細" & vbCrLf & _
        "2. 細い" & vbCrLf & _
        "3. 中くらい" & vbCrLf & _
        "4. 太い", _
        title:="罫線の太さの選択", _
        Default:=2, _
        Type:=1)
    
    
    ' 入力値に基づいて罫線の太さを設定(Enumの定数とは異なることに注意)
    Select Case weightInput
        Case 1
            borderWeight = xlHairline
        Case 2
            borderWeight = xlThin
        Case 3
            borderWeight = xlMedium
        Case 4
            borderWeight = xlThick
        Case Else
            MsgBox "無効な値が入力されました。処理を終了します。", vbExclamation
            GetBorderWeight = xlThin
            isCancell = True
            Exit Function
    End Select
    
    GetBorderWeight = borderWeight
End Function

' 列名→列番号
Function convertColNameToColIndex(ByVal colName As String) As Long
    On Error GoTo ErrorHandler
    convertColNameToColIndex = columns(colName).Column
    Exit Function
    
ErrorHandler:
    MsgBox "無効な列名が指定されました: " & colName, vbExclamation, "エラー"
End Function

' 列番号→列名
Function convertColIndexToColName(ByVal colIndex As Long) As String
    Dim colLetter As String
    Dim tempNum As Integer
    
    tempNum = colIndex
    colLetter = ""
    
    While tempNum > 0
        tempNum = tempNum - 1
        colLetter = Chr((tempNum Mod 26) + 65) & colLetter
        tempNum = tempNum \ 26
    Wend
    
    convertColIndexToColName = colLetter
End Function

おわりに

本記事では、テーブルの行の値が変わるときに罫線を引くマクロコードを紹介しました。
我ながらに超絶便利ツールだと思っており、共有いたしました。誰かのお役に立てていることを祈るばかりです。

業務中のちょっとしたイライラをVBAで解決するの、おすすめです。
生成AIのおかげで、ちょっと日常が潤うコードを簡単に生成できるようになりました。
今回のコードはブログ用に大部分に手を加えましたが、諸々を妥協すれば、そのままマクロとして活用できることが多いです。
是非。

補遺:コード解説

大したコード書いてないのですが、一応技術ブログなんで、気持ち程度に詰まった部分を書いておきます。
良いことは書いてません。

InputBoxの利用

本当はユーザーフォームを利用した方が、ラジオボタンやチェックボックスを使えて使いやすいです。しかし、ユーザーフォームのMacでの挙動が分からなかったので、間違いなくMacでも動くInputBoxを採用しました。

もともとはExcelのみで使えるApplication.InputBoxメソッド使っていたのですが、表示できる文字列の最大長が255文字なんですね…これが原因のエラーをデバッグするのにとっても時間がかかりました…
https://learn.microsoft.com/ja-jp/office/vba/api/excel.application.inputbox

InputBoxのキャンセル

inputKeyColumnsNames = GetKeyColumnsNames(selectedRange, isCancell)
If isCancell = True Then
    Exit Sub
End If

InputBoxでキャンセルが押された際の挙動を自分で書く必要があります。

一般的にInputBoxでキャンセルする際はこのようにExit Subするのですが、今回InputBoxは関数の内部で呼び出していたので、それが出来ませんでした。

input = InputBox()
If input = "" Then
    Exit Sub
End If

ということで、関数の返り値にキャンセルされたか否かを含める必要があったのですが、VBAでは複数の返り値は返せないと…
どないするんやと調べると、関数に参照渡しでisCancellを渡して、疑似的に複数の返り値を返すやり方があるんですね。分かりづらいですが、ちょっと感動しました。

列番号↔列名の変換

' 列名→列番号
Function convertColNameToColIndex(ByVal colName As String) As Long
    ' 省略
End Function

' 列番号→列名
Function convertColIndexToColName(ByVal colIndex As Long) As String
    ' 省略
End Function

Excelって列番号と列名を変換するデフォルトの機能がありません。そのため、自分で関数を作る必要があります。
諸々調べたところ、列番号が26進数に従っていることを利用した公式ドキュメントを参考にしました。
https://learn.microsoft.com/ja-jp/office/troubleshoot/excel/convert-excel-column-numbers

脚注
  1. 個人用マクロはとても便利なので、全Excelユーザーにおすすめです。特に、F1無効化で心穏やかな日常となります。https://forest.watch.impress.co.jp/docs/serial/offitech/1475606.html ↩︎

Discussion