ラベル Excel の投稿を表示しています。 すべての投稿を表示
ラベル Excel の投稿を表示しています。 すべての投稿を表示

VBAでCSVを分割する(カンマ、改行対応)

 カンマ、改行を含むCSVファイルを取り込む処理を作ろうかと思っていたら、有名なアドオンRelaxTools Addinの作者の方がGitHubにすでに公開してくれていた。
ただ、自分の実力ではインターフェースの実装などで読み切れないところがあったので最小単位に分割してCSV分割機能だけを分離した。勉強のためにいったん載せておく。
なお、StringBuilderクラスについては、別のサイトから転記した
転記元:https://github.com/RelaxTools/Hidennotare/tree/master/src
    http://dev-clips.com/clip/vba/stringbuilder-for-vba/

呼び出し元(モジュール)
********************
Public Sub CsvParser_Sample()
    Dim strBuf As String
    Dim Row As Collection
    Dim Col As Collection
    Dim v As Variant
    strBuf = "1, Watanabe, Fukushima, 36, ""カンマがあっても,OK""" & vbCrLf & "2, satoh, chiba, 24, ""改行があっても" & vbLf & "OKやで"""
    Dim IC As Object
    Set IC = New CharCursor
    
    Set Row = CsvParser(strBuf, True)
    For Each Col In Row
        For Each v In Col
            Debug.Print v
        Next
    Next
End Sub

'--------------------------------------------------------------
' CSVパーサー RFC4180準拠
' コーテーションの有無, デリミタ指定あり
'--------------------------------------------------------------
Public Function CsvParser(ByVal strBuf As String, Optional ByVal Quatation As Boolean = False, Optional ByVal Delimiter As String = ",") As Collection
    Const C_QUAT As String = """"
    Dim IC As CharCursor
    Dim sw As Boolean
    Dim blnLineBreak As Boolean
    Dim blnItemBreak As Boolean
    Dim Col As Collection
    Dim Row As Collection
    Dim lngQuot As Long
    lngQuot = 0
    sw = False
    Set Row = New Collection
    Set IC = New CharCursor
    Call IC.NewInstance(strBuf)
'
    Do Until IC.Eof
        '初期化
        Set Col = New Collection
        blnLineBreak = False
        Do Until IC.Eof Or blnLineBreak
            '初期化
            Dim SB As StringBuilder
            Set SB = New StringBuilder
            blnItemBreak = False
            Do Until IC.Eof Or blnLineBreak Or blnItemBreak
                Select Case IC.Item
                    Case C_QUAT
                        'コーテーションありの場合
                        If Quatation Then
                            lngQuot = lngQuot + 1
                            If sw Then
                                '次の文字がQuatation
                                If IC.Item(1) = C_QUAT Then
                                    lngQuot = lngQuot + 1
                                    SB.Append C_QUAT
                                    IC.MoveNext
                                Else
                                    sw = False
                                End If
                            Else
                                sw = True
                            End If
                        End If
                    Case vbCr
                        If sw Then
                            SB.Append IC.Item
                        End If
                    Case vbLf
                        If sw Then
                            SB.Append IC.Item
                        Else
                            blnLineBreak = True
                        End If
                    Case Delimiter
                        If sw Then
                            SB.Append IC.Item
                        Else
                            blnItemBreak = True
                        End If
                    Case Else
                        SB.Append IC.Item
                End Select
                IC.MoveNext
            Loop
            '列追加処理
            Col.Add SB.ToString
        Loop
        '行追加処理
        Row.Add Col
    Loop
    'ダブルコーテーションが偶数ではない場合
    If lngQuot Mod 2 <> 0 Then
'        Message.Throw 1, Me, "CsvParser", "Invalid Format(Quotation)"
    End If
    Set CsvParser = Row
    
End Function
********************


CharCursorクラス

********************
Private mBuf As String
Private mIndex As Long
Private Sub Class_Initialize()
    mIndex = 1
End Sub

Public Function NewInstance(ByVal Source As String) As CharCursor
    Dim p As CharCursor
    Set p = New CharCursor
    mBuf = Source
    Set NewInstance = p
    NewInstance.MoveFirst
End Function
Public Function Eof() As Boolean
    Eof = Len(mBuf) < mIndex
End Function
Public Sub MoveFirst()
    mIndex = 1
End Sub
Public Sub MoveNext()
    mIndex = mIndex + 1
End Sub
Public Function Item(Optional ByVal opt As Variant) As Variant
    Dim lngPos As Long
    If IsMissing(opt) Then
        opt = 0
    End If
    lngPos = mIndex + opt
    If lngPos < 1 Or lngPos > Len(mBuf) Then
        Item = ""
    Else
        Item = Mid$(mBuf, lngPos, 1)
    End If
End Function
-------------------------



StringBuilderクラス
-------------------------
'************************************************************************

'*-----------------------------------------------------------------------
'*  Name: StringBuilder (Class Module)
'*-----------------------------------------------------------------------
'*  Descriptioin:StringBuilder for VBA
'*-----------------------------------------------------------------------
'*  Copyright: HAYs  http://dev-clips.com , 2015 All Rights Reserved.
'*-----------------------------------------------------------------------
'*  <Update>
'*  Date        Version     Author     Memo
'*-----------------------------------------------------------------------
'*  2015.11.25  1.00        HAYs       New Release
'************************************************************************
' option
Option Explicit
'************************************************************************
'*  variable
'************************************************************************
Private pCapacity As Long
Private pLength As Long
Private mBuffer As String
'************************************************************************
'*  class event
'************************************************************************
'*-----------------------------------------------------------------------
'*  constructor
'*-----------------------------------------------------------------------
Private Sub Class_Initialize()
    pCapacity = 1023
    Me.Clear
End Sub
'*-----------------------------------------------------------------------
'*  destructor
'*-----------------------------------------------------------------------
Private Sub Class_Terminate()
    'clean up
    mBuffer = vbNullString
End Sub
'************************************************************************
'*  property
'************************************************************************
'*-----------------------------------------------------------------------
'*  Capacity
'*-----------------------------------------------------------------------
Friend Property Let Capacity(ByVal NewValue As Long)
    'ignore smaller NewValue
    If NewValue > pCapacity Then
        're-allocate
        mBuffer = mBuffer & String(NewValue - pCapacity, vbNullChar)
        'save new value
        pCapacity = NewValue
    End If
End Property
Friend Property Get Capacity() As Long
    Capacity = pCapacity
End Property
'*-----------------------------------------------------------------------
'*  Length
'*-----------------------------------------------------------------------
Friend Property Let Length(ByVal NewValue As Long)
    If NewValue < pLength Then
        Mid(mBuffer, NewValue + 1, pLength - NewValue) = _
            String$(pLength - NewValue, vbNullChar)
    End If
    pLength = NewValue
End Property
Friend Property Get Length() As Long
    Length = pLength
End Property

'************************************************************************
'*  method
'************************************************************************
'*-----------------------------------------------------------------------
'*  clear
'*-----------------------------------------------------------------------
Friend Function Clear() As StringBuilder
    'initialize length
    pLength = 0
    'allocate memory
    mBuffer = String$(pCapacity, vbNullChar)
    'return me
    Set Clear = Me
End Function
'*-----------------------------------------------------------------------
'*  append
'*-----------------------------------------------------------------------
Friend Function Append(ByRef StringValue As String) As StringBuilder
    Dim pos As Long
    Dim tmpCap As Long
    'set position
    pos = pLength + 1
    'add new length
    pLength = pLength + Len(StringValue)
    'check overflow
    If pLength > pCapacity Then
        'expand capacity *doubles up
        tmpCap = pCapacity
        Do While tmpCap < pLength
            tmpCap = tmpCap * 2
        Loop
        'save new capacity
        Me.Capacity = tmpCap
    End If
    'append
    Mid(mBuffer, pos) = StringValue
    'retrun me
    Set Append = Me
End Function
'*-----------------------------------------------------------------------
'*  insert
'*-----------------------------------------------------------------------
Friend Function Insert(ByRef StringValue As String, _
                        ByVal position As Long) As StringBuilder
    Dim tmpCap As Long
    Dim tmpLen As Long
    'check position
    Select Case position
        Case 1 To pLength
        Case Is < 1: position = 1
        Case Else
            Set Insert = Append(StringValue)
            Exit Function
    End Select
    'save length
    tmpLen = pLength
    'add new length
    pLength = pLength + Len(StringValue)
    'check overflow
    If pLength > pCapacity Then
        'expand Capacity *doubles up
        tmpCap = pCapacity
        Do While tmpCap < pLength
            tmpCap = tmpCap * 2
        Loop
        'save new capacity
        Me.Capacity = tmpCap
    End If
    'slide
    Mid(mBuffer, position + Len(StringValue) _
        ) = Mid$(mBuffer, position, tmpLen)
    'insert
    Mid(mBuffer, position) = StringValue
    'retrun me
    Set Insert = Me
End Function
'*-----------------------------------------------------------------------
'*  string value
'*-----------------------------------------------------------------------
Friend Function ToString() As String
    ToString = Left$(mBuffer, pLength)
End Function
********************

CSVデータをExcelファイルに取り込んで数式を設定するマクロ

 sqlite3のバッチで出力したCSVファイルを取り込んで、数式を設定し、一覧を作成するマクロを作成した。データも項目も適当・・・

・設定シート

・CSVデータ


・結果一覧


※設定シートの注文数×200を送金額という数式を設定してあって、数式を貼り付けているので結果一覧は計算された結果になっている
ソース
----------------------------------------------------------------------------

'最大で300項目まで処理
Private Const FINT_MAX_COL_COUNT As Integer = 300
Private Const FINT_CSV_DATA_STA_COLUMN As Integer = 3
Private Const FINT_CSV_DATA_STA_ROW As Integer = 4
Private Const FINT_TITLE_STA_COLUMN As Integer = 3
Private Const FINT_TITLE_STA_ROW As Integer = 5
Private Const FINT_DATA_STA_COLUMN As Integer = 3
Private Const FINT_DATA_STA_ROW As Integer = 6

Public Sub test()
    Application.ScreenUpdating = False
    
    Dim iMaxColumn As Integer
    iMaxColumn = GetMaxColumnCount(Sheet1.Name, FINT_TITLE_STA_ROW, FINT_TITLE_STA_COLUMN)
    Dim rdCsv As ADODB.Recordset
    Set rdCsv = New ADODB.Recordset
    'データ取り込み処理
    Call GetCsvData(rdCsv)
    Dim intColCount As Integer
    Dim iColTmp As Integer
    Dim rdResult As ADODB.Recordset
    Set rdResult = New ADODB.Recordset
    
    intColCount = iMaxColumn - FINT_TITLE_STA_COLUMN + 1
    ReDim strCsvDataColumnName(intColCount) As String
    ReDim strTitleDataColumnName(intColCount) As String
    For iColTmp = 0 To intColCount - 1
        strCsvDataColumnName(iColTmp) = ThisWorkbook.Sheets(Sheet1.Name).Cells(FINT_CSV_DATA_STA_ROW, FINT_CSV_DATA_STA_COLUMN + iColTmp)
        strTitleDataColumnName(iColTmp) = ThisWorkbook.Sheets(Sheet1.Name).Cells(FINT_TITLE_STA_ROW, FINT_TITLE_STA_COLUMN + iColTmp)
        rdResult.Fields.Append strTitleDataColumnName(iColTmp), adBSTR
    Next
    
    
    Dim iRowCnt As Long
    iRowCnt = 0
    rdResult.Open
    Do Until rdCsv.Eof
        rdResult.AddNew
        For iColTmp = 0 To intColCount - 1
            If strCsvDataColumnName(iColTmp) <> "" Then
                rdResult.Fields(strTitleDataColumnName(iColTmp)) = rdCsv.Fields(strCsvDataColumnName(iColTmp))
            Else
                rdResult.Fields(strTitleDataColumnName(iColTmp)) = ""
            End If
        Next
        rdResult.Update
        rdCsv.MoveNext
    Loop
    
    'データを削除する
    ThisWorkbook.Sheets(Sheet2.Name).Rows("1:100000").Delete
    'データを書き出す
    rdResult.MoveFirst
'    見出しは後で貼り付ける
'    For iColTmp = 1 To rdResult.Fields.Count
'        ThisWorkbook.Sheets(Sheet2.Name).Cells(1, iColTmp) = rdResult.Fields(iColTmp - 1).Name
'    Next
    ThisWorkbook.Sheets(Sheet2.Name).Range("A2").CopyFromRecordset rdResult
    
    Dim strRange As String
    Dim strRangeSta As String
    
    'タイトルを貼り付ける
    strRangeSta = Replace(Replace(ThisWorkbook.Sheets(Sheet1.Name).Cells(FINT_TITLE_STA_ROW, FINT_TITLE_STA_COLUMN).Address, "$" & FINT_TITLE_STA_ROW, ""), "$", "")
    strRange = Replace(Replace(ThisWorkbook.Sheets(Sheet1.Name).Cells(FINT_TITLE_STA_ROW, iMaxColumn).Address, "$" & FINT_TITLE_STA_ROW, ""), "$", "")
    ThisWorkbook.Sheets(Sheet1.Name).Range(strRangeSta & FINT_TITLE_STA_ROW & ":" & strRange & FINT_TITLE_STA_ROW).Copy
    ThisWorkbook.Sheets(Sheet2.Name).Select
    ThisWorkbook.Sheets(Sheet2.Name).Range("A1").Select
    ActiveSheet.Paste
    
    'データの書式を貼り付ける
    ThisWorkbook.Sheets(Sheet1.Name).Select
    ThisWorkbook.Sheets(Sheet1.Name).Range(strRangeSta & FINT_DATA_STA_ROW & ":" & strRange & FINT_DATA_STA_ROW).Copy
    strRange = Replace(Replace(ThisWorkbook.Sheets(Sheet2.Name).Cells(1, intColCount).Address, "$1", ""), "$", "")
    ThisWorkbook.Sheets(Sheet2.Name).Select
    ThisWorkbook.Sheets(Sheet2.Name).Range("A2:" & strRange & CLng(rdResult.RecordCount + 1)).Select
    Selection.PasteSpecial Paste:=xlPasteFormats, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
    
    '数式を貼り付ける
    For iColTmp = 0 To intColCount - 1
        If strCsvDataColumnName(iColTmp) = "" Then
            strRange = Replace(Replace(ThisWorkbook.Sheets(Sheet2.Name).Cells(1, iColTmp + 1).Address, "$1", ""), "$", "")
            
            ThisWorkbook.Sheets(Sheet1.Name).Cells(FINT_DATA_STA_ROW, FINT_DATA_STA_COLUMN + iColTmp).Copy
            ThisWorkbook.Sheets(Sheet2.Name).Select
            ThisWorkbook.Sheets(Sheet2.Name).Range(strRange & "2:" & strRange & CLng(rdResult.RecordCount + 1)).Select
            Selection.PasteSpecial Paste:=xlPasteFormulas, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
        End If
    
    Next
    Application.ScreenUpdating = True
    
End Sub

'最大の列番号を取得する
Private Function GetMaxColumnCount(ByVal strSheetName As String, ByVal intStaCol As Integer, ByVal intStaRow As Integer) As Integer
    ThisWorkbook.Sheets(strSheetName).Select
    ThisWorkbook.Sheets(strSheetName).Cells(intStaCol, intStaRow).Select
    Selection.End(xlToRight).Select
    GetMaxColumnCount = Selection.Column
End Function

Public Sub GetCsvData(ByRef rdData As ADODB.Recordset)
    Dim varFileName As Variant
    Dim intFree As Integer
    Dim strRec As String
    Dim strSplit() As String
    Dim i As Long, j As Long, k As Long
    Dim lngQuote As Long
    Dim strCell As String
    varFileName = Application.GetOpenFilename(FileFilter:="CSVファイル(*.csv),*.csv", _
                                                Title:="CSVファイルの選択")
    If varFileName = False Then
        Exit Sub
    End If
'    intFree = FreeFile '空番号を取得
'    Open varFileName For Input As #intFree 'CSVファィルをオープン
  
    Dim rdReturn As ADODB.Recordset
    Set rdReturn = New ADODB.Recordset
    Dim blnFirst As Boolean
    blnFirst = True
    i = 0
    Dim itemCount As Integer
    
    With CreateObject("ADODB.Stream")
        .Charset = "UTF-8"
        .Open
        .LoadFromFile varFileName
        
        Do While Not (.EOS)
            strRec = .ReadText(adReadLine)
            i = i + 1
            j = 0
            lngQuote = 0
            itemCount = 0
            strCell = ""
            
            If blnFirst = False Then rdReturn.AddNew
                            
            For k = 1 To Len(strRec)
                Select Case Mid(strRec, k, 1)
                    Case "," '「"」が偶数なら区切り、奇数ならただの文字
                        If lngQuote Mod 2 = 0 Then
                            'Call PutCell(i, j, strCell, lngQuote)
                            If blnFirst = True Then
                                rdReturn.Fields.Append ConvertValue(strCell, lngQuote), adBSTR
                            Else
                                'データを格納
                                rdReturn.Fields(itemCount) = ConvertValue(strCell, lngQuote)
                                itemCount = itemCount + 1
                            End If
                        Else
                            strCell = strCell & Mid(strRec, k, 1)
                        End If
                    Case """" '「"」のカウントをとる
                        lngQuote = lngQuote + 1
                        strCell = strCell & Mid(strRec, k, 1)
                    Case Else
                        strCell = strCell & Mid(strRec, k, 1)
                End Select
            Next
            '最終列の処理
            
            If blnFirst = True Then
                rdReturn.Fields.Append ConvertValue(strCell, lngQuote), adBSTR
                blnFirst = False
                rdReturn.Open
            Else
                'データを格納
                rdReturn.Fields(itemCount) = ConvertValue(strCell, lngQuote)
                itemCount = itemCount + 1
                rdReturn.Update
            End If
            
        Loop
        
        .Close
        
    End With
    
    rdReturn.MoveFirst
    
    'ThisWorkbook.Sheets(Sheet3.Name).Range("A2").CopyFromRecordset rdReturn
    Set rdData = rdReturn
    
End Sub
Function ConvertValue(ByRef strCell As String, ByRef lngQuote As Long) As String
    j = j + 1
    '「""」を「"」で置換
    strCell = Replace(strCell, """""", """")
    '前後の「"」を削除
    If strCell = """" Then
        strCell = ""
    ElseIf Left(strCell, 1) = """" And Right(strCell, 1) = """" Then
        strCell = Mid(strCell, 2, Len(strCell) - 2)
    End If
    ConvertValue = strCell
    strCell = ""
    lngQuote = 0
End Function


VBAからsqlite3.exeを呼び出してdllやODBCを使用せずにsqlite3を利用する

VBAからsqlite3.exeを呼び出してdllやODBCを使用せずにsqlite3を利用する

バッチファイルを実行し、結果ファイルをHTMLのTableタグで出力し、出力した結果を開いてRecodesetへ取り込む。

-------------------------------------------------

Private Const SQLITE_EXE As String = "sqlite3.exe"
Private Const RESULT_FILENAME As String = "Result.xml"
Private Const EXEC_SQL As String = "ExecSQL.sql"
Private Const EXEC_BATCH As String = "ExecBatch.bat"
Private Const DB_PATH As String = "C:\Users\UserName\Desktop\PgUserName\DataBase\開発PJ.prj"
Private Const FOLDER_PATH As String = "C:\Users\UserName\Desktop\PgUserName\"
Private Const CHARSET_UTF8 As String = "UTF-8"
Private Const CHARSET_SJIS As String = "Shift-JIS"
'参照設定:Microsoft ActiveX Data Object Library 2.8
Private Function GetData(ByVal strSql As String, ByRef rdSet As ADODB.Recordset)
 
    'SQLを実行する
    Call ExecuteSqlite3(strSql)
    '出力結果のファイルを取得し、RecordSetに取り込む
    Dim strResultFilePath As String
    strResultFilePath = ThisWorkbook.Path & "\temp\" & RESULT_FILENAME
    Dim buf As String, Target As String, lngRow As Long, lngItem As Long
    Dim blnIsHeader As Boolean
    Dim strRows As Variant, strItems As Variant, j As Long
    Dim strSplitName As String
    
    blnIsHeader = True
    
    Dim rdResult As ADODB.Recordset
    Set rdResult = New ADODB.Recordset
    
    With CreateObject("ADODB.Stream")
        .Charset = "UTF-8"
        .Open
        .LoadFromFile strResultFilePath
        buf = .ReadText
        .Close
        '<TR>~</TR>で1行
        
        
        buf = Replace(buf, "<TR>", "")
        strRows = Split(buf, "</TR>" & vbLf)
        For lngRow = 0 To UBound(strRows)
            If blnIsHeader = True Then
                strSplitName = "</TH>"
            Else
                strSplitName = "</TD>"
                '行を追加
                rdResult.AddNew
            End If
            
            strItems = Split(strRows(lngRow), strSplitName & vbLf)
            
            Dim strtemp As String
            strtemp = ""
            For lngItem = 0 To UBound(strItems) - 1  '空白行が最後に含まれる
                strtemp = Mid(strItems(lngItem), 5)
                
                If blnIsHeader = True Then
                    rdResult.Fields.Append strtemp, adLongVarWChar, 255, adFldIsNullable
                Else
                    rdResult.Fields(lngItem) = strtemp
                End If
                
            Next
            
            If blnIsHeader = True Then
                blnIsHeader = False
                rdResult.Open
            Else
                rdResult.Update
            End If
        Next
        
    End With
    Set rdSet = rdResult
End Function

Private Sub ExecuteSqlite3(ByVal strSql As String)
    Dim strCommand As String
    Dim strSqlCommand As String
    
    Dim strTempFolderPath As String
    strTempFolderPath = ThisWorkbook.Path & "\temp"
    If Dir(strTempFolderPath, vbDirectory) = "" Then
        MkDir strTempFolderPath
    End If
    
    Dim strSqlite3ExePath As String
    strSqlite3ExePath = ThisWorkbook.Path & "\" & SQLITE_EXE
    
    Dim strResultFilePath As String
    strResultFilePath = "./" & RESULT_FILENAME
    
    Dim strBatchFilePath As String
    strBatchFilePath = strTempFolderPath & "\" & EXEC_BATCH
    
    Dim strExecSqlPath As String
    strExecSqlPath = strTempFolderPath & "\" & EXEC_SQL
    'sqlite3を実行するときのオプションコマンド
    strSqlCommand = ".headers on " & vbCrLf
    strSqlCommand = strSqlCommand & ".mode html " & vbCrLf
    strSqlCommand = strSqlCommand & ".output '" & Replace(strResultFilePath, "\", "/") & "'" & vbCrLf
    
    strSqlCommand = strSqlCommand & strSql & vbCrLf
    
    'sqlite3に渡すコマンドを出力する
    Call TextOutput(strSqlCommand, strExecSqlPath, CHARSET_UTF8, True)
    'バッチ用コマンド
    strCommand = ""
    strCommand = strCommand & strSqlite3ExePath & " " & DB_PATH & " < " & strExecSqlPath
    'sqlite3に渡すコマンドを出力する
    Call TextOutput(strCommand, strBatchFilePath, CHARSET_SJIS)
    Dim wsh As Object
    Dim result As Integer
    
    Set wsh = CreateObject("WScript.Shell")
    
    'バッチファイルを同期実行
    result = wsh.Run(Command:=strBatchFilePath, WaitOnReturn:=True)
    If (result = 0) Then
        Debug.Print ("バッチファイルは正常終了しました。")
    Else
        Debug.Print ("バッチファイルは異常終了しました。")
    End If
    
    '後片付け
    Set wsh = Nothing
    
End Sub
Public Sub TextOutput(ByVal strText As String, ByVal strFilePath As String, ByVal strCharSet As String, Optional ByVal blnNoBOM As Boolean = False)
    Dim i As Integer
     
    'ADODB.Streamオブジェクトを生成
    Dim ado As Object
    Set ado = CreateObject("ADODB.Stream")
     
    'ADODB.Streamで扱う文字コードを設定する
    ado.Charset = strCharSet
     
    'ADODB.Streamを開く
    ado.Open
     
    '開いたADODB.Streamに内容を保管する
    'adWriteLineは改行する時に入れる
     ado.WriteText strText
        ' ---------- ここから ----------
    If blnNoBOM = True And strCharSet = CHARSET_UTF8 Then
    
        With ado
            ' タイプをバイナリにして、先頭の3バイトをスキップ
            .Position = 0
            .Type = 1 ' ADODB.StreamTypeEnum.adTypeBinary ' タイプ変更するにはPosition = 0である必要がある
            .Position = 3
            ' 一時格納用
            Dim p_byteData() As Byte
            p_byteData = .Read
            .Close ' 一旦閉じて
            .Open ' 再度開いて
            .Write p_byteData ' ストリームに書き込む
        End With
    
    End If
     
    'ADODB.Streamに保管されている内容をファイルに保存する
    ado.SaveToFile strFilePath, 2
     
    'ADODB.Streamを閉じる
    ado.Close
   
End Sub

VBAからSqlite3.exeを利用する方法を検討(ODBC・dllを利用せずにVBAからSqlite3を利用する)

VBAでSqlite3を利用する方法としては、何個かネットに上がっているがODBCのインストールができず、dllの利用は汎用性が高いが難易度が高いと思われたので、sqlite3.exeを利用するケースを検討してみた。

下記の流れで何とかなるのではないかと思い、いろいろと検証しているが、現時点で処理ができた分をメモのために記載する。
 ・実行するSqlを作成(UTF-8 BOM無しである必要がある)
   例)SQL文の内容(最初の3行はsqlite3.exeのオプション)
     .headers on 
     .mode html 
     .output ./result.xml 
      SELECT * FROM T_Card;

 ・Sqlを呼び出すためのバッチを作成(Shift-JISである必要がある)
     C:\Users\UserName\Desktop\PgUsrName\sqlite3.exe C:\Users\UserName\Desktop\PgUsrName\DataBase\開発PJ.prj < C:\Users\UserName\Desktop\PgUsrName\ExecSQL.sql

 ・出力形式はhtml形式を指定し、出力された結果をVBAでデシリアライズする
 ・処理完了後には一時ファイルを削除する(予定)
 ・出力されたファイルをRecodesetに読み込む(予定)

--------------------------------- ソース ----------------------
 Private Const SQLITE_EXE As String = "C:\Users\UserName\Desktop\PgUsrName\sqlite3.exe"
Private Const RESULT_FILEPATH As String = "Result.xml"
Private Const EXEC_SQL As String = "ExecSQL.sql"
Private Const EXEC_BATCH As String = "ExecBatch.bat"
Private Const DB_PATH As String = "C:\Users\UserName\Desktop\PgUsrName\DataBase\開発PJ.sqlite3"
Private Const FOLDER_PATH As String = "C:\Users\UserName\Desktop\PgUsrName\"
Private Const CHARSET_UTF8 As String = "UTF-8"
Private Const CHARSET_SJIS As String = "Shift-JIS"

Private Sub Sqlite()
    Dim strCommand As String
    Dim strSqlCommand As String
    Dim strSql As String
    
    '実行するsql
    strSql = strSql & " SELECT * FROM T_Card;"
    'sqlite3を実行するときのオプションコマンド
    strSqlCommand = ".headers on " & vbCrLf
    strSqlCommand = strSqlCommand & ".mode html " & vbCrLf
    strSqlCommand = strSqlCommand & ".output ./result.xml " & vbCrLf
    
    strSqlCommand = strSqlCommand & strSql & vbCrLf
    
    'sqlite3に渡すコマンドを出力する
    Call TextOutput(strSqlCommand, FOLDER_PATH & EXEC_SQL, CHARSET_UTF8, True)
    'バッチ用コマンド
    strCommand = SQLITE_EXE & " " & DB_PATH & " < " & FOLDER_PATH & EXEC_SQL
    'sqlite3に渡すコマンドを出力する
    Call TextOutput(strCommand, FOLDER_PATH & EXEC_BATCH, CHARSET_SJIS)
    Dim wsh As Object
    Dim result As Integer
    Set wsh = CreateObject("WScript.Shell")
    
    'バッチファイルを同期実行
    result = wsh.Run(Command:=FOLDER_PATH & EXEC_BATCH, WaitOnReturn:=True)
    
    If (result = 0) Then
        MsgBox ("バッチファイルは正常終了しました。")
    Else
        MsgBox ("バッチファイルは異常終了しました。")
    End If
    
    '後片付け
    Set wsh = Nothing
    
End Sub
Public Sub TextOutput(ByVal strText As String, ByVal strFilePath As String, ByVal strCharSet As String, Optional ByVal blnNoBOM As Boolean = False)
    Dim i As Integer
     
    'ADODB.Streamオブジェクトを生成
    Dim ado As Object
    Set ado = CreateObject("ADODB.Stream")
     
    'ADODB.Streamで扱う文字コードを設定する
    ado.Charset = strCharSet
     
    'ADODB.Streamを開く
    ado.Open
     
    '開いたADODB.Streamに内容を保管する
    'adWriteLineは改行する時に入れる
     ado.WriteText strText
'    For i = 1 To 5
'        ado.WriteText Cells(i, 1).Value, adWriteLine
'    Next
     
        ' ---------- ここから ----------
    If blnNoBOM = True And strCharSet = CHARSET_UTF8 Then
    
        With ado
            ' タイプをバイナリにして、先頭の3バイトをスキップ
            .Position = 0
            .Type = 1 ' ADODB.StreamTypeEnum.adTypeBinary ' タイプ変更するにはPosition = 0である必要がある
            .Position = 3
            ' 一時格納用
            Dim p_byteData() As Byte
            p_byteData = .Read
            .Close ' 一旦閉じて
            .Open ' 再度開いて
            .Write p_byteData ' ストリームに書き込む
        End With
    
    End If
     
    'ADODB.Streamに保管されている内容をファイルに保存する
    ado.SaveToFile strFilePath, 2
     
    'ADODB.Streamを閉じる
    ado.Close
     
     
End Sub

VBAで画像を表示し、スクロールバーを表示させる


◆概要
 VBAのフォームに画像を表示する際に、スクロールバーを付けて表示させる。
同じ様式の画像の差異をチェックするために、Excelの選択行にファイルパスを設定して起き、カーソル移動で画像の表示を変更する。
スクロールバーを表示させるためにFrameコントロールを利用するが、ScrollHeightとScrollWidthに設定する値が難しい。いろいろとネットを見ているとLoadPictureで読み込んだ画像に「24/635」をかけることで、画像プロパティの詳細設定にある幅、大きさを算出することができる。0.0378をかけているサンプルも多い
※24はビット深さ?635はわからない・・・。
    Dim obj As Object
    Set obj = LoadPicture(fstrPath)
    '画像の大きさはピクセルなので単位変換
    'MsgBox "H" & CLng(obj.Height * 24 / 635) & " W" & CLng(obj.Width * 24 / 635)
    Frame1.ScrollHeight =  CLng(obj.Height * 24 / 635)
    Frame1.ScrollWidth = CLng(obj.Width * 24 / 635)
なんだか係数の理由がわからないのが怖いので、今回はImageコントロールを使って幅と大きさを取得する。

◆ユーザーフォームのマクロ

画面上にImageコントロールとFrameコントロールを配置する。
fstrPathには画像パスを設定する。
------------------------------------------------------------------

Public fstrPath As String
Public Sub SetImage()
    Dim obj As Object
    Set obj = LoadPicture(fstrPath)
    
    'AutoSizeをTrueにしておいて画像に合わせてImageコントロールのサイズが変わるようにしておく
    Image1.Visible = False
    Image1.AutoSize = True
    Image1.Picture = obj
    
    Frame1.ScrollBars = fmScrollBarsBoth
    Frame1.ScrollHeight = Image1.Height
    Frame1.ScrollWidth = Image1.Width
    Frame1.Picture = obj

    '◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
    '必要であればスクロールの初期値位置を設定する
    Frame1.ScrollTop = 500
    Frame1.ScrollLeft = 200
    Frame1.Zoom = 80
    '◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
    
End Sub

◆シート側のマクロ

ユーザーフォームを先に宣言しておいて、表示したままExcelの選択行が変わったタイミングで画像だけ入れ替える。画像を見ながらひたすらEnterキーを押すことで画像だけ切り替えることができる。
------------------------------------------------------------------
  
Private frm As UserForm1
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
  '例として画像のパスをA列の1行目から6行目までに設定
    If 1 <= Target.Row And Target.Row <= 6 Then
        If frm Is Nothing Then
            Set frm = New UserForm1
            frm.Show vbModeless
        End If
        
        frm.fstrPath = Sheet1.Range("A" & Target.Row)
        frm.SetImage
        Sheet1.Select
        
    End If
End Sub


VBAのListViewで利用する共通関数(VBAでのListViewの設定方法)

 VBAのListViewが復活したいまさらながらに知ったので、共通関数として必要そうなものをつくってみた。共通モジュールに「modListView」を作って中に放り込んでおけばいろいろと使える・・・はず。

******* 画面での使い方 *******
Private Const COL_1_No As String = "_No"
Private Const COL_2_NAME As String = "_Name"
Private Const COL_3_COUNT As String = "_COUNT"
Private Sub CommandButton2_Click()
    Call SetData
End Sub
Private Sub LvSample_DblClick()
    MsgBox GetLvValueIdx(lvSample, lvSample.SelectedItem.Index, COL_1_No)
End Sub
Private Sub UserForm_Initialize()
    
    Call ControlInit
    
End Sub

Private Sub ControlInit()
    
    Call LvSample_Initialize
End Sub

'ListViewにテストデータを設定する
Private Sub SetData()
    lvSample.ListItems.Clear
    Dim i As Integer
    'データ設定方法1
    For i = 0 To 10
        lvSample.ListItems.Add
            
        Call SetLvValueIdx(lvSample, i + 1, COL_1_No, i)
        Call SetLvValueIdx(lvSample, i + 1, COL_2_NAME, "日本 太郎" & i)
        Call SetLvValueIdx(lvSample, i + 1, COL_3_COUNT, "1")
    Next
    
    'データ設定方法2
    Dim item As ListItem
    For i = 0 To 10
        
        Set item = lvSample.ListItems.Add
        
        Call SetLvValue(lvSample, item, COL_1_ATENA, "2 - " & i)
        Call SetLvValue(lvSample, item, COL_2_NAME, "日本 太郎2 - " & i)
        Call SetLvValue(lvSample, item, COL_3_COUNT, "1")
        '色変更サンプル
        If i = 1 Or i = 5 Then
            item.ForeColor = RGB(255, 0, 0)
        End If
        
        Set item = Nothing
        
    Next
End Sub

Private Sub LvSample_Initialize()
    
    'ListViewの共通プロパティ設定
    Call SetListViewProperty(lvSample)
    
    'ListViewの列を設定する
    With lvSample
        .ColumnHeaders.Add , COL_1_NO, "番号"
        .ColumnHeaders.Add , COL_2_NAME, "名前"
        .ColumnHeaders.Add , COL_3_COUNT, "数"
    End With
End Sub


*****************************

******* modListView の中身********
Public Sub SetListViewProperty(ByRef lv As ListView)
    With lv
        .View = lvwReport           ''表示
        .LabelEdit = lvwManual      ''ラベルの編集
        .HideSelection = False      ''選択の自動解除
        .AllowColumnReorder = True  ''列幅の変更を許可
        .FullRowSelect = False       ''行全体を選択
        .Gridlines = True           ''グリッド線
    End With
End Sub
'----------------------------------------------------------------------------------------------------------
'関数:列名からリストビューの値を取得する
'----------------------------------------------------------------------------------------------------------
Public Function GetLvValue(ByRef lv As ListView, ByRef lvItem As ListItem, ByVal strColKey As String) As String
    GetLvValue = ""
    
    Dim intColIdx As Integer
    intColIdx = GetColIdx(lv, strColKey)
    
    If intColIdx = -1 Then
        MsgBox "[エラー]関数の指定が間違っています。関数名:GetLvValue"
        Exit Function
    End If
    
    If intColIdx = 1 Then
        GetLvValue = lv.ListItems(lngItemIdx).Text
    Else
        GetLvValue = lv.ListItems(lngItemIdx).SubItems(intColIdx - 1)
    End If
    
End Function
'----------------------------------------------------------------------------------------------------------
'関数:列名からリストビューの値を取得する
'----------------------------------------------------------------------------------------------------------
Public Function GetLvValueIdx(ByRef lv As ListView, ByVal lngItemIdx As Long, ByVal strColKey As String) As String
    GetLvValueIdx = ""
    
    Dim intColIdx As Integer
    intColIdx = GetColIdx(lv, strColKey)
    
    If intColIdx = -1 Then
        MsgBox "[エラー]関数の指定が間違っています。関数名:GetLvValue"
        Exit Function
    End If
    
    If intColIdx = 1 Then
        GetLvValueIdx = lv.ListItems(lngItemIdx).Text
    Else
        GetLvValueIdx = lv.ListItems(lngItemIdx).SubItems(intColIdx - 1)
    End If
    
End Function

'----------------------------------------------------------------------------------------------------------
'関数:ListViewの指定項目の値を行Indexを指定して設定する
'----------------------------------------------------------------------------------------------------------
Public Sub SetLvValueIdx(ByRef lv As ListView, ByVal lngItemIdx As Long, ByVal strColKey As String, ByVal strValue As String)
    Dim intColIdx As Integer
    intColIdx = GetColIdx(lv, strColKey)
    
    If intColIdx = -1 Then
        MsgBox "[エラー]関数の指定が間違っています。関数名:SetLvValueIdx"
        Exit Sub
    End If
    
    If intColIdx = 1 Then
        lv.ListItems(lngItemIdx).Text = strValue
    Else
        lv.ListItems(lngItemIdx).SubItems(intColIdx - 1) = strValue
    End If
    
End Sub
'----------------------------------------------------------------------------------------------------------
'関数:ListViewの指定項目の値をListItemを指定して設定する
' 例: Dim item As ListItem
'       Set item = lvSample.ListItems.Add
'    Call SetLvValue(lvSample,item,"COL1_NAME","テスト 太郎")
'----------------------------------------------------------------------------------------------------------
Public Sub SetLvValue(ByRef lv As ListView, ByRef lvItem As ListItem, ByVal strColKey As String, ByVal strValue As String)
    Dim intColIdx As Integer
    intColIdx = GetColIdx(lv, strColKey)
    
    If intColIdx = -1 Then
        MsgBox "[エラー]関数の指定が間違っています。関数名:SetLvValue"
        Exit Sub
    End If
    
    If intColIdx = 1 Then
        lvItem.Text = strValue
    Else
        lvItem.SubItems(intColIdx - 1) = strValue
    End If
    
End Sub
'----------------------------------------------------------------------------------------------------------
'関数:列名からListViewの列Indexを取得する
'----------------------------------------------------------------------------------------------------------
Private Function GetColIdx(ByRef lv As ListView, ByVal strColKey As String) As Integer
    Dim i As Integer
    GetColIdx = -1
    For i = 1 To lv.ColumnHeaders.Count
        If lv.ColumnHeaders(i).Key = strColKey Then
            GetColIdx = i
            Exit Function
        End If
    Next
End Function
*****************************


VBAでStringを高速に扱う

Insert分を19万件分実行している処理が非常に遅い。
SQLを100件ずつまとめて実行しているけどやはり遅い。
文字列結合で遅くなっているので.NETであるStringBuilderと同じようなものがないか調べたところ、作っているサイトがあったのでメモ。
https://excel-ubara.com/excelvba4/EXCEL271.html

ー---
Option Explicit

Private sBuf As String
Private iBuf As Long

Private Sub Class_Initialize()
    sBuf = String$(32768, vbNullChar)
    iBuf = 0
End Sub

Public Sub Append(ByRef sValue As String)
    If iBuf + Len(sValue) > Len(sBuf) Then
        sBuf = sBuf & String$(CLng(Len(sBuf) * 2) + Len(sValue), vbNullChar)
    End If
    Mid(sBuf, iBuf + 1) = sValue
    iBuf = iBuf + Len(sValue)
End Sub

Public Function ToString() As String
    ToString = Left$(sBuf, iBuf)
End Function

VBA Recordsetの利用について

 VBAでDataTableに似たものを利用したいと思っていろいろと調べているとRecordsetがいい感じとのことだったので調べて必要そうなところを抜粋してメモしている。

Private Sub Sample()
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    
'▽Recordse定義
     Dim myRS As ADODB.Recordset
     Set myRS = New ADODB.Recordset
     With myRS.Fields
          .Append "商品番号", adVarWChar, -1
          .Append "番号", adVarWChar, -1
     End With
     myRS.Open

'▽データ取り込み-----------------------------------------
    Dim i As Long
    Dim FieldList As Variant: FieldList = Array("商品番号", "番号")
    i = 2
    Do
        myRS.AddNew FieldList, CreateArrayData(Sheet2.Range("A" & i & ":B" & i).Resize(1, 2).Value)
        i = i + 1
    Loop Until IsEmpty(Sheet2.Cells(i, 1))
     
     myRS.MoveFirst
    
'Recordsetデータ検索-----------------------------------------
    i = 2
    Do
        myRS.Filter = "商品番号 = '" & Sheet1.Cells(i, 1).Value & "'"
        Sheet1.Cells(i, 2) = myRS!番号
        i = i + 1
        If i Mod 1000 = 0 Then
        End If
    Loop Until IsEmpty(Sheet1.Cells(i, 1))
'閉じる
     myRS.Close
     Set myRS = Nothing

    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic
    
End Sub

Function CreateArrayData(SecDimArray As Variant) As Variant
     Dim myVar As Variant
     Dim i As Long
     ReDim myVar(0 To UBound(SecDimArray, 2) - 1)
     For i = 0 To UBound(myVar)
          myVar(i) = SecDimArray(1, i + 1)
     Next
     CreateArrayData= myVar
End Function

Excel VBAで郵便番号・住所からカスタマーバーコードを作成

 Excelマクロでカスタマーバーコードのもとになるデータを作る必要があったので、調べてみたらいい古い記事だけどわかりやすいプログラムを記載しているブログがあった。

https://shiganaisenotes.blogspot.com/2011/11/blog-post.html

一部うまく変換できなかったところがあったので、その個所を修正して全文を転記させてもらった。例)〇〇丁目〇番地 七番館 101号室 の場合に「七番館」が変換できない

使い方としては、VBAの標準モジュールにコードを張り付けて「参照設定」に「Microsoft VBScript Regular Expressions 5.5」を追加すれば使うことができる。
変換した結果を、Wordの差し込み印刷でバーコードフィールドを利用することでカスタマーバーコードを利用することができ、郵送代を減らすことができる。ハズ。

---------------------------------------------------------------------------
Public Const FIND_NUM As String = "壱弐参〇一二三四五六七八九十百千"
Public Const FIND_TEXT As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
'参照設定を追加する
'手順:[ツール] -> [参照設定] -> Microsoft VBScript Regular Expressions 5.5をチェック
Public Function CreateCustomerBarCode(strZip As String, ByVal strAddress As String) As String
    Dim strRetZip       As String
    Dim strRetAddress   As String
    Dim strChar         As String
    Dim blnConvFlg      As Boolean   'ハイフンに置き換え済みフラグ
    Dim intLoop         As Integer
    Dim i               As Integer
    Dim j               As Integer
    Dim intCount        As Integer
    Dim intPos          As Integer
    Dim intTextLength   As Integer
    Dim intTargetPos    As Integer
    Dim strFindKeyword  As String
    Dim strTargetText   As String
    Dim strConvertText  As String
    Dim strTemp()       As String
    Dim strResult       As String
    Dim varConvertNum   As Variant
    Dim varDeleteText   As Variant
    Dim intFindPos      As Integer
    Dim objRegExp       As RegExp   '参照設定要(Microsoft VBScript Regular Expressions 5.5)
    Dim objMatchCollect As MatchCollection
    Dim objMatch        As Match
    Dim strTarget()     As String
    Dim intMatchPos()   As Integer
   
    varConvertNum = Array("1", "2", "3", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "100", "1000")
   
    varDeleteText = Array("&", "&", "/", "/", "・", "・", ".", ".")
   
    '郵便番号にハイフンがあったら除外します
    strRetZip = Replace(strZip, "-", "", , , vbTextCompare)
   
    '住所の全角文字を半角に変換します
    strAddress = StrConv(strAddress, vbNarrow)
     
    '住所に含まれる記号を除外します
    For intLoop = 1 To UBound(varDeleteText)
      strAddress = Replace(strAddress, varDeleteText(intLoop), "", , , vbTextCompare)
    Next intLoop
    '2つ以上連続したアルファベットを「-」へ置換する
    Set objRegExp = New RegExp
    '正規表現で2つ以上連続したアルファベットを検索し、「-」へ置換する。
    objRegExp.Pattern = "[a-zA-Z][a-zA-Z]+"
    objRegExp.Global = True '複数該当する場合に対応
    strAddress = objRegExp.Replace(strAddress, "-")
   
    '抜き出しの補足ルール
    'http://www.post.japanpost.jp/zipcode/zipmanual/p19.html
   
    '1. 漢数字が下記の特定文字の前にある場合は抜き出し対象とし、算用数字に変換して抜き出します。
    '特定文字群(9種類) "丁目"  "丁 " "番地"  "番"  "号"  "地割"  "線"  "の"  "ノ"
    
    '1つずつ見ていく
    For i = 1 To 9
        strFindKeyword = ""
        Select Case i
            Case 1
                strFindKeyword = "丁目"
            Case 2
                strFindKeyword = "丁"
            Case 3
                strFindKeyword = "番地"
            Case 4
                strFindKeyword = "番"
            Case 5
                strFindKeyword = "号"
            Case 6
                strFindKeyword = "地割"
            Case 7
                strFindKeyword = "線"
            Case 8
                strFindKeyword = "の"
            Case 9
                strFindKeyword = "ノ"
        End Select
       
        Dim ii As Integer
        intPos = 0
        ii = 0
        
        '〇〇丁目〇番地 七番館 101号室 の場合に「七番館」が変換できないため、キーワードでループ
        Do
             '当該キーワードが見つかるか?
             If ii = 0 Then
                intPos = InStr(strAddress, strFindKeyword)
             Else
                '同じキーワードで二回目、三回目をする場合は探す住所を前回の検索結果以降にする
                intPos = InStr(Mid$(strAddress, ii), strFindKeyword)
             
                '見つかった場合は、探す住所から何文字目かどうかをチェックする
                If intPos > 0 Then
                   intPos = intPos + ii - Len(strFindKeyword)
                End If
             
             End If
             ii = ii + intPos + Len(strFindKeyword)
            
             '見つかったら変換処理を行う。
             If intPos > 0 Then
                 '見つかったところから左へ1つずつ移動し漢数字じゃなくなったら終了
                 For intCount = intPos To 2 Step -1
                     strTargetText = Mid$(strAddress, (intCount - 1), 1)
                     intTargetPos = InStr(FIND_NUM, strTargetText)
                    
                     '漢数字なら継続
                     If intTargetPos > 0 Then
                     Else
                         '漢数字以外ならループを抜ける
                         Exit For
                     End If
                 Next intCount
                
                 strResult = ""
                
                 'カウントが開始位置と同一なら漢数字なしと判定して何もしない。
                 If intPos = intCount Then
                 Else
                     strConvertText = Mid$(strAddress, intCount, (intPos - intCount))
                     intTextLength = Len(strConvertText)
                     ReDim strTemp(intTextLength)
                     For j = 1 To intTextLength
                         intTargetPos = InStr(FIND_NUM, Mid$(strConvertText, j, 1))
                         strTemp(j) = varConvertNum(intTargetPos - 1)
                     Next j
                    
                     strResult = "0"
                    
                     '計算して数値を求める
                     For j = 1 To intTextLength
                         If (j = 1) Then
                             strResult = strTemp(j)
                         Else
                             '2桁以上なら前の桁と掛け算して足す
                             If Len(strTemp(j)) >= 2 Then
                                 'すでに代入されている値が2桁以上なら足す
                                 If Len(strResult) >= 2 Then
                                     strResult = CStr(CInt(strResult) + (CInt(strTemp(j - 1)) * CInt(strTemp(j))))
                                 Else
                                     '代入されている数字が1桁なら、そのまま代入
                                     strResult = CStr(CInt(strTemp(j - 1)) * CInt(strTemp(j)))
                                 End If
                             Else
                                 '1つ前が2桁以上か?
                                 If Len(strTemp(j - 1)) >= 2 Then
                                     '最終桁だったら足す
                                     If j = intTextLength Then
                                         strResult = CStr(CInt(strResult) + CInt(strTemp(j)))
                                     Else
                                         'それ以外だったら何もしないで次にまわす
                                     End If
                                 Else
                                     '1桁なら結合する
                                     strResult = strResult & strTemp(j)
                                 End If
                             End If
                         End If
                     Next j
                 End If
                 '変換した結果があれば処理する
                 If Len(strResult) = 0 Then
                 Else
                     strAddress = Left$(strAddress, (intCount - 1)) & strResult & Mid$(strAddress, intPos)
                 End If
             End If
        Loop While intPos > 0
        
    Next i
 
    '住所から数字部分だけを取り出して"("で区切ります
    strRetAddress = ""
   
    blnConvFlg = True '先頭の数字以外の文字は無視する設定
   
    For intLoop = 1 To Len(strAddress)
      '1文字取り出し
      strChar = Mid$(strAddress, intLoop, 1)
     
        If IsNumeric(strChar) Then
           
            '数字のとき
            strRetAddress = strRetAddress & strChar
            blnConvFlg = False
         
        Else
       
            'ハイフンに置き換え済みでないとき
            If Not blnConvFlg Then
                'アルファベットだったらそのままくっつける
                intFindPos = InStr(FIND_TEXT, strChar)
                If intFindPos > 0 Then
                    If intFindPos = 6 Then  'Fだったら特別扱い->"-"に置き換え
                        strRetAddress = strRetAddress & "-"
                    Else
                        If Right$(strRetAddress, 1) = "-" Then
                            strRetAddress = strRetAddress & strChar
                        Else
                            strRetAddress = strRetAddress & "-" & strChar & "-"
                        End If
                    End If
                Else
                    strRetAddress = strRetAddress & "-"
                End If
                blnConvFlg = True
            Else
                'アルファベットだったらそのままくっつける
                intFindPos = InStr(FIND_TEXT, strChar)
                If intFindPos > 0 Then
                    If Right$(strRetAddress, 1) = "-" Then
                        strRetAddress = strRetAddress & strChar
                    Else
                        strRetAddress = strRetAddress & "-" & strChar & "-"
                    End If
                Else
                    strRetAddress = strRetAddress & "-"
                End If
                blnConvFlg = True
            End If
        End If
    Next intLoop
   
    '最終処理
   
    '連続したハイフンは1つする
    objRegExp.Pattern = "\-\-+"
    objRegExp.Global = True '複数該当する場合に対応
    strRetAddress = objRegExp.Replace(strRetAddress, "-")
   
    '最後と先頭のハイフンを除去します
   
    If Left$(strRetAddress, 1) = "-" Then
      strRetAddress = Mid$(strRetAddress, 2)
    End If
   
    If Right$(strRetAddress, 1) = "-" Then
      strRetAddress = Left$(strRetAddress, Len(strRetAddress) - 1)
    End If
   
    '(アルファベットの前後の-(ハイフン)は取り除きます)
    objRegExp.Pattern = "-*[a-zA-Z]-*"
    objRegExp.Global = True '複数該当する場合に対応
   
    'パターンマッチするものをCollectionへ格納
    Set objMatchCollect = objRegExp.Execute(strRetAddress)
    '1つ以上あれば以降の処理を継続
    If objMatchCollect.Count > 0 Then
        '配列を再定義
        ReDim strTarget(objMatchCollect.Count)
        ReDim intMatchPos(objMatchCollect.Count)
       
        '複数該当に対応する
        For i = 0 To (objMatchCollect.Count - 1)
            'Collectionの情報を展開
            Set objMatch = objMatchCollect.Item(i)
            '-(ハイフン)をスペースへ変換して格納
            strTarget(i + 1) = Replace(objMatch.Value, "-", " ", , , vbTextCompare)
            '文字列が見つかった場所を格納
            intMatchPos(i + 1) = objMatch.FirstIndex
        Next i
       
        '実際に置き換える
        For i = 1 To objMatchCollect.Count
            strRetAddress = Left$(strRetAddress, intMatchPos(i)) & strTarget(i) & Mid$(strRetAddress, (intMatchPos(i) + Len(strTarget(i)) + 1))
        Next i
        '文字列中の空白を削って終了
        strRetAddress = Replace(strRetAddress, " ", "", , , vbTextCompare)
       
    End If
    '変換された郵便番号と住所を結合して返します
    CreateCustomerBarCode = strRetZip & strRetAddress
End Function

バーコード読み取りソフト

Excelでバーコードを読み取りたいと思ったけど、dllをsystem32へコピーする必要があったりとちょっと今の環境ではできないことだったので、専用のexeを作ってVectorで公開した。

  https://www.vector.co.jp/soft/winnt/writing/se523178.html


使い方はとっても簡単。Zxing.exeの引数にファイルパスを指定するだけ。

例)

 ・コマンドプロンプトを起動

 ・Zxing.exeのあるフォルダへ移動

 ・Zxing.exe c:\users\ユーザー名\DeskTop\バーコード画像.jpg

 ・Result.txtに結果が出力される


トリミングをする必要があるので、Excelのトリミング機能を使って、トリミングした後の画像を一時的にtemp.bmpとかに保存しておいてその画像をZxing.exeに渡してあげる。


Excelで簡易ガントチャートツール

仕事でプロジェクトツールが全く使われていなく、導入する予定もない部署への応援が決定した。

取り急ぎ、ガントチャートを作れるソフトが欲しかったのでExcelで作れるか試してみた。まぁちゃんとしたソフトがインストールできればそれでよかったんだけど、難しそうなので自作でしのげればと思う。

作成途中だけど、日付を入れるとその日の色が変わる程度なら作れたので一旦、ここまでとしておこうと思う。正直どんな管理項目が必要かもわからないので、そのあたりは融通が利くように列名の定義をできるようにしておいた。さて、必要そうな機能(まぁ色変えるだけなんだけど)の実装は終わったのであとはコードのコピペして完成品を作ろう。


<概要>
 ・処理開始時にメインシートに定義しておいた、色を変えたい日付範囲の項目名を探す
 ・列があとからどんどん追加されていくので「ガントチャート」部分の開始列が分かるように「【ガントチャート開始】」という文字列を入れておく。(消さないでね・・・)
 ・日付セルの場合は加算、減算ができるので基準日から開始日、終了日からの差を見ればどの列範囲かもわかるのでそこの色を変える。
 ・キーワードの設定(〆切日、その他色々)もできればいいな。(願望)
 ・範囲を複数持たせていろんな意味の色を塗れるようにする。発注期間の色、使用期限の色、利用期間の色、などなど(できればいいな)

<プログラム とりあえず4行目の色を変えるだけ>

Private fintBetween1Start As Integer
Private fintBetween1End As Integer
Private fintBetween2Start As Integer
Private fintBetween2End As Integer
Private fintBetween3Start As Integer
Private fintBetween3End As Integer
Private fintBetween4Start As Integer
Private fintBetween4End As Integer
Private fintBetween5Start As Integer
Private fintBetween5End As Integer
Private fdatePrjStartDate As Date

Private Const SHEETNAME_DATA As String = "データ"
Private Const SHEETNAME_MAIN As String = "メイン"
Private Const HeaderRow As Integer = 3
Private Const DataStartRow As Integer = 4


Private Const FINT_MAX_COLUMN_IN_DATE As Integer = 1500

'メインシート
Private Const FINT_KEYWORD_START_ROW As Integer = 6
Private Const FINT_KEYWORD_ITEMNAME_COLUMN As Integer = 12
Private Const FINT_KEYWORD_ITEMVALUE_COLUMN As Integer = 13

'データシート
Private fintGanttChartStartColumn As Integer

Private Const KEY_WORD_MAX_COUNT As Integer = 100 '何件でもいいがとりあえずの総件数

Private Sub Main()

    Call InitializeSetting
    
    Call InitializeRowStatus(4)
    
    Call SetRow(4)

End Sub


Private Sub SetRow(ByVal lngRow As Long)
    Dim lngDiff As Long
    Dim lngEndDiff As Long
    Dim rng As Range
    Dim rngColor As Range
    
    '範囲1の内容を色塗り
    lngDiff = DateDiff("d", fdatePrjStartDate, Sheets(SHEETNAME_DATA).Cells(lngRow, fintBetween1Start))
    lngEndDiff = DateDiff("d", fdatePrjStartDate, Sheets(SHEETNAME_DATA).Cells(lngRow, fintBetween1End))
    Set rngColor = Sheets(SHEETNAME_MAIN).Range("F7")
    Set rng = Sheets(SHEETNAME_DATA).Range(Sheets(SHEETNAME_DATA).Cells(lngRow, fintGanttChartStartColumn + lngDiff), Sheets(SHEETNAME_DATA).Cells(lngRow, fintGanttChartStartColumn + lngEndDiff))
    With rng.Interior
        .Color = rngColor.Interior.Color
        .TintAndShade = rngColor.Interior.TintAndShade
        .PatternTintAndShade = rngColor.Interior.PatternTintAndShade
    End With

    'キーワードを設定(作成中)

'    Dim i, j As Long
'    Dim strKeyWord As String
'    Dim strCellValue As String
'    Dim strColumns As String
'    Dim strColumnArr() As String
'
'    For i = 0 To 100
'        strKeyWord = Sheets(SHEETNAME_MAIN).Cells(i + FINT_KEYWORD_START_ROW, FINT_KEYWORD_ITEMVALUE_COLUMN).Value
'        If strKeyWord = "" Then Exit Sub
'        strColumns = GetSearchStringCell(strKeyWord, True, CStr(lngRow) & ":" & CStr(lngRow), SHEETNAME_DATA)
'
'        strColumnArr = Split(strColumns, ",")
'        For j = 0 To UBound(strColumnArr)
'            If Sheets(SHEETNAME_DATA).Cells(lngRow, strColumnArr(j)).Value = strKeyWord Then
'                Sheets(SHEETNAME_DATA).Cells(lngRow, strColumnArr(j)).Value = ""
'            End If
'        Next
'    Next


End Sub
Private Sub InitializeSetting()

    Dim strGanttChartStartDay As String
    strGanttChartStartDay = GetSearchStringCell("【ガントチャート開始】", True, "1:1", SHEETNAME_DATA)

    fintGanttChartStartColumn = CInt(strGanttChartStartDay)
    fdatePrjStartDate = Sheets(SHEETNAME_MAIN).Range("F4").Value
    fintBetween1Start = GetColumnNo(Sheets(SHEETNAME_MAIN).Range("F5").Value)
    fintBetween1End = GetColumnNo(Sheets(SHEETNAME_MAIN).Range("F6").Value)
    
End Sub

Private Function GetColumnNo(ByVal strColName As String)
    Dim i As Long
    GetColumnNo = 0
    For i = 1 To 100
        If Sheets(SHEETNAME_DATA).Cells(HeaderRow, i).Value = strColName Then
            GetColumnNo = i
            Exit Function
        End If
    Next
End Function
Private Function GetStartGanntChartColumn(ByVal strColName As String)
    Dim i As Long
    GetColumnNo = 0
    For i = 1 To 300
        If Sheets(SHEETNAME_DATA).Cells(1, i).Value = strColName Then
            GetColumnNo = i
            Exit Function
        End If
    Next
End Function


'---------------------------------------------------------
' 指定行の初期化処理を行う
'---------------------------------------------------------
Private Sub InitializeRowStatus(ByVal lngRow As Long)
    
    'Rowの背景色を全て削除
    Call ClearBackgroundInRow(lngRow)

    'キーワードを削除
    Call ClearKeyWordInRow(lngRow)
    
End Sub


Private Sub ClearBackgroundInRow(ByVal lngRow As Long)
    Dim rng As Range
    
    Dim strRange As String
    
    Set rng = Sheets(SHEETNAME_DATA).Range(Cells(lngRow, fintGanttChartStartColumn), Sheets(SHEETNAME_DATA).Cells(lngRow, FINT_MAX_COLUMN_IN_DATE))
    
    '背景色削除
    With rng.Interior
        .Pattern = xlNone
        .TintAndShade = 0
        .PatternTintAndShade = 0
    End With
    
End Sub

'---------------------------------------------------------
' キーワードとして設定されている内容をすべて削除する
'---------------------------------------------------------
Private Sub ClearKeyWordInRow(ByVal lngRow As Long)

    Dim i, j As Long
    Dim strKeyWord As String
    Dim strCellValue As String
    Dim strColumns As String
    Dim strColumnArr() As String
    
    For i = 0 To 100
        strKeyWord = Sheets(SHEETNAME_MAIN).Cells(i + FINT_KEYWORD_START_ROW, FINT_KEYWORD_ITEMVALUE_COLUMN).Value
        If strKeyWord = "" Then Exit Sub
        strColumns = GetSearchStringCell(strKeyWord, True, CStr(lngRow) & ":" & CStr(lngRow), SHEETNAME_DATA)
        
        strColumnArr = Split(strColumns, ",")
        For j = 0 To UBound(strColumnArr)
            If Sheets(SHEETNAME_DATA).Cells(lngRow, strColumnArr(j)).Value = strKeyWord Then
                Sheets(SHEETNAME_DATA).Cells(lngRow, strColumnArr(j)).Value = ""
            End If
        Next
    Next

End Sub


'---------------------------------------------------------
' 日付セルを検索
' シート名と行か列位置を指定し検索する。該当した行、列位置をカンマ区切りで返す。
'  1件ずつループしながらIf関数で1行ずつ判定するより圧倒的に高速
'  複数条件は対応外なので、この関数で該当行を絞ったうえで、If関数で詳細なチェックをかけたほうが高速になる
' 利用例)GetSearchDateCell("2020/08/01",False,"A:A","データ")
' 戻り値例)4,15,20
'---------------------------------------------------------
Public Function GetSearchDateCell(ByVal datValue As Date, ByVal blnSearchInRow As Boolean, ByVal strRange As String, ByVal strSheetName As String) As String
    Dim rng As Range
    Dim adr As String
    Dim strResult As String
    GetSearchDateCell = ""
  
    If blnSearchInRow = True Then
        Set rng = Sheets(strSheetName).Rows(strRange).Find(datValue)
    Else
        Set rng = Sheets(strSheetName).Columns(strRange).Find(datValue)
    End If
  
    If rng Is Nothing Then
        Exit Function
    Else
        adr = rng.Address
        If blnSearchInRow = True Then
            strResult = rng.Column
        Else
            strResult = rng.Row
        End If
    End If

    Do
      
    If blnSearchInRow = True Then
        Set rng = Sheets(strSheetName).Rows(strRange).FindNext(After:=rng)
    Else
        Set rng = Sheets(strSheetName).Columns(strRange).FindNext(After:=rng)
    End If
    
    If rng.Address = adr Then
            Exit Do
        Else
            If blnSearchInRow = True Then
                strResult = strResult & "," & rng.Column
            Else
                strResult = strResult & "," & rng.Row
            End If
        End If
    Loop

    GetSearchDateCell = strResult

End Function


'---------------------------------------------------------
' 文字列セルを検索
' シート名と行か列位置を指定し検索する。該当した行、列位置をカンマ区切りで返す。
'  1件ずつループしながらIf関数で1行ずつ判定するより圧倒的に高速
'  複数条件は対応外なので、この関数で該当行を絞ったうえで、If関数で詳細なチェックをかけたほうが高速になる
' 利用例)GetSearchDateCell("2020/08/01",False,"A:A","データ")
' 戻り値例)4,15,20
'---------------------------------------------------------
Public Function GetSearchStringCell(ByVal strValue As String, ByVal blnSearchInRow As Boolean, ByVal strRange As String, ByVal strSheetName As String) As String
    Dim rng As Range
    Dim adr As String
    Dim strResult As String
    GetSearchStringCell = ""
  
    If blnSearchInRow = True Then
        Set rng = Sheets(strSheetName).Rows(strRange).Find(strValue)
    Else
        Set rng = Sheets(strSheetName).Columns(strRange).Find(strValue)
    End If
  
    If rng Is Nothing Then
        Exit Function
    Else
        adr = rng.Address
        If blnSearchInRow = True Then
            strResult = rng.Column
        Else
            strResult = rng.Row
        End If
    End If

    Do
      
    If blnSearchInRow = True Then
        Set rng = Sheets(strSheetName).Rows(strRange).FindNext(After:=rng)
    Else
        Set rng = Sheets(strSheetName).Columns(strRange).FindNext(After:=rng)
    End If
    
    If rng.Address = adr Then
            Exit Do
        Else
            If blnSearchInRow = True Then
                strResult = strResult & "," & rng.Column
            Else
                strResult = strResult & "," & rng.Row
            End If
        End If
    Loop

    GetSearchStringCell = strResult

End Function









PowerShellでWPFアプリケーションをBindingするときの注意点

 1.PowerShellでWPFアプリケーションのBindingについて マニアックだけどPowerShellでWPFアプリケーションを作っている。INotifyChangedを実装したいけど、PowerShellにはgetterやsetterがないので通知が発行できない。 そ...