【スキマ学習】ガチ初心者と一緒に、Excelでnoteのタグ解析ができるツールを作ってみよう《6》
お待たせ致しました。エクセルシートのコピペ後編です。
前回はエラーが出たまま終わってしまい、本当に申し訳ございませんでした。エラーへの対処という高い高い壁は、多くの初心者をリタイアに追い込む難関です。
どんなコードの作成にも必ずエラーはついて回ります。少し専門用語も増えてきてお疲れかと思いますが、なるべく分かりやすくお伝えできるようがんばります。
もしかしたら一読しただけでは理解が困難な内容かもしれません。プログラミングの一番苦い部分でもあります。
そんな方は分かるまで繰り返し、何回かに分けて読んでみてください。ご質問も喜んでお受け致します。
それでは、少し長くなるかもしれませんが解決編のはじまりです。
宜しくお願い致します。
・会社にいてもちょっとしたスキマ時間に実践できるよう、一回の分量をなるべく少なくしてお送りします。
・特別な準備は要りません。オフィスのPCにだいたい入ってるエクセルとGoogleクロームだけを使って作ります。記事も無料です。
前回の記事はこちら
今回の内容はこんな感じです↓
コメントアウト
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = Range("A1:H1")
Dim data As String
data = rg1.Value
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = Range("A1:H1")
End Sub
さて、エラーが出てしまったこのコード。
上から7行目の「data = rg1.Value」の部分で下のような不具合が起きていました。
一旦、ほかの場所に問題がないかどうかだけ調べたいので、今回エラーが発生した行だけを抜いて実行してみたいと思います。
単純にコードから削除してしまっても良いのですが、再度入力し直すのはちょっと手間ですね。エディターのコメント機能を使いましょう。
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = Range("A1:H1")
Dim data As String
'data = rg1.Value'
'↑ここです'
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = Range("A1:H1")
End Sub
上のように、行の先頭に半角シングルクォーテーション(')を付け加えると、プログラムがその行を無視してくれるようになります。
※noteのコード書式でも見やすいようにコメントの最後にも「'」を付けていますが、ExcelVBAでは必要ありません。
コードの途中に日本語の解説を残しておきたい時に重宝するので、活用してみて下さい。
この状態で実行してみましょう。
...うん、見かけ上はセルの範囲を取得しているだけなので何も起きませんが、特にエラーも発生していないようです。
とりあえず対処すべき問題が一つだけで済みそうで安心しました。
それでは、問題の行の修正に取り掛かりましょう。
デバッグ
プログラムの実行エラーや間違った挙動を発見して修正することを「デバッグ」と言います。バグを取り除くという意味ですね。
このデバッグ用に、プログラムの内部で起こっていることを通知してくれるウィンドウを開いておくと便利です。私もいつもお世話になっています。
表示タブから「イミディエイトウィンドウ」を表示しましょう。
下の方に新しく小さな窓が表示されたと思います。
ここに様々な方法でプログラム上の動きを表示させることができます。
今回は最もお手軽なDebugオブジェクトを使ってみましょう。デバッグのためにわざわざオブジェクトを用意してくれているのは本当に助かります。
書き方:
Debug.Print 表示させたい値
繰り返しになりますが、今回は宣言した変数と代入するデータの間の型の不一致が起きてエラーとなっています。
データの方はそもそも変更できないので、変数dataを宣言している行を修正してあげる必要がありそうですね。
どの型に修正すべきかは、値のデータ型を調べるTypeName()という関数を使うと調べることができます。
書き方:
TypeName(値)
これを先ほどのデバッグオブジェクトとの組み合わせで表示させて、イミディエイトウィンドウに「rg.Value」(A1:H1の値)の型を表示させてみます。
ここまでのコードに追加するとこんな感じですかね。
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = Range("A1:H1")
Dim data As String
Debug.Print TypeName(rg1.Value)
'data = rg1.Value'
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = Range("A1:H1")
End Sub
ちょい分からんという方も、一旦上のコードをコピーして実行してみてください。
実行後、イミディエイトウィンドウに「Variant()」と表示されていれば成功です。
コードを修正する
デバッグを実行し、「rg.Value」の値がVariant()という形であることが分かりました。
つまり、「著者名~コメントする」という一連の文字列の集まりは、Variant型というひとつの大きなかたまりの中に入って取り込まれていたということになります。
これを無理やりString型の変数に押し込めようとしたのでエラーになっていたというわけですね。
セル範囲から値をまとめて取得すると、String型ではなくVariant型で返ってくるということが分かりました。今後同様の処理をする際は気を付けたいポイントです。
上のStringの部分をVariantに修正します。
念のため、全体も書いておきますね。
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = Range("A1:H1")
Dim data As Variant
data = rg1.Value
'Debug.Print TypeName(rg1.Value)'
'今は使わないのでコメントアウトしておく'
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = Range("A1:H1")
End Sub
試しに再度実行してみましょう。エラーにならなければOKです。
値を設定する
やっとのことで、コピー元のデータを変数dataに代入することができました。あとはこれを貼り付け先のセル範囲「rg2」に書き写すだけです。
セルに値を設定する方法は、こんにちはと表示するコードの例が使えそうですね。
Dim rg As Range 'rgという名前のRange型の変数を宣言'
Set rg = Range("A1") 'rgにセル範囲A1を代入'
Dim message As String 'messageという名前のString型の変数を宣言'
message = "こんにちは" 'messageに文字列"こんにちは"を代入'
rg.Value = message 'rgのValueプロパティにmessageを代入'
確かこんな書き方でした。
セル範囲に値を代入する
書き方:
セル範囲のRangeオブジェクト.Value = 代入する値
今回も、
・セル範囲を取得
・セル範囲のValueプロパティに値を代入
という流れは変わらなさそうなので、このままの書き方でやってみましょう。幸い、既に貼り付け先のセル範囲は「rg2」という変数に代入済みでしたね。
このセル範囲rg2のValueプロパティを取得し、先ほど必死に取り込んだ変数dataを代入してみます。
rg2.Value = data
全体で見るとこうなります。
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = Range("A1:H1")
Dim data As Variant
data = rg1.Value
'Debug.Print TypeName(rg1.Value)'
'今は使わないのでコメントアウトしておく'
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = Range("A1:H1")
rg2.Value = data
End Sub
上手く動くでしょうか、ドキドキの瞬間ですね。実行してみましょう。
どうやらエラーはないようですね!
貼り付け先のシートを見に行ってみましょう。
んんん?なんじゃこりゃ。
念のためコピー元も確認しましょう。
うーん、ちゃんとデータは入っていますが、なぜかコピペに失敗してしまったようです。
正常なプログラム
私たちが間違いだと判断できるものごとを、プログラムが教えてくれるとは限りません。
エクセルが通知してくれるのは、「変数の型が合わない」といったプログラムの文法上、定義上の誤りだけなのです。
当然、それに当てはまらないエラーの場合はどこがおかしいかも言ってくれません。文法上は正しいわけですから。
むしろただ一つの手がかりは、エクセル側はこれを「正しいプログラムだ」と判断して動いてくれているということだけです。
さて困りました。
困りましたが、私たちに残された選択肢は3つです。
・自力で考えて修正する
・分かる人に質問する
・あきらめる
正直、分かる人に訊いた方が早い場合もあります。
ですが、教えてくれる人たちが必ずしも私たちにも分かる言葉で回答してくれるとは限りません。
掲示板や知恵袋では、①初心者が質問する。②専門用語の嵐で回答する。③結局良く分からなくて頓珍漢な返答をする。④回答者があきれる。という悲しいミスコミュニケーションが毎日のように繰り返されています。
私個人としては、できるだけ自力で何とかする力を養うのが最も効率の良い方法だと考えていますので、ここからはその解決のプロセスをご紹介しようと思います。
結論が早く見たいという方は、次の見出しまでスキップして下さい。
①結果を素直に受け止める
何よりも重要なのが、間違った結果を素直に認めるということです。
自分が組んだコードに誤りがあって、その結果がそのまま画面に反映されている。
だからコードを直さないといけないのですが、しばしばこれを「プログラム言語が分かりにくいせい」や「エクセルやパソコンの調子が悪いせい」にする方がいらっしゃいます。
任天堂の元社長、故 岩田聡さんも仰っていましたが、プログラムが動かないのは100%組んだ人の責任なのです。
その土俵に立たないと、まず自分のコードから間違いを発見することはできないでしょう。
②結果から逆算する
今回のコードで私たちが実現したいことはこれでした。
コピー元のシートからデータをコピーする
貼り付け先のシートにデータを貼り付ける
これのどこかが間違って、「貼り付け先のシートにデータが貼り付けされていない」ことになっているというわけです。
こういう場合、私はまずデバッグで変数の中身をチェックします。
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = Range("A1:H1")
Dim data As Variant
data = rg1.Value
'Debug.Print TypeName(rg1.Value)'
'今は使わないのでコメントアウトしておく'
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = Range("A1:H1")
rg2.Value = data
End Sub
ws1, rg1, data, ws2, rg2。全部で5つの変数が使われていますね。
プログラムは上から下に実行されますから、一番最後に処理されるところから変数の中身を調べます。試しにdataをイミディエイトウィンドウに表示してみましょう。
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = Range("A1:H1")
Dim data As Variant
data = rg1.Value
'Debug.Print TypeName(rg1.Value)'
'今は使わないのでコメントアウトしておく
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = Range("A1:H1")
rg2.Value = data
Debug.Print data
'変数dataをイミディエイトウィンドウに表示する'
End Sub
上のコードを実行してみますと、なんとデバッグの行がエラーになってしまいます。おそらくVariant型の値は表示できないのでしょう。
これはめんどくさいですね。
デバッグのために発生したエラーなんてものには付き合い切れませんので、別の方法をとります。
③変数をすべて調査する
こうなったら奥の手です。少し高度なデバッグ方法をご紹介します。表示 > ウォッチ ウィンドウをクリックしてください。
また小さなウィンドウが表示されたと思います。
これは「ウォッチウインドウ」といって、調べたい変数を追加していくと、詳細を見れるようしてくれるという優れものです。
もうここまで来たら変数を全部追加してしまいましょう。
なにやら便利そうな雰囲気だけでも感じ取って頂けると嬉しいです。
ただ、このウォッチウィンドウはプログラムの実行中にしか値を取って来てくれません。
今回のように短いコードだと実行時間は一瞬しかないため、プログラムを一時停止して確認する必要があります。
コードの画面左側のグレーの枠をクリックすると、文字が赤くハイライトされ、赤い●が出現します。これは「ブレークポイント」と呼ばれ、ここでプログラムを一時停止する目印です。
とりあえず一番最後の行にセットしておきましょう。
ブレークポイントが設置できたら、プログラムを実行してみてください。最後の行までコードが実行されたあと、プログラムが一時停止します。
それと同時に、ウォッチウインドウの中身も変化したと思います。これで、一時停止した時点の変数の中身を詳しく見ることができるようになりました。
では、一つずつ確認していきましょう。
変数名の左側にある「田」マークを押していくと、変数の中身をチェックできます。
まずは変数data、項目に抜け漏れがないことが確認できます。どうやら正常に取り込まれているようです。
次はは変数rg2ですね。開けてみましょう。
うげ、なにやら細かいものがウジャウジャと出てきました。これはちょっと心が折れそうです...
たった8つのセル範囲でも、オブジェクトの中にはこれだけたくさんの情報が詰まっていたんですね。
詳細な情報を見せてくれるのがウォッチウインドウの便利なところでもありますが、ここから知りたい情報だけを手に入れるのが何より難しいところです。
冷静になって、一旦知りたい情報を整理しましょう。
変数rg2は、「貼り付け先」のシートの「A1:H1」という範囲のRangeオブジェクトのはずでした。これを言い替えれば、
・シート名「貼り付け先」
・行数1
・列数8
というセル範囲のはずです。
このセル範囲が存在するシートの名前が、必ずどこかの項目に書いてあるはずです。
rg2を開いたまま、ひとつひとつ下にスクロールして行ってください。
「Worksheet」という項目がありました!ここにヒントがありそうですね。開けてみましょう。
いきなりビンゴです。シート名のところに、あろうことか"コピー元"と書いてあるではありませんか。
rg2は、貼り付け先のシートからセル範囲を参照してくる筈が、誤って「コピー元のシートのA1:H1」を参照してしまっていたということになります。
そりゃあ貼り付けもうまくいかないわけですね。
要するに
要するに何が間違いかというと、
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = Range("A1:H1") '←ここと'
Dim data As Variant
data = rg1.Value
'Debug.Print TypeName(rg1.Value)'
'今は使わないのでコメントアウトしておく'
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = Range("A1:H1") '←ここ!!'
rg2.Value = data
End Sub
rg1とrg2は全く同じアクティブなシートのセル範囲(コピー元シートのA1:H1)を表していて、コピーしてきたデータがそのまま同じセルに貼り付け直されていたことになります。
これは、プログラムが「Range("A1:H1")」という命令を見て、「特にどこのシートとは言われていないし、きっとアクティブなシートのことでしょ」と勝手に判断してくれていたということでもあります。
ありがたいような、やっぱり迷惑のような気配りですね。
《3》で紹介したオブジェクトの話のおさらいになりますが、実はRangeオブジェクトとは、Worksheetオブジェクトの中にあるプロパティのひとつでもあります。
さらにその外側には、Excel本体を指すWorkbookオブジェクトが存在し、反対にRangeオブジェクトの中にはValueプロパティがある、というような入れ子の構造になっているわけです。
今回はこのRange()によってセル範囲を取得する際、どこのシートから取ってくるべきか明言しなかったことが大きな問題になってしまいました。
よく小学生男子の揚げ足取りで使われる、「主語がありませ~ん!」の状態だったわけです。
正しくは、ws2という変数に代入したシート「貼り付け先」のRangeオブジェクトを取得する必要がありますので、以下のように書きます。
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = ws2.Range("A1:H1")
Rangeオブジェクトでいきなり呼んで来れるのは、アクティブなシートのセル範囲だけです。アクティブでないシートからセルの範囲を持ってくる場合は、必ずはじめにワークシートの指定をする必要があります。
よし、これで正しく動いてくれれば...!
コピペできる喜び
以下のコードが、セル範囲を別のシートにコピペする部分のの完成形です。これで実行してみましょう。
Sub copyData()
Dim ws1 As Worksheet
Set ws1 = Sheets("コピー元")
Dim rg1 As Range
Set rg1 = ws1.Range("A1:H1")
Dim data As Variant
data = rg1.Value
Dim ws2 As Worksheet
Set ws2 = Sheets("貼り付け先")
Dim rg2 As Range
Set rg2 = ws2.Range("A1:H1")
rg2.Value = data
End Sub
念のため和訳も書いておきます。
「copyDataという名前の手続きは以下の通りである。」
「ws1という変数をワークシートの形で宣言」
「ws1は”コピー元”という名前のシート」
「rg1という変数をセル範囲の形で宣言」
「rg1はws1の”A1:H1”という名前の範囲」
「dataという変数を不定形で宣言」
「dataはrg1の値すべて」
「ws2という変数をワークシートの形で宣言」
「ws2は”貼り付け先”という名前のシート」
「rg2という変数をセル範囲の形で宣言」
「rg2はws2の”A1:H1”という名前の範囲」
「rg2の値すべてはdata」
「以上」
これを、先ほどのオブジェクトとプロパティの関係に忠実に書くとこんな感じです。
Sub copyData()
'Workbook型の変数xlsを宣言'
Dim xls As Workbook
'xlsにこのExcelブックを代入'
Set xls = Application.ThisWorkbook
'Worksheet型の変数ws1を宣言'
Dim ws1 As Worksheet
'ws1にappの「コピー元」という名前のシートを代入'
Set ws1 = xls.Sheets("コピー元")
'Range型の変数rg1を宣言'
Dim rg1 As Range
'rg1にws1の「A1:H1」という名前のセル範囲を代入'
Set rg1 = ws1.Range("A1:H1")
'Variant型の変数dataを宣言'
Dim data As Variant
'dataにrg1の値を代入'
data = rg1.Value
'Worksheet型の変数ws2を宣言'
Dim ws2 As Worksheet
'ws1にappの「貼り付け先」という名前のシートを代入'
Set ws2 = xls.Sheets("貼り付け先")
'Range型の変数rg2を宣言'
Dim rg2 As Range
'rg2にws2の「A1:H1」という名前のセル範囲を代入'
Set rg2 = ws2.Range("A1:H1")
'rg2の値にdataを代入'
rg2.Value = data
End Sub
※どちらも挙動としては全く同じです。
無事、貼り付け先にデータがペーストされていれば大成功です。
おめでとうございます!
終わりに
かなり長くなってしまい、本当に申し訳ございませんでした。
辛く道のりでしたが、この一歩は「自分の手でコードをデザインして動かす」という、確かな意味のある一歩です。
もしかしたら、ここまで辿り着くことなく私の記事からリタイアされてしまった方もいるかもしれません。いえ、少なからずいると思います。
もうあなたはガチ初心者ではなく、プログラミング初級者と名乗って差し支えないでしょう。今回成し遂げたのは、それくらい困難なことです。
エラーとデバッグの工程なしに、プログラミングは語れません。ただのコピペを実現するためですらこれだけ大変なのですがから、これからもこの問題はついてまわります。
一方で、VBAを使えばとりあえずExcelの上で起きるあらゆる出来事を制御することができるという感覚も持っていただけたかと思います。
文字通り、これさえあれば「何でも」できます。
まだまだ覚えるべきことは沢山ありますが、とりあえず今回はここまでで余韻に浸っておきましょう。
お付き合いいただきましてありがとうございました。今後とも宜しくお願い致します。
お疲れ様でした。
ご読了ありがとうございました。
こんなやまびこですが、人生の時間をほんのちょっとだけ分けてあげてもいいよという方は、フォローを頂けると大変喜びます。
【だいたい平日18時頃に更新中】
Twitter:
https://twitter.com/echoyamabiko
@echoyamabiko
note:
https://note.com/echo_yamabiko
はてなブログ:
https://echo-yamabiko.hatenablog.com/
※内容は基本的に同じなので、一番身近なアカウントでのフォローをお勧め致します。
【匿名での質問や感想はこちらが便利です】
やまびこへの質問箱:
https://peing.net/ja/echo_yamabiko?event=0
ちなみに
なんで文字列を取って来ているのにString型で返って来てくれないのか
ここにはおそらく、VBA開発者の方々の涙ぐましい努力が隠されているはずです。
Variant型というのは、実は「特に形の定まっていない型」という、VBA側が苦肉の策として用意したデータ型です。
あらゆるデータやオブジェクトを変数に格納する意義については、《1》あたりでお話したと思います。繰り返し説明する手間を省くためです。
今回もいちいち「データってここのことで~」と説明する必要のないよう、セルの範囲から文字列の値をまとめて持ってこようとしていました。
しかしちょっとほかのケースを考えてみましょう。セルの値に入るデータは必ずしも文字列=Stringばかりとは限りません。
数値や計算式、関数など、セルにはユーザーが自由に値を設定できるようになっています。
これらのそれぞれデータ型の異なる値をまとめて取得して来れるように、Variantという比較的自由な型が用意されていたというわけです。
記事は基本無料公開にしようと思うので、やまびこの明日のコーヒー代くらいは恵んでやってもいいぜという方は、お気軽にご支援ください。気長にお待ちしております。