★★★★★★★★★★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)を実現しています。
  

また、(変数が多い、などなど)いろいろとゴチャゴチャしていますが、『 将来的に作りかえや更なる自作関数化がしやすいように 』 と思って、あえて、ゴチャゴチャにしてあります。
ご自分でも必要に応じて、作り変えられるところを作り変えてみてください。

  
  
★ (02)の「空白表示の数式Onlyセルだけをよけて選択する自作関数」を作るケースについて

こちらは、(01)のやりかただとどうしてもダメ、というようなケースを想定しています。

例えば、
「作業用の一時シートが絶対に作成できない状況」だとか、
「(01)よりも細かい処理・加工を、できれば、関数の中だけでやってしまいたい・終えてしまいたい」
などの場合を想定しています。

  
  
★ (02)のケースでのプログラム例

「実は」ですが、このプログラムは、(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)と全く同じコードなので、こちらも(変数が多い、などなど)いろいろとゴチャゴチャしていますが、『 将来的に作りかえや更なる自作関数化がしやすいように 』 と思って、あえて、ゴチャゴチャにしてあります。
ご自分でも必要に応じて、作り変えられるところを作り変えてみてください。

  
  
「GetDBRngOffWhiteFomlaRec01」関数の簡易版として、「GetDBRngOffWhiteFomlaRec02」も作ってみましたので何かの参考にしてみてください。(実は「GetDBRngOffWhiteFomlaRec01」関数の前身のテストプログラムです。)

「空白表示だけども数式は保持しているセル」が、表の「下」にある場合「だけ」にしか対応していませんが・・・。
そのようなセルが、表の上や左右にあると誤作動しますのでご注意ください。

でも、表の上には何もない。一番上は列名のみ。表の左右にも「空白表示だけども数式は保持しているセル」の列も無い、という場合は一応使えます。
コード自体が短いので動きの「大枠」の理解も少しラクですね。
また、ループ処理を使わない方法も学べます。
例えば、ループで目的のセルを検索せずに、「Range.Findメソッド」にて、「Range.Find("", , xlValues)」という感じで「値の表示されたセルだけ」を検索できます。
なお、こちらのWeb記事によると、「Range.Findメソッド」には欠陥があるらしく、「WorksheetFunction.Match メソッド」を使ったほうが確実だそうです。
なので、そのように作り変えてもいいと思います。

  

  
  
★ 「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に変えるだけでは動きません。