【Access VBA】カレンダーコントロールの作成(機能追加)


カレンダーコントロールの作成(機能追加)

下記リンク先にて作成したカレンダーコントロールの「年」テキストボックスをクリックすると、20年分の「年」一覧が表示され、目的の年をクリックすると「年」テキストボックスに入力されるようにします。スピンボタンをクリックすると前の20年分あるいは次の20年分が表示されます。
【Access VBA】カレンダーコントロールの作成 - カットマンブログ


ラベルコントロールの作成

「年」一覧のラベルを生成するために下記コードを標準モジュールに記述し実行します。宣言セクションのPublic変数は「年」一覧かカレンダーのどちらが表示されているかを判定するために使用します。

Public boolFlag As Boolean '年一覧:TRUE,カレンダー:FALSE
Private Sub ラベルコントロール作成_年ラベル()
    DoCmd.OpenForm "Fカレンダー", acDesign
    Dim lbl As Control
    Dim i As Integer, j As Integer, c As Integer
    Const leftMgn As Single = 0.2 '左余白(cm)
    Const rightMgn As Single = 0.2 '右余白(cm)
    Const topMgn As Single = 1.2 '上余白(cm)
    Const bottomMgn As Single = 0.2 '下余白(cm)
    Const lblMgn As Single = 0.1 'ラベル間隔(cm)
    Const lblWidth As Single = 2.46 'ラベル幅(cm)
    Const lblHeight As Single = 1 'ラベル高さ(cm)
    Const cmTwip As Single = 567 '1cm当たりのtwip
    
    c = 1
    For i = 1 To 7
        For j = 1 To 3
            Set lbl = CreateControl("Fカレンダー", acLabel, , , , cmTwip * leftMgn + _
                      cmTwip * (lblWidth + lblMgn) * (j - 1), cmTwip * topMgn _
                       + (i - 1) * cmTwip * (lblHeight + lblMgn), _
                      cmTwip * lblWidth, cmTwip * lblHeight)
            With lbl
                .BorderColor = vbBlack
                .BorderStyle = 1
                .ForeColor = vbBlack
                .FontSize = 22
                .TextAlign = 2
                .TopMargin = 50
                .Name = "年" & c
                c = c + 1
            End With
        Next
    Next
    DoCmd.Close acForm, "Fカレンダー", acSaveYes
End Sub


「年」テキストボックスをクリックしたときの処理の記述

変数boolFlagがTRUEの場合は「年」一覧を表示し、FALSEの場合はカレンダーを表示します。

Private Sub txtYear_Click()
    Dim i As Integer
    If boolFlag Then
        For i = 1 To 7
            Controls("曜日" & i).Visible = True
        Next
        For i = 1 To 42
            Controls("日" & i).Visible = True
        Next
        For i = 1 To 21
            Controls("年" & i).Visible = False
        Next
        boolFlag = False
    Else
        For i = 1 To 7
            Controls("曜日" & i).Visible = False
        Next
        For i = 1 To 42
            Controls("日" & i).Visible = False
        Next
        For i = 1 To 21
            Controls("年" & i).Visible = True
            Controls("年" & i).Caption = Int(Val(txtYear.Value) / 10) * 10 + i - 1 & "年"
            Controls("年" & i).Tag = Int(Val(txtYear.Value) / 10) * 10 + i - 1 & "年"
        Next
        boolFlag = True
    End If
End Sub


年ラベルをクリックしたときの処理の記述

クラスモジュールにclsYearを作成し、下記のコードを記述します。

Private WithEvents mLbl As Label
Private mForm As Form
 
Public Sub Bind(ByVal oCtrl As Control, ByVal oForm As Form)
    Set mLbl = oCtrl
    Set mForm = oForm
    mLbl.OnClick = "[EVENT PROCEDURE]"
End Sub
 
Private Sub mLbl_Click()
    mForm.Form.Controls("txtYear") = mLbl.Tag
    Dim i As Integer
    For i = 1 To 7
        mForm.Form.Controls("曜日" & i).Visible = True
    Next
    For i = 1 To 42
        mForm.Form.Controls("日" & i).Visible = True
    Next
    For i = 1 To 21
        mForm.Form.Controls("年" & i).Visible = False
    Next
    Call mForm.displayLabel(Val(mForm.txtYear), Val(mForm.cboMonth))
    boolFlag = False
End Sub


スピンボタンをクリックしたときの処理の記述

「年」一覧が表示されているときに「年」スピンボタンをクリックすると表示が切り替わるようにコードを修正しました。

Private WithEvents mBtn As CommandButton
Private mTxt As TextBox
Private mCbo As ComboBox
Private mString As String
Private mForm As Form
Private startTime As Double
Private lngSpin As Long
Private mCtrl As Control

Public Sub Bind(ByVal oCtrl As Control, ByVal oForm As Form, ByVal oString As String, ByVal oCommandButton As CommandButton)
    Set mCtrl = oCtrl
    Set mForm = oForm
    mString = oString
    Set mBtn = oCommandButton
    mBtn.OnMouseDown = "[EVENT PROCEDURE]"
    mBtn.OnMouseUp = "[EVENT PROCEDURE]"
End Sub
 
Private Sub mBtn_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim i As Integer
    startTime = CDbl(Timer)
    Select Case TypeName(mCtrl)
    Case "TextBox"
        Set mTxt = mCtrl
        lngSpin = Val(mTxt.Value)
    Case "ComboBox"
        Set mCbo = mCtrl
        lngSpin = Val(mCbo.Value)
    End Select
    
    Do Until startTime = 0
                Select Case mBtn.Tag
                Case "Up"
                    lngSpin = lngSpin + 1
                Case "Down"
                    lngSpin = lngSpin - 1
                End Select
                
                If TypeName(mCtrl) = "TextBox" Then
                    Select Case boolFlag
                    Case True
                        Select Case mBtn.Tag
                        Case "Up"
                        For i = 1 To 21
                            mForm.Form.Controls("年" & i).Caption = Val(mForm.Form.Controls("年" & i).Tag) + 20 & "年"
                            mForm.Form.Controls("年" & i).Tag = mForm.Form.Controls("年" & i).Caption
                        Next
                        
                        Case "Down"                    
                        For i = 1 To 21
                            mForm.Form.Controls("年" & i).Caption = Val(mForm.Form.Controls("年" & i).Tag) - 20 & "年"
                            mForm.Form.Controls("年" & i).Tag = mForm.Form.Controls("年" & i).Caption
                        Next                        
                        End Select
                    
                    Case False
                    mTxt = lngSpin & mString
                    End Select
                Else                
                    If lngSpin > 12 Then
                        lngSpin = 1
                        mForm.txtYear = Val(mForm.txtYear) + 1 & "年"
                    End If
                    If lngSpin < 1 Then
                        lngSpin = 12
                        mForm.txtYear = Val(mForm.txtYear) - 1 & "年"
                    End If
                    mCbo = lngSpin & mString                
                End If
                Call mForm.displayLabel(Val(mForm.txtYear), Val(mForm.cboMonth))
                
                If CDbl(Timer) - startTime < 1 Then
                    Sleep (300)
                Else
                    Sleep (200)
                End If
            DoEvents
    Loop
    startTime = 0
End Sub
 
Private Sub mBtn_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
    startTime = 0
End Sub


カレンダーフォーム用コードの記述

Fカレンダーの読み込み時のイベントプロシージャにコードを追加しました。
「年」ラベル用のクラスのインスタンスを生成し、変数 boolFlagをTrueに設定しました。

Private aClassCalendar() As New clsCalendar
Private aClassSpin(3) As New clsSpin
Private aClassYear() As New clsYear
Private Sub Form_Load()
    
    boolFlag = True
    txtYear_Click
    Dim i As Integer

    If sDate = 0 Then
        txtYear = Year(Date) & "年"
        cboMonth = Month(Date) & "月"
    Else
        txtYear = Year(sDate) & "年"
        cboMonth = Month(sDate) & "月"
    End If

    Call displayLabel(Val(txtYear), Val(cboMonth))

    For i = 1 To 12
        cboMonth.AddItem i & "月"
    Next
    For i = 1 To 42
        ReDim Preserve aClassCalendar(i)
        Call aClassCalendar(i).Bind(Controls("日" & i), Me)
    Next
    For i = 1 To 21
        ReDim Preserve aClassYear(i)
        Call aClassYear(i).Bind(Controls("年" & i), Me)
    Next
    Call aClassSpin(0).Bind(txtYear, Me, "年", btnUpYear)
    Call aClassSpin(1).Bind(txtYear, Me, "年", btnDownYear)
    Call aClassSpin(2).Bind(cboMonth, Me, "月", btnUpMonth)
    Call aClassSpin(3).Bind(cboMonth, Me, "月", btnDownMonth)
End Sub

【Access VBA】フォームの表示位置を指定する


フォームの表示位置を指定する

Windows APIを使ってフォームの表示位置を指定します。下記リンク先にて作成したカレンダーコントロールの表示位置が呼び出し元フォームのテキストボックスの位置に連動して変わるようにします。
【Access VBA】カレンダーコントロールの作成 - カットマンブログ



twipをpixelに変換する関数の記述

テキストボックスの位置がtwip単位で取得されるため、これをpixelに変換する関数を標準モジュールに記述します。下記サイトよりコピペしました。
Access VBA Form の位置、サイズはtwip単位で係数は567である #access - Qiita

Public Const SM_CYCAPTION As Long = 4
Public Const SM_CXFIXEDFRAME As Long = 7
Public Const SM_CYFIXEDFRAME As Long = 8
Public Const SM_CXFULLSCREEN As Long = 16
Public Const SM_CYFULLSCREEN As Long = 17
Public Const WU_LOGPIXELSX = 88
Public Const WU_LOGPIXELSY = 90

Public Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hwnd As LongPtr, ByRef lpRect As RECT) As LongPtr
Public Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
Public Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As LongPtr
Public Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As LongPtr
Public Declare PtrSafe Function MoveWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As Long) As LongPtr
Public Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
Public Declare PtrSafe Function GetClientRect Lib "user32" (ByVal hwnd As LongPtr, lpRect As RECT) As Long
Private Type RECT
        Left As Long
        Top As Long
        Right As Long
        Bottom As Long
End Type

Public meRect_parent As RECT
Public meRect_client As RECT
Public ownerRect As RECT

Public Function ConvertTwipsToPixels(lngTwips As Long, lngDirection As Long) As Long
   'Handle to device
   Dim lngDC As Long
   Dim lngPixelsPerInch As Long
   Const nTwipsPerInch = 1440
   lngDC = GetDC(0)
   If (lngDirection = 0) Then       'Horizontal
      lngPixelsPerInch = GetDeviceCaps(lngDC, WU_LOGPIXELSX)
   Else                            'Vertical
      lngPixelsPerInch = GetDeviceCaps(lngDC, WU_LOGPIXELSY)
   End If
   lngDC = ReleaseDC(0, lngDC)
   ConvertTwipsToPixels = (lngTwips / nTwipsPerInch) * lngPixelsPerInch
End Function


カレンダーフォームの開く時のイベントプロシージャの記述

Fカレンダーの開く時のイベントプロシージャに以下のコードを記述します。
Fカレンダーが画面の右側にはみ出す場合に、Fカレンダーの右端がテキストボックスの右端にくるようにしています。
Fカレンダーが画面の下側にはみ出す場合に、Fカレンダーの下端がテキストボックスの上端にくるようにしています。

Private Sub Form_Open(Cancel As Integer)
    Dim leftPosition As Long
    Dim topPosition As Long
    Dim oForm As Object
    Set oForm = Forms(Application.CurrentObjectName)

    GetWindowRect Forms(Application.CurrentObjectName).hwnd, ownerRect

    Dim meWidth_parent As Long
    Dim meHeight_parent As Long
    GetWindowRect Me.hwnd, meRect_parent
    GetClientRect Me.hwnd, meRect_client
    With meRect_parent
        meWidth_parent = .Right - .Left 'Fカレンダーの幅
        meHeight_parent = .Bottom - .Top 'Fカレンダーの高さ
    End With
    Dim leftPosition_owner As Long
    Dim topPosition_owner As Long
    Dim xframeWidth As Long
    Dim titleHeight As Long
    Dim leftPosition_textbox As Long
    Dim topPosition_textbox As Long
    Dim textboxWidth As Long
    Dim textboxHeight As Long
    Dim screenWidth As Long
    Dim screenHeight As Long
    Dim meWidth_client As Long
    Dim meHeight_client As Long
    leftPosition_owner = ownerRect.Left '呼び出し元フォームの左位置
    topPosition_owner = ownerRect.Top '呼び出し元フォームの上位置
    xframeWidth = GetSystemMetrics(SM_CXFIXEDFRAME) 'ウインドウの水平罫線の太さ
    titleHeight = GetSystemMetrics(SM_CYCAPTION) 'タイトルバーの高さ
    screenWidth = GetSystemMetrics(SM_CXFULLSCREEN) '全画面表示ウインドウのクライアント領域の幅
    screenHeight = GetSystemMetrics(SM_CYFULLSCREEN) '全画面表示ウインドウのクライアント領域の高さ
    meWidth_client = meRect_client.Right 'Fカレンダーのクライアント領域の幅
    meHeight_client = meRect_client.Bottom 'Fカレンダーのクライアント領域の高さ
    
    With oForm.Controls(OpenArgs)
        leftPosition_textbox = ConvertTwipsToPixels(.Left, 0) 'テキストボックスの左位置
        topPosition_textbox = ConvertTwipsToPixels(.Top, 1) 'テキストボックスの上位置
        textboxWidth = ConvertTwipsToPixels(.Width, 0) 'テキストボックスの幅
        textboxHeight = ConvertTwipsToPixels(.Height, 1) 'テキストボックスの高さ
    End With
        If leftPosition_owner + leftPosition_textbox > screenWidth - meWidth_parent Then
            leftPosition = leftPosition_owner + leftPosition_textbox + textboxWidth - meWidth_client
        Else
            leftPosition = leftPosition_owner + leftPosition_textbox
        End If
        
        If topPosition_owner + xframeWidth + titleHeight + topPosition_textbox + textboxHeight > screenHeight - meHeight_parent Then
            topPosition = topPosition_owner + topPosition_textbox - meHeight_client
        Else
            topPosition = topPosition_owner + xframeWidth + titleHeight + topPosition_textbox + textboxHeight
        End If

    MoveWindow Me.hwnd, leftPosition, topPosition, meWidth_parent, meHeight_parent, True
End Sub


呼び出し元フォームのボタンクリック時イベントプロシージャの修正

呼び出し元フォームのボタンクリック時イベントプロシージャのOpenFormメソッドのOpenArgs引数にテキストボックスの名前を追加しました。

Private Sub btnCalendar_Click()
        sDate = Nz(txtDate, 0)
        DoCmd.OpenForm "Fカレンダー", , , , , acDialog, "txtDate"
        If sDate <> 0 Then
            txtDate = sDate
        End If
End Sub

【Access VBA】SQL Serverのテーブルを編集する


AccessからSQL Serverのテーブルを編集する

SQL Serverの「売上伝票」テーブルおよび「売上明細」テーブルのデータをAccessで取得し、修正を加えたのち、SQL Serverに保存します。


テーブルを準備する

SQL Serverで「sample」という名前のデータベースの中に「T売上伝票」、「T売上明細」、「TEMP売上伝票」、「TEMP売上明細」の4つのテーブルを用意しました。「T売上伝票」、「T売上明細」は生データを保存するテーブルであり、「TEMP売上伝票」、「TEMP売上明細」は一時的にAccessから編集後のデータを受け取るテーブルです。この一時テーブルからストアドプロシージャを使って、生データのテーブルにデータを書き込みます。
さらに「T売上伝票」の主キー値発番用に「T発番」という名前のテーブルを用意しました。
発番用のストアドプロシージャについては下記リンク先を参照してください。
【Access VBA】主キー値をSQL Serverから取得する - カットマンブログ
Accessには「WT売上伝票」、「WT売上明細」の2つのテーブルを用意しました。


「T売上伝票」、「T売上明細」の間に連鎖削除を設定しました。

SQL Serverにストアドプロシージャを準備する

SQL Serverに「import売上情報」という名前のストアドプロシージャを用意しました。
これにより一時テーブルのデータを生データのテーブルに書き込みます。

ALTER PROCEDURE [dbo].[import売上情報] 	
	
AS
BEGIN	
	SET NOCOUNT ON;
    BEGIN TRY
      BEGIN TRANSACTION
      --T売上伝票更新----------------------------------------------------	
	MERGE INTO [T売上伝票] AS A    
        USING
       (SELECT [伝票番号],[日付] FROM [TEMP売上伝票] WITH(TABLOCKX)) AS B 
        ON
        (A.[伝票番号] = B.[伝票番号])
        WHEN MATCHED THEN    
          UPDATE SET [日付] = B.[日付]        
        WHEN NOT MATCHED THEN
          INSERT ([伝票番号],[日付])
          VALUES (B.[伝票番号],B.[日付]);        
      ---------------------------------------------------------------
      --T売上明細更新-------------------------------------------------		
        MERGE INTO [T売上明細] AS C    
        USING    
        (SELECT [明細ID],[伝票番号],[商品コード],[数量],[削除]
        FROM [TEMP売上明細] WITH(TABLOCKX)) AS D
        ON    
        (C.[明細ID] = D.[明細ID])
        WHEN MATCHED AND D.[削除]=0 THEN
          UPDATE SET [商品コード] =D.[商品コード], [数量] = D.[数量]		            
        WHEN MATCHED AND D.[削除]=1 THEN   
	      DELETE
        WHEN NOT MATCHED AND D.[削除]=0 THEN
          INSERT ([伝票番号],[商品コード],[数量])
          VALUES (D.[伝票番号],D.[商品コード],D.[数量]);
      -------------------------------------------------------------------			
      COMMIT TRANSACTION 
      RETURN-1 
    END TRY

    BEGIN CATCH
      ROLLBACK TRANSACTION		
      RETURN 0
    END CATCH
END

SQL Serverに「SetID」という名前のストアドプロシージャを用意しました。これにより「売上伝票」の主キー値を発番します。以下に「SetID」のコードを記載します。

ALTER PROCEDURE [dbo].[SetID]	
	@ID int OUTPUT 
AS
BEGIN	
	SET NOCOUNT ON;    
	BEGIN TRY
		BEGIN TRANSACTION
			SELECT @ID=	連番 FROM T発番
			UPDATE T発番 SET 連番=@ID+1
		COMMIT TRANSACTION 
		RETURN -1 
	END TRY

	BEGIN CATCH
		ROLLBACK TRANSACTION		
		RETURN 0
	END CATCH
END


リンクテーブルの作成

Accessで「TEMP売上伝票」と「TEMP売上明細」のリンクテーブルを作成しました。
作成方法は下記リンク先を参照してください。
【Access】SQL Serverのリンクテーブル作成 - カットマンブログ


選択クエリの作成

Accessで「Q売上明細」という名前の選択クエリを作成しました。サブフォームのレコードソースとして使用します。


フォームの準備

下のような「Fサンプル」という名前のフォームを作成しました。「伝票一覧」と「売上明細」はサブフォームです。「売上伝票」の部分には非連結のテキストボックス2つを配置しています。


「売上明細」サブフォームでは「伝票番号」と「削除」フィールドを非表示にして、以下の既定値を設定しました。


SQL Serverのテーブルから「売上伝票」と「売上明細」を取得するコードの記述

標準モジュールにAccessのテーブルをクリアし、SQL Serverのテーブルから売上伝票と売上明細を取得するプロシージャ「wtImport」を記述します。

Public Const strCN As String = "driver={ODBC Driver 17 for SQL Server};server=LAPTOP-114315\SQLEXPRESS;DATABASE=sample;uid=administrator;PWD=1111"
Public Sub wtImport(ByVal strWT As String, ByVal strSQL As String)
    Dim w_cn As New ADODB.Connection
    Set w_cn = CurrentProject.AccessConnection
    Dim w_cmd As New ADODB.Command
    w_cmd.ActiveConnection = w_cn
    w_cmd.CommandType = adCmdText
    w_cmd.CommandText = "DELETE FROM " & strWT
    w_cmd.Execute
    w_cmd.CommandText = "INSERT INTO " & strWT & " " & strSQL
    w_cmd.Execute
    Set w_cmd = Nothing
    w_cn.Close: Set w_cn = Nothing
End Sub


SQL Serverの「T売上伝票」から目的のデータを削除するコードの記述

標準モジュールにSQL Serverの「T売上伝票」から目的のデータを削除するプロシージャ「tDelete」を作成しました。

Public Sub tDelete(ByVal intSlipNo As Integer)
    Dim cn As New ADODB.Connection
    cn.Open strCN
    Dim cmd As New ADODB.Command
    cmd.ActiveConnection = cn
    cmd.CommandType = adCmdText
    cmd.CommandText = "DELETE FROM T売上伝票 WHERE 伝票番号=" & intSlipNo
    cmd.Execute
    Set cmd = Nothing
    cn.Close: Set cn = Nothing
End Sub


SQL Serverから主キー値を取得するコードの記述

標準モジュールにSQL Serverから主キー値を取得する関数「GetID」を作成しました。

Public Function GetID(ByRef n As Integer) As Boolean
    Dim cn As New ADODB.Connection
    cn.Open strCN
    Dim cmd As New ADODB.Command
    cmd.ActiveConnection = cn
    cmd.CommandType = adCmdStoredProc
    cmd.CommandText = "SetID"
    cmd.Parameters.Append cmd.CreateParameter(, adInteger, adParamReturnValue, , Null)
    cmd.Parameters.Append cmd.CreateParameter("@ID", adInteger, adParamOutput, , Null)
    cmd.Execute
    
    If CBool(cmd.Parameters(0).Value) Then
        n = cmd.Parameters("@ID").Value
        GetID = True
    Else
        MsgBox "エラーが発生しました。", vbExclamation, "確認"
        GetID = False
    End If
    Set cmd = Nothing
    cn.Close: Set cn = Nothing
End Function


Accessのテーブルをクリアするコードの記述

標準モジュールにAccessのテーブルをクリアするプロシージャ「wtDelete」を作成しました。

Public Sub wtDelete(ByVal strWT As String)
    Dim w_cn As New ADODB.Connection
    Set w_cn = CurrentProject.AccessConnection
    Dim w_cmd As New ADODB.Command
    w_cmd.ActiveConnection = w_cn
    w_cmd.CommandType = adCmdText
    w_cmd.CommandText = "DELETE FROM " & strWT
    w_cmd.Execute
    Set w_cmd = Nothing
End Sub


フォーム用プロシージャの記述

「Fサンプル」の読み込み時と、「新規作成」ボタンおよび「保存」ボタンのクリック時のイベントプロシージャに以下のコードを記述しました。

Private Sub Form_Load()
    Dim strSQL As String
    strSQL = "SELECT * FROM T売上伝票 IN ''[ODBC;" & strCN & "] "
    Call wtImport("WT売上伝票", strSQL)
    Me.sub伝票一覧.Requery
    If DCount("*", "WT売上伝票") = 0 Then Exit Sub
    [伝票番号] = Forms![Fサンプル].sub伝票一覧.Form![伝票番号]
    [日付] = Forms![Fサンプル].sub伝票一覧.Form![日付]
    strSQL = "SELECT * FROM T売上明細 IN ''[ODBC;" & strCN & "] WHERE 伝票番号=" & [伝票番号]
    Call wtImport("WT売上明細", strSQL)
    Me.sub売上明細.Requery
End Sub

'「新規作成」ボタンクリック時のプロシージャ----------------------------
Private Sub btnNew_Click()
    Dim n As Integer
    If GetID(n) Then
        [伝票番号] = n
    Else
        [伝票番号] = Null
    End If
    Call wtDelete("WT売上明細")
    [日付] = Null
    Me.Painting = False
    Me.sub売上明細.Requery
    Me.Painting = True
End Sub

'「保存」ボタンクリック時のプロシージャ-------------------------------------
Private Sub btnUpdate_Click()
    If IsNull([伝票番号]) Then Exit Sub
    '売上明細ゼロ件の時、売上伝票を削除する-----------------------------------
    If DCount("*", "Q売上明細") = 0 Then
        Call tDelete([伝票番号])
        [伝票番号] = Null
        [日付] = Null
        GoTo UD
    End If
    '---------------------------------------------------------------------------
    On Error GoTo Errh
    'SQLServerの一時テーブルにAccessのデータを転記する--------------------------
    Dim cn As New ADODB.Connection
    cn.Open strCN
    Dim cmd As New ADODB.Command
    Set cmd = New ADODB.Command
    cmd.ActiveConnection = cn
    cmd.CommandType = adCmdText
    cmd.CommandText = "TRUNCATE TABLE TEMP売上明細"
    cmd.Execute
    cmd.CommandText = "TRUNCATE TABLE TEMP売上伝票"
    cmd.Execute
    
    Dim w_cn As New ADODB.Connection
    Set w_cn = CurrentProject.AccessConnection
    Dim w_cmd As New ADODB.Command
    w_cmd.ActiveConnection = w_cn
    w_cmd.CommandType = adCmdText
    w_cmd.CommandText = "INSERT INTO TEMP売上明細 SELECT * FROM WT売上明細"
    w_cmd.Execute
    
    Dim strSQL As String
    strSQL = "VALUES(" & [伝票番号]
    strSQL = strSQL & ",'" & [日付]
    strSQL = strSQL & "')"
    w_cmd.CommandType = adCmdText
    w_cmd.CommandText = "INSERT INTO TEMP売上伝票 " & strSQL
    w_cmd.Execute
   
    Set w_cmd = Nothing
    w_cn.Close: Set w_cn = Nothing
    On Error GoTo 0
    '---------------------------------------------------------------------------
    'SQLServerの一時テーブルから生データテーブルに転記する----------------------
    cmd.CommandType = adCmdStoredProc
    cmd.CommandText = "import売上情報"
    
    cmd.Parameters.Append cmd.CreateParameter(, adInteger, adParamReturnValue, , Null)
    cmd.Execute
    
    If CBool(cmd.Parameters(0).Value) Then
        GoTo UD
    Else
        MsgBox "エラーが発生しました。", vbExclamation, "確認"
        Set cmd = Nothing
        cn.Close: Set cn = Nothing
        Exit Sub
    End If
    '---------------------------------------------------------------------------
UD:
    Set cmd = Nothing
    cn.Close: Set cn = Nothing
    Me.Painting = False
    strSQL = "SELECT * FROM T売上伝票 IN ''[ODBC;" & strCN & "] "
    Call wtImport("WT売上伝票", strSQL)
    
    Me.sub伝票一覧.Requery
    If Not IsNull([伝票番号]) Then
        Me.sub伝票一覧.Form.Recordset.FindFirst "伝票番号=" & [伝票番号]
    End If
    Me.Painting = True
    If DCount("*", "WT売上伝票") <> 0 Then
        [伝票番号] = Me.sub伝票一覧.Form.[伝票番号]
        [日付] = Me.sub伝票一覧.Form.[日付]
    
        strSQL = "SELECT * FROM T売上明細 IN ''[ODBC;" & strCN & "]" _
                        & "WHERE 伝票番号=" & [伝票番号]
    
        Call wtImport("WT売上明細", strSQL)
        Me.Painting = False
        Me.sub売上明細.Requery
        Me.Painting = True
    End If
    MsgBox "保存しました。", vbInformation, "確認"
    Exit Sub
Errh:
    MsgBox "エラーが発生しました。", vbExclamation, "確認"
End Sub


サブフォーム用プロシージャの記述

「F伝票一覧」のクリック時に以下のイベントプロシージャを記述しました。

Public Sub Form_Click()
    Forms![Fサンプル].[伝票番号] = [伝票番号]
    Forms![Fサンプル].[日付] = [日付]
    Dim strSQL As String
    strSQL = "SELECT * FROM T売上明細 IN ''[ODBC;" & strCN & "] WHERE 伝票番号=" & [伝票番号]
    Call wtImport("WT売上明細", strSQL)
    Forms![Fサンプル].sub売上明細.Form.Painting = False
    Forms![Fサンプル].sub売上明細.Requery
    Forms![Fサンプル].sub売上明細.Form.Painting = True
End Sub

「F売上明細」の「削除」ボタンのクリック時に以下のイベントプロシージャを記述しました。

Private Sub btnDelete_Click()
    If Me.NewRecord Then
        MsgBox "新規レコードは削除できません。"
        Exit Sub
    End If

    [削除] = True
    Me.Requery
End Sub

【Access VBA】「あかさたな」ボタンでフォームにフィルターをかける


「あかさたな」ボタンでフォームにフィルターをかける

「あ」ボタンをクリックするとフリガナの頭文字があ行の市区町村のレコードのみ表示されます。


フォームに「あかさたな」ボタンを配置する

フォームに「あかさたな」ボタンを配置しました。名前とタグを以下のように設定しました。


コードの記述

クラスモジュールにclsFilterを作成し、下記コードを記述します。

Private WithEvents mbtn As CommandButton
Private mForm As Form
 
Public Sub Bind(ByVal oForm As Form, ByVal oCtrl As Control)
    Set mForm = oForm
    Set mbtn = oCtrl
    mbtn.OnClick = "[EVENT PROCEDURE]"
End Sub

Private Sub mbtn_Click()
    If mbtn.Tag = "全件" Then       
        mForm.FilterOn = False
    Else
        mForm.Filter = "フリガナ Like '[" & mbtn.Tag & "]*'"
        mForm.FilterOn = True
    End If
    DoCmd.GoToRecord , , acNewRec
    DoCmd.GoToControl "市区町村名"
End Sub

フォームの読み込み時イベントプロシージャに下記コードを記述します。

Private aClassFilter(10) As New clsFilter
Private Sub Form_Load()
    Dim i As Integer
   
    For i = 0 To 10
        Call aClassFilter(i).Bind(Me, Controls("btn" & i))
    Next
    
    DoCmd.GoToRecord , , acNewRec
    DoCmd.GoToControl "市区町村名"
End Sub

【Access VBA】SQL Serverからユニオンクエリの結果を取得する


2つのテーブルを合計した結果をグラフ表示する

SQL Serverにある2つのテーブルを合計した結果をAccessに取り込み、グラフ表示します。


SQL Serverにテーブルを準備する

SQL Serverで「sample」という名前のデータベースの中に「Tサンプル1」、「Tサンプル2」および「Tユニオンサンプル」の3つのテーブルを用意しました。ユニオンクエリの結果を「Tユニオンサンプル」に取り込みます。


Accessでフォームを準備する

下記のブログ記事でグラフを表示するフォームを作成しました。このときは「Tサンプル」テーブルをAccessに作成しましたが、今回は2つのテーブルをSQL Serverに作成し、ユニオンクエリの結果をAccessに取り込みます。。
【Access VBA】月別グラフの作成-データのない日も表示する - カットマンブログ

参照設定の準備

Visual Basic Editorを表示し、「ツール」タブの「参照設定」をクリック
   ↓
Microsoft ActiveX Data Objects ×.× Library」をチェック


コードの記述

標準モジュールの「SetLastDay」プロシージャを下記のコードで置き換えます。

Public Sub SetLastDay(ByVal Y As Integer, ByVal M As Integer)
'----------SQL Serverでユニオンクエリの結果を「Tユニオンサンプル」に取り込む----------
    Dim strCN As String
    Dim cn As New ADODB.Connection
    strCN = "driver={ODBC Driver 17 for SQL Server};server=LAPTOP-114315\SQLEXPRESS;DATABASE=sample;uid=administrator;PWD=1111"
    cn.Open strCN
    Dim cmd As New ADODB.Command
    cmd.ActiveConnection = cn
    cmd.CommandType = adCmdText
    cmd.CommandText = "DELETE FROM Tユニオンサンプル"
    cmd.Execute
    cmd.CommandText = "INSERT INTO Tユニオンサンプル SELECT A.日付,A.数量 FROM Tサンプル1 AS A UNION ALL SELECT B.日付,B.数量 FROM Tサンプル2 AS B"
    cmd.Execute
    Set cmd = Nothing
    cn.Close: Set cn = Nothing
'----------SQL Serverの「Tユニオンサンプル」から目的のデータを取り出す----------------
    Dim w_cn As New ADODB.Connection
    Set w_cn = CurrentProject.AccessConnection
    Dim w_cmd As New ADODB.Command
    w_cmd.ActiveConnection = w_cn
    w_cmd.CommandType = adCmdText
    w_cmd.CommandText = "DELETE FROM WTサンプル"
    w_cmd.Execute
    w_cmd.CommandText = "INSERT INTO WTサンプル SELECT A.日付, A.数量 FROM Tユニオンサンプル AS A IN ''[ODBC;" & strCN & "] WHERE Year(A.日付) =" & Y _
                        & "AND Month(A.日付) =" & M & " ORDER BY A.日付"
    w_cmd.Execute
    Set w_cmd = Nothing
'-------------------------------------------------------------------------------------
    Dim i As Integer
    Dim LastDay As Integer
    LastDay = Day(DateSerial(Y, M + 1, 1) - 1)
'----------「WTサンプル」に月初と月末の空白データを作成-----------------------------
    Dim w_strSQL As String
    w_strSQL = "SELECT * FROM WTサンプル"
    Dim w_rs As New ADODB.Recordset
    w_rs.Open w_strSQL, w_cn, adOpenKeyset, adLockOptimistic
    If w_rs.RecordCount <> 0 Then
        For i = Day(DMin("日付", "WTサンプル")) - 1 To 1 Step -1
            w_rs.AddNew
            w_rs![日付] = DateSerial(Y, M, i)
            w_rs![数量] = 0
            w_rs.Update
        Next
        For i = Day(DMax("日付", "WTサンプル")) + 1 To LastDay
            w_rs.AddNew
            w_rs![日付] = DateSerial(Y, M, i)
            w_rs![数量] = 0
            w_rs.Update
        Next
    End If
'--------------------------------------------------------------------------------------
    w_rs.Close: Set w_rs = Nothing
    w_cn.Close: Set w_cn = Nothing
End Sub

【Access VBA】主キー値をSQL Serverから取得する


主キー値をSQL Serverから取り出す

AccessSQL Serverのテーブルに接続し、新規レコード保存の際、主キー値をSQL Serverで設定します。


SQL Serverにテーブルを準備する

SQL Serverで「sample」という名前のデータベースの中に「Tサンプル」という名前のテーブルと「T発番」という名前のテーブルを用意しました。


SQL Serverにストアドプロシージャを準備する

SQL Serverに「SetID」という名前のストアドプロシージャを用意しました。

ALTER PROCEDURE [dbo].[SetID]
	
	@ID int OUTPUT 
AS
BEGIN
	
	SET NOCOUNT ON;
    
	BEGIN TRY
		BEGIN TRANSACTION
			SELECT @ID=連番 FROM T発番
			UPDATE T発番 SET 連番=@ID+1
		COMMIT TRANSACTION 
		RETURN-1 
	END TRY

	BEGIN CATCH
		ROLLBACK TRANSACTION		
		RETURN 0
	END CATCH
END


フォームの準備

下のようなラベル3個、テキストボックス3個、ボタン1個を配置した非連結フォームを用意しました。


コードの記述

フォームの開く時と「更新」ボタンのクリック時に下記イベントプロシージャを記述しました。

Private cn As New ADODB.Connection
Private rs As New ADODB.Recordset
Private Sub Form_Open(Cancel As Integer)
'-----------SQL Serverの「sample」データベースに接続-----------------------
    Dim strCN As String
    strCN = "driver={ODBC Driver 17 for SQL Server};server=LAPTOP-114315\SQLEXPRESS;DATABASE=sample;uid=administrator;PWD=1111"
    cn.Open strCN
'------------------------------------------------------------------------------------
'-----------ストアドプロシージャからIDを取得---------------------------------
    Dim cmd As New ADODB.Command
    cmd.ActiveConnection = cn
    cmd.CommandType = adCmdStoredProc
    cmd.CommandText = "SetID"
    cmd.Parameters.Append cmd.CreateParameter(, adInteger, adParamReturnValue, , Null)
    cmd.Parameters.Append cmd.CreateParameter("@ID", adInteger, adParamOutput, , Null)
    cmd.Execute
    
    If CBool(cmd.Parameters(0).Value) Then
        If IsNull(txtID) Then
            txtID = cmd.Parameters("@ID").Value
        End If
    Else
        MsgBox "IDの取得に失敗しました。", vbExclamation, "確認"
        cn.Close: Set cn = Nothing
        Cancel = True
    End If
'-----------------------------------------------------------------------------------
End Sub
Private Sub btnUpdate_Click()
    Dim strSQL As String
    strSQL = "SELECT * FROM Tサンプル WHERE ID=" & txtID
    rs.Open strSQL, cn, adOpenKeyset, adLockOptimistic

            rs.AddNew
            rs![ID] = txtID
            rs![日付] = txtDate
            rs![数量] = txtQuantity
            rs.Update

    rs.Close: Set rs = Nothing
    cn.Close: Set cn = Nothing
    DoCmd.Close
End Sub


テスト

現在、「ID」が「130」までのデータが保存されているとします。

フォームを開くと「ID」テキストボックスに「131」が発番されます。

日付と数量を入力し「更新」ボタンをクリックするとテーブルにレコードが保存されます。

【Access VBA】SQL ServerにADOで接続する


SQL Serverにテーブルを準備する

SQL Serverで「sample」という名前のデータベースの中に「Tサンプル」という名前のテーブルを用意しました。


Accessでフォームを準備する

下記のブログ記事でグラフを表示するフォームを作成しました。このときは「Tサンプル」テーブルをAccessに作成しましたが、今回は「Tサンプル」テーブルをSQL Serverに作成し、ADOで接続します。

【Access VBA】月別グラフの作成-データのない日も表示する - カットマンブログ


参照設定の準備

Visual Basic Editorを表示し、「ツール」タブの「参照設定」をクリック
   ↓
Microsoft ActiveX Data Objects ×.× Library」をチェック


コードの記述

標準モジュールの「SetLastDay」プロシージャを下記のコードで置き換えます。

Public Sub SetLastDay(ByVal Y As Integer, ByVal M As Integer)
'----------SQL Serverの「Tサンプル」から目的のデータを取り出す--------------------
    Dim w_cn As New ADODB.Connection
    Set w_cn = CurrentProject.AccessConnection
    Dim cmd As New ADODB.Command
    cmd.ActiveConnection = w_cn
    cmd.CommandType = adCmdText
    cmd.CommandText = "DELETE FROM WTサンプル"
    cmd.Execute
    Dim strCN As String
    strCN = "driver={ODBC Driver 17 for SQL Server};server=LAPTOP-114315\SQLEXPRESS;DATABASE=sample;uid=administrator;PWD=1111"
    cmd.CommandText = "INSERT INTO WTサンプル SELECT A.日付, A.数量 FROM Tサンプル AS A IN ''[ODBC;" & strCN & "] WHERE Year(A.日付) =" & Y _
                        & "AND Month(A.日付) =" & M & " ORDER BY A.日付"
    cmd.Execute
    Set cmd = Nothing
'----------------------------------------------------------------------------------
    Dim i As Integer
    Dim LastDay As Integer
    LastDay = Day(DateSerial(Y, M + 1, 1) - 1)
'----------「WTサンプル」に月初と月末の空白データを作成---------------------------
    Dim w_strSQL As String
    w_strSQL = "SELECT * FROM WTサンプル"
    Dim w_rs As New ADODB.Recordset
    w_rs.Open w_strSQL, w_cn, adOpenKeyset, adLockOptimistic
    If w_rs.RecordCount <> 0 Then
        For i = Day(DMin("日付", "WTサンプル")) - 1 To 1 Step -1
            w_rs.AddNew
            w_rs![日付] = DateSerial(Y, M, i)
            w_rs![数量] = 0
            w_rs.Update
        Next
        For i = Day(DMax("日付", "WTサンプル")) + 1 To LastDay
            w_rs.AddNew
            w_rs![日付] = DateSerial(Y, M, i)
            w_rs![数量] = 0
            w_rs.Update
        Next
    End If
'-----------------------------------------------------------------------------------
    w_rs.Close: Set w_rs = Nothing
    w_cn.Close: Set w_cn = Nothing
End Sub

グラフ表示されました。