愚者の経験

「また今度」はほとんどこない

カテゴリーアーカイブ: VBA

[Access][ODBC][VBA]RefreshLinkとリンクテーブルマネージャでの再リンク

同じことやっているんだろう…そう思っていた時期が私にもありました。
Viewに適用するとRefreshLinkは主キーの擬似インデックスが外れますが、リンクテーブルマネージャは
外れません。なぜでしょう…これのせいでRefreshLinkがかなり使いづらいです。

[Access][VBA]Accessの開発方法考察(模索中)

・単純かつ頻繁に出るコードは標準モジュールのPublic Functionに集める
フォームのオープン、クローズ
コンボボックスのカラム値を代入
リストボックスを複数選択した場合の値を連結
Tempvarsに値を代入
リボンの表示非表示切り替え
RGBを6桁で入力する
・ODBCでパススルークエリを使う場合はなるべくパススルークエリを直接
 フォームのレコードソースにせずにワークテーブルを利用する
(私が知らないだけかもしれないが)パススルークエリで行を最新に更新する場合
Requeryでパススルークエリの対象行全てを問い合わせるしかないため。
面倒だがトラフィックを考慮し、ワークテーブルに対して別のパススルークエリで
更新対象行のUpdate(またはDeleteとInsert)を行ってフォームのレコードを更新する。
パススルークエリが1行しか表示しない場合などはそのままでいいと思います。

[Access][VBA]今更…フォーム側のPrivate Functionはコントロールから直接Call可能だった…

これホントですか…
今まで標準モジュールに書いたPublic Functionしか呼べないと思ってました。
「RelayCommand」とか考えた意味なし…
まあ「見えないから使えない」と思い込んでいた私も悪いのですが…。

1.フォームデザインでコマンドボタン配置
call1

2.フォームのモジュールにPrivate Function記述
call2

3.フォームデザインでコマンドボタンのイベント→クリック時に記述
もちろん候補には出ない(出してほしい笑)
call3
call4

4.クリックすると実行される
call5

これみなさん知ってます?Access5年近く触っているけど気づきませんでした…。
ちなみにPublicにしたところで他のフォームのコントロールから呼べるかというとできないと思います。
イベントプロパティに「=[Forms]![フォーム1].TestCall()」等しても「指定した式の構文が正しくありません。」と怒られてしまいます…

またがる場合は標準モジュールにということだと思うことにします。

[Access]サブフォームの再読み込み時のレコード移動をなんとかする

自分もどこかに書いてるかもしれませんね…手っ取り早く防ぐには
「サブフォームコントロールをRequeryする」です。

ただこの方法でカレントレコードを移動しない条件として
・フォームの「フィルタ」を使用しない
・フォームの「並び替え」を使用しない
があります。上記のいずれかが適用されている場合は
カレントレコードは最上のレコードへ移動してしまいます。

しかし幸いなことに「リンクフィールド」はOKのようです。

上記の方法が使えない場合は
・カレントレコードを変数に格納
・Requery
・カレントレコード戻す
・スクロールバーを戻す
をしないと完璧には戻らないので結構厳しいです…

追記:なんと親フォームの「フィルタ」「並び替え」をしていてもダメなようです…

[Access]EvtSummarizerがAccess2013だと落ちる

前回の投稿で作ったイベント統合クラスはAccess2013で実行すると落ちてしまいます。
なんでだろう…どうも子オブジェクトのTerminateイベントは発生しているがその後に落ちる。

フォーム側で予めNothingしておくと大丈夫。なんだこれ?

[Access][VBA]Openイベントを統合して取得する(コントロール配列クラス改良)

サンプルダウンロード:http://www.mediafire.com/download/mfem6mcgdsq95r3/ex.zip※Access2010で確認

以前の投稿
「Accessでコントロール配列を再現するクラス(EvtSummerizer)作成」で作ったクラスを改良して
フォームのOpenイベントも統合可能にしましたので公開します。
※グループは若干気に入らなかったので外しました。

レポートのOpenイベントも統合したかったのですが、「Docmd.OpenReport acViewNormal」した場合は
ウィンドウが作成されないのでそもそもフックできず。
なので常にacViewPreviewで開き、即印刷したい場合はEchoをFalseした後に
acHiddenでPreviewを開きDoCmd.RunCommand acCmdPrintし、Closeする流れをとることにしました。
(もちろんDocmd.OpenReportをもう一度送っても可ですが、なんとなくです(笑))

Openイベントが統合できることでアプリケーション側の排他機能が書きやすくなるかもしれません。

お決まりですが、使用する場合は自己責任でお願いします。
もしお使いいただけたら、ご利用後に感想等もいただけると嬉しいです。m(__)m

[Access]Openイベントより前にForm参照を取得する

前回の投稿

よくよく見るとこのコードだと対象のフォームにサブフォームかリストボックス
またはタブを配置していないと正常に動作しなかった
のでフックの種類を
変更したところ、うまくいきました。

Option Compare Database
Option Explicit

Private Declare Function UnhookWindowsHookEx Lib "user32" ( _
                ByVal hHook As Long) As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function SetWindowsHookEx Lib "user32" _
                Alias "SetWindowsHookExA" ( _
                ByVal idHook As Long, _
                ByVal lpfn As Long, _
                ByVal hmod As Long, _
                ByVal dwThreadId As Long) As Long
Private Declare Function CallNextHookEx Lib "user32" ( _
                ByVal hHook As Long, _
                ByVal nCode As Long, _
                ByVal wParam As Long, _
                ByRef lParam As Any) As Long
                
Private Const WH_CALLWNDPROC = 4
Private Const HC_ACTION = 0

Private hHook As Long
Private hName As String
Private hOk As Boolean

Public Sub OpenFormEx(FormName As String)
    If (CurrentProject.AllForms(FormName).IsLoaded) Then
        DoCmd.SelectObject acForm, FormName
        Exit Sub
    End If
    hHook = SetWindowsHookEx(WH_CALLWNDPROC, AddressOf CallWndProc, 0, GetCurrentThreadId)
    hName = FormName
    hOk = False
    DoCmd.OpenForm FormName
    UnhookWindowsHookEx hHook
End Sub
      
      
Public Function CallWndProc(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
On Error GoTo Finally
    Dim Target As IFormEx

    If ((nCode = HC_ACTION) And Not hOk) Then
        Set Target = Forms(hName)
        Target.PreOpen
        hOk = True
        UnhookWindowsHookEx hHook
        Set Target = Nothing
    End If
Finally:
    CallWndProc = CallNextHookEx(hHook, nCode, wParam, lParam)
End Function

これでクラスモジュールでFormのオープンイベントをフックできそうです。
かなり制御に幅が広がり、面白いことになりそうです。

[Access]サブフォームを読み込む前に処理したい2

[Access]サブフォームを読み込む前に処理したい1の続き

「いやフック使ってもサブフォームから先に開くんだし、どうやっても無理なんじゃ…」
と思うかもしれませんが、実際は
・サブフォームのクエリ内でメインフォームのコントロールを参照してもパラメータとして聞いてこない
・サブフォームのオープン時にメインフォームのプロパティは操作可能
という点からオープンイベントはサブ→メインの順で発生するが、フォームが作成される順番はメイン→サブである
の仮説がある程度成り立つのではと思っています。実際はもっと違う仕組みかもしれませんが。

というわけである程度の期待を込めてフックしてみます。

フック時にフォーム側へCallするメソッドは固定したいので
私の中でそろそろおなじみとなりつつあるインターフェイスクラスを作成します。
FormExインターフェイスクラス

Option Compare Database
Option Explicit

Public Sub PreOpen()
    'PreOpenメソッド
End Sub

標準モジュール

Option Compare Database
Option Explicit

Private Declare Function UnhookWindowsHookEx Lib "user32" ( _
                ByVal hHook As Long) As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function SetWindowsHookEx Lib "user32" _
                Alias "SetWindowsHookExA" ( _
                ByVal idHook As Long, _
                ByVal lpfn As Long, _
                ByVal hmod As Long, _
                ByVal dwThreadId As Long) As Long
Private Declare Function CallNextHookEx Lib "user32" ( _
                ByVal hHook As Long, _
                ByVal nCode As Long, _
                ByVal wParam As Long, _
                ByRef lParam As Any) As Long
                
Private Const HCBT_ACTIVATE = 5
Private Const HCBT_CREATEWND = 3
Private Const WH_CBT = 5

Private hHook As Long
Private hName As String
' Docmd.Openメソッドの代わりにこちらを使う
Public Sub OpenFormEx(FormName As String)
    If (CurrentProject.AllForms(FormName).IsLoaded) Then
        DoCmd.SelectObject acForm, Name
        Exit Sub
    End If
    hHook = SetWindowsHookEx(WH_CBT, AddressOf CBTHook, 0, GetCurrentThreadId)
    hName = FormName
    DoCmd.OpenForm FormName
    UnhookWindowsHookEx hHook
End Sub
'フックプロシージャ
Private Function CBTHook(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
On Error GoTo Finally
    Static Target As FormEx
    
    'ウィンドウ作成時に割り込み、FormExのPreOpenをCallする
    '※対象のフォームはFormExクラスをImprementsすること
    If nCode = HCBT_CREATEWND Then
        Set Target = Forms(hName)
        Target.PreOpen
        UnhookWindowsHookEx hHook
        Set Target = Nothing
    End If
Finally:
    CBTHook = CallNextHookEx(hHook, nCode, wParam, lParam)
End Function

フォーム1

Option Compare Database
Option Explicit

'FormExクラス継承
Implements FormEx

Private Sub FormEx_PreOpen()
On Error Resume Next
    'Open前に実行されるプロシージャ
    Debug.Print "Test"
End Sub

結論としましてはビンゴでした。
FormEx_PreOpenメソッドで値を代入するとサブフォームをRequeryすることなく
結果を出すことができます。これでまた可能性が広がった気がします。

[Access][VBA]OpenArgsなどに複数の値を渡すとき

「Docmd.OpenForm」と「Docmd.OpenReport」の第6引数である「OpenArgs」ですが
複数の値を入れたいという要望はとてもよくわかります。
そこで苦肉の策で「区切り文字」を決めて値を連結して後からSplitをかけるのが
一般的(?)なんですが気になるのがその「区切り文字」です。

わかりやすいのは「,(コンマ)」や「/(スラッシュ)」です。
渡す値が数値しかないのであれば、いいと思いますが文字列も渡す場合
文字列中に区切り文字がないことを祈りながら使うことになります。

これは精神衛生上よろしくないので、なんとかしたいと思いますよね?
そこで「入力されることのない文字」を探すことになるわけですが

私は「Chr(1-9)」をおすすめします。これらは制御文字で入力は基本的に出来ないからです。
「Chr(0)」の方が良さげなんですがこの文字は変数上文字列連結出来てもOpenArgsに渡すと
「Null」になってしまって使えません。

[Access][VBA]CurrentDbについて

大体のプロパティで返されるオブジェクトは永続化されていて
メソッドで返される場合はその時限りだと思っていいと思います。

Public Sub Test()
    Const UpdateSQL As String = "update 担当者 set ナンバー=担当者ID"
    DBEngine.Workspaces(0).Databases(0).Execute UpdateSQL
    Debug.Print DBEngine.Workspaces(0).Databases(0).RecordsAffected
    
    CurrentDb.Execute UpdateSQL
    Debug.Print CurrentDb.RecordsAffected
    
    With CurrentDb
        .Execute UpdateSQL
        Debug.Print .RecordsAffected
    End With
End Sub

2番目の「CurrentDb.RecordsAffected」は「0」になります。
直前のExecuteした「CurrentDb」とRecordsAffectedプロパティを参照している「CurrentDb」は
全くの別のオブジェクト(インスタンス)です。