部屋割りマクロ(Excel)(3)

f:id:akashi_keirin:20180217113941p:plain

部屋割りマクロのリファクタリング

akashi-keirin.hatenablog.com

このときに作成した部屋割りマクロのコードを見直して修正した。

問題点その1

isFull_変数が無意味

Roomクラスの内部に、インスタンスが定員一杯であることを表すisFull_というPrivate変数を置いていたのだが、全く使用していなかったので、使うようにした。

そこで、

akashi-keirin.hatenablog.com

リスト1のallocateメソッドのところを書き換える。

リスト1 クラスモジュール
Public Function allocate() As Boolean
  If isFull_ Then allocate = False: Exit Function    '……(1)'
  capacityOf_ = capacityOf_ - 1
  If capacityOf_ = 0 Then isFull_ = True    '……(2)'
  allocate = True
End Function

変えたのは(1)と(2)のところ。

(1)の

If isFull_ Then allocate = False: Exit Function

isFull_がTrueだったら部屋割り不能なので、Falseを返す。

isFull_がFalseの場合は、部屋割り可能なので、次の行の

capacityOf_ = capacityOf_ - 1

で定員を1減ずる。

で、その後(2)の

If capacityOf_ = 0 Then isFull_ = True

で、定員が0になっていたら、isFull_をTrueに切り替える。

これで、次回この部屋インスタンスに部屋割りしようとするとallocateメソッドがFalseを返すことになる。

問題点その2

部屋割り処理が不細工すぎる

これは、

akashi-keirin.hatenablog.com

リスト2

Dim isAllocated As Boolean
  Dim n As Integer
  n = 1
  Dim Sh As Worksheet
  Set Sh = targetRange.Parent
  Do
    For i = 1 To roomCount
      With rooms(i)
        If Not Sh.Rows(targetRange.Cells(n, 1).Row).Hidden Then
          If .allocate Then
            targetRange.Cells(n, 1).Value = .nameOf
            n = n + 1
            If n > targetRange.Count Then Exit For
          End If
        Else    '……(*)'
          i = i - 1
          n = n + 1
          If n > targetRange.Count Then Exit For
        End If
      End With
    Next
  Loop Until n > targetRange.Count

この部分。改めて見直すと、マジで何がしたいのか分からんwww

行き当たりばったりでテキトーにコーディングするとこうなる。

個人的には、特に(*)のところがヒド過ぎると思う。Forループの中でイテレータ変数をデクリメントさせるとか、正気の沙汰ではないwww

これは根本から考え直す必要があると思った。

で、修正したのがコチラ。

スト2 標準モジュール
  Dim isAllocated As Boolean
  Dim n As Integer
  n = 1
  Dim Sh As Worksheet
  Set Sh = targetRange.Parent
  Dim tmpCell As Range
  For i = 1 To targetRange.Rows.Count
    Set tmpCell = targetRange.Cells(i, 1)    '……(1)'
    If Not Sh.Rows(tmpCell.Row).Hidden Then    '……(2)'
      Do Until rooms(n).allocate    '……(3)'
        n = n + 1    '……(4)'
        If n > roomCount Then n = 1
      Loop
      tmpCell.Value = rooms(n).nameOf    '……(5)'
      n = n + 1    '……(6)'
      If n > roomCount Then n = 1
    End If
  Next

変更前は、部屋の方をForループで回していたのだけれど、これがそもそもの間違いだったわけですな。

部屋の方は定員一杯になるまで何周もするんですからね。

今回は、部屋割り対象セルを上から順にForで回す、というやり方に改めた。

まず、(1)の

Set tmpCell = targetRange.Cells(i, 1)

部屋割り対象セルを変数tmpCellにぶち込む。後々カンタンに記述するためだけの措置です。

で、(2)の

If Not Sh.Rows(tmpCell.Row).Hidden Then

で、tmpCellの行が非表示になっているかどうかを判定。

非表示でなければ、ブロック内の部屋割り処理に移る。

tmpCellが非表示になっていなければ、(3)からの4行

Do Until rooms(n).allocate
  n = n + 1    '……(4)'
  If n > roomCount Then n = 1
Loop

で割り振るべき部屋を探す。

入り口のところでRoomクラスのallocateメソッドを実行してTrueが返ってきたら、n 番目の部屋に部屋割り可能ということなので、Loopを抜けて次に進む。

allocateメソッドがFalseだったら、(4)からの2行

n = n + 1
If n > roomCount Then n = 1

で n をインクリメントする。ただし、 n が部屋数を超えてしまったら n を1に戻す。

で、次。

ここまで来たら、後は部屋割り対象セルに部屋の名前を書き込むだけなので、(5)の

tmpCell.Value = rooms(n).nameOf

で部屋の名前を書き込み、(6)からの2行

n = n + 1
If n > roomCount Then n = 1

で n をインクリメント、つまり部屋を次の部屋に進める。

おわりに

まあ、だいぶマシなコードになったとは思います。

@akashi_keirin on Twitter

「作業グループ」状態をVBAで作るには[Excel]

「作業グループ」状態にする

複数のシートにまとめて変更を加えたいときには「作業グループ」状態にする。

しかし、VBAからExcelを操作するときに、あまり「作業グループ」状態を使うことがなかった。

そういえば、どうやるんだろう?

で、確かめてみた。

マクロの記録

f:id:akashi_keirin:20180303103905j:plain

ちょっと分かりにくく、かつわけのわからない画像ですまない。

とにかく、2番目~4番目のシートを選択して、「作業グループ」状態にしてある。

これだけで操作をやめ、マクロの記録を終了。んで、できたコードがコチラ。

リスト1 標準モジュール
Sub Macro1()
'
' Macro1 Macro
'

'
    Sheets(Array("ち~んw 1 号(笑)", "ち~んw 2 号(笑)", "ち~んw 3 号(笑)")).Select
    Sheets("ち~んw 1 号(笑)").Activate
End Sub

へえ! 知らんかった!Sheetsコレクションのインデックスに配列をぶち込んだら複数シート選択状態になるのか!

記録マクロだと、シート名の配列を作っているみたいだけれど、これでいけるんなら別にインデックス番号でもいいはず。

で、これを用いてちょっとやってみた。

データを差し込んでは新しいシートを追加し、最後にまとめて印刷するマクロ

Wordの差込印刷みたいな要領で、シートの一部にそれぞれ異なるデータを差し込みつつ新規シートとして追加していき、全部追加し終わったところで一気に印刷プレビュー表示→印刷、という流れの簡単なコードを書いてみた。

f:id:akashi_keirin:20180303103914j:plain

シート「Main」には、こんなふうに準備しておく。

A2セルにデータ(笑)を差し込んでいく、という設定。

スト2 標準モジュール
Public Sub insertionPrintByAddSheets()
  Dim Sh As Worksheet
  Set Sh = ThisWorkbook.Worksheets("Main")
  Dim i As Integer
  For i = 1 To 3
    With ThisWorkbook    '……(1)'
      Sh.Copy After:=.Worksheets(.Worksheets.Count)    '……(2)'
      With .Worksheets(.Worksheets.Count)    '……(3)'
        .Range("A2").Value = "ち~んw " & i & " 号(笑)"    '……(4)'
        .Name = .Range("A2").Value    '……(5)'
      End With
    End With
  Next
  Dim ar(2) As Integer    '……(6)'
  For i = 0 To 2
    ar(i) = i + 2
  Next
  With ThisWorkbook.Worksheets(ar)    '……(7)'
    .PrintPreview
    .PrintOut
  End With
  Sh.Select    '……(8)'
End Sub

とりあえず実験なので、追加するシートは3つだけにした。

まず(1)からの9行

For i = 1 To 3
  With ThisWorkbook
    Sh.Copy After:=.Worksheets(.Worksheets.Count)    '……(2)'
    With .Worksheets(.Worksheets.Count)    '……(3)'
      .Range("A2").Value = "ち~んw " & i & " 号(笑)"    '……(4)'
      .Name = .Range("A2").Value    '……(5)'
    End With
  End With
Next

のForループでは、「Main」シートの内容をコピーして新しいシートを追加しつつ、データ(笑)を差し込む。

まず(2)の

Sh.Copy After:=.Worksheets(.Worksheets.Count)

で現時点での最終シートの次に新しいシートを追加していく。

引数「After」の指定の仕方は、今回の場合は別に

.Worksheets(i)

でも大丈夫だと思うけれど、後で「Main」意外にも固定のシートが生じた場合に書き換える必要が生ずるので、このようにした。

(3)からのWithブロック

With .Worksheets(.Worksheets.Count)
  .Range("A2").Value = "ち~んw " & i & " 号(笑)"    '……(4)'
  .Name = .Range("A2").Value    '……(5)'
End With

は、新しくできたシートに対する処理。Worksheetsコレクションのインデックス番号に、この時点でのWorksheetsコレクションの数を指定しているので、確実に追加されたばかりのシートを指定することができる。

まず(4)の

.Range("A2").Value = "ち~んw " & i & " 号(笑)"

で、新しく追加されたシートのA2セルにデータ(笑)を差し込み、

(5)の

.Name = .Range("A2").Value

でシート名を差し込んだデータ(笑)に変えている。

ここまでで必要なシートが出そろったことになる。

お次は、(6)からの4行。

Dim ar(2) As Integer
For i = 0 To 2
  ar(i) = i + 2
Next

新しく追加したシートをまとめて指定するのに使う配列を準備する。

今回は2シート目~4シート目(最終シート)なのでこんな感じにする。

「2」とか、マジックナンバー丸出しだが、あくまで実験用なのでご容赦を。実際には引数で対応することになるだろう。

後は(7)からの4行

With ThisWorkbook.Worksheets(ar)
  .PrintPreview
  .PrintOut
End With

で配列「ar」で指定されたインデックス番号のシート(今回の場合は、「2」「3」「4」番目のシート)に対してPrintPreviewメソッドとPrintOutメソッドを実行する。

これで処理そのものは終わりだが、「作業グループ」状態になっていることに気づかずにシートを加工して大惨事、ということになったら困るので、

(8)の

Sh.Select

で、元のシートをSelectして終わるようにした。

実行結果

紙でプリントアウトしてもブログでは表現が難しいので、プリンタはJUST PDFを指定してある。

f:id:akashi_keirin:20180303103923j:plain

まずプレビューが表示される。画像では全く伝わらないが、ちゃんと3ページ分ある。

しかる後、印刷が実行されて、PDFが表示された。

f:id:akashi_keirin:20180303103934j:plain

ちゃんと3ページ分一気に表示されている。

おわりに

「直接操作でしかやらないような動作」というのがあって、そういうのは簡単な操作なのに、案外VBAでどうやるか知らないもんだなあ、ということです。

コレクションのインデックスを配列で指定するという発想は、正直全くありませんでした。

@akashi_keirin on Twitter

真の最終行番号を取得するFunction(2)

真の最終行を取得するFunctionの修正

前回の

akashi-keirin.hatenablog.com

にさっそくツッコミがw

@excelspeedupさん曰く、

currentregion使うと、完全な空白行が入っているとき誤動作しませんか?

と。

ははは。確かにおっしゃるとおり。

場合分けミスですな。

たとえば、

f:id:akashi_keirin:20180301202430j:plain

こんなふうにデータ(笑)が入っていたとして、

f:id:akashi_keirin:20180301202442j:plain

こんなふうに行を選択して

f:id:akashi_keirin:20180301202452j:plain

非表示にされてしまったら、

f:id:akashi_keirin:20180301202500j:plain

こんなふうに意図しない行番号が返ってしまう。

「真の最終行番号」の看板に偽りありですがな。

コードの修正

というわけで、前回のリスト1を次のように修正する。

リスト1 標準モジュール
Public Function getLastRowNumber( _
                  ByVal targetColumn As Long, _
                  Optional ByVal targetSheet As Worksheet) As Long
  If targetSheet Is Nothing Then Set targetSheet = ActiveSheet
  Dim tmpLastRow As Long
  '暫定的な最終行を求める'
  tmpLastRow = getLastRowNumberNormal(targetColumn:=targetColumn, _
                                      targetSheet:=targetSheet)
  'UsedRangeの最終行番号を求める'
  Dim maxRowNumber As Long
  maxRowNumber = targetSheet.UsedRange.Rows.Count    '……(*)'
  'tmpLastRowとmaxRowNumberが一致していれば、それが最終行'
  If tmpLastRow = maxRowNumber Then getLastRowNumber = tmpLastRow: Exit Function
  'maxRowNumberの方が大きい場合は、真の最終行を探す'
  Dim i As Long
  For i = maxRowNumber To tmpLastRow Step -1
    With targetSheet
      If .Cells(i, targetColumn).Value <> "" Then
        getLastRowNumber = i
        Exit Function
      End If
    End With
  Next
End Function

'指定した列の最終行を返す'
Public Function getLastRowNumberNormal( _
                  ByVal targetColumn As Long, _
                  Optional ByVal targetSheet As Worksheet) As Long
  If targetSheet Is Nothing Then Set targetSheet = ActiveSheet
  getLastRowNumberNormal = targetSheet.Cells(Rows.Count, targetColumn).End(xlUp).Row
End Function

変えたのは(*)のところだけ。

単純に、UsedRangeの最終行番号をmaxRowNumberにぶち込むようにしただけ。

ちなみに、

f:id:akashi_keirin:20180301202452j:plain

この状態でイミディエイト・ウインドウに

?Activesheet.UsedRange.Rows.Count

と入力して[Enter]を押すと、

f:id:akashi_keirin:20180301202514j:plain

ご覧のように「23」が返る。非表示にしているのも「使用中」ということらしい。

使ってみる

イミディエイト・ウインドウに

?getLastRowNumber(Range("P1").Column)

と入力して[Enter]を押す。

f:id:akashi_keirin:20180301202526j:plain

今度は「21」が返った。

f:id:akashi_keirin:20180301202535j:plain

ご名算!

おわりに

ただ、UsedRangeを使うと、意味もなく最終行(100万超!)まで罫線が設定されたりしていたら、おっそろしいことになるんよなあ……。

@akashi_keirin on Twitter

真の最終行番号を取得するFunction

真の最終行番号を求めるFunction

Twitterで、

オートフィルターで非表示になっている行があると、最終行番号の取得に失敗して困る

みたいなツイを見た。

なるほど、データの入っている最終行を非表示にして、[Ctrl]+[↑]で確かめると、確かに

表示されている中で値の入っている最終行のセル

にカーソルが飛ぶ。

これは困った現象だ。

そこで、非表示になっていたとしても正確に値の入った最終行番号を返すようなFunctionを考えてみた。

考え方

非表示の行があったとしても、CurrentRegionプロパティで取得するRangeオブジェクトのRows.Countプロパティは実際の行数を返すようなので、それを利用する。

手順としては、

  1. 通常の方法(Endプロパティを使うアレ)でひとまず暫定の最終行を求める ・・・ ①
  2. 暫定の最終行にあるセルを基準にCurrentRegionを取得する
  3. CurrentRegionの最終行番号を求める ・・・②
  4. ①と②が等しければ、それが真の最終行番号
  5. ②の方が①よりも大きい場合は、②~①の間に値のあるセルがあれば、そこが最終行

こんな感じ。

この考えに基づいてコーディングしてみた。

コーディング

リスト1 標準モジュール
Public Function getLastRowNumber( _
                  ByVal targetColumn As Long, _
                  Optional ByVal targetSheet As Worksheet) As Long
  If targetSheet Is Nothing Then Set targetSheet = ActiveSheet
  Dim tmpLastRow As Long
  '暫定的な最終行を求める'
  tmpLastRow = getLastRowNumberNormal(targetColumn:=targetColumn, _
                                      targetSheet:=targetSheet)
  Dim tmpReferenceCell As Range
  Set tmpReferenceCell = targetSheet.Cells(tmpLastRow, targetColumn)
  '暫定的な最終行にあるセルのCurrentRegion最終行番号を求める'
  Dim maxRowNumber As Long
  maxRowNumber = getCurrentRegionLastRowNumber(referenceCell:=tmpReferenceCell)
  'tmpLastRowとmaxRowNumberが一致していれば、それが最終行'
  If tmpLastRow = maxRowNumber Then getLastRowNumber = tmpLastRow: Exit Function
  'maxRowNumberの方が大きい場合は、真の最終行を探す'
  Dim i As Long
  For i = maxRowNumber To tmpLastRow Step -1
    With targetSheet
      If .Cells(i, targetColumn).Value <> "" Then
        getLastRowNumber = i
        Exit Function
      End If
    End With
  Next
End Function

'指定した列の最終行番号を求める  ……(1)'
Public Function getLastRowNumberNormal( _
                  ByVal targetColumn As Long, _
                  Optional ByVal targetSheet As Worksheet) As Long
  If targetSheet Is Nothing Then Set targetSheet = ActiveSheet
  getLastRowNumberNormal = targetSheet.Cells(Rows.Count, targetColumn).End(xlUp).Row
End Function

'CurrentRegionの最終行数を取得する  ……(2)'
Public Function getCurrentRegionLastRowNumber( _
                  ByVal referenceCell As Range) As Long
  With referenceCell
    If .Count <> 1 Then Set referenceCell = .Cells(1, 1)
  End With
  Dim Sh As Worksheet
  Set Sh = referenceCell.Parent
  With referenceCell.CurrentRegion
    getCurrentRegionLastRowNumber = .Cells(.Rows.Count, 1).Row    '……(3)'
  End With
End Function
追記

上記コードでは、ある場合に真の最終行番号を返してくれません。

修正版はコチラをどうぞ。

メインは先頭のgetLastRowNumber。内部で(1)のgetLastRowNumberNormalと(2)のgetCurrentRegionLastRowNumberを呼び出すような処理にしている。

(1)、(2)とも、単独でも使い道がありそうなので、PrivateではなくPublic指定している。

(1)の

Public Function getLastRowNumberNormal( _
                  ByVal targetColumn As Long, _
                  Optional ByVal targetSheet As Worksheet) As Long
  If targetSheet Is Nothing Then Set targetSheet = ActiveSheet
  getLastRowNumberNormal = targetSheet.Cells(Rows.Count, targetColumn).End(xlUp).Row
End Function

は、おなじみEndプロパティを用いて最終行番号を求めているだけ。

(2)の

Public Function getCurrentRegionLastRowNumber( _
                  ByVal referenceCell As Range) As Long
  With referenceCell
    If .Count <> 1 Then Set referenceCell = .Cells(1, 1)
  End With
  Dim Sh As Worksheet
  Set Sh = referenceCell.Parent
  With referenceCell.CurrentRegion
    getCurrentRegionLastRowNumber = .Cells(.Rows.Count, 1).Row    '……(3)'
  End With
End Function

は、受け取ったセルのCurrentRegionの最終行番号を求めるFunction。先頭の3行

With referenceCell
  If .Count <> 1 Then Set referenceCell = .Cells(1, 1)
End With

は、今回の使い道には関係ない。単独でCurrentRegionの最終行番号を求める用途で使うときに、引数に複数セルが渡された場合の対応。

(3)の

With referenceCell.CurrentRegion
  getCurrentRegionLastRowNumber = .Cells(.Rows.Count, 1).Row
End With

がちょい分かりにくいかも。

CurrentRegionの最終行番号を求めるために、Cellsプロパティを用いてCurrentRegionの1列目最終行のセルを取得し、そのセルのRowプロパティを参照することによってCurrentRegionの最終行番号を取得している。

CurrentRegionは必ず矩形なので、1列目で良いと判断した。

メインのgetLastRowNumber内の処理は、コチラに書いたとおりなので、特に説明はいらないかな。

使ってみる

まず、シートに

f:id:akashi_keirin:20180301181245j:plain

こんな表を用意して、

f:id:akashi_keirin:20180301181255j:plain

17、18行目を非表示にして実験。

イミディエイト・ウインドウに

?getLastRowNumber(Range("P1").Column)

と入力して[Enter]!

f:id:akashi_keirin:20180301181306j:plain

ほれ、このとおり、ちゃんと真の最終行番号が返っておる。

今度は、オートフィルターでやってみる。

f:id:akashi_keirin:20180301181313j:plain

f:id:akashi_keirin:20180301181321j:plain

f:id:akashi_keirin:20180301181328j:plain

14、15行目の安道全と時遷を非表示にしてから、イミディエイト・ウインドウに

?getLastRowNumber(Range("D1").Column)

と入力して[Enter]!

f:id:akashi_keirin:20180301181335j:plain

ほれ、このとおり真の最終行番号が返った。

おわりに

ご意見承ります。

@akashi_keirin on Twitter

省略可能なオブジェクト型引数のデフォルト値

省略可能なオブジェクト型の引数のデフォルト値

省略可能な引数

プロシージャに省略可能な引数を設定するときには、

Public Sub hogeHoge(Optional ByVal foo As String = "ち~んw")

というふうに書く。

引数名の指定の最初に「Optionalキーワード」を付けて省略可能であることを示し、最後に「 = ~~」の形でデフォルト値を設定することができる。

ここまではまあ、たいていの人が知っているだろう。

じゃ、引数がオブジェクト型だったらどうすんのさ?

普通に書いてみる

とりあえず、

akashi-keirin.hatenablog.com

前回作成したFunctionはWorksheetオブジェクトが引数だったので、こいつを使う。

引数指定がない場合は、アクティブシートを渡す、という形にする。

まず、プロシージャの宣言の部分を

Public Function isAutoFiltered(Optional ByVal targetSheet As Worksheet = ActiveSheet) As Boolean

と書いてみる。

とりあえず、逐次コンパイルには引っかからない。

しかし、実行すると、

f:id:akashi_keirin:20180301081757j:plain

コンパイルエラーwww

ま、当たり前ですけどね。

Public Function isAutoFiltered( _
                  Optional ByVal targetSheet As Worksheet Set targetSheet = ActiveSheet) _
                    As Boolean

こうも書いてみたけれど、これはもう逐次コンパイルに引っかかるレベルでダメw

結局、次のようになった。

リスト1
Public Function isAutoFiltered(Optional ByVal targetSheet As Worksheet) As Boolean
  If targetSheet Is Nothing Then Set targetSheet = ActiveSheet
  If targetSheet.AutoFilter.FilterMode Then isAutoFiltered = True
End Function

引数が省略されると、targetSheetがNothingになるので、プロシージャの先頭でActiveSheetをセットするようにしただけ。

おわりに

先頭のカッコ内(引数指定のところ)でデフォルト値を設定する方法もあるのだろうか???

@akashi_keirin on Twitter

オートフィルターの状態を取得するFunction

オートフィルターの状態を取得する

元データが置いてあるワークシートがオートフィルターで絞り込まれているのに、それに気づかずにマクロを実行してわけのわからない結果が出て(゚Д゚)ポカーンとなったことはないだろうか。

わしはある!!!!!!!!

そんなわけで、オートフィルタの状態を表すオブジェクト、プロパティについて調べてみた。

Worksheet.AutoFilterオブジェクト

コチラによると、

指定されたワークシートのオートフィルターを示します。

AutoFilter オブジェクトを取得するには、 AutoFilter プロパティを使用します。

とのこと。

AutoFilter オブジェクトを取得するには、 AutoFilter プロパティを使用しますというのがちょっと分かりにくいけれど、

WorksheetオブジェクトのAutoFilterプロパティを参照すると、つまり、

「[Worksheetオブジェクト].AutoFilter」と書くと、

AutoFilterオブジェクトが返る


という理解で良いかと。

まあ、最初の指定されたワークシートのオートフィルターを示しますというのもよく分からんなw

「オートフィルターそのもの」とでも考えたら良いのかな。

AutoFilter.FilterModeプロパティ

再びコチラによると、

ワークシートがオートフィルター フィルター モードの場合、 True を返します。値の取得のみ可能です。ブール型 ( Boolean ) の値を使用します。

構文

式 . FilterMode

式AutoFilter オブジェクトを表す変数。

とのこと。

「フィルターモード」??? 何それ???(←素人)

フィルターモード

コチラによると、どうもフィルターで絞り込まれた状態を「フィルターモード」と言うらしい。

アッーーー!!

そ、そういえば……、

f:id:akashi_keirin:20180301075715j:plain

f:id:akashi_keirin:20180301075723j:plain

こういうことかー!

はい、その程度のことも知らなかったExcel初心者ですよ、私は。

ここまでのまとめ

Worksheet.AutoFilterオブジェクトのFilterModeプロパティがTrueだったら、そのシートはどこかの列がオートフィルターで絞り込まれた状態であるということですね。

オートフィルターの状態を調べるFunction

無駄にFunction化する。

リスト1 標準モジュール
Public Function isAutoFiltered(targetSheet As Worksheet) As Boolean
  If targetSheet.AutoFilter.FilterMode Then isAutoFiltered = True
End Function

まあ、ほとんど冗談だと思ってください。

使ってみる

次のコードで実行する。

スト2 標準モジュール
Public Sub testIsAutoFiltered()
  Debug.Print isAutoFiltered(Selection.Parent)
End Sub

選択範囲のあるシートについて、オートフィルターの状態をイミディエイトに表示するだけのコード。

f:id:akashi_keirin:20180301075751j:plain

こんな表を用意してオートフィルターを設定する。

f:id:akashi_keirin:20180301075758j:plain

こんなふうに「武松」と「楊志」(またこの2人w)のチェックを外して、[OK]。

f:id:akashi_keirin:20180301075806j:plain

絞り込まれた。

f:id:akashi_keirin:20180301075814j:plain

範囲を選択して、リスト2を実行!

f:id:akashi_keirin:20180301075822j:plain

当然、「True」が返る。

念のため、

f:id:akashi_keirin:20180301075831j:plain

絞り込みを解除して実行。

f:id:akashi_keirin:20180301075841j:plain

ちゃんと「False」が返った。

おわりに

まあ、Function化するよりも、AutoFilterオブジェクトについてマスターしておくことの方が重要でしょうね。

オートフィルター、Rangeオブジェクト、2次元配列

※題名は、PINK FLOYDのオマージュです。

オートフィルターをかけたRangeオブジェクトを2次元配列にぶち込む

セル範囲の値を2次元配列にぶち込むお馴染みの方法。

ClearContentsメソッドでは非表示行が無視されたが、この場合はどうなるのか。

やってみた

f:id:akashi_keirin:20180227223129j:plain

この状態で、次のコードを実行。

リスト1 標準モジュール
Public Sub testArray()
  Dim Sh As Worksheet
  Set Sh = ThisWorkbook.Worksheets("Sheet3")
  Dim a As Variant
  a = Sh.Range("D1").CurrentRegion.Value    '……(*)'
  Dim maxRow As Integer
  Dim maxColumn As Integer
  maxRow = UBound(a, 1)
  maxColumn = UBound(a, 2)
  Dim i As Integer
  Dim j As Integer
  Dim str As String
  For i = 1 To maxRow
    str = ""
    For j = 1 To maxColumn
      If j = 1 Then
        str = a(i, j)
      Else
        str = str & " | " & a(i, j)
      End If
    Next
    Debug.Print str
  Next
End Sub

D1セルを含むアクティブセル領域の値を2次元配列 a にぶち込んで、Forループで値を取り出しながらイミディエイトに表示する、というアホみたいなコード。

f:id:akashi_keirin:20180227223135j:plain

当然こうなる。

一部非表示にしてやってみる

f:id:akashi_keirin:20180227223142j:plain

例によって「武松」と「楊志」を非表示にした。

この状態でリスト1を実行。

f:id:akashi_keirin:20180227223153j:plain

まったく同じ結果になった。

ちなみに、リスト1の(*)のところを

a = Selection.Value

としても結果は同じだった。

おわりに

「だから何?」とか言われても、知りません。

@akashi_keirin on Twitter