★★★★★★★★★★Access2000VBA・Excel2000VBA独学~★★★超重要!!いろんな要素が入った自作関数の作成~『 あるセル範囲から、「数式が入っているけどIF関数などで空白表示されているセル」だけをほぼ除外する 』、「オブジェクトを引数にして・かつ・オブジェクトを返す」、そういう自作関数の作り方。~★★★ExcelVBAの独学でもっとも重要!!!~
※まだ書きかけです。すみません。
※間違ってたらすみません。
※メモ書きなので、自分でも意味不明な箇所も多いです。ごめんなさい。
目次
★ はじめに(「空白表示の数式Onlyセル」を除外したセル選択について)
★ (01)の「Range.PastSpacialメソッド」を使って「値の貼り付け」で処理するケースについて
★ (01)のケースでのプログラム例
★ (02)の「空白表示の数式Onlyセルだけをよけて選択する自作関数」を作るケースについて
★ (02)のケースでのプログラム例
★ 「test01」プロシージャと「GetDBRngOffWhiteFomlaRec01」関数の重要性(★★★VBAの独学でもっとも重要と言っても過言ではありません!)
※Shift+TABキー、もしくは、Homeキー、Homeキー+TAB数回、を押すと、目次付近に戻れます。
★ はじめに(「空白表示の数式Onlyセル」を除外したセル選択について)
『 あるセル範囲から、「数式が入っているけどIF関数などで空白表示されているセル」「だけ」を、ほぼ除外したい 』と、そういう要望が出ることがあります。
そのようなことを考える場合、ぱっと思いつくだけでも2つの方法があると思います。
以下の2つです。
(01)「Range.PastSpacialメソッド」を使って、「形式を選択して貼り付け」における「値」での貼り付けと同等の処理で対応してしまう。
これは「シンプルな状況」で、手間をかけたくない場合に便利だと思います。
自作関数などを作る必要はありません。
「なぜこれで対応できるか」についてはもう少し詳しく後述します。
また、この方法では、いったん、別のシート(作業専用の一時シート)に貼り付けてから、さらに、「Worksheet.UsedRange」プロパティを使って選択し直して(場合によってはその作業用シートでさらにこまかく処理・加工してから)、最終的な目的のシートに貼り付け、ということもできると思います。
値の表示されたセルの上や左右に「空白表示の数式Onlyセル」がある場合にはこの対応が便利だと思います。
※もちろん、「作業用のシートが必要ない場合」もちゃんとあります。
(02)面倒くさいけど、VBAにてまじめに、『 あるセル範囲から、「数式が入っているけどIF関数などで空白表示されているセル」だけをほぼ除外 』したセル範囲を作る。
これは、そのような「自作関数」を作って対応します。
少し込みいった状況でも使えると思います。
もちろん、(01)以外のいろんな場面でも使えると思います。
例えば、「値の貼り付けをしたいわけじゃなく、” 空白表示の数式Onlyセル ” だけをよけて、セルや文字の色を変えたい」とか、そいういった値の貼り付け「以外」の用途に使えます。
また、プログラムをより細かく変えれば、「より細かい条件・制限」にも対応できると思います。
こちらは例えば、『 「空白表示の数式Onlyのセル」は除外したいんだけれども、でも、「値の表示されたセルは数式もコピペしたい 』という場合、引数を増やして、値だけコピペするか、それとも数式や書式もコピペするか、などを切り替えられるようにすることも可能だと思います。
もちろん、イレギュラーな位置のセル単位での何らかの処理も。
また、「作業用シートが作れない場合」にも、便利に使えると思います。
値の表示されたセルの上や左右に「空白表示の数式だけ入ったセル」がある場合でも、そういったセルも自作関数の中で除外してしまいますので、作業用の一時シートは要りません。
今回は、後者に重点を置いていますが、でも前者も便利だと思いますので、お話をしていきたいと思います。
なお、(01)(02)、いずれの場合も、ソースのセル範囲として「Worksheet.UsedRange」を使いますが、その代わりに「Worksheet.Range("名前定義の名前")」も使えます。
加えて、いずれも、「1つのセルの漏れも見逃さず、完璧に目的のセルだけを選択する」というのは・・・、すごく面倒くさくなってしまう+あまり意味が無いケースも多い、ので、ここではそのお話は「対象外」とします。
どうしてもそれをやりたい場合は、本記事をベースに、さらに、個別に細かく対応していけばいいと思います。
本記事では、「Worksheet.UsedRange」でのセル範囲の選択のような「 ” 基本・矩形 ” の、ざくっとした・でもできるだけ無駄なセルを省く選択」、ということをしています。
★ (01)の「Range.PastSpacialメソッド」を使って「値の貼り付け」で処理するケースについて
VBAの場合では、「Range.PastSpacialメソッド」を使うと、「形式を選択して貼り付け」における「値」での貼り付けができます。
で、そもそもなんですが、「値」で貼り付けた場合、貼り付けた先のデータからは「数式が全部消え」てしまいます。
ですので、もし仮に、
『数式が入っているけど、IF関数などで空白表示がしてあるセル』
がコピー内容に含まれてしまったとしても、結局は、ペースト後(貼り付け後)には数式は消えてしまいます。そういう状態で貼り付きます。
なので、数式がどうこうはまったく考えなくても、
コピー元のセル範囲
を、「ActiveSheet.UsedRange」などを使って「まんまで」コピーして、
で、
コピー先の場所
へ、そのまま、Range.PastSpacialメソッドを使って、「Paste:=xlPasteValues」で貼り付ければ良いような気がします。
そうすれば、「コピー先の場所」のほうで数式は「全部」・「勝手に」、消えています。
(なお、「Paste:=xlPasteValues」が、「形式を選択して貼り付け」における「値」の貼り付けという意味です。)
また、
コピー元のセル範囲
は、「ActiveSheet.UsedRange」 などを使ってセル範囲を特定すれば、列が増えても多少位置がずれてても、表自体の構成がおかしくなければ、目的のようなコピーができると思います。
強いて言えば、1行目の列名を含めるのか含めないのか、等々を気にしないといけなくなるかもしれませんが・・・。
ただ、以上のお話はすべて、
コピー元のセル範囲
というものが、「リスト形式というか、データベース形式というか、明細形式」の表で、かつ、「1シートにつき1つの表しかない」ということを前提としています。
「イレギュラーで無茶苦茶な形の表の場合」や、
「1シート上に(名前定義もしてないのに)たくさんの表がある場合」、
などはなかなかに難しいと思います。
前述したように、「作業用のシート」を設けて、いったんそこに値だ貼り付けてしまえば、「さらに細かい加工」も可能だと思います。
「値が表示されたセルは、値だけでなく、数式も書式もほしい」、といった場合はそういう処理のしかたのほうがラクかもしれません。
※「値が表示されたセルは、値だけでなく、数式も書式もほしい・コピペしたい。」、といった処理は、もちろん(02)でも付け足せます。 今回はやってはいませんが。今後の課題として。
★ (01)のケースでのプログラム例
プログラムを実行する前に・・・、
「D:\フォルダー2\」というフォルダを作り、その中に「test02.xlsx」という空っぽのファイルを作ってから実行します。
もちろん、実行元の「Sheet1」シートに、「空白表示だけど数式は入っている」セルをIF関数などを使って作っておく必要があります。
でないとエラーで止まってしまいます。
なお、「実は」ですが、このプログラムは、(02)のメインプログラムと「全く同じ内容」です。
なんですが、
「Set o_NoFomulaRng01 = GetDBRngOffWhiteFomlaRec01(o_SrcRng01, 1)」と
「o_NoFomulaRng01.Copy」
の2行をコメントアウトして
「o_SrcRng01.Copy」のコメントアウトをはずして有効化することで、(01)を実現しています。
また、(変数が多い、などなど)いろいろとゴチャゴチャしていますが、『 将来的に作りかえや更なる自作関数化がしやすいように 』 と思って、あえて、ゴチャゴチャにしてあります。
ご自分でも必要に応じて、作り変えられるところを作り変えてみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
' ' Sub test01() Dim o_SrcBK01 As Workbook Dim o_SrcWS01 As Worksheet Dim o_SrcRng01 As Range Dim o_NoFomulaRng01 As Range Dim s_DistFlNm As String Dim o_DistBK01 As Workbook Dim o_DistWS01 As Worksheet Dim o_DistRng As Range 'コピー元のファイルの各種設定 Set o_SrcBK01 = ThisWorkbook Set o_SrcWS01 = o_SrcBK01.Worksheets("Sheet1") Set o_SrcRng01 = o_SrcWS01.UsedRange ' Set o_NoFomulaRng01 = GetDBRngOffWhiteFomlaRec01(o_SrcRng01, 1) ' ' ↑ GetDBRngOffWhiteFomlaRec01関数によって、 ' '「空白表示された・でも数式は保持しているセル」「だけ」を、 ' '指定したセル範囲から「ほぼ」消す・除外する処理。 ' '列名もコピー対象に含める。 ' '列名を含めたくない場合は、第2引数を「1」にする。 'コピー先のファイルの各種設定 s_DistFlNm = "D:\フォルダー2\test02.xlsx" Set o_DistBK01 = Application.Workbooks.Open(s_DistFlNm) Set o_DistWS01 = o_DistBK01.Worksheets("Sheet1") Set o_DistRng = o_DistWS01.Range("A1") o_DistWS01.UsedRange.ClearContents ’一応、コピー先のシートをきれいに掃除しておく。 'コピペ操作(メイン処理) o_SrcRng01.Copy ' o_NoFomulaRng01.Copy 'PasteSpecialを使う時は、「Copy」の位置が '直前のほうがいいみたい。 'Copyのあとに変な処理をはさむと、 'Copyが解かれるのか、「失敗しました」エラーになる。 o_DistRng.PasteSpecial Paste:=xlPasteValues o_DistBK01.Save '一応、コピー先ファイルを上書き '後片付け。一応、Nothing(メモリから変数自体を消す)をしておく Set o_SrcBK01 = Nothing Set o_SrcWS01 = Nothing Set o_SrcRng01 = Nothing Set o_NoFomulaRng01 = Nothing Set o_DistBK01 = Nothing Set o_DistWS01 = Nothing Set o_DistRng = Nothing End Sub ' ' |
★ (02)の「空白表示の数式Onlyセルだけをよけて選択する自作関数」を作るケースについて
こちらは、(01)のやりかただとどうしてもダメ、というようなケースを想定しています。
例えば、
「作業用の一時シートが絶対に作成できない状況」だとか、
「(01)よりも細かい処理・加工を、できれば、関数の中だけでやってしまいたい・終えてしまいたい」
などの場合を想定しています。
「実は」ですが、このプログラムは、(01)のメインプログラムと「全く同じ内容」です。
なんですが、
「o_SrcRng01.Copy」
の行をコメントアウトして
「Set o_NoFomulaRng01 = GetDBRngOffWhiteFomlaRec01(o_SrcRng01, 1)」と
「o_NoFomulaRng01.Copy」
の2行のコメントアウトをはずして有効化することで、(02)を実現しています。
なのでこちらも、プログラムを実行する前に・・・、
「D:\フォルダー2\」というフォルダを作り、その中に「test02.xlsx」という空っぽのファイルを作ってから実行します。
もちろん、実行元の「Sheet1」シートに、「空白表示だけど数式は入っている」セルをIF関数などを使って作っておく必要があります。
でないとエラーで止まってしまいます。
ただ、(01)と大きく異なるところもありまして、1行だけとはいえ、「GetDBRngOffWhiteFomlaRec01」関数を使うところです。
「GetDBRngOffWhiteFomlaRec01」関数の詳しい内容については、プログラムのコメントを見ていただければ、なんとなくの動きもわかると思います。もし解説を加える余裕ができましたら、いつか、それも追加したいと思います。
なお、メインプログラムのほうは(01)と全く同じコードなので、こちらも(変数が多い、などなど)いろいろとゴチャゴチャしていますが、『 将来的に作りかえや更なる自作関数化がしやすいように 』 と思って、あえて、ゴチャゴチャにしてあります。
ご自分でも必要に応じて、作り変えられるところを作り変えてみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
' ' Sub test01() Dim o_SrcBK01 As Workbook Dim o_SrcWS01 As Worksheet Dim o_SrcRng01 As Range Dim o_NoFomulaRng01 As Range Dim s_DistFlNm As String Dim o_DistBK01 As Workbook Dim o_DistWS01 As Worksheet Dim o_DistRng As Range 'コピー元のファイルの各種設定 Set o_SrcBK01 = ThisWorkbook Set o_SrcWS01 = o_SrcBK01.Worksheets("Sheet1") Set o_SrcRng01 = o_SrcWS01.UsedRange Set o_NoFomulaRng01 = GetDBRngOffWhiteFomlaRec01(o_SrcRng01, 1) ' ' ↑ GetDBRngOffWhiteFomlaRec01関数によって、 ' '「空白表示された・でも数式は保持しているセル」「だけ」を、 ' '指定したセル範囲から「ほぼ」消す・除外する処理。 ' '列名もコピー対象に含める。 ' '列名を含めたくない場合は、第2引数を「1」にする。 'コピー先のファイルの各種設定 s_DistFlNm = "D:\フォルダー2\test02.xlsx" Set o_DistBK01 = Application.Workbooks.Open(s_DistFlNm) Set o_DistWS01 = o_DistBK01.Worksheets("Sheet1") Set o_DistRng = o_DistWS01.Range("A1") o_DistWS01.UsedRange.ClearContents ’一応、コピー先のシートをきれいに掃除しておく。 'コピペ操作(メイン処理) ' o_SrcRng01.Copy o_NoFomulaRng01.Copy 'PasteSpecialを使う時は、「Copy」の位置が '直前のほうがいいみたい。 'Copyのあとに変な処理をはさむと、 'Copyが解かれるのか、「失敗しました」エラーになる。 o_DistRng.PasteSpecial Paste:=xlPasteValues o_DistBK01.Save '一応、コピー先ファイルを上書き '後片付け。一応、Nothing(メモリから変数自体を消す)をしておく Set o_SrcBK01 = Nothing Set o_SrcWS01 = Nothing Set o_SrcRng01 = Nothing Set o_NoFomulaRng01 = Nothing Set o_DistBK01 = Nothing Set o_DistWS01 = Nothing Set o_DistRng = Nothing End Sub '######################################################### 'IF関数などで「空白=""」の表示になっている・かつ、 '数式の入ったセルを除外する関数。 '「そこそこ詳しく」版です。 ' '表の上や左右にそういう数式セルがあっても一応は大丈夫です。 ' 'ただ、引数のセル範囲も、戻り値のセル範囲も、 'データベース形式のセル範囲=表でないと動かないです。 'イレギュラーな形の表はダメです。 '「Worksheet.UsedRange」でのセル範囲取得に近いイメージです。 ' 'ただ、 '『 縦に「値だけ空白表示」の数式セルの「列」がある場合』 'にも、一応は対応しています。 ' '第1引数は「Rangeオブジェクト」としてのセル範囲です。 '(範囲のアドレスではダメです。それはエラーになります。) '第2引数は「0」が1行目の列名を含め、 ' 「1」が列名を含めない。 '######################################################### Function GetDBRngOffWhiteFomlaRec01 _ (o_Range As Range, _ i_ClmnNmCfg As Integer) As Range Dim i_Clmn1st As Integer Dim i_ClmnLast As Integer Dim l_Row1st As Long Dim l_RowLast As Long Dim i As Long Dim j As Long Dim l_CelCnt As Long Dim o_FirstCel01 As Range Dim o_LastCel01 As Range '念のため初期化 i_Clmn1st = 0 l_Row1st = 0 '★引数から渡されたセル範囲の、行と列の位置を記憶。 i_Clmn1st = o_Range.Column i_ClmnLast = i_Clmn1st + o_Range.Columns.Count - 1 l_Row1st = o_Range.Row l_RowLast = l_Row1st + o_Range.Rows.Count - 1 ' l_CelCnt = o_Range.Cells.Count '★引数から渡されたセル範囲の、 '『 数式は入っているけどIF関数などによって ' 値が空白表示されてしまっているセル 』 '「ではない」、最初のセルの取得。 ' '「セル範囲の中の」先頭のセルからループして。 For i = 1 To o_Range.Rows.Count '縦方向のループ '縦のループを抜けて「最初のセルを探すのをやめるかどうか」のチェック 'このチェックが無いと、最悪「すべてのセル」をチェックしてしまうので、表が大きいと遅くなってしまいます。 If Not (o_FirstCel01 Is Nothing) Then 'もし、横方向のループで目的のセルがヒットして '「o_FirstCel01」にRangeオブジェクトが代入されたら '縦方向のループも抜ける Debug.Print o_FirstCel01.Address Exit For '縦方向のループを抜ける Else End If '目的の「最初の」セルをゲットしてその瞬間に横のループを抜ける処理 For j = 1 To o_Range.Columns.Count '横方向のループ ' Debug.Print i & "---" & j ' Debug.Print i & "---" & j & "---" & o_Range.Cells(i, j).Value If o_Range.Cells(i, j).Value <> "" Then Set o_FirstCel01 = o_Range.Cells(i, j) Exit For '横方向のループを抜ける。 Else End If Next j Next i '★引数から渡されたセル範囲の、 '『 数式は入っているけどIF関数などによって ' 値が空白表示されてしまっているセル 』 '「ではない」、「最後」のセルの取得。 ' '「セル範囲の中の」一番「ケツの」セルからループして。 For i = o_Range.Rows.Count To 1 Step -1 '縦方向のループ '縦のループを抜けて「 "最後" のセルを探すのをやめるかどうか」のチェック 'このチェックが無いと、最悪「すべてのセル」をチェックしてしまうので、表が大きいと遅くなってしまいます。 If Not (o_LastCel01 Is Nothing) Then 'もし、横方向のループで"最後" のセルがヒットして '「o_LastCel01」にRangeオブジェクトが代入されたら '縦方向のループも抜ける Debug.Print o_LastCel01.Address Exit For Else End If '目的の「最後の」セルをゲットしてその瞬間に横のループを抜ける処理 For j = o_Range.Columns.Count To 1 Step -1 '横方向のループ ' Debug.Print i & "---" & j ' Debug.Print i & "---" & j & "---" & o_Range.Cells(i, j).Value If o_Range.Cells(i, j).Value <> "" Then Set o_LastCel01 = o_Range.Cells(i, j) Exit For '横方向のループを抜ける。 Else End If Next j Next i '『 「数式+空白のセル」をよけた・値だけが表示されたセル範囲 』 を返す。 If i_ClmnNmCfg = 0 Then '1行目を列名として、含めるパターン。 Set GetDBRngOffWhiteFomlaRec01 = _ Range(o_LastCel01, o_FirstCel01) ElseIf i_ClmnNmCfg = 1 Then '1行目を含めないパターン。 Set GetDBRngOffWhiteFomlaRec01 = _ Range(o_LastCel01, o_FirstCel01.Offset(1, 0)) Else End If '一応、Nothing(メモリから変数自体を消す)をしておく Set o_FirstCel01 = Nothing Set o_LastCel01 = Nothing End Function ' ' |
「GetDBRngOffWhiteFomlaRec01」関数の簡易版として、「GetDBRngOffWhiteFomlaRec02」も作ってみましたので何かの参考にしてみてください。(実は「GetDBRngOffWhiteFomlaRec01」関数の前身のテストプログラムです。)
「空白表示だけども数式は保持しているセル」が、表の「下」にある場合「だけ」にしか対応していませんが・・・。
そのようなセルが、表の上や左右にあると誤作動しますのでご注意ください。
でも、表の上には何もない。一番上は列名のみ。表の左右にも「空白表示だけども数式は保持しているセル」の列も無い、という場合は一応使えます。
コード自体が短いので動きの「大枠」の理解も少しラクですね。
また、ループ処理を使わない方法も学べます。
例えば、ループで目的のセルを検索せずに、「Range.Findメソッド」にて、「Range.Find("", , xlValues)」という感じで「値の表示されたセルだけ」を検索できます。
なお、こちらのWeb記事によると、「Range.Findメソッド」には欠陥があるらしく、「WorksheetFunction.Match メソッド」を使ったほうが確実だそうです。
なので、そのように作り変えてもいいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
' ' '######################################################### 'IF関数などで「空白=""」の表示になっている・かつ、 '数式の入ったセルを除外する関数。簡易版。 '引数のセル範囲も、戻り値のセル範囲も、 'データベース形式のセル範囲=表でないと動かない。 'イレギュラーな形の表はダメです。 '『 縦に「値だけ空白表示」の数式セルの「列」がある場合』 'にも、対応していません。 '第2引数は「0」が1行目の列名を含め、 ' 「1」が列名を含めない。 '######################################################### Function GetDBRngOffWhiteFomlaRec02 _ (o_Range As Range, _ i_ClmnNmCfg As Integer) As Range Dim i_Clmn1st As Integer Dim i_ClmnLast As Integer Dim l_Row1st As Long Dim l_RowLast As Long i_Clmn1st = o_Range.Column i_ClmnLast = i_Clmn1st + o_Range.Columns.Count - 1 l_Row1st = o_Range.Row l_RowLast = o_Range.Find("", , xlValues).Row - 1 If i_ClmnNmCfg = 0 Then Set GetDBRngOffWhiteFomlaRec02 = _ Range(Cells(l_Row1st, i_Clmn1st), _ Cells(l_RowLast, i_ClmnLast)) ElseIf i_ClmnNmCfg = 1 Then Set GetDBRngOffWhiteFomlaRec02 = _ Range(Cells(l_Row1st + 1, i_Clmn1st), _ Cells(l_RowLast, i_ClmnLast)) Else End If End Function ' ' |
★ 「test01」プロシージャと「GetDBRngOffWhiteFomlaRec01」関数の重要性(★★★VBAの独学でもっとも重要と言っても過言ではありません!)
本記事のプログラムは内容的にはぜんぜんスマートじゃありません。
僕のような超初心者が作ってるから・・・。
でも、この「test01」プロシージャと「GetDBRngOffWhiteFomlaRec01」関数のようなプログラムを作ることで、もしくは、イミディエイトウィンドウやローカルウィンドウなどを使ってステップ実行で動きを確認・調査することで、以下の(01)~(18)のようなことが一度に学べます。
動きが理解でき、それらをより考える「入口」になります。
(もちろん、コードを見るだけでは何も理解できません。体で理解できません。)
逆に、特に、例えば、「GetDBRngOffWhiteFomlaRec01」関数のような関数を作ることは、初心者の方のVBA独習の「最初のゴール」「脱初心者のための最初のゴール・目標」としてもいいかもしれません。
・・・ということに・・・、
たまたま必要があって自分で作ってみて、一週間くらい経ってから、「あれえぇぇぇぇ~???もしかしてこれって・・・」と、気が付きました。
情けないですけど・・・。(^^)
なので、是非、本記事全体はもちろん、特にこの項を参考に独学などを続けていってください。
オブジェクトブラウザの「説明ペイン」の読み解きなどに役立ちます。
ひいては、中級以上になる、最初の一歩ともなります。
では、以下、
「test01」プロシージャと「GetDBRngOffWhiteFomlaRec01」関数のような自作関数の重要性・・・といいますか、
「test01」プロシージャと「GetDBRngOffWhiteFomlaRec01」関数を作ることで「学べること」、
です。
(01)「オブジェクトを返す自作関数」とは何か?(◆自作関数のほう)
「オブジェクトモデル(階層構造一覧)」と「値を返す自作関数」とともに、『 VBA短期上達のため・初心者短期脱出のため 』、の、「最重要事項」です。
(02)「引数を取る(=ひきすうを持つ)プロシージャ」とは何か?(◆自作関数のほう)
同上
(03)「オブジェクトを引数にする」とは何か?(自作関数のほう)
同上。値(=一般データ=文字ベースのデータ)を引数にする場合との違い
(03)「一般データ(文字ベースのデータ)を引数にする」とは何か?(◆自作関数のほう)
同上。オブジェクトを引数にする場合との違い
(04)「Range.Cellsプロパティ」と「For 文 」を使った縦と横のループ処理の方法(◆自作関数のほう)
※ループ処理(繰り返し処理)はどんな言語でもプログラミングの基本で、人間様が「サボる!」ためにどうしても必要なとても重要なものです。場合によっては「夢が実現できちゃうのはループのおかげ!」ということだってあります。
でも、Excelだけに限って言うと、本当は、できるだけ「ループ処理なんか使わず」に、「SQL・もしくは・その他のループしなくても済む命令・方法」を使ったほうがはるかに効率がいいのですが、でも「SQLやその他の便利な方法」自体が使えない」ような「やむをえない時」は仕方がありませんので、ループ処理を利用します。
「CellsやRange+For Next」、あるいは、「For Each +Range」 などを使ったループ処理を利用します。
(05)それを使った「ケツから逆順の1ステップずつの」縦と横のループ処理の方法(◆自作関数のほう)
(06)処理速度を速めるために、必要なデータが見つかったら、(途中であっても)その時点で即座にループを抜ける方法の一例(◆自作関数のほう)
※この関数の場合だと縦ループも横ループも両方とも一度に・同時に抜ける方法です。両方一度に抜けないと、どちらかをやり続けてしまうので、遅くなる場合があるので。(最初から Do While文 を使えば何もしなくても済むことも多いと思いますが、For Next文の場合での無理やりな抜け方です。)
For Next文におけるループを抜ける方法を知っておくと、複雑な条件でも中断しやすいかも?しれません。
あと、一応、Do While文をほぼほぼ使わなくても「なんとかなる」ケースが増えます。それはもちろん、絶対的なメリットではありませんが、初心者のうちはたくさんの構文をなかなか一度には覚えられないので、習い始めだけそうしておく、というやりかたも「推奨はできませんが」「アリ」ではある・「ナシ」ではない、と思います。
(07)オブジェクト変数へのオブジェクトの代入方法(◆自作関数のほう)
(08)「関数」として「オブジェクトを返す」、「具体的な方法」(◆自作関数のほう)
(Setを使うということ。値(=文字ベースのデータ)の返し方との違い。)
(09)「無駄に ” With ” 」を使わない方法(◆両方)
(10)「Withなんて大して重要ではなく、オブジェクト変数やそれへの代入を使えることの方がはるかに重要」ということを理解するということ。(◆両方)
(11)その意味も含めての、「他のブックを開いて処理する」「同時に複数のブックを開いて処理する」ということのやり方(◆「test01」プロシージャ)
(12)「他のブック」や「その中のシート」「さらにその中のセル範囲」をオブジェクト変数に代入する方法(◆「test01」プロシージャ)
(13)色々なプロパティやメソッドの動き(UsedRange、Row、Rows、Count、Column、Columns、Range、Range.Cells、など。)(◆両方)
(14)初心者は「変数をたくさんつくって、いちいち、代入することから」ということの重要性(◆「test01」プロシージャ)
(オブジェクト変数を使わずに、「Withや生のオブジェクト式ばかり使っているから上達できない」ということを「知ること」の「重要性」。)
(15)「近い将来作り変える、更に関数化する」ために、「あえて変数を増やす、ダサくつくる」、ということの重要性(◆両方)
(16)「変数の増加は」、「=設定」「=関数化のもと」だから、初心者のうちは「増えてもみっともなくない」、「素晴らしいこと」「むしろ変数の個数がすくなく作ること(=「生オブジェクト式ばかり」なこと)は、ある意味 ” リーダブルにならない ” のでかえってメンテが大変になる」ということの重要性(◆両方)
(17)2つ以上のファイルを同時に扱うことで「省略の弊害」が理解できる。(◆「test01」プロシージャ)
「オブジェクト式の省略」「その他の省略」は、オブジェクトモデル・階層構造がもっと理解できてからでもまったく遅くないし、それがい理解できる前に省略してカッコつけるので、「いつまでたっても中級にになれない」ということを知ることの重要性。
イコール、「何かがちゃんと理解できてないくせいに省略すること」は、「省略」ではなく「単なる」「手抜き」「バカ」「墓穴堀り」「カッコつけたらダメ」という意識が自分の中に根付くことの重要性。(◆両方)
すくなくとも、「オブジェクトモデル・階層構造」と「オブジェクトブラウザの利用意義」が分からないのに、「カッコつけて省略ばっかり・オブジェクト変数が使えない・Withばっかり使って読みにくい」ことをするのは、本当に「VBAの腕の向上」の「邪魔でしかない」です。
(18)不完全なため、「課題を見つけるネタ」になる。(◆両方)
などなど。
逆に、このような自作関数が実際に作れない人は、「いつまでたっても、まともな理解ができず、初心者のまま、抜け出せない」という状況に陥ってしまいます。
そうなってしまうと、本当に、「せっかく始めたVBAの学習」が台無しになってしまうので、本当にもったいない話なので、そんなことにならないように、ぜひ、本記事のことは忘れないでほしいです。
また、実際に動きを確かめてみてほしいです。
是非、この関数の読みときだけでなく、実際に動かしてみてください。
★ Findついで、に、ループを使わずに「Range.Findメソッド」で次々に検索をしていく方法
ループはループでも、「For Each 文」でのループは比較的にラクなので、その分のサンプルも、さらについでに、載せておきました。2つ目のサンプルです。
ただ、「Range.Findメソッド」も、「WorksheetFunction.Match メソッド」も、「不一致な」「最初のデータの検索」ができないっぽいので、それがネックです・・・。
(実はやれるのかもですが・・・。僕にはやり方が見つけられませんでした。)
「逆For Each」もあったらいいのになー。
ちなみにですが、「WorksheetFunction.Match メソッド」のほうが速いらしく、また、致命的なこととして、Findは「数値や日付の場合には」検索にモレが生じるようです。
(参考→『VBAのFindメソッドの使い方には注意が必要です』)
確かに、このWeb記事の通り、「WorksheetFunction.Match メソッド」のほうが速かったので、もし『 Findは「数値や日付の場合には」検索にモレがあるのが致命的 』、ということなら、通常は「WorksheetFunction.Match メソッド」メインで、どうしてもFindじゃないといけないときだけFindで、というのが良いのかもしれません。
もちろんバージョンによって変わるかもしれないので決めつけはよくないとは思いますが・・・。
でも全バージョンを考えるなら、そうしたほうがいいかもしれません。
これは自己責任で判断すね。
ちなみにですが、「WorksheetFunction.Match メソッド」を使い場合は以下のような感じで、ヒットしたセルを特定できるようです。
Set rng = Cells(WorksheetFunction.Match("", Rangeオブジェクト,0), 1)
ただこれは、最初にヒットしたものだけなので、次々に検索したいなら、その方法は前述のWebページをご参考に。
下記のコードを単にMatchに変えるだけでは動きません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
' ' '####################################### 'Range.Findメソッドのヘルプの例をほぼまんまでパクりました。 '大量データのときはこちらのほうが、もしかしたらですが、 '速いのかもしれません。わかりませんが・・・。 '####################################### Sub TestByFindMethod() Dim o_SrcRng01 As Range Dim o_DistRng01 As Range Dim o_KeyWdCel01 As Range Dim s_KeyWd01 As String ' Dim o_SrchCel01 As Range '使わないのでコメントアウト Dim o_HitCel01 As Range Dim FirstAddress As String Set o_SrcRng01 = Worksheets("Sheet2").UsedRange Set o_DistRng01 = Worksheets("Sheet1").UsedRange For Each o_KeyWdCel01 In o_SrcRng01 '以降、Sheet2の検索語句で1つずつ、全部チェックするループ。 s_KeyWd01 = o_KeyWdCel01.Value '検索語句のチェック Set o_HitCel01 = o_DistRng01.Find(s_KeyWd01, LookIn:=xlValues) 'Sheet1で見つかったセルをオブジェクト変数に代入 If Not o_HitCel01 Is Nothing Then FirstAddress = o_HitCel01.Address Do '次のFindでのヒットセルを検索 o_HitCel01.Offset(0, 1) = "該当あり" Set o_HitCel01 = o_DistRng01.FindNext(o_HitCel01) Loop While Not o_HitCel01 Is Nothing And o_HitCel01.Address <> FirstAddress End If Next End Sub '####################################### '一般的なループ処理で「該当セル」を探すサンプルです。 'Range.FindもWorksheetFunction.Matchも使わない「総当たり」です。 'セル範囲に対しては For Each文が使えるのでそれを使いました。 '少量データならCellsプロパティを使わなくても 'それほど遅くならないと思います。 '1シート上に2つ以上の検索語句の表があるときは 'その複数の表の範囲に名前を付けると少しの書き換えで済みます。 'o_SrcRng01 に、付けた名前定義を指定するだけですみますので。 '####################################### Sub Test01() Dim o_SrcRng01 As Range Dim o_DistRng01 As Range Dim o_KeyWdCel01 As Range Dim s_KeyWd01 As String Dim o_SrchCel01 As Range Set o_SrcRng01 = Worksheets("Sheet2").UsedRange Set o_DistRng01 = Worksheets("Sheet1").UsedRange For Each o_KeyWdCel01 In o_SrcRng01 s_KeyWd01 = o_KeyWdCel01.Value For Each o_SrchCel01 In o_DistRng01 If o_SrchCel01.Value = s_KeyWd01 Then o_SrchCel01.Offset(0, 1).Value = "該当あり" Else End If Next Next End Sub ' ' |
- 投稿タグ
- 「ニセモノ」への道, 「本物」に近づくために, AccessVBA, Accessの独学, Access操作の基礎, Accesの独学, ADO/DAO, ExcelSQL, ExcelVBA, Excelの独学, Excel操作の基礎, Excel連携VBA, MicrosoftQuery, ODBC, SQL, パソコンでの自動化, マクロ, 独学, 自動化