愚者の経験

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

月別アーカイブ: 5月 2013

[Access]レポートで上下で分割する帳票を作る

「控えと一緒に印刷する」等需要がありますが、多くはサブレポートを
利用することになります。

しかし、サブレポートを利用するとページをまたぐ場合には行数を
先に保持していなければならなかったり、オブジェクトが増えるので
嫌っている方もいらっしゃるのではないのでしょうか?

レポートで行数を固定する方法の投稿でお伝えした「MoveLayout」を利用することで
・サブレポートなし
・空白行でも罫線表示(1ページの表示件数は固定)
・レポートを上下(あるいはそれ以上の数)に分割可能
なレポートを作ることができます。
ただし上下の行の高さは同じである必要があり、相変わらずデザインビューだと見難いですが…

以下のように設定します。
1.ページヘッダーに1ページのレイアウトをすべて記述(フォーマット時に「MoveLayout=False」)
2.適切なグループヘッダー(例えば伝票番号など)で実際に行が印刷するまでの高さを指定
3.レコードの主キーのグループヘッダーを作成し、行の高さと同じ高さを指定
4.詳細に必要なコントロールを並べる。上下に分割する場合は2つ並べる
この時に2つめ以降のコントロールはレイアウトでずれている分だけ離して配置(フォーマット時に「MoveLayout=False」)

pageh-vert2

最後にページフッターや余白を調整して、行数を固定すればOKです。

[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]サブフォームを読み込む前に処理したい1

Accessを使うにあたってサブフォームを利用することはかなり多いです。
そしてそのイベント発生順により若干不便さを感じることがあります。

Accessはサブフォームから先にOpenイベントが発生するということです。
この仕様によりどうしてもデータベースへの問い合わせ回数が増えるのです。

例えば「伝票の履歴を参照する画面」があり「顧客」を条件に検索出来る画面があったとして
1.何もない状態から「顧客」から入力して履歴を参照する
2.新規伝票の入力途中等、既に別画面で入力した[顧客]で履歴を参照する
というような複数の画面から同一の画面を参照し「検索条件に既定値を入れたりしたい」場合

履歴を開く(問い合わせ一回目)

検索条件のテキストボックス等に値代入

検索実行(問い合わせ二回目)

という手順をとり、問い合わせ回数が増えます。
特に要望で「検索条件なしの場合は全件表示または一部表示」のような仕様にせざるを得ない場合
パフォーマンスに大きく影響します。一回目の問い合わせで取得するデータは全くの無駄となります。
まあ必ず0件取得から始めればそうでもないかもしれませんがそれはそれで
個人的にはあまり気分のよい状態ではありません笑

この無駄を省く方法がないかというのが今回の記事の発端です。

・方法1:サブフォームの「ソースオブジェクト」を空にしておき
検索条件等代入後、「ソースオブジェクト」を代入する
一番カンタンですが、デザイン時にサブフォームが非連結になるので見難いです。
あと画面を閉じるとき「Docmd.Close acForm, Me.Name, acSaveYes」等で保存することが
出来る場合「ソースオブジェクト」が空の状態を維持できないので破綻します。

・方法2:「DefaultValue」にユーザー関数を利用し、オープン前にユーザー関数の結果を操作する。
結構良い案なのですが、管理が大変です。
オープン前ということは「Docmd.OpenForm」を記述する前です。(当たり前ですが)
となると、記述するモジュールは呼び出す側ということになります。
コーディングの好き嫌いかもしれませんが、私はよばれる側に引数で与えたいです。(OpenArgs)

パッと思いつくのが上記の2案ですが、私はイマイチ気に入らないので
Hookを使って解決できないか調べて見たので次回はその結果を書きます。

[Access]AccessでSQLiteを使用するメリット

SQLiteがAccess自身のデータベースと何が違うかというとやっぱり
・ファイルの容量制限がAccess(2GB)を超えられる
・トリガーが使える
この2点が大きくアドバンテージを得る部分でしょう。

他にも一応メリットになりそうなものは
・SQLが標準に近い(データベースサーバ移行時に再利用しやすい)
・使える構文、演算子がSQLiteのほうが豊富
・関数を追加可能(Cで書く必要が…)
こんな感じでしょうか。

ただデメリットもあり、
・リレーション(外部キー)がない(多分)
・ファイルサイズがAccessよりも大きい(約1.3倍)
・おなじみ通貨型(固定小数点型)がない(SQLiteの小数点型はよくわかりませんm(__)m)
等が挙げられます。

[Access]SQLiteをODBCで使用する件解決?

参考URL:http://kzworks.at.webry.info/200908/article_41.html
上記にSQLiteのODBCドライバを改造して公開されている方がいらっしゃいましたので
使わせていただきましたところちゃんとODBCリンク使えました。

SQL Server Compact EditionのODBCドライバありませんね~

[Access]SQLiteをODBCで使ってみようと思ったんですが…

昔の投稿に「ODBCは枯れつつある技術でこれからはADO」みたいに書いたんですが
実際Accessは2013からADPを使えませんので「やっぱりODBC」と思って
「これから開発するときはODBCか…じゃあAccessのDB使わずに他の組み込みDBで
最初からODBC接続で開発したほうがアップサイジングしやすいし…」
と思ってSQLiteのODBCドライバーがあったのでちょっとやってみたんですが…

ODBCドライバーをインストールしてDSNを作るまでは良かったんですが
リンクテーブルを作ろうとしたら「out of memory」「SQLSetConnectAttr」等が
エラーを吐いてどうしようもないです…知っている方がいたら教えてくださいm(__)m