SpecialCellsを改造した PART2
エクセルでセル同士の差集合が得られるようになったので、
前に作った SpecialCells の拡張版、 ESpecialCells も改造しなおした。
結構複雑な条件の指定も可能になって、なかなかイカシます。
2008/06/19 追記: 定数に o (UsedRange外のセル)を追加した。
ESpecialCells メソッド Rangeオブジェクトを返すメソッドです。 特定範囲のうち、指定された条件を満たしている全てのセル(Rangeオブジェクト)を返します。 ESpecialCells(TargetRange, CellTypesExpr, [Option1], [Option2]) 引数について TargetRange 必ず指定します。対象となるセル範囲を指定します。 CellTypesExpr 必ず指定します。取得するセルの種類を数式の表記で指定します。 使用できる定数は、次に示すいずれかです。 w 対象セル全体 (Whole) c 定数が含まれるセル (Constants) f 数式が含まれるセル (Formulas) o UsedRange外のセル (Outside) b 空のセル (Blanks) v 可視セル (Visible) cm コメントが含まれているセル (Comments) l UsedRangeの最後のセル (LastCell) af 条件付き書式が設定されているセル (AllFormatConditions) av 入力規則が設定されているセル (AllValidation) sf 同じ条件付き書式が設定されているセル (SameFormatConditions) sv 同じ入力規則が設定されているセル (SameValidation) + 和集合 (Union) * 積集合 (Intersect) - 差集合 (Except) () 演算順位 Option1, Option2 は省略可能です。 XlSpecialCellsValues クラスの定数を使用すると、 引数 TypeExpression に c(定数) または f(数式) を設定したとき、 特定の種類の定数や数式を含むセルだけを取得することができます。 複数の値を加算して指定すると、複数の定数や数式を指定できます。 この引数を省略すると、すべての定数および数式が対象になります。 使用できる定数は、次に示す XlSpecialCellsValues クラスのいずれかです。 xlErrors xlLogical xlNumbers xlTextValues Range を指定すると、 引数 CellTypesExpr に sf(同じ条件付書式) や sv(同じ入力規則) を設定したときの、 基準のセル(そのセルと同じ条件付書式や入力規則)となります。 この引数を省略すると、ActiveCell が基準のセルとなります。 使用例 次の使用例はシート全体から定数か数式で数値のセルを選択します。 ESpecialCells(Cells, "c+f", xlNumbers).Select 次の使用例は選択範囲からコメントのないセルを選択します。 ESpecialCells(Selection, "w-cm").Select 次の使用例は選択範囲からRange("A1")と同じ入力規則の空のセルを選択します。 ESpecialCells(Selection, "sv*b", Range("A1")).Select 次の使用例は選択範囲からRange("A1")と同じ条件付き書式のテキストのセルを選択します。 Option1 と Option2 の順番はどちらでも構いません ESpecialCells(Selection, "sf*c", Range("A1"), xlTextValues).Select ESpecialCells(Selection, "sf*c", xlTextValues, Range("A1")).Select 次の使用例は選択範囲から空のセルに "hoge" を入力します。 Selection.SpecialCells(xlBlanks) とは異なり UsedRange を超えた範囲も選択されます。 ESpecialCells(Selection, "b+o").Value = "hoge" 次の使用例は非表示の行を削除します。(非表示の列がある場合はエラーになります) ESpecialCells(Rows, "w-v").EntireRow.Delete
Option Explicit Public Function ESpecialCells _ ( ByRef TargetRange As Range , _ ByVal CellTypesExpr As String , _ Optional ByRef Opt1 As Variant, _ Optional ByRef Opt2 As Variant ) As Range '# オプション引数 Dim SpecialCellsValue As Long Dim BaseCell As Range ' Long型の場合 XlSpecialCellsValue クラスの値とする(Opt1を優先) If TypeName(Opt1) = "Long" Then SpecialCellsValue = CLng(Opt1) ElseIf TypeName(Opt2) = "Long" Then SpecialCellsValue = CLng(Opt2) End If ' 指定がない場合は、全ての種類とする If SpecialCellsValue = 0 Then SpecialCellsValue = xlTextValues + xlNumbers + xlLogical + xlErrors End If ' Range型の場合 sf, sv の時の基準セルとする(Opt1を優先) If TypeName(Opt1) = "Range" Then Set BaseCell = Opt1.Cells(1, 1) ElseIf TypeName(Opt2) = "Range" Then Set BaseCell = Opt2.Cells(1, 1) End If ' 指定がない場合は、アクティブセルを基準セルとする If BaseCell Is Nothing Then Set BaseCell = ActiveCell End If ' 演算用のスタック ReDim StackRange(0) As Variant ' CellTypesExpr を後置記法の配列に変換する Dim Postfix As Variant Postfix = Infix2Postfix(SplitFormula(CellTypesExpr)) ' 後置記法の配列を処理する Dim i As Long For i = 0 To UBound(Postfix) Dim e As Variant e = Postfix(i) Dim CellType As Long CellType = CxlCellType(e) If Not CellType = 0 Then Call StackPush(StackRange, _ mySpecialCells(TargetRange , CellType, _ SpecialCellsValue, BaseCell )) ElseIf e = "*" Then ' スタックから Range を2つ pop し 積集合を push する Call StackPush(StackRange, _ Intersect2(StackPop(StackRange), StackPop(StackRange))) ElseIf e = "+" Then ' スタックから Range を2つ pop し 和集合を push する Call StackPush(StackRange, _ Union2(StackPop(StackRange), StackPop(StackRange))) ElseIf e = "-" Then ' スタックから2つ Range を pop し 差集合を push する Dim r1 As Range, r2 As Range Set r1 = StackPop(StackRange) Set r2 = StackPop(StackRange) Call StackPush(StackRange, Except2(r2, r1)) End If Next ' 戻り値が Range かチェックしてから返す If TypeName(StackPeek(StackRange)) = "Range" Then Set ESpecialCells = StackPop(StackRange) End If End Function ' ESpecialCellsから呼ばれて SpecialCells を実行する Private Function mySpecialCells _ (ByVal TargetRange As Range , _ ByVal CellType As XlCellType , _ ByVal SpecialCellsValue As XlSpecialCellsValue, _ ByRef BaseCell As Range ) As Range ' CellType に応じて処理する Select Case CellType ' 指定した範囲をそのまま返す Case xlWhole ' (これは XlCellType クラスではない) Set mySpecialCells = TargetRange ' UsedRange の外側 Case xlOutside ' (これも XlCellType クラスではない) Set mySpecialCells = Except2(TargetRange, _ TargetRange.Parent.UsedRange) ' 基準のセルがある場合 Case xlCellTypeSameFormatConditions, xlCellTypeSameValidation On Error Resume Next Set mySpecialCells _ = Intersect2(TargetRange, BaseCell.SpecialCells(CellType)) On Error GoTo 0 ' 基準のセルがない場合 (SpecialCellsValue の指定がある) Case xlCellTypeConstants, xlCellTypeFormulas On Error Resume Next Set mySpecialCells _ = Intersect2(TargetRange, _ TargetRange.SpecialCells(CellType, SpecialCellsValue)) On Error GoTo 0 ' 基準のセルがない場合 (SpecialCellsValue の指定がない) Case Else On Error Resume Next Set mySpecialCells _ = Intersect2(TargetRange, TargetRange.SpecialCells(CellType)) On Error GoTo 0 End Select End Function ' CellTypesExpr で使用できる定数 Private Function CxlCellType(ByVal v As Variant) As XlCellType If Not TypeName(v) = "String" Then Exit Function Select Case LCase(Trim(v)) ' 全てのセル(1) Case "w" : CxlCellType = xlWhole ' 条件付き書式が設定されているセル(-4172) Case "af": CxlCellType = xlCellTypeAllFormatConditions ' 入力規則が含まれているセル(-4174) Case "av": CxlCellType = xlCellTypeAllValidation ' 空のセル ( 4) Case "b" : CxlCellType = xlCellTypeBlanks ' UsedRangeの外側 ( 3) Case "o" : CxlCellType = xlOutside ' コメントが含まれているセル(-4144) Case "cm": CxlCellType = xlCellTypeComments ' 定数が含まれているセル( 2) Case "c" : CxlCellType = xlCellTypeConstants ' 数式が含まれているセル(-4132) Case "f" : CxlCellType = xlCellTypeFormulas ' 使われたセル範囲内の最後のセル( 11) Case "l" : CxlCellType = xlCellTypeLastCell ' 同じ条件付き書式が設定されているセル(-4173) Case "sf": CxlCellType = xlCellTypeSameFormatConditions ' 同じ入力規則が含まれているセル(-4175) Case "sv": CxlCellType = xlCellTypeSameValidation ' すべての可視セル( 12) Case "v" : CxlCellType = xlCellTypeVisible ' いずれでもない Case Else: CxlCellType = 0 End Select End Function '############################################################################ '## 集合操作用の関数 '############################################################################ ' 複数のセル ArgList の和集合(Range)を返す Public Function Union2(ParamArray ArgList() As Variant) As Range Dim buf As Range Dim i As Long For i = 0 To UBound(ArgList) If TypeName(ArgList(i)) = "Range" Then If buf Is Nothing Then Set buf = ArgList(i) Else Set buf = Application.Union(buf, ArgList(i)) End If End If Next Set Union2 = buf End Function ' 複数のセル ArgList の積集合(Range)を返す Public Function Intersect2(ParamArray ArgList() As Variant) As Range Dim buf As Range Dim i As Long For i = 0 To UBound(ArgList) If Not TypeName(ArgList(i)) = "Range" Then Exit Function ElseIf buf Is Nothing Then Set buf = ArgList(i) Else Set buf = Application.Intersect(buf, ArgList(i)) End If If buf Is Nothing Then Exit Function Next Set Intersect2 = buf End Function ' SourceRange から ArgList を差し引いた差集合(Range)を返す Public Function Except2 _ (ByRef SourceRange As Variant, ParamArray ArgList() As Variant) As Range If TypeName(SourceRange) = "Range" Then Dim buf As Range Set buf = SourceRange Dim i As Long For i = 0 To UBound(ArgList) If TypeName(ArgList(i)) = "Range" Then Set buf = Intersect2(buf, Invert2(ArgList(i))) End If Next Set Except2 = buf End If End Function ' SourceRange の補集合(Range)を返す Public Function Invert2(ByRef SourceRange As Variant) As Range If Not TypeName(SourceRange) = "Range" Then Exit Function Dim sh As Worksheet Set sh = SourceRange.Parent Dim buf As Range Set buf = SourceRange.Parent.Cells Dim a As Range For Each a In SourceRange.Areas Dim AreaTop As Long Dim AreaBottom As Long Dim AreaLeft As Long Dim AreaRight As Long AreaTop = a.Row AreaBottom = AreaTop + a.Rows.Count - 1 AreaLeft = a.Column AreaRight = AreaLeft + a.Columns.Count - 1 '■□□ '■×□ ×が Area~ の四隅の座標 '■□□ 抽出するのは■の部分 Dim RangeLeft As Range Set RangeLeft = GetRangeWithPosition(sh, _ sh.Cells.Row, sh.Cells.Column, sh.Rows.Count, AreaLeft - 1) ' Top Left Bottom Right '□□■ '□×■ ×が Area~ の四隅の座標 '□□■ 抽出するのは■の部分 Dim RangeRight As Range Set RangeRight = GetRangeWithPosition(sh, _ sh.Cells.Row, AreaRight + 1, sh.Rows.Count, sh.Columns.Count) ' Top Left Bottom Right '□■□ '□×□ ×が Area~ の四隅の座標 '□□□ 抽出するのは■の部分 Dim RangeTop As Range Set RangeTop = GetRangeWithPosition(sh, _ sh.Cells.Row, AreaLeft, AreaTop - 1, AreaRight) ' Top Left Bottom Right '□□□ '□×□ ×が Area~ の四隅の座標 '□■□ 抽出するのは■の部分 Dim RangeBottom As Range Set RangeBottom = GetRangeWithPosition(sh, _ AreaBottom + 1, AreaLeft, sh.Rows.Count, AreaRight) ' Top Left Bottom Right Set buf = Intersect2(buf, _ Union2(RangeLeft, RangeRight, RangeTop, RangeBottom)) Next Set Invert2 = buf End Function ' 四隅を指定して Range を得る Private Function GetRangeWithPosition( _ ByRef sh As Worksheet, _ ByVal Top As Long , ByVal Left As Long, _ ByVal Bottom As Long , ByVal Right As Long) As Range '# 無効条件 If Top > Bottom Or Left > Right Then Exit Function ElseIf Top < 0 Or Left < 0 Then Exit Function ElseIf Bottom > Cells.Rows.Count Or Right > Cells.Columns.Count Then Exit Function End If Set GetRangeWithPosition _ = sh.Range(sh.Cells(Top, Left), sh.Cells(Bottom, Right)) End Function '############################################################################ '## 中置記法から後置記法に変換 '############################################################################ ' 数式を各要素に分割した配列で返す Public Function SplitFormula(ByVal Expression As String) As Variant Dim buf As String, Literal As String Dim i As Long For i = 1 To Len(Expression) + 1 Dim c As String * 1 c = Mid(Expression, i, 1) ' へんな書き方だが正規表現[-+*()\s]を使うよりは安いはず If InStr(1, "+-*() ", c) > 0 Then If Not Literal = "" Then buf = buf & " " & Literal End If If Not c = " " Then buf = buf & " " & c End If Literal = "" Else Literal = Literal & c End If Next ' 要素を空白で分割したのを Split で配列にする SplitFormula = Split(Trim(buf), " ") End Function ' 中置記法の配列を後置記法の配列に変換 Public Function Infix2Postfix(ByVal Infix As Variant) As Variant ReDim Postfix(0) As Variant ' Array2Stack は pop を shift のように使うためのセコ技 ' shift より pop のほうがコスト安い Call PostfixExpression(Array2Stack(Infix), Postfix) Infix2Postfix = Postfix End Function ' PostficFactor, PostfixTerm, PostfixExpression は ' 奥村晴彦『C言語による最新アルゴリズム事典(ISBN4-87408-414-1)を参考にした Private Sub PostfixFactor(ByRef Infix As Variant, ByRef Postfix As Variant) If Not Not StackEmpty(Infix) Then Exit Sub If StackPeek(Infix) = "(" Then Call StackPop(Infix) Call PostfixExpression(Infix, Postfix) If StackEmpty(Infix) Then If StackPeek(Infix) = ")" Then Call StackPop(Infix) End If Else Call StackPush(Postfix, StackPop(Infix)) End If End Sub Private Sub PostfixTerm(ByRef Infix As Variant, ByRef Postfix As Variant) Call PostfixFactor(Infix, Postfix) Do While Not StackEmpty(Infix) Dim c As String * 1: c = StackPeek(Infix) If c = "*" Or c = "/" Then Call StackPop(Infix) Call PostfixFactor(Infix, Postfix) Call StackPush(Postfix, c) Else Exit Do End If Loop End Sub Private Sub PostfixExpression(ByRef Infix As Variant, ByRef Postfix As Variant) Call PostfixTerm(Infix, Postfix) Do While Not StackEmpty(Infix) Dim c As String * 1: c = StackPeek(Infix) If c = "+" Or c = "-" Then Call StackPop(Infix) Call PostfixTerm(Infix, Postfix) Call StackPush(Postfix, c) Else Exit Do End If Loop End Sub '############################################################################ '## スタック操作用の関数 '############################################################################ ' スタックへプッシュする Public Function StackPush _ (ByRef ArrayItems As Variant, ByVal PushedItem As Variant) ReDim Preserve ArrayItems(UBound(ArrayItems) + 1) As Variant If IsObject(PushedItem) Then Set ArrayItems(UBound(ArrayItems)) = PushedItem Else ArrayItems(UBound(ArrayItems)) = PushedItem End If End Function ' スタックからポップする Public Function StackPop(ByRef ArrayItems As Variant) As Variant If Not StackEmpty(ArrayItems) Then If IsObject(ArrayItems(UBound(ArrayItems))) Then Set StackPop = ArrayItems(UBound(ArrayItems)) Else StackPop = ArrayItems(UBound(ArrayItems)) End If ReDim Preserve ArrayItems(UBound(ArrayItems) - 1) End If End Function ' スタックの頂上を得る Public Function StackPeek(ByRef ArrayItems As Variant) As Variant If Not StackEmpty(ArrayItems) Then If IsObject(ArrayItems(UBound(ArrayItems))) Then Set StackPeek = ArrayItems(UBound(ArrayItems)) Else StackPeek = ArrayItems(UBound(ArrayItems)) End If End If End Function ' スタックは空か? Public Function StackEmpty(ByRef ArrayItems As Variant) As Boolean StackEmpty = (UBound(ArrayItems) = 0) End Function ' 配列をスタックに変換 Public Function Array2Stack(ByRef ArrayItems As Variant) As Variant ReDim ReturnValue(UBound(ArrayItems) + 1) As Variant Dim i As Long For i = 0 To UBound(ArrayItems) ReturnValue(i + 1) = ArrayItems(UBound(ArrayItems) - i) Next Array2Stack = ReturnValue End Function