*****VBちょっといい話***** 2002/09/01 版 ☆☆☆はじめに☆☆☆   ここで紹介する内容は、VB6を基準にしています。   その他のバージョンでは当てはまらないこともあります。   元ネタは、様々な書籍・雑誌・自分の経験等です。   そこから、使えそうな内容を紹介しています。   ちなみに、自分でメモしていた内容を切り貼りして作っていますので、   言葉使いがおかしい所も多々ありますが、ご了承ください。   (「です」「である」が混在している等)   また、ここで紹介する内容が必ず正しいとは限りません。   もしかしたら何処かに間違いがあるかもしれませんので、ご了承下さい。   また、「ここが間違ってるよ」「その説明はおかしいよ」というご指摘があれば   メールにてお知らせ下さいませ。m(_ _)m ☆☆☆☆☆☆☆☆☆☆ −−−−−−−規約−−−−−−− <コントロール・オブジェクト名の付け方> コントロールの名前は、デフォルトでは「Text1」とか「Command1」というようになっています。 ですが、使用するコントロールが増えてくると、このような名前では後々ソースを読む際に 苦労する事になるでしょう。 そこで、テキストボックスならば、「Text1」ではなく「txtName」とか「txtAddress」 というように、最初の3文字をコントロールの種類を示す識別子として「txt」とし、 残りは機能を表す文字を付ける事を勧めます。(「名前」を書く欄なら「txtName」等) また、4文字目は大文字で入力しておくと便利です。 というのは、VBでは、宣言された変数やオブジェクト名は、自動的に大文字小文字を 変換してくれますので、上記の様に名前を付けておき、コードを入力する時は全て 小文字で行うようにしていると、「txtname」と入力して(TabやEnterで)確定すると 「txtName」と変換してくれます。 こうしておくと、スペルを間違った時等に気付き易くなります。 (変換されない時はスペルミスという事ですからね) <変数名の付け方> 同じように変数の名前も規則を決めて、それに従っておいた方がいいでしょう。 私は主に、以下の様にしています。 ・プロシージャ内のローカル変数(宣言したプロシージャ内でのみ有効な変数) MaxScore Average Name のように、最初の1文字目を大文字にする。(途中でも、意味合いによって大文字を使用) ・標準モジュールなら intMaxScore(Integer 型) lngAverage (Long 型) strName (String 型) のように、最初の3文字を識別子とし、4文字目からはローカルと同じようにする。 ・フォームの共通変数(General)なら fintMaxScore or intfMaxScore(Integer 型) flngAverage or lngfAverage (Long 型) fstrName or strfName (String 型) のように、3文字の識別子に加えて、フォームを示す「f」を始めに付け、 4文字目からはローカルと同じようにする。 こうしておくと、グローバル変数かどうかの判断もしやすい。 また、ローカル変数はすぐに型を調べる事ができるので、型名を付けなくても 良いと考えた。 また、ユーザー定義関数の名前の先頭に「u」をつけておき、 他の人が見た時に、すぐ区別できるようにしておくといった手法も考えられる。 注意)あくまで私が付けた規則ですので、何処でもこうしている訳ではありません。 ちなみに、VBのヘルプでは「プリフィックス」は以下のようになっていました。 変数の適用範囲による例 グローバル g モジュール・レベル m プロシージャー内 つけない <変数宣言はきちんと書く> 変数の宣言をまとめて Dim aaa , bbb , ccc As Integer のように書いてしまうと、aaa , bbb はバリアント、ccc のみ整数型となるので Dim aaa As Integer, bbb As Integer, ccc As Integer というように書く必要があります。 −−−−−−−操作・機能−−−−−−− <プログラムを強制的に終了する> プログラムを実行した時に、無限ループにはまってしまう事があります。 こうなると、DoEvents をループ中に入れていなければ、 VB上の終了(プログラムの停止)ボタンも押せなくなります。 かといって、Ctrl+Alt+Delete で強制終了すると、 ソースを保存していなければ、その分が消えてしまいます。 こんな時には、以下の操作でプログラムを終了できます。 Ctrl+Break (CTRL+STOP) <ショートカットキー> サンプルプログラム等を見ていくうちに、分からない命令や関数が出てくることがよくあります。 この時、調べたい関数名の上にカーソル(マウスポインタではない)を置くか、 関数名を範囲指定して F1キーを押すと、その関数のヘルプが表示されます。 (事前にヘルプファイルをインストールしておく必要有り) また、自作の関数であるならば、F1キーではなく、Shiftキーを押しながら、F2キーを押せば、 自作関数を定義した場所にジャンプします。 他にも、 F5キー「プログラムの実行」 F8キー「プログラムを1行ずつ実行」 F9キー「ブレークポイントの設定」 といった使い方があります。 <入力候補機能> 例)「mstr」と入力した後で、Ctrl+Spaceを押すと、   mstr で始まる変数の一覧が表示される。   表示後は押すキーによって、候補を表示した後の動作が変わる  Tab   カーソルが一番後ろへ移動する  Enter 改行される  Space 空白があく <複数行をまとめてコメントアウト> コメントアウトとは、一時的に実行したくない行をコメントにして、 その部分を実行しないようにする事です。 VBでは、「'」を入力すれば、以降の文はコメント扱いになります。 ですが、一度にたくさんの行をコメントアウトする時は、この操作は面倒です。 そこで、この操作を簡単に行う方法をご紹介しましょう。 メニューバーの、「表示」→「ツールバー」から、「編集」にチェックを入れると、 編集ツールバーが画面に表示されます。そこに、 「コメントブロック」「非コメントブロック」というボタンがあります。 行を選択した状態でこれらのボタンをクリックすると、 コメントアウトの設定・解除が簡単に行えます。 <簡単デバッグ> F8キーを使って、プログラムのデバッグを行う際に、現在の変数の値や、 関数を実行したときの値を知りたくなる事は多々あります。 この時(トレース中)に、変数などにマウスを合わせて少し待つと、変数の中身が表示されます。 同じように、関数を範囲指定して、マウスポインタを合わせれば、関数の実行結果が出てきます。 この時に、右クリックして「ウォッチ式の追加」を行うと、何度も同じ変数・関数を 調べたい時に便利になります。 また、デバッグ(1行ずつ実行)中、カーソルを任意の場所に置き、Ctrl+F9キーを 押すと、その場所に制御が移ります。(強制ジャンプ) ただし、無理矢理ジャンプするわけですから、本来の制御ならあり得ない状態 を作り出す事もできてしまうので、あまりおすすめできません。 <複雑なデバッグ> MsgBox 変数名 Debug.Print 変数名 などというデバッグ用のコードをプログラムに埋め込んでおき、変数の値を確認します。 ただし、デバッグが終了してユーザーに配布する時には、これらを全て無効化しておく 必要があるので、これを簡単に行う為に、以下のようにしておくとよいでしょう。 ・標準モジュール Public Const ctDEBUG = True ・各デバッグ処理 If ctDEBUG Then Debug.Print 変数名 End If コンパイル時には、ctDEBUG = True → False にしておけばOKです。 <タブの順番を簡単に設定する> 1.全てのコントロールを作成する 2.割り当てたい順とは逆に設定していく 3.TabIndex プロパティを「0」にする これで、タブの順番が自動的に揃います。 要するに、手順としては、 最後のコントロールをクリックして、画面右のプロパティウィンドウから TabIndexをクリックし、0を入力する。 次に、最後から2番目のコントロールをクリックして、0を入力する。 次に、最後から3番目のコントロールをクリックして、0を入力する。 ・・・以下同様に (最後から2番目のコントロールからは、プロパティウィンドウから  いちいちTabIndexの部分をクリックしなくてもOK) −−−−−−−内部動作・イベント−−−−−−− <フォーカスを持てるのはどんな時?> 以下の2つの条件を満たしている必要があります。 ・そのコントロールが、ユーザーに見えている(Visible=True) ・そのコントロールが、イベントを受け取れる(Enabled=True) <マウスのイベントが起きる順番> 1.クリック時 MouseDown→MouseUp→Click→MouseMove 2.ダブルクリック時 MouseDown→MouseUp→Click→MouseMove→DblClick→MouseUp→MouseMove <フォームのイベントが起きる順番> 起動時 1.Initialize フォームオブジェクトを作成する・コードとプロパティを持つ 2.Load ウィンドウとコントロールを作成し、ハンドルを持つ 3.Resize フォームが表示直前になる 4.Activate フォームをアクティブにする 5.Paint フォームを描画する 終了時(End で終了すると何のイベントも発生しない) 1.QueryUnload Unload直前で、Unloadの原因を示す 2.Unload Unload直前で、キャンセルできる 3.Terminate メモリーを解放した 以上のイベントを発生させたい時は、End ステートメントを使わず Unload ステートメントを使用する。 <フォーム関連のステートメント・メソッド> Load フォームをメモリに読み込むステートメント Unload フォームをメモリから削除するステートメント Show フォームを表示するメソッド Hide フォームを非表示にするメソッド 以下の図のような関連になる。 +--------------+ Load +--------------+ Show +--------------+ | |----------->| |----------->| | | HDD | | メモリ | | 画面 | | |<-----------| |<-----------| | +--------------+ Unload +--------------+ Hide +--------------+ つまり、フォームを使用するためには、以下のように記述する必要があります。 Load Form1 Form1.Show ・・・フォーム毎の処理・・・ Form1.Hide Unload Form1 ですが、Load されていない状態で Show メソッドが実行されると、自動的に Load してくれますし、 Hide されていない状態で、Unload ステートメントが実行されると、Hide してくれるので、 実際には、以下のように記述すればよいわけです。 Form1.Show ・・・フォーム毎の処理・・・ Unload Form1 また、よく使うフォームは、メモリから Unload してしまわずに、Hide で非表示にしておくと、 次回の表示が早くなります。 ただし、この場合は、再表示の際 Form_Load イベントが発生しないので、 初期化処理等には注意する必要があります。 <イベントの再入を防ごう> イベントの再入とは、関数の再帰呼び出しのようなもので、 あるイベントの中で、そのイベント自身を呼び出す事をいいます。 例えば、From_Resizeイベントの中に Width や Height を変更するような処理を書くと、 Width を変更する処理を実行した時点で、また、FormResize イベントを呼び出してしまします。 ですから、フラグを等使ってこれを防いであげましょう。 <入力内容のチェック> テキストボックス等にデータを入力したかどうかをチェックするのに、 LostFocus イベントを使う方法が考えられます。 1.コントロールがフォーカスを失った時に、データが入力されているかどうかをチェック 2.データが入力されていなければ、そのコントロールにフォーカスを移す というような処理を使っている方も多いと思います。 しかし、この場合は不具合が起きる可能性があります。 例えば、2つのコントロールA,Bがあり、これらの LostFocus に 先ほどのチェックのコードが記述されているとします。 そして、Aに何も入力せず、Tabキー等を押して、Bに移ろうとするとどうなるでしょう? フォーカスが、AとBの間を延々と移動し続け、強制終了するしかなくなります。 なぜこんなことになったのでしょうか? これは、LostFocus イベントが、フォーカスが次に移った後で起きる事が原因です。 先ほどの例を、順を追って説明します。 AからBにフォーカスが移った時点でチェックを行い、データが入力されていないため Aにフォーカスを移します。しかし、当然この時点では、Bもデータが入力されていません。 ですから、Bのチェックが働き、Bにフォーカスを移します。 そうしたら、Aもデータが入力されていないので・・・・・・以下省略 という事が起きてしまったわけですね。 そこで、VB6から追加された、Validateイベントを使用してみましょう。 このイベントは、フォーカスが移る直前に起きるので、先ほどの不具合を回避できます。 ただし、移動先のコントロールのCauseValidationプロパティをFalseにしていると起きません。 また、Alt+F4等でフォームを閉じられるとイベントが発生しないので、こんな時は フォームのValidateControlsメソッドを使ってフォーム上の全てのコントロールに 強制的にValidateイベントを起こしてやります。 これは、全ての項目を入力し終わった後の、最終チェックにも有効です。 −−−−−−−プロパティ−−−−−−− <Locked プロパティとEnabled プロパティ> ・Locked プロパティ テキスト ボックス (TextBox) コントロールの場合、コントロール内のテキストの スクロールと強調表示は可能ですが、変更はできません。 ・Enabled プロパティ フォームやコントロールに、ユーザーの操作で発生したイベントを 認識させるかどうかを設定します。 上記の2つの大きな違いは、フォーカスをセットできるかどうか、 中の文字列などを範囲指定したり、コピーしたりできるかどうかという点です。 どちらも、中の文字列を変更する事はできません。 <フォームを画面の中央に表示> StartUpPositionプロパティを使用するとよい。 <より軽量なコントロールを使う> 単純な画像表示ならPictureBoxコントロールよりも、 Imageコントロールを使い、 単純な文字表示なら、テキストボックスよりもラベルを使用する方が プログラムが軽くなります。 (プロパティ・メソッド・イベントの少ない軽量なコントロールを使う) −−−−−−−概念−−−−−−− <関数の改良> 関数を作った後でバグの修正や機能の追加をした場合、基本的にインターフェース部分 (引数や戻り値の形式)は変更してはいけません。 なぜなら、関数の使用方法まで変更してしまうと、その関数を使用している全てのプログラムに 影響を与えてしまうからです。 もし、安易にインターフェースを変更してしまうと、 バージョンを上げると動かなくなった、エラーが出た・・・といった事になります。 そこで、関数を修正した結果、不要な引数等が出てきた場合でも、その引数を残しておく という事があります。(作ったばかりの関数の修正ならば、話は別) <APIをVBから使用する際の注意> APIとは、「アプリケーション プログラミング インターフェース」の略です。 意味は「OSの持っている機能を呼び出して使うためのもの」となります。 VBでAPI(ウィンドウズのAPI)を使用する意義は、 「VBの標準機能ではできない事をする」 「高速化を行う」 といったところでしょうか? 今回は「高速化」については触れません。 「VBの標準機能でできない事をする時に使う」という前提で話を進めます。 例えば、メモリの内容を直に読みとったり、書き込んだりするといった場合です。 こういった場合はAPIを使わざるを得ません。 しかし、そうでないなら(VBの標準機能でできる事なら)、 APIを使わない方が良いと言えるでしょう。 なぜなら、プログラムが複雑になり、バグの入り込む可能性が高くなるからです。 次に、使わなくてはならない場合の注意点を述べておきます。 元々VBでは、APIの使用は正式にサポートされていません。 そのため使用には、以下のような問題があります。 1.日本語のヘルプが標準で付属していない    書籍などで別途調べる必要がある。(最低でも5000円以上) 2.文字コードの違い(UniCode,ANSI)・・・バイト数が異なる等 3.C言語から使う事を前提に書かれている    ・文字列は固定長の配列に格納    ・文字列の最後にはヌル文字が入る 特に3番が問題で、C言語とVBの差をプログラマ側で吸収してやる必要があります。 主な注意点は上記の通りですが、これ以外にもいくつかの問題を抱えています。 <定数を使おう> 定数とは「プログラム中で値の変更をしない数」の事です。 逆に、値が変更されるものは変数と呼びます。 よくある例として円周率の話をしてみましょう。 円周率は一般的に3.14として知られています。 円の面積を計算する場合、以下のような計算式を書きますよね。 Menseki = Hankei * Hankei * 3.14 勿論、これはこれで正解です。 しかし、少し計算の精度を上げようとするなら、どうすればいいでしょう? 円周率は3.1415926535897・・・・・・ と続く無限小数です。(この値が正確かは不明) ですから、先ほどの計算式を以下のように書き換えれば精度は上がります。 Menseki = Hankei * Hankei * 3.1415926 とはいえ、これはあまり効率的ではありません。 というのも、精度をちょくちょく変更したいという場合、プログラム中に記述した 3.14という数字を全て変更してやらないといけないからです。 では、この不都合を避けるために変数を使用してみましょう。 Dim Pai As Double Pai = 3.14 Menseki = Hankei * Hankei * Pai としておけば、精度を上げるときには、変数Paiを変更するだけですみますね。 さらに、直に数字を3.14と記述するよりも、それが何を示す数値なのかが 分かりやすくなっています。(「Pai」とあれば円周率を思い浮かべますよね) まあ、円周率はあまりにもメジャーなので、3.14でも分かるかもしれませんが・・・ おや?じゃあ定数なんて使う必要があるのか!? という事なんですが、定数にはそれなりの利点があります。 定数は、例えば以下のように使用します。 Const PAI As Double = 3.14 Menseki = Hankei * Hankei * PAI あまり違いが分からないかもしれません。 しかし、このように書いておくと、変数を使ったときよりも速く実行できます。 (このぐらいでは、ほとんど差は出ませんが) というのも、定数はコンパイル前に変換されるので、数字を直に書いた場合と 動作速度は変わらないのです。 つまり、上の例の1番目と3番目の動作速度は同じになるという事ですね。 一方、2番目の例では変数を利用しているため、メモリの確保や値の代入が プログラムの実行中に行われます。 じゃあ必ず定数を使った方がいいかというと、そうでもありません。 定数はプログラム実行中に値の変更ができないので、上の例で「計算の精度を毎回指定する」 というような要求事項があった場合は、変数を使う必要があります。 まあ、ケース・バイ・ケースという所ですね。 皆さんも、今まで数字を直にかいている所がありましたら、定数を試してみて下さい。 <列挙型を使おう> 数値型の定数やフラグには列挙型を使用すると便利です。 フラグの例として、入出力の処理モード(3パターン)を示してみます。 Public Enum enmShori enmBrouse enmAddNew enmEdit End Enum と、宣言しておいて、フォームモジュール内で以下のように列挙型変数を宣言します。 Dim menmShori As enmShori こうすると、分かりやすい上、コーディングの際に自動メンバ表示機能が働くので 入力も速くなるでしょう。 例)enmShori = ←と書いた時点で、3つの候補が一覧に表示される。 また、1週間を表すために使うといった事も考えられます。 例)ある店の1週間の開店日数 Const lngWeekdayMax As Long = 6 Enum enmWeekday Monday = 1 Tuesday = 2 Wednesday = 3 Thursday = 4 Friday = 5 Saturday = 6 End Enum Dim menmWeekday As enmWeekday Dim acurUriage(lngWeekdayMax) As Long と宣言しておくと、以下のような記述が可能になり、見やすくなります。 矢印の上の例と、下の例を比べて見ましょう。 For i = 1 To 6 acurUriage(i) = ... Next ↓ For menmWeekday = Monday To Saturday acurUriage(menmWeekday) = ... Next Print acurUriage(5) ↓ Print acurUriage(Friday) <Withステートメントを使おう> あるコントロールについて、複数のプロパティを設定したい時、 毎回コントロール名を書くのは面倒なので、Withを使って設定してみましょう。 例) CommonDialog1.DialogTitle = "開きたいファイルの選択" CommonDialog1.FilterIndex = 2 CommonDialog1.FileName = ""   ・   ・   ・ とするよりも、以下のように書く方がすっきりする。 With CommonDialog1 .DialogTitle = "開きたいファイルの選択" .Filter = "テキストファイル(*.txt)|*.txt|" & _ "すべてのファイル(*.*)|*.*" .FilterIndex = 2 .FileName = "" .Flags = cdlOFNHideReadOnly End With <複数の戻り値> 関数は通常1つの戻り値しか持つことができません。 しかし、場合によっては戻り値を複数持ちたい時があります。 例えば、エラーかどうかの判断と、関数の実行結果の両方を知りたい場合です。 戻り値が1つしかなかった場合で考えてみます。 エラーを「0」、正常終了を「1」と定義します。 そして、関数の実行結果は「0〜9」までの数値だとします。 この時、戻り値が「2〜9」の場合は問題ありません。 関数は成功し、実行結果が返されたという事です。 ですが、「0」「1」の時はどうでしょう? これだけでは、エラー情報を表しているのか、実行結果を表しているのかが判断できません。 そこで、こういう場合には、実行結果を引数に格納して返します。 具体的には、実行結果を返したい引数を「ByRef」(省略時は「ByRef」となる)で定義します。 以下に使用例を記述します。 Function TestFunc(ByVal Data As Long, ByRef Modori As Long) As Long という関数があったとします。 与えられた数値を何らかの形に変換して返し、エラー時「0」、成功時「0以外」を返します。 呼び出し側では以下のように記述します。 Dim A As Long Dim B As Long Dim C As Long C = TestFunc(A ,B) これで、エラーかどうかの判断(通常の戻り値)は、変数「C」に、 実行結果は、変数「B」に格納されます。 つまり、「ByRef」とは、C言語でいう所のポインタに相当します。 変数の値を引数として渡すのではなく、変数のアドレスを渡しているのです。 従って、引数を受け取った関数の側でその変数の値が変更されると、 呼び出し側の変数の値も変更されるというわけです。 <引数のチェック> 引数が入力されているかどうかは、実行時のエラーメッセージで判別できますが、 指定された引数の中身までは分かりませんので、できれば 引数が空でないかのチェックをしておきます。 エラーの例 Func(a, ,c) 「引数は省略できません」というエラーが出る。 b = "" Func(a,b,c) エラーは出ない。 <Nullかどうかをチェックする> ある変数が Null であるかどうかのチェックには、IsNull 関数を使用します。 では、以下のように書いてはいけないのでしょうか? If a = Null Then 実はこのように記述すると、この If 文は常に偽となり実行されません。 というのも、「=」等の演算子のオペランドに Null を与えると、 その結果は必ず Null となるからです。 そして、Null は偽(False)であると判断されるので、この If 文の中身は実行されません。 <For文の終了条件に気を付ける> For 文を書くときに、以下のような記述をすると、何回繰り返しが行われるでしょうか? j = 10 For i = 0 To j j = j - 1 Next i 1回ループする毎に、終了条件である、jの値が1ずつ減っていきますので、 6回繰り返されると考えられますね。(終了時・・・i=6,j=4) しかし、この繰り返しは11回実行されてしまいます。(終了時・・・i=11,j=−1) これは、判定条件である、jが、開始時点の「10」に固定されてしまっているからです。 (C言語ならこうはなりません) ですから、上記のような条件を書きたい場合は、While 文を使って書き直す必要があります。 (あるいは、条件を変更するか) <リストボックスの内容を削除の際の注意> リストボックスの内容を削除する時は、後の要素から消してきます。 前から消していくと、番号が1つずつ上にずれてしまい、うまく動作しません。 例) 1.AAA 2.BBB 3.CCC というリストボックスを1,2,3という順に消そうとすると、 1を消した時点で 1.BBB 2.CCC となり、2,3の項目が、1,2にずれ込みます。 同様に、2.を消すと 1.BBB となり、この状態で、3.を消そうとすると、エラーになってしまいます。 <プロパティの参照速度> コントロールのプロパティを参照するより、変数を参照、書き換えする方が早いので 何度もプロパティの値を変更する場合(ループ等)は、いったん変数に値を移して それを変更した方が良い時があります。(10倍以上速度が変わることもある) For i = 1 To 1000 s = s & Text1.Text Next i こうするよりも、以下のほうが速くなります。 tmp = Text1.Text For i = 1 To 1000 s = s & tmp Next i <浮動小数点の誤差に注意> Single型や、Double型で宣言した変数を次のように比較してはいけません。 If sngResult = sngSomeValue 代わりに、 If Abs(sngResult - sngSomeValue) < MinDifference としておきます。 MinDifferenceは、許容できる誤差を設定 具体的には、 Single型なら、smgSomeValueに10の−6乗をかけた値 Double型なら、10の−16乗を・・・ 例) Private Sub cmdCalc_Click() Dim i As Long Dim sngResult As Single For i = 1 To 100000 sngResult = sngResult + 0.1 Next i MsgBox "0.1を10万回足した結果は" & vbCrLf & sngResult End Sub この結果は普通10000になると思いますだが、実際には9998.557に なってしまいます。 <UnLoadしたフォームのプロパティにアクセスできる!?> UnLoadしたフォームのプロパティを呼び出すことができてしまいますが、 これは、その度に自動的にフォームをLoadしてしまっているからです。 また、このようにロードされてしまったフォームは、うまくUnLoadできない 事があるので、極力このような処理は避けましょう!! <Endは使ってもよいか?> Endステートメントを使うと、プログラムは強制的に終了します。 作成されたオブジェクトは破棄され、開かれたファイルは閉じられ、 全てのメモリーは開放されます。 普通なら、これで問題ないように思われます。ただし、 この際には、フォームのUnload,QueryUnload,Terminateイベントが実行されないので、 全てのフォームをアンロードすることによって終了した方がよいでしょう。 特に、QueryUnloadには、終了確認のメッセージや、各種変数の保存など プログラムの終了処理を書くことが多いので、ここをパスするのは望ましくありません。 −−−−−−−プログラミング・テクニック−−−−−−− <印刷と印刷プレビューは、同じプログラムにできる> 印刷も、印刷プレビューも印刷する対象が異なるだけで、 処理内容はほぼ同じなので、別々に書くのは非効率的である。 (1)別々に書いた場合 '印刷プレビュー Form1.Print "Hello" '印刷 Printer.Print "Hello" Printer.EndDoc (2)両方扱うサブルーチンを作成した場合 Public Sub DoPrint(obj As Object) obj.Print "Hello" if obj Is Printer Then obj.EndDoc End If End Sub '印刷プレビュー DoPrint(Form1) '印刷 DoPrint(Printer) <コマンドライン引数(関連付け)> ファイルを関連付けして、そのプログラムから開きたいような場合は以下のように記述する。 (エクスプローラ等から、ファイルをダブルクリックすると目的のプログラムが起動する) 例) Private Sub Form_Load() 'コマンドライン引数が指定されていれば、 'そのファイルをオープンするイベントを呼び出す If Command <> "" Then 'FileOpen というサブルーチンを呼び出す(引数は、Command) 'このサブルーチンは別途作成しておく事 FileOpen Command End If End Sub <フォーム間の変数の受け渡しに、標準モジュールを使用しないための方法> 通常、フォームをまたがって値を受け渡ししたい場合には、 標準モジュールに変数を宣言することになります。 ただし、このように宣言された変数は、何処からでも利用できるため、 思わぬところからアクセスされる等の、トラブルを引き起こす事があります。 そこで、受け渡したい値が少ない場合には、その変数をフォームのプロパティ として宣言することにより、受け渡しが可能になります。 例)frmMain から、frmPrint にユーザー名を渡したい時 frmMain のGlobal 宣言部(共通変数よりは下に書く) Public Property Get PID() As Long PID = ProcessID End Property frmPrint の Load イベントプロシージャ Dim ProcessID As Long ProcessID = frmProcessSelect.PID <動的配列を使って、要素を1度に他の配列にコピー> 代入先の配列が、動的配列であれば、要素を1度にコピーすることができます。 また、代入先と代入元の配列の構造が同じ場合にも可能です。 Dim a() As String Dim b() As String For i=0 To 100 A(i)="Z" Next i b = a <Array関数で配列を初期化する> 配列を初期化する時に、1要素ずつループを回して初期化するのは 面倒なので、Array関数でまとめて行う事ができます。 例) Dim i As Long Dim Hairetu As Variant Hairetu = Array("AAA","BBB","CCC","DDD","EEE") For i = 0 To UBound(Hairetu) ... Next i <巨大なメモリーは動的に確保する> 例えば、サイズの不明なファイルを開くときのバッファを宣言するとき、 Dim bytFileBuf(1000000) As Byte として、1MBの配列を宣言してしまうと メモリー解放までの間ずっと1MBを占有してしまう。 実際には、以下のようにして 1.Dim ステートメントで配列のサイズ指定を省略し、動的配列として宣言する。 2.必要なサイズが分かった時点で、ReDim ステートメントでメモリーを確保する。 3.メモリーが不要になったら、Erase ステートメントで解放する。 例) Dim bytFileBuf() As Byte Private Function ReadFile(strFileName As String) As Boolean On Error GoTo ErrHandler ・ ・ blnOpen = True ReDim bytFileBuf(LOF(intFileNumber)) Get #intFileNumber, , bytFileBuf ・ ・ ErrorHandler: if blnOpen Then Close #intFileNumber Erase bytFileBuf ReadFile = False End Function <リストボックスにすばやく項目を追加する> 一旦、リストボックスを非表示にしておきます。(いちいち再表示されるので) 項目が多い時などに有効です。 また、フォームロード時などに使っても良いでしょう。 <構造体のソート> 単純配列のソートならともかく、構造体のソートでは、 項目が10個あったとすれば、単純に10倍の計算が必要になります。 そこで、 Type Person strName As String '氏名 strTel As String '電話番号 dtBirthday As Date '生年月日 lngNextPerson As Long '次のデータの要素番号 End Type というように宣言しておき、 lngNextPerson に対して並べ替えを行えば簡単に済みます。 <関数内で小数の計算は行わない> 関数内で小数の計算を行うと、期待した結果を返さない事があります。 例えば、以下のプログラムを実行すると、「5」と表示されるはずですが、 実際には「4」と表示されます。 MsgBox Int(5.1 - 0.1) これを防ぐためには、以下のように計算結果を一旦変数に格納し、 これを関数の引数に使います。 a = 5.1 - 0.1 MsgBox Int(a) <文字列操作関数には、型宣言文字を付ける> Mid Left Right Formatは、 Mid$ のようにした方が、戻り値を特定できます。 (指定しない場合はバリアント型になる) aaa = Mid$ (bbb,1,3) とすれば、aaa は、文字列型($)になる。 <数値データの型を明示的に決定する(型宣言文字の使用)> プログラム中に直接数値を記述する時に、そのサイズを決定したい場合は、 数値の後ろに型宣言文字を付けましょう。 例) 500& 長整数(Long)型はアンパサンド (&) 500@ 通貨(Currency)型はアットマーク (@) 各データ型の型宣言文字、MSDNライブラリの検索タブで 「型宣言文字」と入力して検索すると確認できます。 <データ型の変換を省略しない> Dim A As Integer Dim B As Double Dim C As String A = 123 B = 0.123 C = A + B ・・・(1) とすれば、問題なく変数Cに「123.123」が格納されるが、 本来は、(1)の部分は以下のように記述するべきである。 C = CStr(CDbl(A) + B) <32ビットCPUでは、Long,Double型を使う> Integer は、Long に、Single は、Double に内部で変換されてしまうので 初めから Long 等を使っておいた方がその分だけ早くなります。 <整数の割り算は「¥」を使う> 「/」を使うと、Double型に変換されるので、整数ならば「¥」を使いましょう。 <金銭の演算で、Currency以外を使ってみる> Currency型は、丸め誤差の発生を少なくできるのですが、乗除算のスピードが落ちます。 場合によっては、Long,Doubleを使った方がよいでしょう。 <テキストボックスに、文字列を素早く追加する> Text1.Text = Text1.Text & MyString Text1.SelStart = Len(Text1.Text) 上の方法よりも、下の方法の方がメモり使用量も少なく、実行も早い Text1.SelStart = Len(Text1.Text) Text1.SelText = MySring <String型の変数に、文字列を素早く追加する> Dim Moji As String Moji = "ABC" Moji = Moji + "DEF" Moji = Moji + "GHI"   ・   ・   ・ として、3文字ずつ「A」〜「Z」文字列を追加していくような場合、上のようにすると、 毎回必要なメモリが増えていき、その度にメモリを新たに確保するため 効率が良くありません。 そこで、まず必要なだけの大きさのメモリを確保しておき、そこにMid$関数等を 利用して、データを上書きしていく方法があります。 Dim Moji As String Moji = String(26, " ") '26個の空白で、変数を初期化 Mid$(Moji, 1, 3) = "ABC" Mid$(Moji, 4, 3) = "DEF" Mid$(Moji, 7, 3) = "GHI" <オブジェクトは固有のオブジェクト型で宣言する> Dim objXxxx As Object という宣言を行うと、総称オブジェクト型となり、 あらゆるオブジェクトへの参照を行う事ができます。 ただし、実行時に参照先のオブジェクトのメソッドやプロパティを確認するため 実効速度が落ちます(実行時バインディングと呼ぶ)。 Dim txtXxxx As TextBox とすると、固有のオブジェクト型(ここでは、テキストボックス)を参照します。 (事前バインディングと呼ぶ) また、 Dim objXxxx As New clsXxxx という処理も以下のようにします。 Dim objXxxx As clsXxxx set objXxxx = New clsXxxx なお、オブジェクトが不要になったら、必ずNothingで領域を開放しましょう。 <フォルダ内のすべてのファイルについて処理をする> Dir 関数を使用 Dim AnPath , AnFilename As String AnPath = App.Path Do While AnFilename <> "" 'ここにファイルごとに繰り返されるコードを記述 AnFilename = Dir Loop <カレントディレクトリの移動> 独自のDLLを使用するような場合、 フォームロードイベントに以下の行を追加しておくと便利です。 ChDrive Left$(App.Path , 2) ChDir App.Path これによって、カレントディレクトリを、EXEファイルのある場所に移しておけるので、 プログラムが別の場所から実行されたとしても、DLLを見つけだす事ができます。 <選択されているオプションボタンの判定> If optSentaku(0).Value = True Then ElseIf optSentaku(1).Value = True Then ElseIf optSentaku(2).Value = True Then End If とするよりも、 Private Sub optSentaku_Click(Index As Integer) intSentaku = Index End Sub としておき、intSentaku を判定する方が効率的です。 (キーボードで選択されても、クリックイベントは発生するので大丈夫) <Select Case の応用> 以下のように、先に式の値を書いておくことも可能です。 Select Case True Case optA.Value Case optB.Value End Select <文字列の出現回数を求める> Replace 関数は、ある文字列について、指定した文字列を全て別の文字列で 置き換えることができます。これを利用して、 ある文字列が別の文字列の中に何回出てきたかを数える関数を作成します。 Function InstrCount(Source As String, Search As String) As Long InstrCount = Len(Source) - Len(Replace(Source,Search,Mid(Search,2))) End Function 見つかった文字列を1文字短くした別の文字で置き換えることにより、 元の文字列とその結果を引き算した結果が、その文字列の出現回数となるわけです。 <文字配列のセーブ・ロード> Join 関数 選択した区切り記号を使って配列の全項目を1つの文字列にする Split 関数・・・Join 関数と逆の働き 長い文字列を、選択した記号で区切って個々の要素に分割してから その要素を文字配列に読み込む 文字配列の内容をディスクに保存する Sub StringArraySave(Filename As String, Text() As String) Dim f As Integer f = FreeFile Open Filename For Output As #f Print #f,Join(Text,vbCrLf); Close #f End Sub テキストファイルを文字配列に読み込む Function StringArrayLoad(Filename As String) As String() Dim f As Integer f = FreeFile Open Filename For Input As #f StringArrayLoad = Split(Input$(LOF(f),f),vbCrLf) Close #f End Function 使用例 Dim Text() As String Text = StringArrayLoad("c:\autoexec.bat") <文字列に Format 関数を使用> クレジットカードの番号(4桁区切り)の場合 CreditCardNum = Left(x,4) & "" & Mid(x,5,4) & "" & Mid(x,9,4) & "" & Right(x,4) とするよりも CreditCardNum = Format(x."!@@@@ @@@@ @@@@ @@@@") とした方が分かり易いですよね。 <システム情報コントロールを使用して、画面解像度の変化に応じてフォームを最大化> フォームが最大化されているときに、画面をより高解像度に切り替えたとき、 フォームを自動的に画面一杯に最大化する。 Private Sub Sysinfo1_DisplayChanges() 'フォームが最大化されている場合 '元のサイズに戻してから再度最大化する With me If .WindowState = vbMaximaized Then .Visible = False .WindowState = vbNormal .WindowState = vbMaximaized .Visible = True End If End With End Sub <ボタンクリックの後にフォーカスを元の場所に戻す> Private mCtl As Control Private Sub Command1_Click() 'ここに実行したい処理を各 On Error Resume Next mctl.SetFocus End Sub Private Sub Text1_GotFocus() Set mCtl = Text1 End Sub <UNDOボタンを作る> Private Sub Command1_Click() Text1.SetFocus SendKeys "^z" End Sub <キーボードのイベント> 1.KeyPress 押したキーのASCIIコードを返す 2.KeyDown 押したキーのコードを返す 例) KeyPressを使えば、EnterキーをTabキーの代わりにしたり、 入力したキーのASCIIコードを”0”にすることで 入力そのものを無効にすることも可能です。 ・空白を入力不可にする Private Sub Text1_KeyPress(KeyAcsii As Integer) If KeyAscii = Asc(" ") Then KeyAscii = 0 End If End Sub ・Enterキーでフォーカス移動 Private Sub Text1_KeyPress(KeyAcsii As Integer) If KeyAscii = vbKeyReturn Then SendKeys "{TAB}" KeyAscii = 0 End If End Sub <DoEventsを使う時> Do Loop , For Next 等のループ分で処理時間が長いときに Windowsに制御を戻すために使います。 ループ時間が長い場合に、他の操作ができなくなってしまうのを防ぐため。 例)処理をキャンセルボタンで中断したいとき Dim tomaru As Boolean '実行ボタン Private Sub Command1_Click() tomaru = True Command1.Enabled = False keisan End Sub 'キャンセルボタン Private Sub Command2_Click() tomaru = False End Sub Private Function keisan() Dim i As Integer Dim x As Double For i = 1 To 15000 x = x + 10 Text1.Text = x DoEvents ・・・(1) If tomaru = False Then MsgBox "cancel" Exit For End If Next i MsgBox "owari" End Function 注意1 DoEvents関数は実行時間が比較的長いので、(1)の部分は 以下のように改良したほうが良いでしょう。 if i Mod 100 = 0 Then DoEvents End If 注意2 DoEventsを使って、他のアプリケーションに制御を渡した後、 DoEventsに制御が戻る前に、元のプロシージャを呼び出してしまうと システムリソースを使い果たすまで同じプロシージャを呼び出そうとする。 上の例なら、ループ実行中は、実行ボタンを押せないようにしておく必要があります。 <アサーションを使ってエラーチェック> ここでいうアサーションとは、与えた条件が偽の場合にエラーと判断して、 プログラムを停止する仕組み・・・と考えてもらえればOKです。 通常は開発時のみ有効とし、配布時には無効にしておきます。 なお、コンパイルされた実行ファイルには、 アサーションのコード部分は、自動的に含まれなくなります。 例)アサーションを使って、値の正当性を確認   変数xは、1か2のどちらかの値しか取らないものとした時 Select Case x Case 1 'x=1の時の処理 Case 2 'x=2の時の処理 Case Else 'ここには来ないはず Debug.Assert False End Select