幽霊続きのお話・・・

幽霊続きの話です。
幽霊セルというものがあることを偶然知ってしまいました。

幽霊セルは、セルに画面上表示されない値が入力されているのが原因で以下の3つが挙げられていました。

1.スペースのみのセル
2.空文字列のセル
3.シングルクォーテーションのみのセル

この中で項番2の空文字列のセルは過去に自分も生成してたかもというやつでした。

次のようなIF関数などを使った数式で空文字列を設定している場合に生じるという話だった。
=IF(A1="", "", A1*B1)
この数式では、入力のない行の計算結果に0を表示させないよう、A1セルが空のときにあえて空文字列("")を設定しています。
このように数式で空文字列を設定したセルをコピーし、別のセルに「値のみ貼り付け」をすると、その空文字列(長さ0の文字列)を値として保持するセルができてしまうという仰天するような話だった。これはこれで何も悪さしないのなら問題ないんだけど・・・
<悪さの例>
1.COUNTA関数が実際より多い数になる
2.空白なのにISBLANK関数がFALSEを返す
3.Ctrl+矢印のジャンプで何もないと所で止まる
結局、 目には見えないのに確かにEXCELは何かいると答える、まるで幽霊のようなセルと言えます。
時に、数式や集計を狂わせ、CSVや印刷で大量の空行を吐き出すなどの損害を被ることになります。
そんな幽霊セルを解決するブログに巡り合いました。

2020-02-24 空白なのに空白じゃないセルを空白にしたい
シートの空文字列を除去
空白なのに空白じゃないセルを空白にしたい - シーゴの Excel 研究室

上記のサイトのコード解析をする過程でこれまで、使ったことのなかったコードスニペットが以下になります。
■空白を網掛けにする
Range("C11").Select
Selection.SpecialCells(xlCellTypeBlanks).Select

■PCの画面全体を撮る

Public Sub 全体を撮る()
    keybd_event vbKeySnapshot, 0&, &H1, 0&
    keybd_event vbKeySnapshot, 0&, &H1 Or &H2, 0&
End Sub

■アクティブウィンドウを撮る

Public Sub アクティブ画面を撮る()
    keybd_event &HA4, 0&, &H1, 0& ' &HA4がAltキー、&H1キーを押す
    keybd_event vbKeySnapshot, 0&, &H1, 0& ' vbKeySnapshotがPrtScキー番号は&H2Cらしい
    keybd_event vbKeySnapshot, 0&, &H1 Or &H2, 0& 'PrtScキー &H2でキーを放す
    keybd_event &HA4, 0&, &H1 Or &H2, 0& ' Atlキーを放す
End Sub

■スペースのみの文字列を削除となっているが、結果的に以下の3つすべてを除去することに成功した。
(上記ブログの改変を行いシングルクォーテーションのみのセルのみ除去するようにした)
1.スペースのみのセル
2.空文字列のセル
3.シングルクォーテーションのみのセル

Sub テキスト置換_スペースのみの文字列を削除()

    Dim QMSG As String
    Dim RC
    
    QMSG = "「テキスト置換_スペースのみの文字列を削除」はシート上の"
    QMSG = QMSG & vbLf & "「スペースのみ」を一括除去します。"
    QMSG = QMSG & vbLf & "結果的に「'」のみのセル、空文字も除去します。"
    
    RC = MsgBox(QMSG, vbYesNo, "■シートの「スペースのみ」を除去")
    
    If RC = vbYes Then
    Else
        Exit Sub
    End If
    
    'Dim WS0 As Worksheet
    'Set WS0 = ActiveSheet
    
    Dim WS1 As Worksheet
    
    Set WS1 = Sheets("幽霊セル")
    WS1.Activate
        
    If TypeName(Selection) <> "Range" Then Beep: Exit Sub
    
    Dim rng As Range
    
    Set rng = Intersect(Selection, ActiveSheet.UsedRange)
    
    If rng Is Nothing Then Exit Sub
    
    Application.ScreenUpdating = False
    Call rangeTextReplaceAll(rng, "^( | )+$", "")
    Application.ScreenUpdating = True
    
    Range("C11").Select
    Selection.SpecialCells(xlCellTypeBlanks).Select
    MsgBox "「スペースのみの文字列を削除」が終了しました!"
    
End Sub

Private Sub rangeTextReplaceAll(rng As Range, rePattern As String, reReplace As String)

    Dim re As Object
    
    Set re = CreateObject("VBScript.RegExp")
    
    re.Global = True
    
    re.Pattern = rePattern
    
    Dim col As Range
    
    For Each col In rng.Columns
    
        Dim vals As Variant
        
        Dim preChar As Variant
        ReDim preChar(1 To col.Cells.Count, 1 To 1)
        
        Dim i As Long
        For i = 1 To col.Cells.Count
            If col.Cells(i, 1) <> "" Then
                preChar(i, 1) = col.Cells(i, 1).PrefixCharacter
            End If
        Next
        
        If col.Cells.Count > 1 Then
            vals = col.Formula
        Else
            vals = col.Resize(, 2).Formula
        End If
                
        For i = 1 To col.rows.Count
            If Left(vals(i, 1), 1) <> "=" Then
                vals(i, 1) = re.Replace(vals(i, 1), reReplace)
            End If
        Next
        
        col.Formula = vals
        
        For i = 1 To col.Cells.Count
            If preChar(i, 1) <> "" Then
                col.Cells(i, 1).Value = preChar(i, 1) & vals(i, 1)
            End If
        Next
      
    Next
    
End Sub

■シートにごみが残り、EXCELのサイズが膨れ上がっている場合には
 以下のサブルーチンが使えそう。
 選択範囲外の作成にこんなテニクニックを使うとは何か勉強になった気がした。
 冷静に考えるとわざわざこんなテク使わなくても単純に選択範囲外の作成は
 可能な気がしている。次回紹介することにする。

Sub セルのクリア_選択範囲以外をクリア()

    Dim QMSG As String
    Dim RC
    
    QMSG = "「セルのクリア_選択範囲以外をクリア」はシート上の"
    QMSG = QMSG & vbLf & "「選択範囲以外」を一括除去します。"
    
    RC = MsgBox(QMSG, vbYesNo, "■シートの「選択範囲以外」を除去")
    
    If RC = vbYes Then
    Else
        Exit Sub
    End If
    
    Dim WS1 As Worksheet
    
    Set WS1 = Sheets("幽霊セル")
    WS1.Activate
    
    If TypeName(Selection) <> "Range" Then Beep: Exit Sub
    If Selection.Cells.CountLarge = 1 Then Beep: Exit Sub
    
    Application.ScreenUpdating = False
    
    Dim diff As Range
    
    Set diff = rangeDiff(ActiveSheet.UsedRange, Selection)
    
    If Not diff Is Nothing Then
        diff.Clear
    End If
    
    Application.ScreenUpdating = True
    
End Sub

Private Function rangeDiff(rng1 As Range, rng2 As Range) As Range

    Dim tmp As Range
    
    Set tmp = Intersect(rng1, rng2)
    
    If tmp Is Nothing Then Set rangeDiff = rng1: Exit Function
    
    If tmp.Address = rng1.Address Then Set rangeDiff = Nothing: Exit Function
    
    With ActiveWorkbook.Worksheets.Add
    
        .Range(rng1.Address).Value = 1
        .Range(rng2.Address).Clear
        
        On Error Resume Next
        
        Set rangeDiff = rng1.Worksheet.Range( _
            .Range(rng1.Address).SpecialCells(xlCellTypeConstants).Address)

        On Error GoTo 0
        
        Application.DisplayAlerts = False
        .Delete
        Application.DisplayAlerts = True
        
    End With
    
End Function