2012年12月23日日曜日

Excel罫線のオートフィルタの問題点

Excel VBA で描いた罫線で オートフィルタ を使ったとき。フィルタ結果の一部に「 本来表示されるべきではない罫線が表示される現象 」が発生していました。

なかなか解決策が見つからず、この対策としては「 フィルタ結果に対して罫線を再描画する 」といった、場当たり的な方法でやり繰りしていたのですが、ようやく解決策が見つかりました。



「 本来表示されるべきではない罫線、が表示される現象 」とは

ここで改めて、この現象の説明をしておきます。下の図は VBA で罫線を描いた表のオートフィルタ前の状態です。表中で LineColor 列の red 行のデータ red を赤い下罫線、 blue 行のデータ blue を青い下罫線で描いています。

つぎに、オートフィルタを使って LineColor 列の、 red を選択した場合のフィルタ結果が下図です。期待する正しい結果は、赤い下罫線だけの表示なのですが、この表示結果では関係のない所に青い罫線が表示されていたり、赤い罫線のはずの red の所が青い罫線になっており、フィルタ結果がみっともない状態です。

オートフィルタで LineColor 列の、 blue を選択した場合も同様です。


スタイル設定を調べたときにヒントが!

Excel VBA の HELP で、 Bordersオブジェクト を調べると、こんな説明になっています。

このことから下罫線を描く場合には、 Bordersオブジェクト に指定する XlBordersIndex クラス の定数から、 xlEdgeBottom を使って描いていました。

以前、「 スタイル設定をシートに出力する VBA マクロ 」を作りました。このとき Stylesコレクション から取得した、 Bordersプロパティ indexの値 は以下のものでした。

  1. xlLeft, xlRight, xlTop, xlBottom, xlDiagonalDown, xlDiagonalUp

Excel VBA の HELP、 Stylesコレクション のサンプルコードにも xlTop が使われていました。

HELP の Itemプロパティ では、「 xlEdgeXXX 」を使うように書かれています。

さて、ここで疑問が湧き出します。上下左右の罫線にある、「 xlEdgeXXX 」と「 xlXXX 」の定数の違いは何なのでしょうか?



「 xlEdgeBottom 」と「 xlBottom 」を比較してみる

両者の違いを探るため、比較用の VBA マクロを作り調べてみました。(この VBA マクロは文末に記載しています。)

テスト環境は、Excel 2003 SP3・Windows XP SP3 です。

下図左側の下罫線は「 xlEdgeBottom 」、下図右側の下罫線は「 xlBottom 」を使って描いています。その他は同一内容です。

オートフィルタで LineColor 列の、 red を選択したものです。下図右側の「 xlBottom 」の表示が良い感じです。

オートフィルタで LineColor 列の、 blue を選択。こちらの表示結果でも、下図右側の「 xlBottom 」で描いたほうは、正しくフィルタ表示できています。


オートフィルタでは「 xlBottom 」なら OK なの?

そこで、他のバージョンの Excel でも大丈夫なのか調べてみました。

他に使用できる Excel は、

  • Excel 2000 SP3
  • Excel 2010 評価版 (Office Professional 2010 Trial)
  • Excel 2013 Preview (Microsoft Office Professional 2013 Preview)
の 3 種類です。

まずはじめに Excel 2000 SP3・Windows XP SP3 から試してみました。

オートフィルタ項目 red

オートフィルタ項目 blue


つづいて、Excel 2010 評価版・Windows 7 Enterprise 評価版です。

オートフィルタ項目 red

オートフィルタ項目 blue


最後は Excel 2013 Preview・Windows 8 Release Previewです。

オートフィルタ項目 red

オートフィルタ項目 blue


結論、オートフィルタでは「 xlBottom 」を使えば OK

下罫線に「 xlBottom 」を使ったほうでは、いずれの結果もうまく動作しています。これでオートフィルタには「 xlBottom 」を使うことで正しく表示できることがわかりました。

今回調査した結果と、 Web で調べた結果から xlEdgeXXX と xlXXX の違いについて纏めてみると。(内容には未確認部分も含んでいます。)

  • ひとつのセルの上・下・左・右の罫線指定には、それぞれ 「 xlTop 」, 「 xlBottom 」, 「 xlLeft 」, 「 xlRight 」の定数を使う。
  • セル範囲(外周)の上・下・左・右の罫線指定には、それぞれ 「 xlEdgeTop 」, 「 xlEdgeBottom 」, 「 xlEdgeLeft 」, 「 xlEdgeRight 」 の定数を使う。

オートフィルタを使わなければ、どちらの方法で描いても大して変わらないのですが、オートフィルタを使うと挙動が違ってくるという訳です。これがオートフィルタの仕様なのか、バグなのか良く判りませんが、今まで懸案だった問題が少しスッキリした感じです。

今回の調査に使った VBA マクロを以下に記載します

  1. Option Explicit
  2. '///// 罫線のオートフィルタ動作テスト、メイン /////
  3. Sub Test_Borders()
  4. Dim mysheet As Worksheet
  5. Dim rTable_tmp As Range, rTable_left As Range, rTable_right As Range
  6. Dim xVal As Long
  7. Const xTable_Width As Integer = 6
  8. Const xTable_Height As Integer = 19
  9. Dim sTable_tmp As Variant
  10.  
  11. On Error GoTo ERR
  12. '表示データセット
  13. sTable_tmp = Array(Array("LineColor", " ", " ", " ", " ", " "), _
  14. Array("red ", "red ", "red ", " ", " ", " "), _
  15. Array("blue", " ", " ", " ", "blue", "blue"), _
  16. Array("red ", " ", "red ", "red ", " ", " "), _
  17. Array("blue", " ", " ", "blue", "blue", " "), _
  18. Array("red ", " ", " ", "red ", "red ", " "), _
  19. Array("red ", " ", " ", " ", "red ", "red "), _
  20. Array("blue", " ", "blue", "blue", " ", " "), _
  21. Array("blue", "blue", "blue", " ", " ", " "), _
  22. Array("red ", "red ", "red ", "red ", " ", " "), _
  23. Array("red ", " ", "red ", "red ", "red ", " "), _
  24. Array("red ", " ", " ", "red ", "red ", "red "), _
  25. Array("blue", "blue", "blue", "blue", " ", " "), _
  26. Array("blue", " ", "blue", "blue", "blue", " "), _
  27. Array("blue", " ", " ", "blue", "blue", "blue"), _
  28. Array("red ", "red ", "red ", "red ", "red ", " "), _
  29. Array("blue", " ", "blue", "blue", "blue", "blue"), _
  30. Array("red ", " ", "red ", "red ", "red ", "red "), _
  31. Array("blue", "blue", "blue", "blue", "blue", " "))
  32. Set mysheet = ActiveSheet
  33. Set rTable_left = mysheet.Range("A2")
  34. Set rTable_right = mysheet.Range("H2")
  35. 'オートフィルタ解除
  36. If mysheet.AutoFilterMode = True Then
  37. mysheet.AutoFilterMode = False
  38. End If
  39. '領域を消去
  40. Call DeleteTable(Range("A1:M20"))
  41. 'テーブル作成 (失敗する罫線 xlEdgeBottom)
  42. rTable_left.Offset(-1, 0).Value = "失敗する罫線"
  43. Call Write_Table(rTable_left, sTable_tmp, xTable_Height, xTable_Width)
  44. xVal = xlEdgeBottom
  45. Set rTable_tmp = rTable_left.Offset(1, 1).Resize(xTable_Height - 1, xTable_Width - 1)
  46. Call Draw_Borders(rTable_tmp, xVal)
  47. 'テーブル作成 (成功する罫線 xlBottom
  48. rTable_right.Offset(-1, 0).Value = "成功する罫線"
  49. Call Write_Table(rTable_right, sTable_tmp, xTable_Height, xTable_Width)
  50. xVal = xlBottom
  51. Set rTable_tmp = rTable_right.Offset(1, 1).Resize(xTable_Height - 1, xTable_Width - 1)
  52. Call Draw_Borders(rTable_tmp, xVal)
  53.  
  54. 'オートフィルタ オン
  55. If mysheet.AutoFilterMode = True Then
  56. mysheet.AutoFilterMode = False
  57. End If
  58. mysheet.Range("A2:M2").AutoFilter
  59. mysheet.Columns("A:M").AutoFit
  60.  
  61. ERR:
  62. Set rTable_right = Nothing
  63. Set rTable_left = Nothing
  64. Set rTable_tmp = Nothing
  65. Set mysheet = Nothing
  66. End Sub
  67. '///// テーブルデータを表示 /////
  68. Sub Write_Table(ByVal rTable_in As Range, ByVal sTable_in As Variant, nRow As Integer, nCol As Integer)
  69. Dim nCount As Integer, rTable As Range
  70. Set rTable = rTable_in.Resize(, nCol)
  71. For nCount = 0 To nRow - 1
  72. rTable.Offset(rowOffset:=nCount).Value = sTable_in(nCount)
  73. Next nCount
  74.  
  75. Set rTable = Nothing
  76. End Sub
  77. '///// テーブルに下罫線を描画 /////
  78. Sub Draw_Borders(rTable As Range, xVal As Long)
  79. Dim r As Range
  80.  
  81. '下罫線を描画
  82. For Each r In rTable
  83. With r.Borders(xVal)
  84. Select Case r.Text
  85. Case "red "
  86. .LineStyle = xlContinuous
  87. .Weight = xlThick
  88. .ColorIndex = 3
  89. Case "blue"
  90. .LineStyle = xlContinuous
  91. .Weight = xlThick
  92. .ColorIndex = 5
  93. Case Else
  94. .LineStyle = xlNone
  95. End Select
  96. End With
  97. Next r
  98.  
  99. Set r = Nothing
  100. End Sub
  101.  
  102. '///// テーブルを消去 /////
  103. Sub DeleteTable(rTable_range As Range)
  104. Dim r As Range
  105. rTable_range.Clear
  106. '下罫線を消去(消去は xlEdgeBottom を使う。)
  107. For Each r In rTable_range
  108. r.Borders(xlEdgeBottom).LineStyle = xlLineStyleNone
  109. Next r
  110.  
  111. Set r = Nothing
  112. End Sub

0 件のコメント :