更新:
06/07/21
|
|VB、VBAのTIPS| |
SOFTWARE TIPS |
VBA(Excel、Access)
基本事項
出退勤&給与システム(?)モドキをExcel VBA+ADO(Access|mdb)で作っています。
その中で困った点、解決方法などを備忘録の意味も含めて書いていきます。
VBで出来て、VBAで出来ないこと
VBで出来てVBAでは出来ないことっていろいろとあります。
Microsoftは、VBAは一部の機能が使えないが、基本的にVBと同じだ!と言い切っていますが、利用者側からするとなんとも乱暴な言い方に思えてなりません……、そのくらい使い勝手は悪くなります。
1.コントロール配列が使えない
VBでは当たり前のように使っていますが、これを使えないとかなりの痛手です。
コントロールとはテキストボックス、ラベル、リストボックス等々、フォームに貼り付けて使うコンポーネントを広く指します。
コピペすると「同じやつがありますが、配列にしますか?」と聞いてくるあれです。
配列にすると、TextBos(10).text="3"などとして、配列のように扱えるためForNextなどや各種イベントの記述が非常に簡潔に出来るのですが、VBAではそうした手法が利用できないのです。
ではどうするか……私がとっている方法をご紹介します。
VBの場合。
For i=1 to 100
TextBox( i ).text = CStr(i)
Next
などとすれば、テキストボックス100個に1〜100の数字を入力することが出来ます。
これをVBAでやる場合には、最初に大変な思いをしなくてはなりませんが、テキストボックスの名前を
TextBox1、TextBox2、TextBox3……のように手動で採番する必要があります。
そこまで何とか乗り切れば次のように記述できます。
For i=1 to 100
Controls( "TextBox" & Cstr( i ) ).text = CStr(i)
Next
気をつけなくてはいけないのは、コーディング中に上記の方法では.(ドット)を入力しても当然 メソッド一覧などの補完機能が効かないということです。また、複数のコントロールについて同じ動作をさせる場合にはFor Eachが便利です。
Dim v, vv As Variable
vv = Array("TextBoxA", "TextBoxB", "TextBoxC")
For Each v In vv
For i=1 to 100
Controls( vv & Cstr( i ) ).text = CStr(i)
Next
Next
といった感じです。
ForEachではVariable型の変数を利用する必要があります。宣言時には注意しましょう。
そして、Inに続く変数にはArray関数で一気に複数の値を放り込む方法が便利です。
これに、前述のControls関数を組み合わせることで、かなり少ないコードに集約することが出来ます。
2.ユーザーによるフォームのサイズ変更が使えない
VBのフォームだと、はじっこを掴んでドラッグすることで、ユーザーが自由にフォームサイズを変更できます。
また、場合によっては閉じるボタンや縮小ボタンの表示を自由に変更することが出来ます。
ところが、VBAのフォームではこれらの機能は利用できないのです。
しかし、VBAでも幸いにもWinAPIを利用できるため、なんとか同様のことを実装することが出来ます。
リストビューコントロール
VBシリーズには幾つかのリストビューコントロールが存在します。
そのそれぞれに使い方異なるため、毎回頭がこんがらがります。
そこで、実際に私が使っているListViewコントロールについて利用方法を書いておきます。
lst_view.ColumnHeaders(1) |
lst_view.ColumnHeaders(2) |
lst_view.ColumnHeaders(3) |
lst_view.ColumnHeaders(4) |
|
lst_view.ListItems(1).Text |
lst_view.ListItems(1).SubItems(1) |
lst_view.ListItems(1).SubItems(2) |
lst_view.ListItems(1).SubItems(3) |
|
lst_view.ListItems(2).Text |
lst_view.ListItems(2).SubItems(1) |
lst_view.ListItems(2).SubItems(2) |
lst_view.ListItems(3).SubItems(3) |
|
lst_view.ListItems(3).Text |
|
|
|
|
lst_view.ListItems(4).Text |
|
|
|
|
|
|
|
|
|
各リストアイテムの値は上気のように3種類の指定方法に分かれています。
列名と、行頭とサブアイテムです。何も見ないでコーディングしているとこんがらがります。
簡単・自作関数
VBやVBAにない機能を、自作の関数で実装してしまおうというコーナーです。
インターネットで結構紹介されているのですが、かなりソースに納得いかないので、いつも自分で作っています。
どうして納得いかないかといえば、だらだらしていて、最適化されていなくて、スマートではないと感じることがあるためです。
文字列をカウントする関数
文字列長(Len関数)ではありません。
ある文字列に含まれる、ある文字列の数を数えたいのです。
例えば、「abckdjkisabcssktr」に含まれる「abc」 の数を数えたいのです。
この場合は、2が戻り値となる関数を作ります。
この関数だけ、念のため「ASK 図書館」から引用しておきます。
dim ct as long
ct = CntStr("abcdefabgabh", "ab")
Public Function CntStr(s as string, org as string) as long
dim i as long
dim j as long
dim k as long
k = len(org)
i = 1
j = 0
do
i = instr(i, s, org)
if i > 0 then
i = i + k
j = j + 1
end if
loop until i = 0
CntStr = j
end Function
これでいいよ! という方は、特にこのページを楽しむことが出来ないかもしれません(汗
上のコードはInstr関数を用いた方法ですね、発想は悪くないですが、個人的にはすっきりしません。
私の場合は次のようにしてみました。
Public Function CntStr(source As String, target As String) As Long
Dim lng_len_source As Long
Dim lng_len_target As Long
Dim lng_ttl_target As Long
lng_len_source = Len(source)
lng_len_target = Len(target)
lng_ttl_target = lng_len_source - Len(Replace(source, target, ""))
CntStr = lng_ttl_target / lng_len_target
End Function
分かりやすいように変数を用意して計算させていますが、実際には一行で済ませることも出来ます。
CntStr = Len(source) - Len(Replace(source, target, "")) / Len(target)
とすれば良いのです。
これが何を意味しているのかは、直ぐに分かると思います。
分子はReplace関数を使って、数えたい文字列を消し去った時の消し去られた全体長です。
分母は数えたい文字列の文字列長です。
つまり、 「abckdjkisabcssktr」がsourceであった場合、abcを消し去ると、「kdjkisssktr」となります。
文字列長の差分は6文字です、これがabcabcに当たるわけです。
そして、abcの3文字で割ってあげれば2となって、2セットでしたという結果が得られるわけです。
これ以上スマートな方法があれば教えてください。
想像力がないプログラマがコードを見る可能性がある場合、ソースコードをおいづらくなるという可能性はありますので、注釈をつけておいた方が良いかもしれません。
モーダレスフォームをモーダルに見せる
「もーだるいっすよ先輩〜」の「もーだる」ではありません。
これらの用語を知らない方は、モーダルダイアログの説明を読んだ方が早いかも知れません。
一言で言えば「そのフォームが閉じないと、ユーザーもプログラムも先に進めないフォーム」の事です。
例えば、
msgbox("モーダルで悪いか!?")
とすると、モーダルダイアログが表示されます。
OKなりのボタンをクリックしないと裏側のフォームに手出しすることは出来ません。
プログラムも同様で、OKなりのボタンが押されないと、これ以降のプログラムへは進まないのです。
実際には、フォームが処理されるまでプログラムが中断する事自体は便利な場合もあります。
しかし、同様に不便な場合もあります。
例えば、自前でウィザードを作成する場合などがそれに当たります。
ウィザードには「戻る」ボタンがつきものですが、戻るボタンが押されると前のフォームを表示して自分はUnloadするようにしたとします。ところが、それでもプログラムは途中で一時停止しているだけでメモリにスタックされているだけなのです。
再帰的に処理が深掘りされてしまい、不要なりソース消費とバグの温床となってしまうわけです。
ここはやはり、各フェーズのフォーム毎に完結して動作するようにしたいものです。
話しが長くなりましたが、そんなわけでモーダルは使わないのが基本となります。
モーダルでないフォームはモーダレスと呼ばれます。
モーダレスにして不便になるのはいじって欲しくない裏側のフォームをいじれてしまうことです。
VBでは裏側のフォームのEnableプロパティをFalseにすることでこれを阻止できます。
VBAでは何故過去の方法は通用しなかったと思います。
裏側がいじれないのでは、モーダルフォームと同じではないか? という疑問も残りますが、はて、これいかに……。
見た目は同じでもプログラムはフォームを表示した後も続きを処理しきるので、変にスタックすることがありません。
frmSub.show vbModal
とすると、この後に控えているプログラムはびたっと止まります。
frmSubから新しいプロシージャーやフォームが呼び出されると、スタックされたままプログラムは進行していきます。
frmSub.show vbModeless
とすると、フォームも表示されますがこの後のプログラムも平行して処理されます。
End SubやExit Subなりが登場すれば、呼び出し元の処理は消滅しますので、独立して処理を行えることになります。
まだ未完成ですが、表示したモーダレスフォームを順序よくモーダルフォームに見せるクラスを作ってみました。
Controls関数みたいなものがVBで見あたらなかったので、今はハードコーディングしてしまっています。
これさえ汎化できれば便利なのですが……今のところフォームの数が3つと少ないので放ってあります。
Dim stock() As String
Public Sub push(ByVal target As String)
ReDim Preserve stock(UBound(stock) + 1)
stock(UBound(stock) - 1) = target
End Sub
Public Sub pop()
Select Case stock(UBound(stock) - 1)
Case "frmMain"
frmMain.Enabled = True
Case "frmOption"
frmOption.Enabled = True
End Select
ReDim Preserve stock(UBound(stock) - 1)
End Sub
Private Sub Class_Initialize()
ReDim stock(1)
End Sub
Me.Enable = False
とした後に、 PUSHメソッドにMe.Nameを引数で渡せば、プッシュ完了です。
Me.Enableの行もメソッドのプロシージャに書いた方が良いんでしょうね……。
面倒なのでやっていませんが、汎用化できた暁にはきちんと書き直します。
戻すときは、その後表示したモーダレスフォームからPOPメソッドを呼べば完了です。
標準モジュールなどからコントロールを操作したい
例えば、フォームfrmTESTのコードの中から、フォームに配置されたテキストボックスtxtTESTのないようにアクセスする場合には、何も考えずに
txtTEST.text = "test"
などとすれば良いのですが、標準モジュールや他のフォームから抽象的に操作したい場合にはどうしたらいいのでしょう?
抽象的にというのは、上記のようにハードコーディングするのではなくコントロール名を使って汎用的に色々なコントロールを扱えるプロシージャを作りたいという意味です。こんな時に便利なのがcontrolsです。
Controls("txtTEST").text = "test"
とすれば、前述の内容と同じ意味を表現できます。
しかし、このControlsはフォームのメンバーですのでフォームはハードコーディングする必要があります。
こんな時に更に利用できるのがFormsオブジェクトです。
FormsはControlsのように名前を渡してフォームを題名することが出来ないのですが、参照型引数として外部のプロシージャーに引き渡して再利用することでかなり使いやすくなります。
ここからはフォーム内のコード
Private Sub txtTEST_Changed()
Call myRoutine(me, "txtTEST")
End Sub
ここからは標準モジュール内のコード
Public Sub myRoutin(ByRef myForm As Form, ByVal Target As String)
myForm.Controls(Target).text = "test"
End Sub
という風に、引数として参照されたフォームオブジェクトにControlsを続けてあげればOKです。
渡す際にも何も考えずにMeを渡してあげれば、適切なフォームオブジェクトの参照を渡すことが出来ます。
DOS/V全般
◆ ◆ ◆
|
Copyright (c)1998-2006
Fujigoma Project Presents.
CNXGROUP All Rights Reserved.
| |