セル | Excel作業をVBAで効率化 https://vbabeginner.net いつものExcel作業はVBAを使えば数秒で終わるかもしれませんよ Sat, 09 Nov 2024 05:36:05 +0000 ja hourly 1 https://wordpress.org/?v=6.6.2 https://vbabeginner.net/wp-content/uploads/2019/02/favicon-150x150.png セル | Excel作業をVBAで効率化 https://vbabeginner.net 32 32 VBAで空白セルを判定する方法 https://vbabeginner.net/check-blank-cell/ Sat, 24 Jun 2023 13:46:12 +0000 https://vbabeginner.net/?p=6808 空白セルかどうか判定するには

VBAの処理でよくあるのが「セルが空白かどうか」を判定する場面です。

ほとんどの場合は以下のようにRangeオブジェクトのValueプロパティが空文字列””かどうかの書き方でOKです。

If Range(“A1”).Value = “” Then

この書き方でほとんどの場合は判定できます。Rangeオブジェクトの書き方がいくつかあるのでそれを以下で紹介します。

なお、この書き方で判定できないのはセルの数式がエラーになっている場合です。それについては後述します。

空白判定の書き方

Rangeオブジェクトの書き方には複数あります。「Range(“セル座標”)」、「Cells(行位置, 列位置)」の主に2つの書き方と、それに加えてRangeオブジェクト変数を扱う書き方などです。

以下に4種類のコードがありますが、いずれの書き方もA1セルの値が空文字列かどうか判定しています。

Range(“セル座標”) での書き方

Sub checkBlankCell1()
    '// A1セルの値が空文字列の場合
    If Range("A1").Value = "" Then
        Debug.Print "blank"
    End If
End Sub

Cells(y, x) での書き方

Sub checkBlankCell2()
    '// A1セルの値が空文字列の場合
    If Cells(1, 1).Value = "" Then
        Debug.Print "blank"
    End If
End Sub

Rangeオブジェクト変数に代入する書き方

Sub checkBlankCell3()
    Dim r   As Range    '// セル
    
    '// A1セルのRangeオブジェクトを変数rに格納
    Set r = Range("A1")
    
    '// A1セルの値が空文字列の場合
    If r.Value = "" Then
        Debug.Print "blank"
    End If
End Sub

Rangeオブジェクト変数とValue値の文字列変数に代入する書き方

Sub checkBlankCell4()
    Dim r   As Range    '// セル
    Dim s   As String   '// セルのValue値
    
    '// A1セルのRangeオブジェクトを変数rに格納
    Set r = Range("A1")
    
    '// A1セルのRangeオブジェクトのValue値を取得
    s = r.Value
    
    '// A1セルの値が空文字列の場合
    If s = "" Then
        Debug.Print "blank"
    End If
End Sub

セルの数式がエラーになっている場合

上記はセルの値がエラーになっていない場合の書き方ですが、セルに「#N/A!」(参照先が無いエラー)や「#DIV/0!」(0割りエラー)のようなエラー表示がされている場合は、「Range.Value」のValueプロパティで値が取得できないため、エラーとしてVBAの処理が止まってしまいます。

もしエラー値かどうかを判定したいのであれば以下のようにRangeオブジェクトのValue値に対してIsError関数を利用します。

Sub checkBlankCell5()
    '// A1セルの値がエラー値の場合
    If IsError(Range("A1").Value) = True Then
        '// どういうエラーなのかCVErr関数でを出力する
        Debug.Print CVErr(Range("A1").Value)
    End If
End Sub

IsError関数の詳細は「VBAでセルの数式がエラーか判定する(IsError)」をご参照ください。

ここで問題になるのが、エラー判定のコードを書いた方がいいかどうか、という点です。

このようなエラー値の対応を入れるかどうかは、状況によって変わります。具体的には、セルにエラーが無いことを前提とするか、エラーがあることを前提とするか、です。

VBAを実行するとかしないとかの前に、ブックのデータがエラーになっていておかしいのでセルの値が正しく表示されるように修正するのが先、という場合の方がおそらく多いでしょう。

ここではセルがエラー値かどうかという話だけですが、他にもブックのデータが想定していない内容であることが多岐にある場合、それら全部の不正チェックをVBAでやるのか、という話です。

おそらくそこで想定されるエラー処理を入れても日の目を見ないことの方が圧倒的に多いはずです。多くの場合はブックを直せばエラーは解消するので。

なので、セルの値がエラーかどうかの判定はよほどの場合にのみ入れるようにしておき、通常は上記の「If Range(セル座標).Value = “” Then」での空白チェックでよいと思われます。

ちなみに私はほとんどの場合においてIsError関数での判定処理は入れません。面倒なので。

]]>
VBAでセルが数式か値かを判定する(HasFormula) https://vbabeginner.net/hasformula/ Thu, 15 Jun 2023 16:15:03 +0000 https://vbabeginner.net/?p=6789 セルが値か数式かを判定するには

セルには値と数式の2種類が設定できます。このとき、値と数式のどちらが設定されているのかを調べたい場合に、HasFormulaプロパティで判定できます。

HasFormulaプロパティはRangeオブジェクトのプロパティの1つです。

なお、HasFormulaプロパティを使ってシートにある数式セルを探す方法を別ページで紹介しています。詳細は「VBAで数式が設定されているセルを高速で探す」をご参照ください。

構文

Property Range.HasFormula As Variant

親オブジェクト Rangeオブジェクトを指定します。セルは1つでも複数セル範囲でも構いません。
戻り値 Rangeオブジェクトが示すセル範囲の全てのセルに数式が設定されている場合はTrueを返します。

Rangeオブジェクトが示すセル範囲の全てのセルに数式が設定されていない場合はFalseを返します。

それ以外はNullを返します。

HasFormulaプロパティでNullを返す条件は、Rangeオブジェクトが複数のセル範囲の各セルで値や数式が混在している場合です。

例えば、4つのセル範囲をRangeオブジェクトが指している場合に、1つが値で、2つが数式で、1つが未設定(空)のような場合に値なのか数式なのかを判定できないためNullを返します。

同様に、4つのセルが全て数式であればTrueを返し、全て値であればFalseを返します。

1. セルが値か数式か調べる

A1セルに値か数式かどちらが設定されているかを判定するサンプルです。数式が設定されていればTrue、値であればFalseがイミディエイトウィンドウに出力されます。

Sub HasFormulaTest()
    Debug.Print Range("A1").HasFormula
End Sub

2. 選択セル範囲の数式セルを調べる.

セル範囲を選択して以下の関数を実行すると、数式セルの座標と値がイミディエイトウィンドウに出力されます。

Sub HasFormulaInSelection()
    Dim r   As Range    '// セル
    
    '// 選択セル範囲をループ
    For Each r In Selection
        '// 数式セルの場合
        If r.HasFormula = True Then
            '// セル座標とセル値を出力
            Debug.Print r.Address(False, False) & ":" & r.Value
        End If
    Next
End Sub

実行例

B1,B2,C4の3つのセルに数式がある状態で、B1~C4セルを選択して実行すると、以下が出力されます。

B1:1
B2:1
C4:4

]]>
VBAでセルの0を削除する(空にする)方法 https://vbabeginner.net/zero-to-void/ Wed, 14 Jun 2023 17:06:33 +0000 https://vbabeginner.net/?p=6782 セルの値が0を空に変えるには

シートにある各セルの値の0を消したい場合があります。0が多すぎて分かりにくい場合などです。

Excelでの通常の操作では、検索と置換ダイアログを使って0を空文字列に置換する、という方法になります。

VBAで行う方法には2つ挙げられます。

  • セル範囲のセルが0であれば消す。(VBAで対象セル範囲をループして1セルずつ処理する方法)
  • Excelの検索と置換ダイアログの機能をVBAで使って0を空に置換する。(検索と置換機能のReplaceメソッドでセル範囲の0を一括で空に置換する方法)

どちらの方法でもいいのですが、2つ目のExcelの検索と置換ダイアログでの方法にはデメリットがあります。そのあたりも含めて後述します。

1. VBAの処理でセルの値の0を消す(おすすめ)

VBAでシートの各セルの値が0かどうかを判定して、0であれば空にする方法のコードが以下になります。

やっているのは、シートの入力セル範囲を示すUsedRangeを使って、1セルずつループして、セルの値が0であれば消す、という内容です。

UsedRangeの詳細については「VBAで入力済みセル範囲を判定する(UsedRange)」をご参照ください。

RangeオブジェクトのValueプロパティを使うと、セルの値が0かどうか判定できます。ただし、「=1-1」のような数式の結果が0の場合もありえるため、数式が設定されている場合は空にしないようにしています。

数式が設定されているかどうかはRangeオブジェクトのHasFormulaプロパティで判定できます。Trueであれば数式が設定されており、Falseであれば数式ではありません。

Sub SheetZeroClear1()
    Dim r   As Range    '// セル
    Dim sht As Worksheet    '// シート
    
    '// アクティブシートを処理対象のシートとして設定する
    Set sht = ActiveSheet
    
    '// 入力されたセル範囲をループ
    For Each r In sht.UsedRange
        '// セルの値が0の場合
        If r.Value = "0" Then
            '// セルが数式ではない場合
            If r.HasFormula = False Then
                '// セルに空文字列を設定する
                r.Value = ""
            End If
        End If
    Next
End Sub

2. Replaceメソッドを使ってセルの値の0を消す

検索と置換ダイアログをVBAで行う場合は、Replaceメソッドを使うことで同じように動作します。ただし、Replaceメソッドには条件によってはデメリットになる挙動があります。デメリットについては後述します。

Replaceメソッドでのコードはとても単純です。

以下のコードはシートの全てのセルで値が0の場合に空にします。数式のセルは空にはしません。

Sub SheetZeroClear()
    Cells.Replace What:="0", Replacement:="", LookAt:=xlWhole, SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, ReplaceFormat:=False, FormulaVersion:=xlReplaceFormula2
End Sub

Replaceメソッドには複数の引数があります。それらの詳細については「VBAでセルの置換を行う(Replaceメソッド)」をご参照ください。

なお、Replaceメソッドにはデメリットがあります

それは、Replaceメソッドを実行したときの設定内容が、Excel上での検索を行う際の「検索と置換」ダイアログにも反映されてしまう点です。

Excel上で「検索と置換」ダイアログを使った場合、前回の検索条件が引き継がれて初期表示されます。VBAのReplaceメソッドも同様で、Replaceメソッド実行後に、Excelで「検索と置換」ダイアログを表示すると、Replaceメソッド実行時の条件が引き継がれて表示されます。

それは場合によっては利点として考えられるかもしれませんが、一般的にはExcel上の検索条件をVBAで書き換えてほしいとはあまり思わないので、欠点として扱われることの方が多い気がします。

]]>
VBAでセル内の最終行に空行を追加する https://vbabeginner.net/add-newline-terminal/ Fri, 26 May 2023 16:46:28 +0000 https://vbabeginner.net/?p=6726 セルに改行を入れる方法

セルに書かれた文章には、改行を入れたい箇所でAlt + Enterを押すと改行を入れることができます。

A2セルが改行を入れた状態です。

このときの改行にはLFが設定されています。LFとはLineFeedの略で、文字コードが10の1バイトの文字です。LFかどうかはExcelの見た目上は分かりません。

なお、改行コードについては以下に詳しく書いていますのでご参照ください。
VBAの改行コード(CR、LF、CRLF)の使い方

セルの最終行に空行を入れるには

セルに文章を書く際に、最下段に空行を入れたい場合があります。下のセルとの空白を入れたい場合や、印刷時の文字切れを防ぎたい場合に使われる手法です。

手で空行を入れるにはセルの文章の終端でAlt + Enterを押せばいいのですが、数が多いと面倒です。

以下の関数は、選択セル範囲の各セルの終端にLFを追加します。セルが空の場合は追加しません。

Sub AddNewLine()
    Dim r   As Range        '// セル
    
    '// 選択セル範囲をループ
    For Each r In Selection
        '// セルが空の場合は改行を入れない
        If r.Value = "" Then
            GoTo CONTINUE
        End If
        
        '// セルの終端がLFではない場合
        If Right(r.Value, 1) <> vbLf Then
            '// 終端にLFを追加する
            r.Value = r.Value & vbLf
        End If
        
CONTINUE:
    Next
End Sub

この関数は、セルに何も入力されていない場合は改行を入れないようにしています。

もし、空のセルでも改行を入れたい場合は、以下のように空セルかどうかの判定を外してください。

Sub AddNewLine()
    Dim r   As Range        '// セル
    
    '// 選択セル範囲をループ
    For Each r In Selection
        '// セルの終端がLFではない場合
        If Right(r.Value, 1) <> vbLf Then
            '// 終端にLFを追加する
            r.Value = r.Value & vbLf
        End If
    Next
End Sub

使い方

上記AddNewLine()関数の使い方は、まずセルを選択します。セル範囲を選択しても動作します。

あとは、実行するだけです。実行すると以下のように入力されているセルの最終行に空行が追加されます。A3セルは空のため改行を入れていません。

なぜセル内の改行がLFと分かるのか?

改行コードにはCRとLFとCRLFの3種類がありますが、なぜセル内の改行がLFなのか分かるかというと、事前にお試しコードを書いて試しているためです。

まず、手でセル内にAlt + Enterで最終行に改行を入れます。

あとは入力したセルを選択した状態で、以下のコードでセルに書いてある文字の終端文字の文字コードを検出します。

Sub GetTerminalCharacter()
    Dim r   As Range
    
    Set r = ActiveCell
    
    Debug.Print Asc(Right(r.Value, 1))  '// 10 が出力
End Sub

実行すると「10」がイミディエイトウィンドウに出力されます。10という文字コードはLFのため、「セル内の改行コードはLFね」と判定できます。なお、「13」であればCRと判定します。

では、10がLFであることはどうやって知ればいいかですが、それはASCIIコード表を見ます。「ASCIIコード表」でネット検索すれば出てきますので探してみてください。

]]>
VBAでセルの数式がエラーか判定する(IsError) https://vbabeginner.net/iserror/ Sun, 30 Apr 2023 05:30:23 +0000 https://vbabeginner.net/?p=6674 数式がエラーのセルのRange.Valueはエラーになる

Excelのセルで「#N/A」や「#NAME?」といった表示を見たことがあると思います。これらはセルの数式が正しく動作できていないことを表しており、多くの場合は数式の修正が必要になります。

セルの値を取得するには「Range(“A1”).Value」のように書くとA1セルの内容を取得することができますが、セルに数式が設定されている場合で、かつ、その数式がエラーになっている場合はValueプロパティがエラーになります。

以下の関数はA1セルに「ABCDEFG()」という存在しない関数を埋め込んで、そのセルの値を参照しています。

Sub cellError1()
    Dim s   As String
    
    '// A1セルに不正な数式を設定
    Range("A1").Formula = "=ABCDEFG()"
    
    '// 実行時エラー13が発生
    s = Range("A1").Value
End Sub

「ABCDEFG()」という関数は存在しないためセルの表示には「#NAME?」と表示されます。その状態でValueプロパティを使ってセルの値を取得しようとすると実行時エラー13が発生します。

エラーダイアログが表示されるとそこでVBAの処理は止まってしまいます。これを回避する方法を以下で紹介します。

IsError関数

上記のようにセルのエラーが発生している場合にRangeオブジェクトのValueプロパティなどの参照を行うとエラーが発生してそこで処理が止まってしまいます。本来であればそういうエラーが無いようにセルの数式を修正しておいた方がいいのですが、状況によってはエラーのままVBAの処理を続行しなければならないこともあります。

そういう場合は、IsError関数を使います。

構文
Function IsError(Expression) As Boolean

Expression エラーか判定する式や値を指定します。
戻り値 引数がエラーの場合はTrue、エラーでない場合はFalseを返します。

サンプルコード

以下のコードは上記のコードにIsError関数でセルの値がエラーかどうかの判定を加えています。

コード内のコメントに書いている通りですが、A1セルの値(Range(“A1”).Value)がエラーかどうかをIsError関数で判定し、エラーでない場合は変数にセルの値を代入し、エラーの場合はイミディエイトウィンドウにCVErr関数を使ってエラー内容を出力しています。

Sub cellError3()
    Dim s   As String
    
    '// A1セルに不正な数式を設定
    Range("A1").Formula = "=ABCDEFG()"
    
    '// A1セルの値がエラーではない場合
    If IsError(Range("A1").Value) = False Then
        s = Range("A1").Value
    Else
        '// 「エラー 2029」がイミディエイトウィンドウに出力される
        Debug.Print CVErr(Range("A1").Value)
    End If
End Sub

セルのエラーの種類

IsError関数で判定できるセルの数式のエラーにはいくつかの種類があり、それぞれセル上での表示が異なります。そして、エラーの種類ごとにVBAで扱える定数としてXlCVError列挙型が定義されています。

RangeオブジェクトのValueプロパティをDebug.Printなどで参照したときに「エラー 2029」などと表示されるのはエラーの種類の値を示しています。そのため「エラー 2029」であれば「#NAME?」を意味します。

XlCVError列挙型の定数には不明なものも多いですが、一応全て載せておきます。

繰り返しになりますが、いずれのエラーもIsError関数で検出できます。

定数 定数値 エラー 内容
xlErrBlocked 2047 不明 不明
xlErrCalc 2050 不明 不明
xlErrConnect 2046 不明 不明
xlErrDiv0 2007 #DIV/0! 数式で0での割り算が発生している。
xlErrField 2019 不明 不明
xlErrGettingData 2043 不明 不明
xlErrNA 2042 #N/A Excel関数の引数が不適切で正しく動作していない。
xlErrName 2029 #NAME? Excel関数名が間違っている。
xlErrNull 2000 #NULL! Excel関数の引数にスペースが入っている。
xlErrNum 2036 #NUM! Excel関数の引数の値が不正。
xlErrRef 2023 #REF! Excel関数で参照しているセルが無い。
xlErrSpill 2045 不明 不明
xlErrUnknown 2048 不明 不明
xlErrValue 2015 #VALUE! Excel関数の引数が間違っている。
]]>
VBAのSelectionのセルの格納順序 https://vbabeginner.net/selection-cell-storage-order/ Sun, 06 Nov 2022 00:37:13 +0000 https://vbabeginner.net/?p=6536 Selectionのセル範囲はどの順に格納されているのか

Selectionプロパティは現在選択されているセル範囲を表します。セルだけでなくオートシェイプでも複数選択した場合に利用できますが、ここではセル範囲のSelectionプロパティについて説明します。

Selectionプロパティのセル範囲が格納されている場合、セル範囲の左上から右下のセルに向かって格納されています。正確には1行分の一番左から右まで、そして次の行の、という順です。英字のZの書き順と同じです。

セルを選択する場合、開始セルはセル範囲の四隅(左上、右上、左下、右下)のどこからで範囲選択可能ですが、いずれの場合も、Selectionは一番左上のセルが先頭に格納され、一番右下のセルが最後になります

セル範囲のSelectionプロパティのセルを1つずつ処理する場合、For Eachを使って1つずつ取り出すのが一番簡単な方法です。

Sub SelectionForEach()
    Dim r   As Range        '// セル1つ分を示すRangeオブジェクト
    
    '// セル範囲をループ
    For Each r In Selection
        '// ループで取得したセルの座標を出力
        Debug.Print r.Address(False, False)
    Next
End Sub

実行結果
C5
D5
C6
D6
C7
D7

上の絵のように、左上から右下に向かって、ループで取得したセルの座標が出力されます。

開始セルから終了セルに向かって処理をしたい場合

上に書いたとおり、Selectionプロパティに格納される順は左上のセルから右下のセルに向かって格納されます。

そのため、セル範囲の開始セルから終了セルに向かう順番で処理をしたい場合で、かつ、開始セルが左上でない場合は、開始セルがどこ(右上?左下?右下?のどこ)なのかを考慮する特殊処理が必要になります。

方法としてはCells(行, 列)を使って処理する方法があります。

まず、アクティブセルの座標を取得します。これが開始セルになります。

そのあとに、Selectionプロパティの四隅の座標を取ります。そうすると、アクティブセルが左上、右上、左下、右下のどこなのかを特定できます。

そして、開始セルを特定したとは、対角線上のセルが終了セルと判定できます。

あとは、開始セルから終了セルに向かってCells(行, 列)のRangeオブジェクトを使って処理すれば、開始セルから終了セルの順に処理できます。ただ、このような特殊処理が必要な状況は相当なレアケースと思います。

セル範囲のSelectionプロパティはRangeオブジェクトに変換できる

上のコードではループの書き方で、

For Each r In Selection

と書いています。rはRangeオブジェクトの変数です。

この書き方でもいいですし、私もこのように書くことが多いのですが、SelectionプロパティをRangeオブジェクト変数に代入して書くことも出来ます。

Sub SelectionForEach2()
    Dim sr  As Range        '// Selectionプロパティ用のRangeオブジェクト
    Dim r   As Range        '// セル1つ分を示すRangeオブジェクト
    
    '// SelectionプロパティをRangeオブジェクトにセット
    Set sr = Selection
    
    '// Selectionプロパティのセル範囲座標を出力
    Debug.Print sr.Address(False, False)
    
    '// セル範囲をループ
    For Each r In sr
        '// ループで取得したセルの座標を出力
        Debug.Print r.Address(False, False)
    Next
End Sub

実行結果
C5:D7
C5
D5
C6
D6
C7
D7

最初のコードと違うのは、SelectionプロパティをRangeオブジェクト変数の「sr」に代入して、あとはそれを利用している部分です。

このようにSelectionプロパティをRangeオブジェクトとして代入すると、1つ利点があります。それは、ドット「.」を押したあとにプロパティやメソッド候補が出るようになる点です。

Selectionプロパティ自体には明確なデータ型がありません。セル範囲を示すこともあれば、オートシェイプの選択範囲を示すこともあるので、どちらのデータ型かをSelectionプロパティ自体が持てないためです。

そこで、明らかにセル範囲を示していると分かっている場合はRangeオブジェクト変数に入れてしまえば、あとはRange型として扱えます。

]]>
VBAのRangeに違う座標が設定されている理由 https://vbabeginner.net/different-coordinates-set-in-range/ Thu, 11 Aug 2022 16:56:29 +0000 https://vbabeginner.net/?p=6504 Rangeが違う座標を指している?

Rangeオブジェクトにセル範囲を代入したのに、「Rangeオブジェクトがへんな座標になっている。ちゃんと設定しているはずなのに・・・」というようなセル座標がおかしくなっている状況になることがあります。

例えば、B2セルからC5セルまでの8個のセルをRangeオブジェクトの変数に入れているはずなのに、C3からD6になっている、という状況です。実際のExcelではこんな感じです。

これは、一度セル範囲を設定済みのRangeオブジェクトに対して、さらにRangeオブジェクトでセル範囲を重ねて指定するために起きます。

上の例を発生させるだけのコードであればこんな感じになります。

Sub RangeRangeTest1()
    Debug.Print Range("B2:C5").Range("B2:C5").Address(False, False)  '// C3:D6 が出力される
End Sub

コードにはセル範囲として”B2:C5″しか書かれていませんが、Addressプロパティでセル範囲が”C3:D6″で出力されています。

「Range(“B2:C5”).Range(“B2:C5”)」は、左側のRange(“B2:C5”)のセル範囲の左上をA1とみなして、そこを基準として、右側のRange(“B2:C5”)が判定されます。左側のRange(“B2:C5”)のB2セルをA1セルとみなすため、そこから右側のRange(“B2:C5”)は、C3:D6になります。

このように、Range.Range、としてRangeを連続して書いた場合には、左側のRangeオブジェクトのセル範囲の左上を基準にして、右側のRangeオブジェクトで指定したセル範囲が判定されます。

RangeのRangeになる書き方1:Rangeオブジェクト変数を使う

最初の例では単純に「Range(“B2:C5”).Range(“B2:C5”)」と連続して書いていますが、実際のコードでは以下のような書き方もできます。

Rangeオブジェクトの変数を用意しておき、そこにRangeオブジェクトでセル範囲を指定して代入する方法です。このサイトではこの書き方をよくします。

Sub RangeRangeTest()
    Dim r   As Range
    Dim r2  As Range
    
    Set r = Range("B2:C5")
    Set r2 = r.Range("B2:C5")
    
    Debug.Print r2.Address(False, False)  '// C3:D6 が出力される
End Sub

最初の例と同じ結果になります。Rangeオブジェクトの変数を2つ用意していて、それぞれに”B2:C5″の座標を設定しています。にも関わらず、コードに書いていない”C3:D6″が出力されます。

問題なのは、6行目の「Set r2 = r.Range(“B2:C5”)」の「r.」の部分です。

5行目で変数rにはRange(“B2:C5”)が設定されています。6行目の「r.Range(“B2:C5”)」は書き換えると「Range(“B2:C5”).Range(“B2:C5”)」となります。このような書き方はエラーではなく、コードとしては問題なく動作します。

ただ、Rangeオブジェクト変数を使ってコードを書いている人は、おそらく概念をちゃんと理解していることが多いと思いますので、そもそもRange.Rangeのような罠にハマることは少ないのかもしれません。

RangeのRangeになる書き方2:Withを使う書き方

おそらくRange.Rangeの罠にハマる書き方で多いのはWithを使ったコードを書いている場合が考えられます。

Withを使ったコードはマクロの記録機能を使った場合に見ることがよくあります。

マクロの記録で、セル範囲の黄色背景色+赤文字+太字、を設定するとこのようなコードが記録されます。

Sub Macro1()
'
' Macro1 Macro
'

'
    Range("B12:D17").Select
    With Selection.Interior
        .Pattern = xlSolid
        .PatternColorIndex = xlAutomatic
        .Color = 65535
        .TintAndShade = 0
        .PatternTintAndShade = 0
    End With
    With Selection.Font
        .Color = -16776961
        .TintAndShade = 0
    End With
    Selection.Font.Bold = True
End Sub

ここで注目すべきなのはWithを使っている2か所です。「With Selection.Interior」と「With Selection.Font」の部分です。WithとEnd Withの間には、Withで指定されたオブジェクトのプロパティが書かれています。このように親オブジェクトを省略して書くことが出来るのですが、理解していないのであれば使わない方がいいです。

このWithですが、Rangeオブジェクトでも使えます。上のコードをWithを使って書くとこんな感じになります。

Sub RangeRangeTest2()
    Dim r   As Range
    Dim r2  As Range
    
    Set r = Range("B2:C5")
    
    With r
        Set r2 = .Range("B2:C5")
    End With
    
    Debug.Print r2.Address(False, False)  '// C3:D6 が出力される
End Sub

問題なのは「Set r2 = .Range(“B2:C5”)」の部分です。「.Range」がWithに掛かっていることを分かる人は相当慣れている人です。初心者の方はなかなか気づかないでしょう。

Withを使ってこのような書き方をすると、どの部分がRange.Rangeの形になっているのかが分かりにくくなります。

Range.Rangeを書くのではなくOffsetを使いましょう

上で書いたとおり、Range.Rangeの形になるセル範囲の書き方はやめておいた方がいいです。

コードに書いた”B2:C5″などの座標とは全く違う座標が参照されることになり、コードを追いかけないと本当に参照しているセル範囲が分かりません。これはどんなに熟練のプログラマーでも同じです。

Range.Rangeの書き方はVBAのコードとしては正しいですが、Excelの操作を行う上ではまず必要ありません。

座標を変えたい場合は以下のようにRangeオブジェクトのOffsetプロパティを使えば可能です。

Sub RangeRangeTest3()
    Dim r   As Range
    
    Set r = Range("B2:C5")
    Debug.Print r.Address(False, False)     '// B2:C5 が出力される
    
    '// 下に1、右に2移動
    Set r = r.Offset(1, 2)
    Debug.Print r.Address(False, False)     '// D3:E6 が出力される
End Subこ

このコードは、元々B2:C5のセル範囲を保持していたRangeオブジェクト変数のrに対して、下に1、右に2、の部分にあるセル範囲を代入しなおしています。代入後はD3:E6のセル範囲が変数rに保持されます。

Offsetプロパティについては「VBAで基準セルから相対セルを参照する(Offset)」をご参照ください。

]]>
VBAのRangeオブジェクトのValueは省略禁止 https://vbabeginner.net/omit-value-property/ Thu, 16 Dec 2021 17:02:10 +0000 https://vbabeginner.net/?p=6416 Valueプロパティの省略

セルの値を参照する際に「Range(“A1”).Value」のようにValueプロパティを使って書きますが、.Valueを省略して「Range(“A1”)」と書いてもセルの値を参照することが出来ます。

ありがちなのが「付け忘れてたけど、たまたま動いてた」って奴です。

ただ、先に結論から書くと、.Valueの省略はお勧めしません。むしろ、ちゃんと「.Value」と書きましょう。もっと言うと、「こんな余計な機能を入れるから初学者が混乱すんだよ」というのが本音です。Valueの省略禁止の理由については後述します。

以下のコードは.Valueを付けた場合と付けない場合の書き方で、どちらもA1セルの値をイミディエイトウィンドウに出力します。

Sub RangeValueTest1()
    Debug.Print Range("A1").Value
    Debug.Print Range("A1")
End Sub

同じことをやってるのに書き方が違う、というのは特に初学者には大変苦痛です。なんでもいいから正解を1つ教えてくれ、というのが心情でしょう。

Valueを省略してもセルの値が参照できる理由

言語仕様の話になりますが、Valueプロパティを書かなくてもセルの値が参照されるのには理由があります。

それは、Rangeクラスに対して引数無しで渡された場合、Valueプロパティの呼び出しを行うようにRangeクラスの内部処理で実装されているためです。ちなみに、省略した場合と省略しなかった場合での処理速度の差はありません。省略したからと言って遅くなるようなことは無いです。

VBAのクラスモジュールでは引数なしのコンストラクタは実装できないため、VBAではRangeクラスの内容を表現できないのですが、他プログラミング言語が分かる方に向けて言えば、C++で言えば以下のような感じです。Rangeクラスのコンストラクタに引数で座標アドレスの文字列を渡されたら、Valueメソッドを呼び出すような考え方です。

一応ですが、考え方が分かればOKな例として書いてるだけでC++のコンパイルは通してないです。通す気もないのでご了承を。

class Range
{
private:
    char sCellAddress[10];
    
public:
    // Rangeクラスのコンストラクタ:引数はセル座標文字列
    Range(char *psCellAddress)
    {
        // Valueメソッドを呼び出し
        Value(psCellAddress);
        
        memset(sCellAddress, 0x00, sizeof(sCellAddress));
        strcpy(sCellAddress, psCellAddress);
    }
    
    // Valueメソッド:引数はセル座標文字列
    char* Value(char *psCellAddress)
    {
        return GetCellValue(psCellAddress);
    }
    
    // Valueメソッド:引数なし
    char* Value()
    {
        return GetCellValue(sCellAddress);
    }
}

Valueを省略しない方がいい理由

上記の通り、.Valueを書かなくてもセルの値を参照することは出来るのですが、.Valueを省略した場合にはRangeオブジェクトを返すという役割持っています。

書き方が同じなのに、.Valueを省略した場合には2つの用途があります。

以下のコードは「Range(“A1”)」を2か所で使っていますが、1つはセル自体を表すRangeオブジェクトとして使っており、もう1つはValueプロパティを省略した場合のセルの値の取得に使っています。言い方を変えると「Range(“A1”)」という書き方は同じでも違う用途で使っているということです。

Sub RangeTest2()
    Dim r   As Range    '// セル自体
    Dim s   As String   '// セル値
    
    '// A1セルのRangeオブジェクトを取得
    Set r = Range("A1")
    '// 取得したRangeオブジェクトの座標を出力
    Debug.Print r.Address
    
    '// A1セルの値を取得
    s = Range("A1")
    '// セルの値を出力
    Debug.Print s
End Sub

このように、「Range(“A1”)」という書き方だけでセルの値を取得しているのか、それともRangeオブジェクトを取得しているのかがすぐに分かる方は相当VBAのコードに慣れている方です。

これが.Valueを省略しない方がいい一番の理由です。オブジェクトとセル値のどちらの代入をやっているのかわかりにくいのです。

おそらく言語仕様を決める当初は「省略して書けるように言語設計しておけばプログラミングをしたことがない人でも動くプログラムになるからお助け機能としてValueを省略してもOKにしよう」とか考えたのだと思いますが、結果としてそれが余計な混乱を生むだけになっています。

Valueを省略する場合としない場合の書き分け

プログラミングに慣れている方であればあるほど、.Valueプロパティの省略はしない傾向が強くなると思われます。その理由は、プログラミング上級者であればあるほど不確定要素を排除し、より安全なコードを書こうとする意識が働くからです。上級者は「Valueを省略するとあまり詳しくない人が見たときや自分が将来忘れたときに勘違いするかもしれんから避けておこう」と考えます。そのため、Valueを省略して書くのは初学者の方が多いはずです。

以降は初学者向けの説明です。

セルの値を参照したい場合は.Valueを付けるようにした方がいいです。

.Valueを付けない場合はRangeオブジェクトとして扱う場合のみです。具体的には以下のようにRangeクラス型の変数に代入するときだけです。

Dim r As Range
Set r = Range("A1")

もしあなたがVBAに慣れていなかったり、「Rangeオブジェクト」と言われてもよく分からない、というのであれば、そもそもこのコードのようなSetでのRangeオブジェクトの代入をするようなコードを自分で書く機会はまずありません。

基本的にはValueを付けておいて、なんかエラーが出たら外してみる、ぐらいで構いません。

段階ごとに単純に書くと以下のようになります。

  1. よくわからんのならとりあえず.Valueを付ける
  2. セルの値を使うときは.Valueを付ける
  3. =での代入の場合は.Valueを付ける
  4. Setでの代入の場合は.Valueは付けない
  5. 関数の引数の場合は.Valueを付けて、エラーが出たら外してみる。(引数の説明で「Rangeオブジェクト」と書いてあったら外すのが正解です)

繰り返しになりますが、Valueはきちんと書きましょう。省略禁止です。

]]>
VBAで入力済みセル範囲を判定する(UsedRange) https://vbabeginner.net/usedrange/ Sat, 23 Oct 2021 15:58:58 +0000 https://vbabeginner.net/?p=6317 必要なセルだけを処理したい

VBAでセルの値を使った処理を行う場合、必ず考えないといけなくなるのが、「何行目、何列目まで処理するか?」という点です。それを無視するとシートの最大行や最大列に達してしまい、それ以上の範囲を処理しようとしてエラーが発生してしまいます。

エラーにならないように考えないといけないこととして以下のようなことが挙げられます。

  1. 空行や空列があった場合どうしたらよいか?
  2. 値が入っていないけど背景色だけ設定している場合はどうしたらよいか?
  3. 関数が入っているけど表示は空になっている場合はどうしたらよいか?

このように、いろいろとセルの内容によって考慮しなければならないことが発生しますが、「処理しなければならないセル範囲はどこか」という点については、UsedRangeプロパティを使えば、これらの考慮しないといけないことは解決します

構文

Property Worksheet.UsedRange As Range

UsedRangeプロパティは親オブジェクトとしてワークシートを指定します。ワークシートに入力されているセルの範囲をRangeオブジェクトを返します。

新規シートに追加しただけで何も入力されていないシートの場合は、入力されているセル範囲がないですがA1セルのみをセル範囲として返します。

そのため、UsedRangeは親オブジェクトとしてワークシートが存在していれば必ずRangeオブジェクトを返してくれます。

セル入力範囲を処理する場合のコード

入力されているセル範囲のセルを1つずつ処理する、というのはセルの処理を行う場合には結構使い道が多いです。

以下のコードはそのひな形として使える形にしています。

やっていることは、ワークシート(変数名:sht)のUsedRangeプロパティが示すセル範囲のセルをFor Eachで1セルずつ取得し、★マークの部分で実施したいセルの処理を書きます。例としてAddressプロパティでのセル座標を出力しています。

Sub UsedRangeSample()
    Dim sht As Worksheet    '// ワークシート
    Dim r   As Range        '// セル
    
    '// アクティブシートをWorksheetオブジェクトに設定
    Set sht = ActiveSheet
    
    '// シートのセル範囲を1セルずつループ
    For Each r In sht.UsedRange
        '// ★ここにセルの処理を書く

        '// セル座標を出力
        Debug.Print r.Address(False, False)
    Next
End Sub

他のサンプル

上のUsedRangeプロパティを使ったサンプルコードですが、セル範囲を処理したい場合にはいろんな用途で利用できます。

以下は当サイトでUsedRangeプロパティを使っているページを抜粋しました。それぞれ用途は異なりますが、セル範囲を1セルずつ処理する、という考え方は上のサンプルコードと同じです。リンク先のコードはUsedRangeプロパティを使って実際にやりたいことが具体的に書いていますので、書き方などで迷うことがあったら参考にしてみてください。

  1. VBAで先頭のシングルクォーテーションを一括削除する
  2. VBAで数字や数式のセルの書式を文字列から標準に変更する
  3. VBAでセルの書式変更を反映させる
  4. VBAで非表示の行や列を見つける
  5. VBAで2つの表の違いを調べる
  6. VBAでシートの全セルの全角英数字を半角に変換する
  7. VBAで最終入力行のすぐ下の空白セルを選択する
  8. VBAで正規表現でのセル検索と置換を行う
  9. VBAで指定値を超えたセルを探す
  10. VBAで配列から重複する値を順序を変えずに削除する
  11. VBAで現在位置や表の一番下から1つ下のセルを選択
  12. VBAでシートのスクロール範囲を指定する
  13. VBAでシートの全セルの背景色をクリアする
  14. アクティブセルが入力セル範囲内か判定する
  15. VBAで同じ値のセルがいくつあるかを高速に数える
  16. VBAで数式が設定されているセルを高速で探す
  17. VBAで結合セルの場所を高速に探す
  18. VBAで別ブックのシートやセルを参照する
  19. VBAでシートの内容をテキストファイルに出力する
  20. VBAで取消線が付いた文字を削除する
  21. VBAで指定文字列があるセルに背景色を設定する
  22. VBAで空行を削除して行を詰める
  23. データがあるセル範囲に罫線を設定する
  24. 入力領域の終端セルを参照する
  25. VBAで2次元配列の初期化と利用方法
  26. VBAで編集セル範囲の選択と最終行と最終列の取得
]]>
VBAで先頭のシングルクォーテーションを一括削除する https://vbabeginner.net/delete-begin-single-quotation/ Sun, 17 Oct 2021 14:57:16 +0000 https://vbabeginner.net/?p=6305 セル先頭のシングルクォーテーションが邪魔

セルの表示形式は初期状態は「標準」になっています。「標準」のセルに数字を入力すると、数値として解釈されます。例えば「0.0」と入力しても右寄せで「0」と表示されます。

Excel使い始めの方が嫌がるExcelのおせっかい機能とも言えます。

その対応方法としてよく使われる方法が、先頭にシングルクォーテーションを付けて「’0.0」と入力する方法です。こうすることでセルに「0.0」と表示されます。

ただ、このシングルクォーテーションで数値を数字とする入力方法は欠点があります。それは、セルの表示形式を「標準」から「数値」などに変更してもシングルクォーテーションがあるせいでそちらの方が有効になり、見た目は「文字列」のままで扱われてしまう点です。

なので、Excelの操作に慣れている人はセルの書式設定(Ctrl + 1キー、または、セルを右クリック+セルの書式設定)で、表示形式から「文字列」を選んで、入力した内容をそのまま表示することもあるでしょう。

このように、表示形式でセルの表示をきちんと設定したい人にとっては、セル先頭のシングルクォーテーションの入力が邪魔な場合が多々あります。

そこで以下ではセル先頭にシングルクォーテーションを設定されているセルの検索、および、シングルクォーテーションを一括削除する方法を紹介します。

先頭がシングルクォーテーションのセルに背景色を設定する

以下のコードはアクティブシートでシングルクォーテーションが先頭に設定されているセルが分かるようにします。

5行目のActiveSheet.UsedRangeはアクティブシートで入力されているセル範囲を意味します。

明示方法は背景色を黄色にしています。イミディエイトウィンドウにもセル座標を出力します。

Sub SearchSingleQuote()
    Dim r   As Range    '// セル
    
    '// アクティブシートで入力されている範囲をループ
    For Each r In ActiveSheet.UsedRange
        '// 先頭がシングルクォーテーションの場合
        If r.PrefixCharacter = "'" Then
            '// 座標をイミディエイトウィンドウに出力
            Debug.Print r.Address(False, False)
            
            '// セルの背景色を黄色にする
            r.Interior.Color = vbYellow
        End If
    Next
End Sub

実行結果

A1セルとB3セルに「’0.0」と入力している場合の結果です。それぞれが黄色に設定され、イミディエイトウィンドウにもセル座標が出力されます。

選択範囲のセル先頭のシングルクォーテーションを一括削除する

以下のコードは選択セル範囲の中で、先頭文字がシングルクォーテーションの場合は削除します。

コメントにも書いていますが、RangeオブジェクトのValueプロパティを再設定するとシングルクォーテーションが削除されます

これはどういうことかというと、セル先頭のシングルクォーテーションはValueプロパティには含まれておらず、例えば「’1.00」とシングルクォーテーション付きで入力していてもValueプロパティでは「1.00」として扱われているため、それをValueプロパティに再設定することでシングルクォーテーションが外れることになります。

先のコードで先頭がシングルクォーテーションかどうかの判定にPrefixCharacterプロパティを使っていましたので、これを編集すればシングルクォーテーションを削除できそうな気になりますが、残念ながらPrefixCharacterプロパティは参照しかできません。PrefixCharacterプロパティを””に更新してシングルクォーテーションを削除しようとしてもエラーになります。

なお、コード実行後はセルの表示形式に合わせて表示されます。表示形式が「標準」のままであれば「’0.0」は「0」が表示されますが、「数値」になっていれば表示形式の実体は「0_」のため、「’0.0」は「0△」(△は半角スペース)のようにの右に空白が付いて表示されます。

Sub DeleteBeginSingleQuote()
    Dim r   As Range    '// セル
    
    '// 選択セル範囲をループ
    For Each r In Selection
        '// 先頭がシングルクォーテーションの場合
        If r.PrefixCharacter = "'" Then
            '// 座標をイミディエイトウィンドウに出力
            Debug.Print r.Address(False, False)
            
            '// Valueを再設定するとシングルクォーテーションが削除される
            r.Value = r.Value
        End If
    Next
End Sub

実行結果
実行前(「’0.0」と入力)

実行後(「’0.0」が「0.0」として入力されて「0」として表示)

]]>