Excelで上の行から値が変わるときに罫線を引く
この記事はBrainPad Advent Calender 20247日目の記事です。
はじめに:やりたいこと
データ分析では、生データを目検して特徴やバグを探す作業がよくあります。その際、私はExcelにデータを貼り付けて確認しますが、IDや日付の切れ目が見づらく、無駄な脳のリソースを割いてしまっておりました。
そこで、上の行から値が変わるときに罫線を引くマクロを組むことで、この問題を解決しました。
マクロの使い方
最初にマクロの使い方をご紹介します。
- (最初だけ)マクロを登録する
- 罫線を引きたい表を選択してマクロを実行
- 罫線を引く基準となる列を入力
- 罫線の太さを選択
- 終わりです!
1. (最初だけ)マクロを登録する
マクロを個人用マクロブック(PERSONAL.XLSB)に登録するのが良いと思います。マクロは次節に貼っているのをコピペしてください。
個人用マクロを利用したことがない方は以下をご参照ください。[1]
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文字なんですね…これが原因のエラーをデバッグするのにとっても時間がかかりました…
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進数に従っていることを利用した公式ドキュメントを参考にしました。
-
個人用マクロはとても便利なので、全Excelユーザーにおすすめです。特に、F1無効化で心穏やかな日常となります。https://forest.watch.impress.co.jp/docs/serial/offitech/1475606.html ↩︎
Discussion