条件付き書式の「条件」が数式の場合のカウント
条件付き書式で「条件」が数式の場合、その条件を満たすセルの数をカウントするマクロを作ってみました。しかし、条件を満たすセルの数をカウントするには、こんなもん使うよりも作業セルを用いて判定結果を出しておいて、それをカウントするようにした方が確実です。
結構手抜きで作っているので、「条件」が1つの場合だけしか使えません。しかも、いろいろ不備がある可能性があり、実用向きではありません(汗)。
なお、Call している関数は「数式文字列-A1方式・R1C1方式-変換」に書いてあるものです。
Sub Test1() Dim rngT As Range ' 条件付き書式の設定されているセル範囲 Dim rngA As Range Dim fcA As FormatCondition Dim wkCount As Long Dim wkFA1 As String Dim wkFR1C1 As String Set rngT = Worksheets("Sheet1").Range("B2:J10") rngT.Parent.Select Application.ReferenceStyle = xlA1 For Each rngA In rngT Set fcA = rngA.FormatConditions(1) wkFA1 = fcA.Formula1 Call A1ToR1C1(wkFA1, rngT.Cells(1, 1).Address(False, False), wkFR1C1) Call R1C1ToA1(wkFR1C1, rngA.Address(False, False), wkFA1) If Application.Evaluate(wkFA1) Then wkCount = wkCount + 1 End If Next rngA MsgBox wkCount, vbInformation End Sub
数式文字列-A1方式・R1C1方式-変換
Excelでは、セルの参照形式として、A1方式とR1C1方式があります。
A1方式で記述されている数式をR1C1方式の記述に変更するプロシージャと、その逆をするプロシージャを作ってみました。
実は、「条件付き書式の「条件」が数式の場合のカウント」のために作ったものです。
' # 数式文字列変換(A1→R1C1) # ' argFmlA1 :i 数式文字列(A1) ' argBaseAdls :i 基準セルアドレス ' argFmlR1C1 :o 数式文字列(R1C1) Public Function A1ToR1C1(ByVal argFmlA1 As String _ , ByVal argBaseAdls As String _ , ByRef argFmlR1C1 As String) As Boolean Dim aryD As Variant Dim wkF As String Dim i As Variant Dim V As Variant Dim j As Long Dim wkRowB As Long Dim wkColB As Long Dim wkExPos As Long Dim wkAdlsA As String Dim wkSheetA As String Dim rngA As Range Dim wkRowA As Long Dim wkColA As Long wkRowB = Range(argBaseAdls).Row wkColB = Range(argBaseAdls).Column aryD = Array("=", "<", ">", "(", ")", "+", "-", "*", "/", "^", "&", " ", ":", ",") wkF = argFmlA1 For Each i In aryD wkF = Replace(wkF, i, vbTab & i & vbTab) Next i V = Split(wkF, vbTab) For j = LBound(V) To UBound(V) ' シート参照の場合、シート部分とセル部分に分割する wkExPos = InStrRev(V(j), "!") If wkExPos = 0 Then wkSheetA = "" wkAdlsA = V(j) Else wkSheetA = Left$(V(j), wkExPos) wkAdlsA = Mid$(V(j), wkExPos + 1) End If ' セルかどうか判別する Set rngA = Nothing On Error Resume Next Set rngA = Range(wkAdlsA) On Error GoTo 0 ' セルの場合、R1C1形式に変換する If Not rngA Is Nothing Then wkRowA = rngA.Row wkColA = rngA.Column V(j) = "R[" & (wkRowA - wkRowB) _ & "]C[" & (wkColA - wkColB) & "]" V(j) = wkSheetA & Replace(V(j), "[0]", "") End If Next j argFmlR1C1 = Replace(Join(V, vbTab), vbTab, "") A1ToR1C1 = True End Function
' # 数式文字列変換(R1C1→A1) # ' argFmlR1C1 :i 数式文字列(R1C1) ' argBaseAdls :i 基準セルアドレス ' argFmlA1 :o 数式文字列(A1) Public Function R1C1ToA1(ByVal argFmlR1C1 As String _ , ByVal argBaseAdls As String _ , ByRef argFmlA1 As String) As Boolean Dim aryD As Variant Dim wkF As String Dim i As Variant Dim V As Variant Dim j As Long Dim wkRowB As Long Dim wkColB As Long Dim wkExPos As Long Dim wkAdlsA As String Dim wkSheetA As String Dim wkTxt As String Dim wkAdlsRowFlg As Boolean Dim wkAdlsColFlg As Boolean Dim wkRowA As Long Dim wkColA As Long wkRowB = Range(argBaseAdls).Row wkColB = Range(argBaseAdls).Column aryD = Array("=", "<", ">", "(", ")", "+", "-", "*", "/", "^", "&", " ", ":", ",") wkF = argFmlR1C1 For Each i In aryD wkF = Replace(wkF, i, vbTab & i & vbTab) Next i V = Split(wkF, vbTab) For j = LBound(V) + 2 To UBound(V) - 0 If Right(V(j - 2), 1) = "[" And V(j - 1) = "-" And InStr(1, V(j), "]") > 0 Then V(j) = V(j - 2) & V(j - 1) & V(j) V(j - 2) = "" V(j - 1) = "" End If Next j For j = LBound(V) To UBound(V) ' シート参照の場合、シート部分とセル部分に分割する wkExPos = InStrRev(V(j), "!") If wkExPos = 0 Then wkSheetA = "" wkAdlsA = V(j) Else wkSheetA = Left$(V(j), wkExPos) wkAdlsA = Mid$(V(j), wkExPos + 1) End If ' セルかどうか判別し、行・列の番号を求める wkAdlsRowFlg = False wkAdlsColFlg = False If wkAdlsA Like "R*C*" Then wkTxt = Mid$(wkAdlsA, 2, InStr(1, wkAdlsA, "C") - 2) If wkTxt = "" Then wkAdlsRowFlg = True wkRowA = wkRowB ElseIf IsNumeric(wkTxt) Then wkAdlsRowFlg = True wkRowA = CLng(wkTxt) ElseIf Left(wkTxt, 1) = "[" And Right(wkTxt, 1) = "]" Then wkTxt = Mid(wkTxt, 2, Len(wkTxt) - 2) If IsNumeric(wkTxt) Then wkAdlsRowFlg = True wkRowA = CLng(wkTxt) + wkRowB End If End If wkTxt = Mid$(wkAdlsA, InStr(1, wkAdlsA, "C") + 1) If wkTxt = "" Then wkAdlsColFlg = True wkColA = wkColB ElseIf IsNumeric(wkTxt) Then wkAdlsColFlg = True wkColA = CLng(wkTxt) ElseIf Left(wkTxt, 1) = "[" And Right(wkTxt, 1) = "]" Then wkTxt = Mid(wkTxt, 2, Len(wkTxt) - 2) If IsNumeric(wkTxt) Then wkAdlsColFlg = True wkColA = CLng(wkTxt) + wkColB End If End If End If ' セルの場合、A1形式に変換する If wkAdlsRowFlg And wkAdlsColFlg Then V(j) = wkSheetA & Cells(wkRowA, wkColA).Address(False, False) End If Next j argFmlA1 = Replace(Join(V, vbTab), vbTab, "") R1C1ToA1 = True End Function
両方ともあまり完成度の高くないコードなので、数式次第では期待した結果を返さない場合があります。
例えば、数式内に文字列があり、それがセルアドレスとaryDで使っている区切りで構成される場合などがそうです。
文字列分割プロシージャ(泣き別れ防止)
住所などの長い文字列を印刷する場合、適度な長さで分割して複数行にすることがよくあります。
その場合、単純に文字の長さだけで分割すると、変な位置で切れてしまって見栄えがよくありません。
そういうときのために、適切な位置で切るプロシージャを用意しておくと何かと便利です。
汎用的なので使いまわしがききますし。
なお、スペースで区切りを判定するようにしてあるので、スペースで区切られていないと単純に文字の長さだけで分割します。
' # 文字列分割(泣き別れ防止) # ' argI :i 分割対象文字列 ' argLen :i 分割単位文字数 ' argO :o 分割結果文字列 Private Function DivideText4(ByVal argI As String _ , ByVal argLen As Long _ , ByRef argO As String) As Boolean Dim wkRow(1 To 5) As String Dim wkTextBuf As String Dim wkText1 As String Dim DivPos As Long Dim FindFlg As Boolean Dim I As Long Dim j As Long wkTextBuf = argI wkRow(1) = argI For j = 1 To 4 If Len(wkRow(j)) <= argLen Then Exit For End If ' 区切り位置を探す FindFlg = False For I = argLen + 1 To 1 Step -1 wkText1 = Mid(wkTextBuf, I, 1) If wkText1 = Space(1) Or wkText1 = " " Then DivPos = I FindFlg = True Exit For End If Next I ' 区切り位置で区切る If FindFlg Then wkRow(j) = Mid(wkTextBuf, 1, DivPos - 1) wkRow(j + 1) = Mid(wkTextBuf, DivPos + 1) Else wkRow(j) = Mid(wkTextBuf, 1, argLen) wkRow(j + 1) = Mid(wkTextBuf, argLen + 1) End If ' 次の文字列を用意する wkTextBuf = wkRow(j + 1) Next j If wkRow(5) = "" Then argO = wkRow(1) & vbLf _ & wkRow(2) & vbLf _ & wkRow(3) & vbLf _ & wkRow(4) DivideText4 = True End If End Function
以下は、サンプルテスト用のドライバです。
Sub TEST() Dim I As String Dim O As String Dim Re As Boolean I = "鹿児島県 札幌市 千代田区 嵯峨二尊院門前善光寺山町 大字高津原 12−34 シティテラス大井高津原ヒルトップガーデン 777号室" Re = DivideText4(I, 25, O) Debug.Print I Debug.Print O Debug.Print "----+----1----+----2----+----3----+----4----+----5" I = "東京都 港区 六本木 1−6−1 泉ガーデンタワー 18階" Re = DivideText4(I, 25, O) Debug.Print I Debug.Print O Debug.Print "----+----1----+----2----+----3----+----4----+----5" End Sub
セルへの出力で配列を使う場合の注意点
Excelで大量の値を連続したセルに代入したい場合、値を2次元配列に格納しておいて、それを同じサイズのセル範囲のValueプロパティに代入するということはよく行われます。
これは、高速化のテクニックとして非常にポピュラーなものです。
1セルずつ出力しても配列で一気に出力しても処理結果はほとんどの場合は同じですが、常にそうなるとは限りません。
データの長さ次第で、後者に限ってエラーになります。
まずは、同じ処理結果になる場合のサンプルコードです。
Sub TEST() Const cLen As Long = 8203 ' ココ Dim shtA As Worksheet Dim aryX(1 To 1, 1 To 2) Set shtA = Worksheets("Sheet1") shtA.Cells(1, 1).Resize(1, 4).Clear aryX(1, 1) = cLen aryX(1, 2) = String(cLen - 1, "A") & "B" ' # 1セルずつ出力 # With shtA.Cells(1, 1) .Value = aryX(1, 1) .Offset(, 1).Value = aryX(1, 2) End With Debug.Print "1セルずつ出力 無事終了" ' # まとめて出力 # shtA.Cells(1, 3).Resize(1, 2).Value = aryX Debug.Print "まとめて出力 無事終了" End Sub
このコードを
Const cLen As Long = 8204 ' ココ
とすると、以下のエラーが出ます。
実行時エラー'1004':
アプリケーション定義またはオブジェクト定義のエラーです。
このエラーは、# まとめて出力 #で起こり、その前の# 1セルずつ出力 #は正常に動作します。
つまり、文字列長が8203文字を超える場合には、配列を使って一気に出力することができないということです。
(上記は、Excel2007で試しています。Excel2003以前では長さのリミットはこれよりも短く(911文字)なるようです。)
テキストファイル(UTF-8)の作成
シート上の一定のセル範囲にあるデータを UTF-8(Uni-Code) のテキストデータとして出力するマクロのサンプルです。
ただそれだけです。
' # 参照設定:Microsoft ActiveX Data Objects 2.8 Library Public Sub Main() Const cFilename As String = "TESTOUT.txt" Const cStartRow As Long = 2 Dim EndRow As Long Dim ADOStream As ADODB.Stream Dim shtT As Worksheet Dim i As Long Dim aryItem As Variant Dim OtRec As String Dim FName As String Set shtT = ThisWorkbook.Worksheets("Sheet1") ' B列を基準に最終行番号を取得する EndRow = shtT.Cells(shtT.Rows.Count, 2).End(xlUp).Row FName = Application.GetSaveAsFilename(InitialFileName:=ThisWorkbook.Path & "\" & cFilename _ , FileFilter:="テキストファイル (*.txt), *.txt" _ , Title:="出力ファイルの指定") If FName = CStr(False) Then Exit Sub Set ADOStream = New ADODB.Stream ADOStream.Open ADOStream.Charset = "UTF-8" On Error GoTo Error_Rtn For i = cStartRow To EndRow ' B列を基準に3列分のデータを取得する aryItem = shtT.Cells(i, 2).Resize(, 3).Value ' 3列分のデータを1レコードにする OtRec = aryItem(1, 1) & "," & aryItem(1, 2) & "," & aryItem(1, 3) & vbCrLf 'Debug.Print OtRec ADOStream.WriteText OtRec Next i ADOStream.SaveToFile Filename:=FName _ , Options:=adSaveCreateOverWrite ADOStream.Close MsgBox "出力完了", vbInformation Exit Sub Error_Rtn: Debug.Print "Error_Rtn", "i: " & i ADOStream.Close MsgBox "異常終了", vbExclamation End Sub
コピペとセル先頭の ' の削除
Excelは、数値や日付等に変換できる値をセルに入力した場合、自動的に数値や日付等に変換してセルに格納します。
セルの書式設定を「標準」にしたまま、これらの値を文字列として入力する場合には ' を先頭につけて入力します。
セルの書式設定「標準」のままで、セルの値のデータ型を保って、先頭の ' をとるには次の方法でできます。
- 値が入っているセルに対して、「コピー」する
- 別のセルに対して、「形式を選択して貼り付け」で「数式」or「値」で貼り付ける
ここでポイントなのは、貼り付ける際に別のセルを選択することです。
コピーしたセルと同じセルに貼り付けると、先頭の ' は残ってしまいます。
Excel側の内部処理で不要な貼り付けはスキップするようになっているのかもしれませんね。
(上記は、Excel2007 で試しています。)
ファイルが破損したと言われてもダメもとで…
Excelは、Ver.12 − Excel2007からファイル形式も変わって破損しにくくなったらしいです。
ブックの共有機能を使わなければ、以前からファイルの破損に遭遇することが少なかったのであまり実感はありませんが…。
Ver.12からシートあたりの行数も列数も格段に増えたので、非常に重宝してます。
しかし、環境に依存するのかもしれませんが、1シートに大量のデータを格納したまま保存すると、そのブックを開く時にファイルが破損した旨のメッセージが出ることがあるようです。
なお、シートの中はデータのみで数式やシェイプ等は一切なし、マクロもなし、ファイル形式は XLSB です。
(必ず再現する条件があるわけではないのですが、ここ半年くらいで2回遭遇しています。)
ここでブックを開く時にエラーが出た場合、どうも本当はファイルは壊れていないにも関わらずエラーだけが出ているということがあるようです。
この場合、ブックを開くのをキャンセルし、Windowsを一旦ログオフします。
そして、改めて、Windowsにログインして、ブックを開くと、今度はすんなりと開けることがあります。
勿論、常にこれでうまく行くという保証はまったくありませんが、ダメもとで試す価値は多いにあります。