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

備忘録 Having句で集計関数を利用する

 HAVING句をうまく利用する
 
 意外と思いつかなかったけど、HAVING句でCASE句を利用することができる
 ※例は適当です。グループの中で条件を満たしていないものがあるかどうかのチェックができます。
 
  ・会社ごとに振込先口座を持っている
  ・グループ会社の中で異なる振込先があるグループがあるか
 
   (例)
    
   
    会社CD |   会社名   | グループ会社CD | 請求会社CD | 親会社CD | 銀行CD | 支店CD | 口座番号 | 口座名義人
    00001  |テスト1    | 001            | 00001      |  00001   |  0001  |  002   | 1234567  | カ)テスト1
    00002  |テスト1-1   | 001            | 00002      |  00001   |  0001  |  002   | 0004567  | カ)テスト2
    00003  |テスト1-2   | 001            | 00001      |  00001   |  0001  |  002   | 1234567  | カ)テスト1
    10001  |テスト2    | 002            | 10001      |  10001   |  0001  |  002   | 1234567  | カ)テスト2
    10002  |テスト2-1   | 002            | 10001      |  10001   |  0001  |  002   | 1234567  | カ)テスト2
    
    
    -- 「請求先」が「親会社」になっていないデータが混じっているグループ会社を抽出
  -- グループCDで集約を行い、グループCDでまとめた件数=レコードの中で「請求会社CD」
  SELECT グループ会社CD
  FROM 契約会社テーブル
  GROUP BY グループ会社CD
  HAVING COUNT(*) <> SUM(CASE 
                        WHEN 請求会社CD = 親会社CD THEN 1 
                        ELSE 0 
                    END) AS 抽出対象
  上記のデータ例で行くと「グループ会社CD:001」が抽出される
  
 集計関数の中にもCASE式が書けるって初めて知ったときに結構衝撃だったけど、月ごとの集計結果を列で持ちたいときとかにも使えるので素晴らしい。
 
  SELECT 
         商品品種名
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '01' THEN 売上金額 ELSE 0 END) AS 1月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '02' THEN 売上金額 ELSE 0 END) AS 2月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '03' THEN 売上金額 ELSE 0 END) AS 3月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '04' THEN 売上金額 ELSE 0 END) AS 4月売上
             ・
             ・
             ・
  FROM 売上テーブル
  WHERE 売上日 BETWEEN '20210101' AND '20211231'
  GROUP BY 商品品種名
             
  
  
 

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

Accessがインストールされていない環境でExcelからAccessファイルを作る

 Accessは使いこなせば非常に有効だけど、結構高い。

使いこなせばすぐに費用対効果は出せるけど、一般の人には理解してもらえないところがある。

ただExcelはほぼ必ずと言っていいほど入っているので、Excelが入っていない環境でAccessファイルを作ることができることがわかった。


<必要な参照設定>


<コード>

’Accessファイル(.accdb)を作成

Public Sub CreateAccdbFile()

    Dim objMdb As DAO.Database                                      

    Set objMdb = DBEngine.CreateDatabase("C:\Users\SampleUser\Desktop\sample.accdb", dbLangJapanese)

End Sub

これでAccessファイルだけは作成できる。データベースの最適化の処理などはないので、ひたすら肥大化し続けることあるので注意すること。
まぁSqlite3.dllがある64bit環境であればsqlite3を利用するのが一番だと思うけども。
後はA5 SQL Mk-2などで作成されたファイルに接続して、Create Tableを実行すればいい。


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

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









DataGridにコンボボックスを表示する

 WPFのDataGridでコンボボックスを表示するのにちょっと詰まってしまったのでメモ。

<結論>
 バインドするデータのクラスにコンボボックスの選択肢のインスタンスを作る
 
<経緯>
 1.自動化ツール(以下、RPA)を作ろう
 2.どんな動作をするか定義をしよう
    例)
      ・マウスを(X,Y)=(20,200)へ移動
      ・クリック
      ・3秒待機
 3.動作定義のためのクラスを定義
  
        public ViewModel ThisViewModel;
        public MainWindow()
        {
            InitializeComponent();

            this.ThisViewModel = new ViewModel();

            this.ThisViewModel.Title = "タイトル";
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "マウス移動", SetValue = "20,200" });
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "クリック", SetValue = "" });
            this.ThisViewModel.Actions.Add(new Action() { ActionName = "待機", SetValue = "2" });


            this.DataContext = this.ThisViewModel;
        }

        public class ViewModel : ModelBase
        {
            private string _title;
            public string Title
            {
                get { return this._title; }
                set { this._title = value; NotifyPropertyChanged(); }
            }

            public ObservableCollection<Action> Actions { get; set; }
            public ViewModel()
            {
                this.Actions = new ObservableCollection<Action>();
            }
        }

        #region PropertyChangeイベントを通知する処理を実装したベースクラス
        public class ModelBase : INotifyPropertyChanged
        {
            #region インターフェイス実装イベント
            public event PropertyChangedEventHandler PropertyChanged;
            public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            #endregion
        }
        #endregion

        /// <summary>
        /// 動作定義を登録するクラス
        /// </summary>
        public class Action: ModelBase
        {
            private string _actionName;
            public string ActionName
            {
                get { return this._actionName; }
                set
                {
                    this._actionName = value;
                    SetSelectedItem();
                    NotifyPropertyChanged();
                }
            }

            private string _setValue;
            public string SetValue
            {
                get { return this._setValue; }
                set
                {
                    this._setValue = value; NotifyPropertyChanged();
                }
            }

            public ComboBoxItem SelectedItem { get; set; }

            public void SetSelectedItem()
            {
                foreach (var item in ComboItems)
                {
                    if(item.Name==this.ActionName)
                    {
                        this.SelectedItem = item;
                        break;
                    }
                }
            }

            public ObservableCollection<ComboBoxItem> ComboItems { get; set; }

            public Action()
            {
                this.ComboItems = new ObservableCollection<ComboBoxItem>();
                this.ComboItems.Add(new ComboBoxItem() { Name = "マウス移動", Value = "001" });
                this.ComboItems.Add(new ComboBoxItem() { Name = "クリック", Value = "002" });
                this.ComboItems.Add(new ComboBoxItem() { Name = "待機", Value = "002" });
            }
        }
      
 4.下記のXAMLにバインド
    <Grid>
        <Canvas>
            <TextBox Canvas.Left="10" Canvas.Top="26" Text="{Binding Title}" TextWrapping="Wrap" Width="400"/>

            <DataGrid Height="217" Width="400" Canvas.Left="10" Canvas.Top="60" ItemsSource="{Binding Actions}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="動作名" Width="150">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <ComboBox ItemsSource="{Binding ComboItems}" Width="140"
                                                 SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                 DisplayMemberPath="Name" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn Width="150" Header="引数" Binding="{Binding SetValue}"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Canvas>
    </Grid>

 5.結果について
    DataGridのRowにバインドされているデータにコンボボックスの選択肢のインスタンスを持っておく必要がある。(検証不足?)
    ってことはDataTableをバインドして一瞬でできたぜヒャッハー!
    ってのは無理ってことなのかなぁ。まぁ入力値のチェックとかを実装することを考えるとどうしてもクラスを一つ作らざるをえないのかな・・・。
    一覧コピー的なことを一括でやろうとすると少し手間かもしれない。

 6.新規行のコンボボックスでてんやわんや
   新規行のコンボボックスにはNewItemPlaceHolderってのがバインドされているため、コンボボックスの選択肢が表示されない。
   この例でいうと「引数」列に何らかの値を入れて、行を確定させてあげると選択肢がわいてくる。何とかしたいなーって思ったので下記のサイトを見つけた。
   https://social.msdn.microsoft.com/Forums/ja-JP/7b1630de-0982-4191-bd73-3cd060e89439/wpfdatagriddatagridtemplatecolumninitializingnewitemraise?forum=wpfja
   このサイトの通りにやると一瞬ちょっとずれは生じるけどなんとかなった。
  
 7.補足
   一覧の件数がめっちゃ多い場合については表示が遅くなることがある。
   DataTemplateで作成している列については、ComboBoxがひたすら表示されているだけなので、DataGridのEditモードに移行しなくても触ることができるのが便利な反面、描画速度には問題が出ることがあるかもしれない。
   まぁ一覧を1000件表示するぐらいなら問題ないだろうし、そんなに入力できるわけないからいいと思うんだけども。
   もしどうしてもしないといけなくなったら、CellTemplateにはTextBlockでも表示しておいて、CellEditingTemplateにComboBoxを配置してあげればいい。
   DataGridTextColumnと同じでDataRowが編集モードになれば触るようになる。
    

VBA 画面タイトルを指定しIEを待機する

VBAで基幹システム(Web)を操作するためのRPAツールを作っている。

画面に値を入力してから、内部処理が完了するまでの間に動作を待機する必要がある。一律でSleepを入れると無駄な時間が発生して動作が不安定になるのでIEの動作が完了するまで待機する処理を実装する。

うまく作れればExcelで簡単なRPAツールを作ることができる。マウス操作、クリック、指定した画面をアクティブにする、コピー、貼り付け、ぐらいは問題なく関数は作れているのであとは組み合わせるだけ。

フリーソフトをインストールする必要がないのでExcelだけ入っていれば使える簡易なRPAになる。のんびりと作っていこうと思う。


<使い方>

 ・標準モジュールに貼り付け

 ・GetIEWindowTitlesでタイトルを確認

 ・WaitIEの引数に確認したタイトルを指定(部分一致)


 Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)


'IEの動作を待機する

Public Function WaitIE(ByVal strCaption) AS Boolean

    Dim shells As Object

    Dim shell As Object

    DIm tgtIE As Object

    For i = 0 To 100

        Set shells = CreateObject("Shell.Application")

        For Each shell In shells.Windows

            If TypeName(shell.document) = "HTMLDocument" Then

                If InStr(shell.document.Title,strCaption) > 0 Then

                    Set tgtIE = shell

                    Exit For

                End If

            End IF

        Next

        If Not tgtIE Is Nothing Exit For

        Sleep 100

    Next


    If tgtIE Is Nothing Then

        WaitIE = False

    Else

        'IEが動作中の場合は待つ

        Do While tgtIE.Busy = True Or tgtIE.readyState <> 4 

            DoEvents

        Loop

        Sleep 100

        WaitIE = True

    End If


End Function


'WaitIEで指定できる画面を全て取得する

Public Function GetIEWindowTitles() As String

    Dim shells As Object

    Dim shell As Object

    Dim strValue As String

    GetIEWindowTitles = ""

    Set shells = CreateObject("Shell.Application")

    For Each shell In shells.Windows

        If TypeName(shell.document) = "HTMLDocument" Then

            If strValue <> "" Then strValue = strValue & ","

            strValue = strValue & shell.document.Title

        End IF

    Next

    Set shell = Nothing

    GetIEWindowTitles = strValue

End Function


PowerShellでEdgeを自動化(インストール不要。参考:郵便追跡サービス自動操作)

1.経緯について  RPAのソフトをインストールできないので、これまでVBSでCreateObjectでブラウザの自動操作をすることがたまにあった。 ※いざというときの手札として持っているだけで安心感が段違い  見た目上IEがインストールされていなくても、CreateObject...