...

VB.NET 等での基本構文 - CORESERVER.JP

by user

on
Category: Documents
5

views

Report

Comments

Transcript

VB.NET 等での基本構文 - CORESERVER.JP
プログラミング関係補足資料 1
VB.NET 等での基本構文(ステートメント)
プログラミング関係補足資料 2
VBA(Visual Basic for Application)実践資料
(主としてエクセル+VBA)
とつげき東北
【考 え 方】まずは実際に動かして理解する!
【概
要】VBA コード作成の一連の手順(平方根を求めて表示するマクロ作成を例に)
【サンプル】文字列の操作の一例
【サンプル】シンプルなコピー等の操作の例
【サンプル】各シートに対して処理を行う例
【手法紹介】デバッグ作業の方法
【手法紹介】マクロの記録を利用した VBA プログラム開発
【サンプル】ユーザにファイルを選択させ、選択したファイルの全シートを印刷
【サンプル】エクセルのグラフ上の各点にラベルをつける
【サンプル】VBA でのファイル入出力例
【サンプル】ワークシートの操作
まずは実際に動かして理解する!
プログラミングは、机上で勉強していても眠たくなるだけである。
とりあえず動くものを動かしてみることが大切である。
一番良いのは、
「○○という処理を自動化したい!」
「こういうことを調べたい!」という動機(モ
チベーション)があって、それを解決するためにプログラミングをすることである。勉強と同様だが、
目標がなければ決して身につかないからだ(もし東京大学の入学試験に数学や古文がなかったら、こ
の中のうち何割が数学や古文を熱心に勉強していましたか?)。
一方で、
「何ができるか」が曖昧な状態から、いきなり高度なことを目指して挫折するのももった
いない。そもそも、特に目的が見つからない場合もあるだろう。
そこで、ここでは、職務等で使いまわしが可能なコードのサンプルをいくつか記述する。通常使用
する範囲なら、大概の操作は、これらの使いまわし+αで事足りるだろう。どういったことが可能か
薄々理解し、
「尐し改良すればこう使えるのではないか?」とひらめいていただければ幸いである。
エクセル VBA は、職務遂行等の際に非常に強力な効率化ツールとして機能する(とある事務処理
に 8 時間かかっていた友人は、筆者が提供した自動化ツールを利用することで、毎日の仕事がたった
の 10 分で終わるようになってしまった!)
。事務作業が仕事の全てではないが、
「仕事ができる」と
は、一つはそういうことだろう。
あなたの目の前に今、エクセルのシートがあるとする。そこにはメールアドレス、先方の氏名、先
方に送るべきファイルの場所(ドライブ上のファイル名)がずらりと並んでいる。それぞれ、誤りな
く正確に、あて先と氏名と当人用のファイルをセットにして、5000 通送信するのがあなたのタスク
だ。フリーソフト等は、会社のセキュリティポリシー上使用できない。何分かかるだろうか?
尐し余裕を持って「30 分程度」と答えられれば、あなたの事務処理能力は日本中の事務処理作業
従事者のうちでも、かなりのものだ。が、VBA を扱うことが可能なら、これはさほど困難ではない。
さて、エクセル VBA を学ぶ際の参考として、Gen Muto 氏のサイト「エクセル大事典」を紹介し
ておく(http://home.att.ne.jp/zeta/gen/excel/)
。極めて広範な事例を扱っており、お勧めである。エ
クセル VBA で麻雀ゲームを作成された方で、数々の栄誉ある賞を取得されている。
他にも優良なサイトは数多くある。
エクセル VBA 関連項目の一覧などを眺めていれば、「お、こういうこともできるのか」とわかり、
結果として「それなら××処理を自動化するプログラムが作れる」とモチベーションが生まれる。
プログラミングでつまった時にやるべきことは、優先度順で言えば次のとおりである。
身近にわかる人がいないか? → マクロの記録(後述)でできないか? → ネット上に
ソースは転がっていないか? → ネット上やヘルプで調べられないか? → 資料で勉強
順序は、いわゆる高校までの勉強と全く逆になる。
無駄に調べるのではなく、動く楽しみを最大限味わいながら理解を深めてほしい。
なお、本資料では細かい文法等を解説しない。あくまでも「実践」資料である。文法等は、ネット
等を活用してその場その場で身につければ充分である。
VBA コード作成の一連の手順(平方根を求めて表示するマクロ作成を例に)
エクセルを開く。
「表示→マクロの表示」を選択する。なお、セキュリティのオプションでマクロの実行が禁止され
ている場合、あらかじめ解除しておくこと。
作成するマクロ名の入力欄が表示されるので、適宜名称を決める。
「作成」を押す。
エディタ画面(VBE)が表示されるので、上記のようにコードを記入し、エクセル側の画面に戻る。
A1 セルに数字を入力後、
「表示→マクロの表示」から、今作成したマクロを選択し、
「実行」を押す。
マクロが実行される。
このマクロを、ボタン等に関連づけて利用したい場合、適宜図形等を設置し、
図形を右クリックして「マクロの登録」によって関連づければ良い。
ボタン等を押すと、マクロが実行されるようになる。
文字列の操作の一例
文字列の操作の一例を示す。
moji_1 = Cells(1, 1) „A1 セルの文字列
moji_2 = Cells(1, 2) „B1 セルの文字列
Cells(3, 1) = moji_1 & "と" & moji_2 ' &で、複数の文字列同士を連結
Cells(4, 1) = Mid(moji_1, 2, 4) ' 文字列のうちの 2 文字目から 4 文字分を切り出す
Cells(5, 1) = Replace(moji_2, "い", "お") ' 文字列の特定の文字を別の文字に置き換える
Cells(6, 1) = InStr(1, moji_1, "5") ' 文字列の 1 文字目から、"5"という文字の位置を探す
(実行結果)
シンプルなコピー等の操作の例
各セルや行に対して、いくつかの基本的な操作を行うサンプルコードを示す。
Sub test()
Sheets("Sheet1").Select ' "Sheet1"を選択
Range(Cells(2, 2), Cells(3, 3)).Select ' B2~C3 の範囲を選択
Selection.Copy (Cells(5, 5)) ' 選択されている範囲を E5 にコピー
Cells(5, 5).Interior.Color = RGB(255, 0, 0) ' E5 に着色
Rows(4).Select ' 4 行目全体を選択
Selection.Delete ' 選択されている範囲(4 行目全体)を削除
End Sub
(実行結果)
各シートに対して処理を行う例
・・・・・・
まず、シートを 10 個用意し(面倒なら 1 シートでも良いが)、上記のようなファイルを作成する。
A1 セルにはシートごとに 1~10 の数字を入れておくこと。
なお、ボタンは配置しなくてよい。
この各々のシートの各々のセルについて、買えるかどうかを返すプログラム例を示す。
Sub 買えるかどうか教えて()
' a,b,x の値を格納する配列を宣言
'1次元目は各行を、2次元目は各シートを表す
Dim a(100, 10) As Integer
Dim b(100, 10) As Integer
Dim x(100, 10) As Integer
'結果を「はい」
「いいえ」で格納する配列を宣言
Dim YESorNO(100, 10) As String
'シート番号を読み取るために使用する変数を宣言
Dim no As Integer
'ループ用変数を宣言
Dim i As Integer
'アクティブなワークブックの各シートを「sh」で表して繰り返し処理
For Each sh In ActiveWorkbook.Sheets
'「sh」の A1 セルを数字として読み込み
no = sh.Cells(1, 1).Value
'1~100 行目の各 a,b,x の読み込み
For i = 1 To 100
a(i, no) = sh.Cells(i + 2, 3).Value
b(i, no) = sh.Cells(i + 2, 4).Value
x(i, no) = sh.Cells(i + 2, 5).Value
Next
'1~100 行目の各々について、買えるかどうかを判定したい
For i = 1 To 100
'買えるかどうかを判定する関数を呼び、結果を代入
YESorNO(i, no) = Kaeruka(a(i, no), b(i, no), x(i, no))
Next
'それぞれの結果を表示する
For i = 1 To 100
sh.Cells(i + 2, 6) = YESorNO(i, no)
Next
Next '各シートのループの終了
End Sub
'100 円玉 a 枚,10 円玉 b 枚で、x 円の商品が買えるかを返す関数
'第1引数:100 円玉の数(Integer 型)
'第2引数:10 円玉の数(Integer 型)
'第3引数:商品の値段(Integer 型)
' 戻り値:
「はい」
「いいえ」のいずれかの文字列(String 型)
Function Kaeruka(ByVal a As Integer, ByVal b As Integer, ByVal x As Integer) As String
If 100 * a + 10 * b >= x Then
Kaeruka = "はい"
Else
Kaeruka = "いいえ"
End If
'VBA では、関数の戻り値は「Return 戻り値」と書くのではなく、
'関数名=戻り値 と書くことに注意する。
End Function
(実行結果例)
変数や関数については講義で触れた。
重要なことは、
「変数=Cells(行,列)」で、セルの値や文字列を変数に代入できる(入力)こと、
「Cells(行,
列)=変数等」で、セルに値を書き込める(出力)ことである。エクセルで読み・書きができれば、条
件式や繰り返し処理と併用するだけで、非常に多くの作業が自動化できる。
デバッグ作業の方法
(エラーが出た場合の例1)
コンパイル時や実行時にエラーが出た場合は、まずはコードをよく確認すること。エラーメッセー
ジ内容とバグの原因は、関連しない場合も多いので注意すること(上記「引数の数が一致していませ
ん」は、関数の宣言と呼び出しの引数誤りを示唆しており、比較的適切なエラーメッセージである。
だが、可能性はそれだけではない。たとえば複数の似たような名前の関数を作っていて、呼び出した
関数名を間違えた可能性もある。または、引数の綴りを誤って、異なる型に代入しようとしたのかも
しれない。
「不正なプロパティを指定」しているわけでもない)。
(エラーが出た場合の例2)
次は「型が一致しません」と出た。とりあえず「デバッグ」を選択する。
エラーが発生した箇所が表示される。
型が一致しないということは、例えば、Integer 型の a に対して、セルの値が文字列型になってい
る等の要因が考えられる。しかし、エクセルの表を何度見ても、おかしなところは見つからない。こ
のエラーの真の原因は、参照していた箇所が違うことだった(For 文の i の値は、1 から開始するよ
うに設計していたのに、0 からでコーディングしてしまっていた)
。
ひとまず■を押して、実行を中断する。
エラーの原因がわからない場合、
「デバッグ」という作業に入る。
例えばこの For 文のうち、i の値がいくつのときにエラーが出たかがわかれば、原因がわかるかも
しれない(i が 50 や 100 でエラーとなっているのか、初めからいきなりエラーとなっているのか)。
デバッグするために有効な方法は、
「ブレークポイント」を使用することである。
●のついた部分をクリックすると、ブレークポイントを設定できる(もう一度押すと解除できる)
。
プログラムが実行されたとき、この場所に来ると実行が一時停止される。その場で、各変数の値な
どを「イミディエイトウインドウ」で確認することができる。イミディエイトウインドウが表示され
ていない場合、
「表示→イミディエイトウインドウ」で表示させる。
イミディエイトウインドウでは、
「?変数名等」と入力することで、各変数等を確認することができ
る。
今回、関係のありそうな変数を全て確認してみた。すると、本来数字であるべき sh.cells(i+2,3)の
値が文字列となっていた。このことから、誤った場所を参照していることがわかった。原因を考え、
For 文を直せばよい。
ところがそれだけでは終わらなかった。no には、本来 1~10 の数字が入っていなければならない
が、0 となっていた(実際にこの資料を作成しているときにわかった)
。no には、直前でシートの A1
セルの値を代入しているので、A1 セルに数字を入力し忘れているのかと確認したが、そうでもない
ようだ。そこで念のため、シートの名前を sh.Name で表示してみたところ、
「1 (1)」という、想定し
ていないシートを参照していることが発覚……!
なんとエクセルの左側に、バックアップ用にコピーしておいたシートが移動して隠れていた(←を
押さないと見えない!)ことが判明。当該シートの A1 セルは空欄だった。
もし A1 セルが正常な数字だったら、一見プログラムが正しく回ったように見えて、正しくない結
果が入る可能性もあった。こうしたエラーを厳密に回避するには、処理したシート数をカウントして
おいて最後に表示する等の措置が必要になる。
ともあれ、エラーを除去したら、■を押して実行を中断し、ブレークポイントを外して再度実行す
る。ただし、一度エラーが発生した以上、本来はエラーが直ったかをもう一度ブレークポイントで確
認してから実行すべきである。
ブレークポイントを複数設置したり、1 行ずつ実行してみる「ステップ実行」等の機能を利用した
りすることで、よりデバッグが容易になる。本格的なプログラミングを行う場合は、処理中の注目す
る変数やデータを、別のファイルに一括で書き出して確認する等の方法でデバッグを行うこともある。
プログラミングに慣れてくると、コードを書くことは比較的容易になる。戦うべき相手はバグなの
だ……。
なお、VBA によってエクセルに変更を加えた場合、
「やり直し」
(Ctrl+Z)では元に戻らないため、
重要なファイルへの VBA 実行の前には、必ず保存しておくこと。
マクロの記録を利用した VBA プログラム開発
VBA のすばらしい機能の一つが、
「マクロの記録」である。これは、ユーザがアプリケーション上
で操作した記録を、そのまま VBA プログラムとして保存してくれる機能である。
例えば「自動的に全シートを印刷するプログラムを作りたい」と考えたとする。開かれているブッ
クの各シートに対して処理する方法は、先の例で学んだ(For Each sh In ActiveWookBook.Sheets
~Next を使えばよい)
。ところが、
「印刷の方法」がわからない。筆者も覚えていない。そこで、
「シ
ートを選んで印刷する」動作をプログラムとして記録しておき、それを再利用してプログラムを作成
してみよう。
単にエクセルを開き、
「マクロ→マクロの記録」を押す。
マクロ名は何でもよい。
OK を押したら、マクロが既に記録され始めている。シートを選び(シート 1 をクリックし)
、Ctrl+P
で印刷画面を表示し、印刷を実行する。
印刷が終わったら、
「マクロ→記録終了」を押す。
次に、
「マクロ→マクロの表示→編集」で、今記録されたマクロを編集してみよう。
コード画面が現れ、次のようなコードが記述されていることがわかる。
Sub Macro1()
'
' Macro1 Macro
Sheets("Sheet1").Select
ExecuteExcel4Macro "PRINT(1,,,1,,,,,,,,2,,,TRUE,,FALSE)"
End Sub
この内容を見る限り、Sheets(シート名文字列).Select でシートを選択し、その次の行で印刷が行
われているようだ(バージョン等によってこの部分は数行になる場合もある。だが、とにかくコピー
すれば使える)
。
今回は、ブック上の全てのシートを印刷したいわけだから、次のようにコードを書けばよいことが
わかるだろう(ただし厳密に言うと、For Each 文は処理順序の同一性が保証されていない。本来は
「シートカウント」を取得し、For~Next で行うのが適切であるが、通常はこれで動く)
。
For Each sh In ActiveWorkbook.Sheets
sh.Select
ExecuteExcel4Macro "PRINT(1,,,1,,,,,,,,2,,,TRUE,,FALSE)"
Next
実際にいくつかのシートを作成して何か文字を書き込み、動かしてよう。
「通常使うプリンタ」に、
各シートの内容が印刷されるはずだ。
この「マクロの自動記録」機能は、エクセル VBA プログラミングを筆者がお勧めする非常に大き
なポイントである。プログラム初心者だけでなく、上級者であっても、何かを実現したい際に、全て
のコードが頭から出てくるわけではない。VBA ではその場でコードを自動生成できるのである。
ユーザにファイルを選択させ、選択したファイルの全シートを印刷
次に、先のプログラムを応用して、大量のエクセルファイルを開いて処理を行う例を示す。
筆者も「ユーザにファイルを選択させる」部分のコードを忘れていたため、マクロ記録によって雛
形コードを作成したうえで、ネット上で尐し調べて利用した(もちろん、初めからネットでコードを
探してみてもよい)
。
以下がプログラム例となる。
Sub 選択したエクセルファイルを開き全てのシートを印刷()
'パスの長さが 255 文字を超えると動作しません
'FileDialog を開き、ブックを選択させる
Set fdHearingSheetFile = Application.FileDialog(msoFileDialogFilePicker)
With fdHearingSheetFile
.Title = "印刷したいエクセルファイルを選択"
.Filters.Clear
.Filters.Add "Excel ファイル", "*.xls"
.Filters.Add "全てのファイル", "*.*"
.FilterIndex = 1
.InitialFileName = ActiveWorkbook.Path
'.Show = 「Application.FileDialog」のメソッド。
'実行が押されたときに-1、キャンセルが押されたときに 0 が返る。
If .Show = -1 Then
'各ファイルごとに処理
'以下、ヘルプより。
'.SelectedItems = 「Application.FileDialog」のプロパティで、
'FileDialogSelectedItems コレクションを取得します。
'このコレクションには、FileDialog オブジェクトの Show メソッド
'によって表示されたファイルのダイアログ ボックスでユーザーが選択
'したファイルのパスの一覧が保存されています。
For Each vrtSelectedFile In .SelectedItems
'指定ファイルをオープン
Workbooks.Open Filename:=vrtSelectedFile, ReadOnly:=False
strtmpfilename = ActiveWorkbook.Name
'全てのシートを選択
Sheets.Select
'選択されたシートを印刷
ActiveWindow.SelectedSheets.PrintOut Copies:=1, Collate:=True
' 指定ファイルをクローズ
Workbooks(strtmpfilename).Close savechanges:=False
Next vrtSelectedFile
Else
Exit Sub
End If
End With
Set fdHearingSheetFile = Nothing
End Sub
実行すると、以下のようなダイアログが表示されるので、全シート印刷したいファイルを選択して
OK を押せば良い。
全シート印刷のかわりに、開かれたファイルの特定のセルの値を配列変数等に保管しておき、最後
に「自分のブック」に表形式に書き込むような処理を入れれば、大量に送られてくるエクセルファイ
ルから名簿等を作成することも簡単である。
【裏話】
この「ファイル一括印刷」のコードを書いたのには裏話がある。複数のエクセルファイルをフォル
ダ上で選択して「印刷」をしても、エクセルの 1 ページ目だけしか印刷されないのだ。よって、大量
のエクセルファイルを全て印刷するには、1 つ 1 つ手作業で行わなければならない。
筆者は国家公務員として中央官庁に勤務している(自称)
。
有識者と呼ばれる夢見がちな人々が思いつく「政府の業務を電子化して効率化する。人員が削減で
きる」という名目のもと、とある一流コンサルタント業者と仕事をしたことがあった。ところが当該
企業の担当者は、毎週の定例会議の際、各種業務の改善案などをまとめた資料を、手作業にて半日が
かりで印刷していたのである。
そこで筆者がこの印刷プログラムを作成し、そっと担当者に渡したのだった。彼はほとんど半泣き
で喜んでいた。良いことをしたなぁと感じた反面、次のような疑念が浮かんだのも事実である。
「たかだかエクセル印刷業務の電子化さえ効率化していない民間コンサルタント企業に、
『政府の業
務を電子化して効率化する』ことが、いったいいかにして可能なのだろうか?」
「IT 革命」とやらを推進していた内閣総理大臣は、その「イット革命」とやらによって、行政が
電子化されると信じていた。だが実際には、業務が多重化するとともに運用コスト等が膨らみ、国家
公務員の人員あたり仕事量が激増するといった事態を迎えた……疑うのならば、確定申告に税務署に
出向いてみるといい。職員が休日出勤して、紙で持ち込まれた申告書類を PC に手打ちで打ち込んで
「電子化」を推進しているではないか。
国家公務員側からすればこの結果は目に見えていた。ほとんど誰もが失敗を確信していた。いかな
る先進諸外国の例を見ても、国民一般の「電子申請」等の利用率が 30%を超えることなどほとんど
あり得ないことだ。奇跡と Change が起きて利用率が 50%に達したとしても、業務負担が減ること
など絶対にあり得ない。
「IT 革命」など、はなから存在しはしないのだ。
しかし残念なことに、「世論」は「電子化による効率化」という魔法の言葉を信じて疑わず、政府
にこの試みをやめさせることを許さなかった。数年後には「使い物にならない」
「利用率が低い」と
して「仕分け」されることになるあのシステム設計・システム開発に費やされた数年、莫大なコスト
は何だったのだろうか。
ところで 1 点、不思議なことがある。
「効率化」は明らかに失敗したにもかかわらず、
「効率化によ
って可能となる定員の 25%削減」という数値目標は、残されたままとなった。効率化はできなかっ
たが、効率化はしようというわけである。筆者の個人的な見解だが、愚鈍な発想ではないか。
「ムダの削減」に最良の方法を 3 点提案する。1、ムダに思いつかないこと。2、
「改革」「効率化」
などと狂喜乱舞しないこと。3、現実を見ること。
エクセルのグラフ上の各点にラベルをつける
上図は、総務省統計局の国勢調査の結果をもとに、都道府県別に、横軸を 1975 年人口、縦軸を 2005
年人口にとってエクセルでグラフを作成してみたものである。
この各々の点に、都道府県名のラベルを付したい。最新のエクセルではわからないが、尐なくとも
Office XP 等では、こうした作業ができなかった。
ここでは、VBA を用いてそのような処理を行ってみる。もちろん、やり方を覚えてはいないので、
とりあえず「マクロの記録」を実行することから始めるわけだ。
マクロの記録を開始し、1 点だけデータラベルを選択して、データラベルの追加をしてみる。
数値が書き込まれた。
データラベルを変更してみてから、マクロの記録を終了し、マクロの内容を見る。
Sub Macro1()
'
' Macro1 Macro
'
'
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Select
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Points(13).Select
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Points(13).ApplyDataLabels
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).DataLabels.Select
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Points(13).DataLabel.Select
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Points(13).DataLabel.Text = "12 あああ,601"
End Sub
色々と操作をしたため、たくさんの文字列が入っているが、実行したい操作は「グラフの中の特定
の点にラベルをつけ、ラベル名を対応する都道府県名とする」ことである。
本来はムダな部分を削るべきなのだが、ここではそのまま愚直に、これを For~Next で回してみ
ることにしよう。
Sub Macro1()
'
Dim row As Integer
For row = 1 To 47
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Select
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Points(row).Select
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Points(row).ApplyDataLabels
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).DataLabels.Select
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Points(row).DataLabel.Select
ActiveSheet.ChartObjects("グラフ 1").Activate
ActiveChart.SeriesCollection(1).Points(row).DataLabel.Text = Cells(row+6,3)
Next
End Sub
青色部分が修正箇所である(都道府県の名称は 7 セル目から入っていたため、ラベルにする際の
Cell は Row+6 とした)。
とりあえず動くことが確認された。
ただし、毎回同じグラフを選択しなおしているなど、無駄の多いプログラムとなっている。マクロ
の自動保存機能でコードを書いた場合は、とりあえずはそのまま動かしてみた後に、必要に応じて不
要な処理をカットするとよい(この場合の例で言えば、グラフそのものの選択は For~Next の外に
出す、等)
。
さてグラフだが、データラベルのサイズを調整しても、左下の固まった部分はさすがにうまく表示
できないだろう。左下部分だけの表を取り出し、データラベルがどうしても重なる部分だけ手作業で
動かせば、一応それらしいグラフが作れる。
なお、ラベルの大きさはエクセル上で一括して変更できるが、県ごとに、プログラム的にラベルの
大きさを制御することを考え、特定のマーカーのフォントサイズを変更してみたところ、マクロ記録
が残らなかった。このように、必ずしも全ての操作が記録できるわけではない。
こういった場合は、例えば「VBA DataLabel 変更」等で検索をかけて調べればよい。
すぐに、
ActiveChart.SeriesCollection(表番号).Points(点の番号).DataLabel.Font.Size = サイズ
と記述すれば解決することがわかった。
目的があれば、それにあわせてプログラムを簡単に書け、自動化できることがエクセル VBA の魅力
である。
VBA でのファイル入出力例
ファイルの入出力の基本の流れは、
「ファイルを開くための制御番号を取得する→ファイルを開く
→ファイルから読み込む(書き込む)→ファイルを閉じる」である。
必ず最後に「Close(閉じる)
」を行うこと。Close を忘れると、ファイルが開かれたままの状態に
なったり(排他制御がかかる)
、正しくデータが書き込まれない等の事象が発生する。
VBA でファイルからの入力、ファイルへの出力を行う例を示す。
このプログラムでは、入力したデータに 3 桁の一連番号を付して出力する。
Dim InpFile As String
Dim OutFile As String
InpFile = "C:¥test.txt"
OutFile = "C:¥test_out.txt"
Dim InpFil As Integer
Dim OutFil As Integer
Dim InpData As String
Dim OutData As String
Dim cnt As Integer
cnt = 0 '一連番号
InpFil = FreeFile() 'ファイル制御空き番号を取得
Open InpFile For Input As #InpFil
OutFil = FreeFile()
Open OutFile For Output As #OutFil
Do While EOF(InpNo) = False 'ファイル終端になると TRUE
Line Input #InpFil, InpData 'InpData に1行読み込み
cnt = cnt + 1 '一連番号を加算
OutData = Format$(cnt, "000") & InpData '連番を付けて OutData を作成
Print #OutFil, OutData 'OutData を書き込み
Loop '繰り返し
Close #InpFil, #OutFil
エクセルにまとめたデータを一括してファイルに書き出す等の処理は、研究等にも有用である。
ワークシートの操作
ワークシートのコピーは次のようにして行う。
Dim wb As WorkBool
Set wb = Workbooks(“BookA.xls”)
Dim ws As WorkSheet
Set ws = wb.Worksheets(“Sheet2”)
ws.Copy after:=wb.Worksheets(“Sheet5”) „シート 5 の後ろにコピー
wb.WorkSheets(“sheet2”).Copy after:=Wooksheets(“Sheet5”) „上と同じ
新規ブックを作成して、ワークシートをコピーする例は以下のとおり。
Dim wb As Workbook
Set wb = Workbooks(“Book1.xls”)
wb.WorkSheets(“Sheet2”).Copy
ここで取り上げた例は一例であり、セルの結合、罫線の操作等、様々な操作を行うことができる。
必要に応じて検索等をして調べると良い。
プログラミング関係補足資料 3
VB.NET(Visual Basic .NET)実践資料
とつげき東北
【導
入】やはり「実際に動かせる」環境を!
【概
要】フォームアプリケーションを作る
【手法紹介】デバッグ作業の方法
【補足情報】型の変換、ファイル入出力等、その他の補足情報
やはり「実際に動かせる」環境を!
何度でも繰り返すが、プログラミングは、机上で勉強していても眠たくなるだけである。Dim だの、
For~Next の動きだのと覚えるのは苦痛なだけだ。
筆者がプログラムを覚えたのは、小学生の頃、「ファミコン禁止令」が施行されたからである。ゲ
ーム大好きな尐年は、必然的に、プログラムを始めることとなった。プログラミング関係の雑誌には、
ゲームのプログラムが掲載されていたからである。尐年は、
「まる写し」から始めた。次第に、
「画面
の色を変えてみよう」
「主人公を無敵にしてみよう」「いっそ、新しい面を追加できないか」「いや、
もっと面白いゲーム作れるんじゃないか」……そうした目的が生じた。そのたびごとに、文法などを
勉強しながら、プログラミングを覚えていったものだ。目的があるから勉強は苦痛ではなかった。
ではみなさんは、いまさら「ゲームを作ろう」と思うだろうか。思う方はすばらしい、プログラミ
ングに向いている。そうでない方は、こう考えよう。
あなたは、あるときふと、ある数学的な(別に経済学的であっても哲学的であっても良いが)疑問
を思いついた。そのとき、目の前に、その疑問を解決するツールが一切ないとしよう。鉛筆も、紙も、
過去の資料や論文も何もない。あなたにいったい何ができるだろう? 逆に、目の前にコンピュータ
があり、その疑問をすぐにでも解決できるかもしれない環境とスキルがあればどうだろう。検索が可
能で、調べられるならどうだろう。もしかすると、あなたの疑問は解決するかもしれない。運がよけ
れば、新発見になるかもしれない。
解決できないタイプの人は、無力感を学習し、「疑問」など頭に抱かなくなるのではないか。どう
せ考えてもムダだ、わかりっこない、と。解決できるタイプの人は、ますます新しい「疑問」を考え
始め、次々と問題解決ができる人間になってゆく。ちょうど、勉強ができない人が人生の途上で「勉
強することそのものを諦める」のと同じことだ(みなさんは逆だったのだろう)
。
とにかく目の前に問題解決のための手段が常備されていることが、スパイラルの中で、問題発見の
ための動機を次々と生み出してゆくのである。
だから、今日家に帰ったら、さっそくプログラミングの開発環境を PC に導入しよう。
「VB.Net Express」でググる検索する。
Microsoft のページへ。
画面の下部から、Visual Basic 2008 をダウンロードしてインストール。
ユーザ登録等は必要だが、無料で使用することができる(期間制限なし)
。
起動して、一通り初期設定を終えれば、開発環境の導入は終了。
さっそく 1 つのプログラムを作ってみる。
フォームアプリケーションを作る
「ファイル→新しいプロジェクト」を選択。
「Windows アプリケーション」を選択し、プロジェクト名を適当に決めて「OK」を押す。
まずは、画面の左側に「ツールボックス」を表示するようにしておく。
「表示→ツールボックス」で表示される。
なお、ツールボックスがすぐに消えてしまって困る場合、「虫ピン」で止めておけば良い。
ツールボックスを展開し(すべての Windows フォーム)、Button というコンポーネントを選択す
る。
画面中央のフォーム上の適当な位置をクリックし、Button を設置する。
Button は、ユーザからの入力(ボタン押下)に対して処理を行いたい場合に設置する。
同様に、TextBox を選択する。
フォームに 2 つの TextBox を設置する。設置する場所は適当で良い。
TextBox は主に、単一の情報の入力や出力のために利用される。
Button1 をクリックすると、右側に「プロパティ」が表示される。プロパティとは、この Button
の各種の設定である。プログラム内で用いる名称、サイズ、色など様々な情報が入っている。ここで
は、そのうち「Text」を選ぶ(なお、プロパティのウインドウが表示されていない場合、適宜表示す
ること)
。
Text を「平方根を求める」に変更。
フォーム上の表示が変化する。ついでに、フォーム上の Button の端をドラッグして、尐しサイズ
を調整すると良い。このとき、Button のプロパティの Size は自動的に変更されている。
続いて、Label を設置する。Label は、主に静的な情報をユーザに伝えるために利用される。
Label の Text プロパティを「平方根」に変える。
フォーム上の Button をダブルクリックする。
Button1_Click というサブルーチンが自動生成される。
赤線で囲った部分を入力すること。
なお、この Sub ルーチンは、Handles Button1.Click として宣言されており、Windows 側から
「Button1 がクリックされた」というイベントが届いた場合に自動的に実行される Sub ルーチンで
あるが、最初の段階では意識する必要はない。
もし、画面下部に「エラー一覧」の画面が出ていなければ、表示しておくと良い。
プログラムの記述等で誤った場合、その場で誤りを指摘してくれる。
コードが書けたら、おもむろに「F5」を押してみる。
これによりプログラムがビルドされ、実行される。
正しくコードを記入していれば、上図のようなフォームが表示されるはずである。
左側の TextBox に適当な数字を入れてボタンを押すと、右側の TextBox に平方根が表示される。
今回の講義で示した他の各種コードも、このように Button を押した場合に実行されるようコーデ
ィングしておけば、ただちに実行できる。
出力先も、メッセージボックス(MsgBox)だけでなく、TextBox 等が利用できる。
なお、数学関数を使う場合は、フォームのコードの一番上部分に、上図のとおり Imports 文を入れ
ておくこと。
デバッグ作業の方法
さて、先のプログラムで、もし数字以外を入力するとどうなるだろうか。
このように、実行時エラーが発生するはずである。
原因は、Double 型に変換不可能な文字列を、無理に変換しようとしたことにあるのだが、それが
わからない場合には、バグの原因を探ってゆく必要がある。
●部分をクリックすると、ブレークポイントを設置できる。
VBA の場合と同様、ブレークポイントまで実行したうえで、イミディエイトウィンドウを開き、
各種変数の値等を参照することができる。
TextBox1.Text の内容が Double 型に変換できないことが原因であるとわかる。
その他、Step 実行(1 行ずつ実行)等も活用しながら、バグの原因を見つけることとなる。
なお、プロジェクト→プロパティのコンパイルオプション「ビルド出力パス」で、exe 実行ファイ
ルを任意に指定できる。
当然、一度ビルドしたプログラムは、毎回統合開発環境を起動せずとも、exe ファイル実行で起動
できる。
型の変換、ファイル入出力等、その他の補足情報
●型の変換
文字列型(String 型)の”10”を、整数型の 10 に変換する等、型の変換が必要になることは多い。
主な変換の例を以下に示す。
変換
記述方法
例
Str = i.ToString()
Integer 型→String 型
変数.ToString()
Str = d.ToString()
Double 型→String 型
変数.ToString()
i = convert.ToInt32(str)
String 型→Integer 型
Convert.ToInt32(変数)
d = convert.ToDouble(str)
String 型→Double 型
Convert.ToDouble(変数)
d = convert.ToDouble(i)
Integer 型→Double 型
Convert.ToDouble(変数)
i = convert.ToInt32(d)
Double 型→Integer 型
Convert.ToInt32(変数)
当然、”ABC”といったような文字列は数値型に変換できない(実行時にエラーとなる)
。数値に変
換可能かどうかを事前に調べる関数として、IsNumber(文字列型)関数がある(戻り値は Boolean 型。
If IsNumber(str) = TRUE Then のように使う)が、”¥1,000”といった「日常的」な表記や、”10e2”
のような指数表現と呼ばれる特有の文字列も数字とみなすので、処理によっては注意する必要がある。
素朴な意味で「数値かどうか」を知りたい場合は、ネットを探せばいくつかコードを見つけることが
できる。
また、2.2 等の小数を安易に整数型に変換すると、思わぬバグにつながる恐れがあるため、明示的
に四捨五入・切捨て等の処理を行うことを推奨する。
●VB における値型と参照型、関数への値渡しと参照渡し
データ型(変数の型)には、値型(Value Type)と参照型(Reference Type)の 2 通りが存在する。
値型とは、データがメモリ上に作られ、そのデータ自体を保存する型である。
参照型とは、データがメモリ上に作られたうえで、そのメモリ上の位置(アドレス)を保存する型
である。
値型の例
Integer 型,Double 型,Decimal 型,Boolean 型
参照型の例、
String、オブジェクト型、配列型、クラス
※C 言語で言えば、参照型は「ポインタ」で管理されるものである。
関数の引数を受け渡す方法として、値渡し(ByVal)と参照渡し(ByRef)の 2 通りが存在する。
値型のデータを値渡しで受け取った場合、受け取った側はメモリ上に新たに値型のデータ領域を作
成し、内容をコピーし、当該データ領域に対して操作が行われる。したがって、関数側でその内容を
変更しても、関数から戻った後には元の値は変更されない。
値型のデータを関数側で変化させたい場合、参照渡しで渡すと良い。受け取った側の関数は、その
データをコピーするのではなく、メモリ上の同じ位置を参照する。したがって、変更を加えると、元
のデータそのものが変更される。
一方で、参照型のデータの場合、変更を加えた場合には関数から戻った後にも変更が有効になる。
String 型だけは特例で、ByVal で受け渡すと変更が無効、ByRef で渡すと変更が有効になるように動
作する(仕様)
。ただし、参照渡しの場合には、オブジェクトそのものを変更することが可能となる
等、詳細については動作が異なり、専門的になるので割愛する。
このあたりは一定程度言語に慣れないと使い分けが難しいので、最初は次のように覚えておくと良
い。
・Integer 型や Double 型、String 型などは、ByVal で渡せば関数での変更が反映されない。
・ByRef で渡せば変更が反映される。
・それ以外の型(配列も含むので注意)は、関数での変更が反映される。
●多次元配列を引数にする場合の受け渡し
多次元配列を引数とした関数の記述は、VBA と VB.NET に違いがあるので注意する。
他にも、細かい点で言語仕様が異なるので注意する。
※筆者自身も時々間違える……その場で「ググる」かヘルプを見る!
VB.NET での多次元配列の受け渡し例
(※参考)VBA での多次元配列の受け渡し例
その他にも VB.NET と VBA には細かな相違点があるため、注意すること。
●ファイル入出力(VB.NET の場合)
本講義では、入力には TextBox やエクセルのセル、出力には MsgBox やエクセルのセルを用いて
いたが、実際に研究等で大量データ処理を行う場合には、ファイル入出力が必要になる場合が多い。
そこで、テキスト形式のファイルの入出力を行うための簡単な例を示しておく。基本的にはこの程
度の入出力ができれば、シミュレーション等で困ることはない。
詳細については、インターネット等に情報があるため、各自で調べること。
Imports System
Imports System.IO
Imports System.Text
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
Button1.Click
'読み込み用ファイルと書き込み用ファイルを開き、読み込み用ファイルから行ずつ
'カンマ区切りのデータを読み込む。その全てのデータを倍し、カンマ区切りで書き込む。
'エンコードはShift-JIS としている。
'なお、データに数字以外が含まれていた場合はエラーとなる。
'また、読み込みファイルや書き込みファイルが排他的に開かれている場合もエラーとなる。
'(必要があればエラー処理を入れること)
'ファイルを開く処理。
Dim reader As New StreamReader("D:\inp.csv", Encoding.GetEncoding("Shift_JIS"))
Dim writer As New StreamWriter("D:\out.csv", False, Encoding.GetEncoding("Shift_JIS"))
Try
'読み込めるデータがファイルに残っている間、処理を続ける
Do While reader.Peek() >= 0
'読み込んだ文字列を格納する変数
Dim inpData As String
inpData = reader.ReadLine()
„――――――――――実際の各種処理はここから記述すればよい。――――――――――
'空白行でなければ、処理を行う
If inpData.Length > 0 Then
'読み込んだ文字列を","で区切って、strSplit()の文字列型配列に代入
Dim strSplit() As String = inpData.Split(","c)
'Double型に変換するための配列変数
Dim doubleSplit(strSplit.Length) As Double
'strSplitの各要素を、Convert.ToDoubleでDouble型に変換
For i As Integer = 0 To strSplit.Length - 1
doubleSplit(i) = Convert.ToDouble(strSplit(i).Trim)
'各々の要素を倍する
doubleSplit(i) = doubleSplit(i) * 2
Next
'文字列を連結するために便利な「StringBuilder」クラスを利用する
Dim strBuilder As New StringBuilder
'各々の要素を","で連結していく。
For i As Integer = 0 To strSplit.Length - 1
strBuilder.Append(doubleSplit(i).ToString)
'最後のデータの後ろ以外のみ、","をつける
If i < strSplit.Length - 1 Then
strBuilder.Append(","c)
End If
Next
'書き込むための文字列
Dim outData As String
'StringBuilderで連結した文字列をつの文字列にする
outData = strBuilder.ToString
„――――――――――実際の各種処理はここまで記述すればよい。――――――――――
'実際にファイルに書き込む
writer.WriteLine(outData)
Else
'空白行の場合は、対応する空白行を出力する
writer.WriteLine("")
End If
Loop
'処理の途中で例外が発生した場合
Catch ex As Exception
Finally
'入力ファイルが正しく開かれていれば、閉じる
If Not reader Is Nothing Then
reader.Close()
End If
'出力ファイルが正しく開かれていれば、閉じる
If Not writer Is Nothing Then
writer.Close()
End If
End Try
End Sub
End Class
●Imports
プログラム上部に
Imports System.IO
Imports System.Text
などと記述されている場合がある。
これらは、プロジェクト内で参照しているクラスライブラリ(さまざまなクラスや関数が入ったも
の)等にアクセスする際に、名前空間の指定を簡略化するための記述である。
例えば、絶対値を求める Abs()関数は、本来は System.Math.Abs()という名前で呼び出す必要があ
るが、毎回この記述をするのは手間である。そこで、最初に Imports System.Math としておくこと
で、前半部分を省略して「Abs()」と書くことができるようになる。
・なぜ最初からすべてが Imports された状態ではないのか?
クラスライブラリ等には様々なものが存在し、自作したクラスライブラリを含めると、関数等の名
前が被ってしまう場合がある。他方で、あらゆるクラスライブラリの機能を用いるプログラムは通常
存在しない。そこで、必要に応じて Imports させる方法が採られる。
仮に同じ名前のクラスが株ってしまった場合は、Imports していても、System.Math.Abs のよう
に「正確に」指定しなければならない。
Fly UP