2011年3月25日金曜日

VBAで四捨五入、切り捨て、切り上げ

VBAでの四捨五入はRound関数、
切り捨てはRoundDown関数、
切り上げはRoundUp関数を使用します。
よく使う処理なので手軽に関数で行えるのは便利ですね。


Debug.Print "-----Round関数で四捨五入-----"
Debug.Print Round(123.4567, 1)
Debug.Print Round(123.4567, 2)
Debug.Print Round(123.4567, 3)

Debug.Print "-----RoundDown関数で切り捨て-----"
Debug.Print WorksheetFunction.RoundDown(123.4567, 1)
Debug.Print WorksheetFunction.RoundDown(123.4567, 2)
Debug.Print WorksheetFunction.RoundDown(123.4567, 3)

Debug.Print "-----RoundUp関数で切り捨て-----"
Debug.Print WorksheetFunction.RoundUp(123.4567, 1)
Debug.Print WorksheetFunction.RoundUp(123.4567, 2)
Debug.Print WorksheetFunction.RoundUp(123.4567, 3)


結果は以下のようになります

-----Round関数で四捨五入-----
123.5
123.46
123.457
-----RoundDown関数で切り捨て-----
123.4
123.45
123.456
-----RoundUp関数で切り捨て-----
123.5
123.46
123.457

2011年3月24日木曜日

VBA 高速にテキストファイル末尾を取得する

Excel、Access VBAでのファイル末尾取得する方法です
この前、InputBでのファイル読み込みが高速なことに気がついたので、
ついでにテキストファイル末尾をそこそこ高速に取得するコードを考えました。

なお、CSVファイルなんかはファイル末尾の行が改行コードのみ。
なんてことも多々ありますので、取得したファイル末尾がデータ無し、または改行コードのみなら
データのある行のデータを自動で取得する機能もつけました。
Optional引数で指定できます。

条件

・約3MBの可変長CSVファイルのファイル末尾取得。
・処理時刻の計算はVBA 超高精度タイマークラスを使っています
・時刻の単位は秒です。

ソースコード


Option Explicit

Public Function TailTextFile(ByVal File_Target As String, Optional UntilExisting As Boolean = True) As String
'===========================================================================
'ファイルの最終行を取得します
'UntilExistingがTrueなら最終行が空の場合、データが存在する行までさかのぼり、そのデータを取得します
'===========================================================================

Dim intFF As Long 'ファイル番号
Dim cnt_StrFirst As Long, cnt_StrLast As Long
Dim str_Result As String, str_Sjis As String, str_Uni As String

Dim cls_Timer As New Cl_QueryPerformance '高精度タイマークラス

Call cls_Timer.GetQueryPerformanceTime 'タイマー初期化

intFF = FreeFile
Open File_Target For Input As #intFF
str_Sjis = InputB(FileLen(File_Target), #intFF) ' SJISのテキスト読み込み
Close #intFF

Debug.Print cls_Timer.GetQueryPerformanceTime '前回タイマー初期化時からの時間

str_Uni = StrConv(str_Sjis, vbUnicode) ' SJISからUNICODEへ

cnt_StrFirst = InStrRev(str_Uni, vbCrLf) 'テキストの最後から最初の改行コードまで
cnt_StrLast = Len(str_Uni) 'テキストすべての文字数

Debug.Print cls_Timer.GetQueryPerformanceTime '前回タイマー初期化時からの時間

If UntilExisting = False Then '最終行が空でもおかまいなし
str_Result = Mid(str_Uni, cnt_StrFirst, cnt_StrLast - cnt_StrFirst) '最終行取得
Else
Do Until cnt_StrLast < 1
str_Result = Mid(str_Uni, cnt_StrFirst, cnt_StrLast - cnt_StrFirst) '指定文字数区間取得
If str_Result <> "" And str_Result <> Chr(13) Then Exit Do '文字無し、改行コードのみでなかったらループ終了
cnt_StrFirst = InStrRev(str_Uni, vbCrLf, cnt_StrFirst) '次の行の開始文字数を取得
cnt_StrLast = InStr(cnt_StrFirst + 1, str_Uni, vbCrLf) '次の行の終了文字数を取得
Loop
End If

Debug.Print cls_Timer.GetQueryPerformanceTime '前回タイマー初期化時からの時間

Debug.Print str_Result

TailTextFile= str_Result

End Function


結果はこんな感じ。

0.0483  ← ファイル読み込みにかかった時間
0.0384  ← Unicodeへのコンバートにかかった時間
0.0007  ← 最終行取得抽出にかかった時間

"1999.10.26","13:18","1.05740","1.05790","1.05720","1.05770","10" ←取得したデータ

合計 87.4ミリ秒

ファイル読み込み、Unicodeコンバートに時間がかかっています。
Loop文でファイル末尾抽出をやってるのはただ高速だからです。
改行コードでSplitするほうが簡単ですが、メモリも食いますし、遅いです。

今回3MBのファイルでしたが、これが数百MBになるとこの関数は使えません。
一度メモリに全部のテキストを読み込んでることになるのでメモリが足りなくなります。

せいぜい10MBくらいのファイルに使用してやってください。

VBA テキストファイルの最も高速な読み込み方法

Excel、AccessのVBAでのファイル読み込み方法はたくさん有ります。

でも結局どのやり方が一番早いんだ!ってことでいろいろコード書いて計測してみました。

条件

・約3MBの可変長CSVファイルの全読み込みと一部のコードでは行数取得。
・処理時刻の計算はVBA 超高精度タイマークラスを使っています
・メモリ処理のRedim preserveは重たい処理ですが、可変長ファイルなのでメモリの一括確保はしませんでした。
 ファイルサイズからメモリ容量を計算してメモリを確保するともっと早くなります。メモリマッピングのような処理です。
・For文で10回同じコードを走らせてます。ですので初回以降はキャッシュが効いて高速になります。
・時刻の単位は秒です。
・最終的な文字コードはUnicodeです

・計測用コードは以下の通り
Option Explicit

Public Function ReadFileTest_LineInput(ByVal File_Target As String)
'===========================================================================
'OpenステートメントのLine Inputでの順次読み込みを行います
'===========================================================================

    Dim intFF As Long                           'ファイル番号
    Dim str_buf As String                       'ただの汎用変数
    Dim str_Strings() As String                 'ファイルの中身全部の文字列型配列
    Dim Row As Long                             '行数カウント
    Dim cls_Timer As New Cl_QueryPerformance    '高精度タイマークラス
    
    Call cls_Timer.GetQueryPerformanceTime 'タイマー初期化
    
    intFF = FreeFile
    Open File_Target For Input As #intFF
        Do While Not EOF(intFF)           'ファイルの終端かどうかを確認します
        
            Line Input #intFF, str_buf  'データ行を読み込みます
            
            '==============================================================
            '配列化有効時
            'ReDim Preserve str_Strings(Row) As String
            'str_Strings(Row) = str_buf
            'Row = Row + 1
            '==============================================================
            
        Loop
    Close #intFF
    
    Debug.Print cls_Timer.GetQueryPerformanceTime   '前回タイマー初期化時からの時間

End Function

Public Function ReadFileTest_InputB(ByVal File_Target As String)
'===========================================================================
'OpenステートメントのInputBでの順次読み込みを行います
'===========================================================================

    Dim intFF As Long                           'ファイル番号
    Dim var_buf                                 'ただの汎用変数
    Dim str_Strings() As String                 'ファイルの中身全部の文字列型配列
    Dim Row As Long                             '行数カウント
    Dim cls_Timer As New Cl_QueryPerformance    '高精度タイマークラス
    
    Dim bytSjis As String
    Dim str_Uni As String
    
    Call cls_Timer.GetQueryPerformanceTime 'タイマー初期化
    
    intFF = FreeFile
    Open File_Target For Input As #intFF
        bytSjis = InputB(FileLen(File_Target), #intFF)      ' SJISのテキスト読み込み
    Close #intFF
    
    str_Uni = StrConv(bytSjis, vbUnicode)    ' SJISからUNICODEへ
                                    
'    '==============================================================
'    '配列化有効時
'    var_buf = Split(str_Uni, vbCrLf) '改行コードごとに区切って配列化
'    Row = UBound(var_buf)            '行数取得
'    '==============================================================
    
    Debug.Print cls_Timer.GetQueryPerformanceTime   '前回タイマー初期化時からの時間

End Function

Public Function ReadFileTest_Open_Binary(ByVal File_Target As String)
'===========================================================================
'OpenステートメントのBinaryでの順次読み込みを行います
'===========================================================================

    Dim intFF As Long                           'ファイル番号
    Dim byt_buf() As Byte
    Dim var_buf
    Dim str_buf  As String                      'ただの汎用変数
    Dim str_Strings() As String                 'ファイルの中身全部の文字列型配列
    Dim Row As Long                             '行数カウント
    Dim cls_Timer As New Cl_QueryPerformance    '高精度タイマークラス
    Dim i As Long
    
    Dim bytSjis As String
    Dim str_Uni As String
    
    Call cls_Timer.GetQueryPerformanceTime 'タイマー初期化
    
    intFF = FreeFile
    Open File_Target For Binary As #intFF
        ReDim byt_buf(LOF(intFF))
        Get #intFF, , byt_buf
    Close #intFF
    
    str_Uni = StrConv(byt_buf(), vbUnicode) 'Unicodeに変換
    
    '==============================================================
    '配列化有効時
    var_buf = Split(str_Uni, vbCrLf) '改行コードごとに区切って配列化
    Row = UBound(var_buf)            '行数取得
    '==============================================================
    
    Debug.Print cls_Timer.GetQueryPerformanceTime

End Function

Public Function ReadFileTest_FSO_ReadAll(ByVal File_Target As String)
'===========================================================================
'FileSystemObjectのReadAllでの一括読み込みを行います
'===========================================================================

    Dim var_buf
    Dim str_buf As String
    Dim Row As Long                             '行数カウント
    Dim cls_Timer As New Cl_QueryPerformance    '高精度タイマークラス
    Dim FSO As New FileSystemObject             'FileSystemObject
    
    Call cls_Timer.GetQueryPerformanceTime 'タイマー初期化
    
    With FSO.OpenTextFile(File_Target, ForReading)
        str_buf = .ReadAll  '一括読み込み
        .Close
    End With
    
'    '==============================================================
'    '配列化有効時
'    var_buf = Split(str_buf, vbCrLf) '改行コードごとに区切って配列化
'    Row = UBound(var_buf)            '行数取得
'    '==============================================================
    
    Set FSO = Nothing
    Debug.Print cls_Timer.GetQueryPerformanceTime
    
End Function

Public Function ReadFileTest_FSO_ReadLine(ByVal File_Target As String)
'===========================================================================
'FileSystemObjectとTextStreamでのReadLineを行います
'===========================================================================

    Dim str_buf As String                       'ただの汎用変数
    Dim str_Strings() As String                 'ファイルの中身全部の文字列型配列
    Dim Row As Long                             '行数カウント
    Dim cls_Timer As New Cl_QueryPerformance    '高精度タイマークラス
    
    Dim FSO As New FileSystemObject
    Dim FSO_TS As TextStream            ' TextStream
    
    Call cls_Timer.GetQueryPerformanceTime 'タイマー初期化
    
    Set FSO_TS = FSO.OpenTextFile(File_Target, ForReading)
    With FSO_TS
        Do Until .AtEndOfStream
            str_buf = .ReadLine
'            '==============================================================
'            '配列化有効時
'            ReDim Preserve str_Strings(Row) As String
'            str_Strings(Row) = str_buf
'            Row = Row + 1
'            '==============================================================
        Loop
        .Close
    End With
    Set FSO_TS = Nothing
    Set FSO = Nothing
    
    Debug.Print cls_Timer.GetQueryPerformanceTime
    
End Function



・結果はこのようになりました

順次読み込み 一括読み込み 一括読み込み
Line Input
(配列化有)
Line Input
(配列化無)
InputB
(配列化有)
InputB
(配列化無)
Binary
(配列化有)
Binary
(配列化無)
1回目 0.4208 0.3645 0.1739 0.1394 0.0998 0.1167
2回目 0.2878 0.2487 0.1413 0.1072 0.0928 0.0602
3回目 0.2821 0.2459 0.1178 0.0917 0.0979 0.0549
4回目 0.2817 0.2498 0.1081 0.0909 0.0992 0.0569
5回目 0.2808 0.248 0.1083 0.0893 0.09 0.0591
6回目 0.2881 0.2452 0.1181 0.0899 0.0926 0.0568
7回目 0.283 0.2457 0.1129 0.0906 0.0922 0.0573
8回目 0.2801 0.252 0.1095 0.0908 0.0925 0.0588
9回目 0.28 0.2461 0.1085 0.0917 0.092 0.059
10回目 0.2916 0.2455 0.109 0.0917 0.0945 0.0572
平均 0.2976 0.25914 0.12074 0.09732 0.09435 0.06369
一括読み込み 順次読み込み
FileSystemObject
ReadAll
(配列化有)
FileSystemObject
ReadAll
(配列化無)
FileSystemObject
ReadLine
(配列化有)
FileSystemObject
ReadLine
(配列化無)
1回目 0.4591 0.4369 0.4437 0.4139
2回目 0.2952 0.2922 0.3263 0.2748
3回目 0.2969 0.2787 0.3184 0.274
4回目 0.2973 0.2789 0.3188 0.2757
5回目 0.3001 0.2786 0.322 0.2763
6回目 0.2964 0.292 0.319 0.2768
7回目 0.295 0.2786 0.3186 0.2743
8回目 0.2988 0.2787 0.3248 0.2739
9回目 0.2992 0.282 0.3203 0.2754
10回目 0.2942 0.2962 0.3199 0.2772
平均 0.31322 0.29928 0.33318 0.28923


結果

・OpenステートメントのBinaryモードにより一括バイナリ読み込み、Unicode変換が最も高速という結果になりました。
 まぁ当然ですね。配列化も簡単なので今後はこれをメインに使っていこう。

 おそらくこれがVBAでAPIを使わない場合でもっとも高速です。
 さらにテキストファイルの最終行の取得なんてのはさらに早くできます。
 これはログを監視するようなマクロではかなり重要です。
 まぁファイル開きっぱなしの方が高速ですが。

・数百MBものファイルなら一気に配列(メモリ)に読み込むなんてマネは恐ろしくてできません。
 FileSystemObjectとTextStreamでの順次読み込み。同時にデータに対する処理を行うっていのがいいと思います。
 これだとメモリをほとんど使わないで済みます。その変わりファイルIO中に他の作業をするので堅実なコードが必要です。

用途によって使い分けるといいものが作れそうです。

VBAでFTPクライアント(Win32API)

VBAでFTPサーバにアクセスする方法はいくつかあります。
ActiveXだったりRuntimeだったり専用DLLだったり。

しかし企業ではセキュリティ上外部のソースコードの使用が禁止されていたり機能が少なかったりします。

そこでWin32APIのみでFTPクライアントを実現するクラスを作成しました。

動作保証できるほど作りこんでいませんが、手軽にファイルのダウンロード、アップロード、ファイル一覧の取得などができます。

・Cl_FTP.cls

Option Explicit

'**********************************************************************************
'列挙型宣言
'**********************************************************************************
Public Enum FILEMEMBER 'ファイル情報を外部に公開するためだけに必要
FileName
IsDirectory
Permission
Permission_Octal
Link
Owner
Group
Size
LastModifiedDate
End Enum

'**********************************************************************************
'定数宣言
'**********************************************************************************
' Constants - InternetOpen.lAccessType
Private Const INTERNET_OPEN_TYPE_PRECONFIG As Long = 0&
Private Const INTERNET_OPEN_TYPE_DIRECT As Long = 1&
Private Const INTERNET_OPEN_TYPE_PROXY As Long = 3&
Private Const INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY As Long = 4&
' Constants - InternetOpen.dwFlags
Private Const INTERNET_FLAG_ASYNC As Long = &H10000000
Private Const INTERNET_FLAG_FROM_CACHE As Long = &H1000000
Private Const INTERNET_FLAG_OFFLINE As Long = INTERNET_FLAG_FROM_CACHE
' Constants - InternetConnect.nServerPort
Private Const INTERNET_INVALID_PORT_NUMBER As Long = 0&
Private Const INTERNET_DEFAULT_PORT As Long = 21&
Private Const INTERNET_DEFAULT_GOPHER_PORT As Long = 70&
Private Const INTERNET_DEFAULT_HTTP_PORT As Long = 80&
Private Const INTERNET_DEFAULT_HTTPS_PORT As Long = 443&
Private Const INTERNET_DEFAULT_SOCKS_PORT As Long = 1080&
' Constants - InternetConnect.dwServi0ce
Private Const INTERNET_SERVICE_FTP As Long = 1&
Private Const INTERNET_SERVICE_GOPHER As Long = 2&
Private Const INTERNET_SERVICE_HTTP As Long = 3&
' Constants - InternetConnect.dwFlags
Private Const INTERNET_FLAG_PASSIVE As Long = &H8000000
' Constants - FtpGetFile.dwFlags (FTP TransferType)
' Constants - FtpPutFile.dwFlags (FTP TransferType)
Private Const TRANSFER_TYPE_UNKNOWN As Long = &H0&
Private Const TRANSFER_TYPE_ASCII As Long = &H1&
Private Const TRANSFER_TYPE_BINARY As Long = &H2&
Private Const INTERNET_FLAG_TRANSFER_ASCII As Long = TRANSFER_TYPE_ASCII
Private Const INTERNET_FLAG_TRANSFER_BINARY As Long = TRANSFER_TYPE_BINARY
' Constants - FtpGetFile.dwFlags (Cache Flags)
' Constants - FtpPutFile.dwFlags (Cache Flags)
Private Const INTERNET_FLAG_RELOAD As Long = &H80000000
Private Const INTERNET_FLAG_RESYNCHRONIZE As Long = &H800
Private Const INTERNET_FLAG_NEED_FILE As Long = &H10
Private Const INTERNET_FLAG_HYPERLINK As Long = &H400
' Constants - FtpGetFile.dwFlagsAndAttributes
Private Const FILE_ATTRIBUTE_ARCHIVE As Long = &H20
Private Const FILE_ATTRIBUTE_ENCRYPTED As Long = &H4000
Private Const FILE_ATTRIBUTE_HIDDEN As Long = &H2
Private Const FILE_ATTRIBUTE_NORMAL As Long = &H80
Private Const FILE_ATTRIBUTE_OFFLINE As Long = &H1000
Private Const FILE_ATTRIBUTE_READONLY As Long = &H1
Private Const FILE_ATTRIBUTE_SYSTEM As Long = &H4
Private Const FILE_ATTRIBUTE_TEMPORARY As Long = &H100
Private Const FILE_ATTRIBUTE_DIRECTORY As Long = &H10
Private Const FILE_ATTRIBUTE_COMPRESSED As Long = &H800

'dwFlags(転送方法)
Private Const FTP_TRANSFER_TYPE_ASCII As Long = &H1 'アスキーモード
Private Const FTP_TRANSFER_TYPE_BINARY As Long = &H2 'バイナリモード

Private Const MAX_PATH = 260
Private Const BufSize As Long = 256 'バッファサイズ

'**********************************************************************************
'API宣言
'**********************************************************************************
Private Declare Function FtpCommand Lib "wininet.dll" Alias "FtpCommandA" (ByVal hConnect As Long, fExpectResponse As Long, ByVal dwFlags As Long, ByVal lpszCommand As String, ByVal dwContext As Long, ByRef phFtpCommand As Long) As Long
Private Declare Function InternetReadFile Lib "wininet.dll" (ByVal hRequest As Long, ByRef lpBuffer As Any, ByVal dwNumberOfBytesToRead As Long, ByRef lpdwNumberOfBytesRead As Long) As Integer

Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" (ByVal lpszAgent As String, ByVal dwAccessType As Long, ByVal lpszProxyName As String, ByVal lpszProxyBypass As String, ByVal dwFlags As Long) As Long
Private Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" (ByVal HINTERNET As Long, ByVal lpszServerName As String, ByVal nServerPort As Integer, ByVal lpszUsername As String, ByVal lpszPassword As String, ByVal dwService As Long, ByVal dwFlags As Long, ByVal dwContext As Long) As Long
Private Declare Function InternetCloseHandle Lib "wininet.dll" (ByVal HINTERNET As Long) As Integer
Private Declare Function FtpGetCurrentDirectory Lib "wininet.dll" Alias "FtpGetCurrentDirectoryA" (ByVal hConnect As Long, ByVal lpszCurrentDirectory As String, ByRef lpdwCurrentDirectory As Long) As Boolean
Private Declare Function FtpSetCurrentDirectory Lib "wininet.dll" Alias "FtpSetCurrentDirectoryA" (ByVal hConnect As Long, ByVal lpszDirectory As String) As Long
Private Declare Function FtpGetFile Lib "wininet.dll" Alias "FtpGetFileA" (ByVal hConnect As Long, ByVal lpszRemoteFile As String, ByVal lpszNewFile As String, ByVal fFailIfExists As Boolean, ByVal dwFlagsAndAttributes As Long, ByVal dwFlags As Long, ByVal dwContext As Long) As Long
Private Declare Function FtpPutFile Lib "wininet.dll" Alias "FtpPutFileA" (ByVal hConnect As Long, ByVal lpszLocalFile As String, ByVal lpszNewRemoteFile As String, ByVal dwFlags As Long, ByVal dwContext As Long) As Long
Private Declare Function FtpDeleteFile Lib "wininet.dll" Alias "FtpDeleteFileA" (ByVal hConnect As Long, ByVal lpszFileName As String) As Long
Private Declare Function FtpRenameFile Lib "wininet.dll" Alias "FtpRenameFileA" (ByVal hConnect As Long, ByVal lpszExisting As String, ByVal lpszNew As String) As Long
Private Declare Function FtpRemoveDirectory Lib "wininet.dll" Alias "FtpRemoveDirectoryA" (ByVal hConnect As Long, ByVal lpszDirectory As String) As Long
Private Declare Function InternetGetLastResponseInfo Lib "wininet.dll" Alias "InternetGetLastResponseInfoA" (lpdwError As Long, ByRef lpBuffer As Any, lpdwBufferLength As Long) As Long

'Private Declare Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" (ByVal hFtpSession As Long, ByVal lpszSearchFile As String, ByRef lpFindFileData As WIN32_FIND_DATA, ByVal dwFlags As Long, ByVal dwContent As Long) As Long
'Private Declare Function InternetFindNextFile Lib "wininet.dll" Alias "InternetFindNextFileA" (ByVal hFind As Long, ByRef lpvFindData As WIN32_FIND_DATA) As Long
'Private Declare Function SystemTimeToFileTime Lib "kernel32" (lpSystemTime As SYSTEMTIME, lpFileTime As FILETIME) As Long
'Private Declare Function FileTimeToLocalFileTime Lib "kernel32" (lpFileTime As FILETIME, lpLocalFileTime As FILETIME) As Long
'Private Declare Function FileTimeToSystemTime Lib "kernel32" (lpFileTime As FILETIME, lpSystemTime As SYSTEMTIME) As Long

Private cServer As String '現在のサーバ
Private cUser As String '現在のユーザ
Private cPassWord As String '現在のパスワード
Private cDirectory As String '現在のディレクトリ
Private cPasvMode As Boolean 'PasvModeの有効/無効

Private hOpen As Long 'インターネットサービスのハンドル
Private hConnection As Long 'インターネットセッションのハンドル

Private col_FileInfoList As New Collection 'ファイルリスト。コンストラクタ書くのめんどいからここで初期化

'**********************************************************************************
'ファイル情報取得系関数群
'**********************************************************************************
Public Function GetFileList(ByVal vFileName As String) As Boolean
'============================================================
'LISTコマンドでファイルリスト取得
'============================================================

Dim var_SplitSpace As Variant 'スペース区切り
Dim col_FileInfo As Collection
Dim i As Long

Set col_FileInfoList = New Collection

'On Error Resume Next

var_SplitSpace = Split(LIST(vFileName), vbCrLf) 'LISTコマンドの結果を改行区切り

If var_SplitSpace(0) = "" Then
MsgBox vFileName & "は存在しない可能性があります", , "ftp client class error"
Exit Function
End If

For i = 0 To UBound(var_SplitSpace)
If SetFileItem(var_SplitSpace(i), col_FileInfo) = True Then Call col_FileInfoList.Add(col_FileInfo) 'ファイル情報をコレクションにしてリスト
Next i

GetFileList = True

End Function

Public Function GetFileListCount() As Long
'============================================================
'GetFileList関数で取得したファイルリストのコレクションのカウントを返す
'============================================================
GetFileListCount = col_FileInfoList.Count
End Function

Public Function GetFileListItem(ByVal Index As Long, vFileMember As FILEMEMBER) As Variant
'============================================================
'GetFileList関数で取得したファイルリストのインデックスの指定されたファイルの情報(メンバ)を返す
'============================================================

If 1 > Index Or Index > GetFileListCount Then
MsgBox "インデックスが不正です", , "ftp client class error"
Exit Function
End If

GetFileListItem = GetFileMember(col_FileInfoList(Index), vFileMember) 'ファイルリストの指定インデックスの指定メンバを帰す

End Function

Public Function GetFileItem(ByVal vFileName As String, vFileMember As FILEMEMBER) As Variant
'============================================================
'LISTコマンドでファイルの情報(メンバ)を返す
'============================================================

Static col_FileInfo As Collection
Static LastServer As String
Static LastUser As String
Static LastFileName As String
Static LastDirectory As String

'つづけて同じファイルの情報を取得する際に無駄にLISTするのを防ぐ
If LastServer = Server And LastUser = User And LastFileName = vFileName And LastDirectory = Directory Then
GetFileItem = GetFileMember(col_FileInfo, vFileMember) '前回のファイルメンバ取得
Else
If SetFileItem(LIST(vFileName), col_FileInfo) = False Then Exit Function 'ファイル情報取得
GetFileItem = GetFileMember(col_FileInfo, vFileMember) 'ファイルメンバ取得
End If

'今回の情報を保存
LastServer = Server
LastUser = User
LastFileName = vFileName
LastDirectory = Directory

End Function

Private Function SetFileItem(ByVal str_FileInfo As String, ByRef col_FileInfo As Collection) As Boolean
'============================================================
'LISTコマンドの結果を解析してコレクションのメンバにする
'============================================================

Dim var_SplitSpace As Variant 'スペース区切り

Set col_FileInfo = New Collection 'コレクション初期化

'On Error Resume Next

Do Until str_FileInfo = Replace(str_FileInfo, " ", " ") '文字列をスペース区切りにする
str_FileInfo = Replace(str_FileInfo, " ", " ")
Loop

var_SplitSpace = Split(str_FileInfo, " ") 'スペース区切りにする

If UBound(var_SplitSpace) < 8 Then Exit Function 'データがそろっているか確認

'データ格納
With col_FileInfo

Call .Add(True, "Exists") 'ファイルが存在しないフラグを立てておく

If Left(var_SplitSpace(0), 1) = "d" Then 'ディレクトリならTrue
Call .Add(True, "IsDirectory")
Else
Call .Add(False, "IsDirectory")
End If

Call .Add(Mid(var_SplitSpace(0), 2), "Permission") 'パーミッション
Call .Add(0, "Permission_Octal") 'パーミッション(8進数)
Call .Add(var_SplitSpace(1), "Link") 'リンク数
Call .Add(var_SplitSpace(2), "Owner") 'オーナー
Call .Add(var_SplitSpace(3), "Group") 'グループ
Call .Add(var_SplitSpace(3), "Size") 'サイズ

If IsDate(var_SplitSpace(5) & " " & var_SplitSpace(6) & " " & var_SplitSpace(7)) Then '最終更新日
Call .Add(Format(CDate(var_SplitSpace(5) & " " & var_SplitSpace(6) & " " & var_SplitSpace(7)), "yyyy/mm/dd"), "LastModifiedDate")
Else
Call .Add(0, "LastModifiedDate")
End If

If Left(var_SplitSpace(8), 1) = "/" Then '最初の/を削除
Call .Add(Mid(var_SplitSpace(8), 2), "FileName") 'ファイル名
Else
Call .Add(var_SplitSpace(8), "FileName") 'ファイル名
End If

End With

SetFileItem = True

End Function

Private Function GetFileMember(col_FileInfo As Collection, vFileMember As FILEMEMBER) As Variant
'============================================================
'コレクションから指定されたファイルの情報(メンバ)を返す
'============================================================

Select Case vFileMember
Case FileName
GetFileMember = col_FileInfo("FileName")
Case IsDirectory
GetFileMember = col_FileInfo("IsDirectory")
Case Permission
GetFileMember = col_FileInfo("Permission")
Case Permission_Octal
GetFileMember = col_FileInfo("Permission_Octal")
Case Link
GetFileMember = col_FileInfo("Link")
Case Owner
GetFileMember = col_FileInfo("Owner")
Case Group
GetFileMember = col_FileInfo("Group")
Case Size
GetFileMember = col_FileInfo("Size")
Case LastModifiedDate
GetFileMember = col_FileInfo("LastModifiedDate")
End Select

End Function


'**********************************************************************************
'FTP操作関数群
'**********************************************************************************
Private Function LIST(Optional ByVal vFileName As String = "") As String
'============================================================
'LISTコマンドの送信、結果取得
'============================================================

Dim lng_bytesRead As Long
Dim lng_phFtpCommand As Long
Dim byt_Buf() As Byte
Dim str_Bur As String

Call FtpCommand(hConnection, True, FTP_TRANSFER_TYPE_ASCII, "LIST " & vFileName, 0, lng_phFtpCommand) 'LIST送信

Do 'FTPcmdLISTの結果を取得
ReDim byt_Buf(BufSize - 1) As Byte 'バッファ初期化
Call InternetReadFile(lng_phFtpCommand, byt_Buf(0), BufSize, lng_bytesRead) 'バッファ読み出し
If lng_bytesRead = 0 Then Exit Do '読みだすものがなくなれば終了
str_Bur = str_Bur & StrConv(byt_Buf(), vbUnicode)
Loop

If InStr(str_Bur, Chr(0)) > 0 Then str_Bur = Left(str_Bur, InStr(str_Bur, Chr(0)) - 1) 'NULLの除去

If Right(str_Bur, 1) = Chr(13) Or Right(str_Bur, 1) = Chr(10) Then str_Bur = Left(str_Bur, Len(str_Bur) - 2) 'NULLの除去

Call GetInternetConnect 'コネクション再確立

LIST = str_Bur

End Function

Public Function Cd(ByVal vDirectory As String) As Boolean 'カレントディレクトリ移動
'=========================
'カレントディレクトリの変更
'=========================

If Connected = False Then
MsgBox "cd失敗。接続がありません", , "ftp client class error"
Exit Function
End If

If FtpSetCurrentDirectory(hConnection, vDirectory) = 0 Then 'ディレクトリ移動
MsgBox "ディレクトリの移動に失敗しました", , "ftp client class error"
Exit Function
End If

cDirectory = vDirectory 'カレントディレクトリ保存

Cd = True

End Function

Public Function Pwd() As String
'=========================
'カレントディレクトリの取得
'=========================

Dim lpszDir As String
Dim ret As Long

If Connected = False Then
MsgBox "pwd失敗。接続がありません", , "ftp client class error"
Exit Function
End If

lpszDir = String(BufSize, Chr(0)) 'バッファ領域の確保

If FtpGetCurrentDirectory(hConnection, lpszDir, BufSize) = 0 Then 'カレントディレクトリ取得失敗
MsgBox "カレントディレクトリの取得に失敗しました", , "ftp client class error"
Pwd = ""
Exit Function
End If

lpszDir = Left(lpszDir, InStr(lpszDir, Chr(0)) - 1) 'NULLの除去

cDirectory = lpszDir 'カレントディレクトリとして保存

Pwd = lpszDir

End Function

Public Function DeleteFile(ByVal vFileName As String) As Boolean
'=========================
'ファイル削除
'=========================

If Connected = False Then
MsgBox "DeleteFile失敗。接続がありません", , "ftp client class error"
Exit Function
End If

If FtpDeleteFile(hConnection, vFileName) = 0 Then
MsgBox "ファイルの削除に失敗しました", , "ftp client class error"
Exit Function
End If

DeleteFile = True

End Function

Public Function RemoveDirectory(ByVal vDirectoryName As String) As Boolean
'=========================
'ディレクトリ削除
'=========================

If Connected = False Then
MsgBox "RemoveDirectory失敗。接続がありません", , "ftp client class error"
Exit Function
End If

If FtpRemoveDirectory(hConnection, vDirectoryName) = 0 Then
MsgBox "ディレクトリの削除に失敗しました", , "ftp client class error"
Exit Function
End If

RemoveDirectory = True

End Function

Public Function RenameFile(ByVal vRenameFromFile As String, ByVal vRenameToFile As String) As Boolean
'=========================
'ファイル名変更
'=========================

If Connected = False Then
MsgBox "RenameFile失敗。接続がありません", , "ftp client class error"
Exit Function
End If

If FtpRenameFile(hConnection, vRenameFromFile, vRenameToFile) = 0 Then
MsgBox "ファイル名の変更に失敗しました", , "ftp client class error"
Exit Function
End If

RenameFile = True

End Function

Public Function MKD(ByVal vDirectoryName As String) As Long
'=========================
'ディレクトリの作成
'=========================

Dim lng_bytesRead As Long
Dim lng_phFtpCommand As Long

If Connected = False Then
MsgBox "MKD失敗。接続がありません", , "ftp client class error"
Exit Function
End If

MKD = FtpCommand(hConnection, False, FTP_TRANSFER_TYPE_BINARY, "MKD " & vDirectoryName, 0, lng_phFtpCommand) 'MKD送信

Call GetInternetConnect 'コネクション再確立

End Function

Public Function CDUP() As Long
'=========================
'一つ上のディレクトリへ移動
'=========================

Dim lng_bytesRead As Long
Dim lng_phFtpCommand As Long

If Connected = False Then
MsgBox "CDUP失敗。接続がありません", , "ftp client class error"
Exit Function
End If

CDUP = FtpCommand(hConnection, False, FTP_TRANSFER_TYPE_BINARY, "CDUP", 0, lng_phFtpCommand) 'CDUP送信

Call GetInternetConnect 'コネクション再確立

cDirectory = Me.Pwd 'カレントディレクトリの保存

End Function

Public Function FtpCmd(ByVal command As String) As Long
'=========================
'FtpCommandを実行
'=========================

Dim lng_phFtpCommand As Long

If Connected = False Then
MsgBox "ftpcommand失敗。接続がありません", , "ftp client class error"
Exit Function
End If

FtpCmd = FtpCommand(hConnection, False, FTP_TRANSFER_TYPE_BINARY, command, 0, lng_phFtpCommand) 'ftpcommand送信

Call GetInternetConnect 'コネクション再確立

End Function

Public Function CHMOD(ByVal vFileName As String, ByVal Permission As String) As Long
'=========================
'CHMODを実行
'=========================

Dim lng_phFtpCommand As Long

If Connected = False Then
MsgBox "CHMOD失敗。接続がありません", , "ftp client class error"
Exit Function
End If

CHMOD = GetInternetLastResponseInfo(FtpCommand(hConnection, False, FTP_TRANSFER_TYPE_BINARY, "SITE CHMOD " & Permission & " " & vFileName, 0, lng_phFtpCommand)) 'CHMOD送信

Call GetInternetConnect 'コネクション再確立

End Function


'**********************************************************************************
'プロパティ群
'**********************************************************************************

'=====================================================
'接続先FTPサーバ(IP)
'=====================================================
Public Property Get Server() As String
Server = cServer
End Property

Private Property Let Server(ByVal vServer As String)
cServer = vServer
End Property

'=====================================================
'ユーザー
'=====================================================
Public Property Get User() As String
User = cUser
End Property

Private Property Let User(ByVal vUser As String)
cUser = vUser
End Property

'=====================================================
'パスワード、値の設定のみ可能
'=====================================================
Private Property Get Password() As String
Password = cPassWord
End Property

Private Property Let Password(ByVal vPassword As String)
cPassWord = vPassword
End Property

'=====================================================
'パッシブモードの有効/無効、無効だとFTPCommand系がほとんど駄目になる
'=====================================================
Private Property Get PasvMode() As Boolean
PasvMode = cPasvMode
End Property

Private Property Let PasvMode(ByVal vPasvMode As Boolean)
cPasvMode = vPasvMode
End Property

'=====================================================
'接続状況、値の設定のみ可能
'=====================================================
Private Property Get Connected() As Boolean
If hOpen <> 0 And hConnection <> 0 Then
Connected = True
Exit Property
End If
End Property

'=====================================================
'現在のディレクトリ、値の取得のみ可能
'=====================================================
Public Property Get Directory() As String
Directory = cDirectory
End Property

'Public Property Let Directory(ByVal vDirectory As String)
'
'End Property


'**********************************************************************************
'インターネットセッション操作関数群
'**********************************************************************************

'=====================================================
'インターネットセッションをオープン
'=====================================================
Private Function GetInternetOpen() As Long

'すでにあるFTP接続を切断
If hConnection <> 0 Then InternetCloseHandle hConnection
If hOpen <> 0 Then InternetCloseHandle hOpen

hOpen = InternetOpen(vbNullString, INTERNET_OPEN_TYPE_DIRECT, vbNullString, vbNullString, 0)
GetInternetOpen = hOpen

End Function

'=====================================================
'コネクション取得
'=====================================================
Private Function GetInternetConnect() As Boolean

'すでにあるコネクションを切断
If hConnection <> 0 Then InternetCloseHandle hConnection

If PasvMode Then 'パッシブモード
hConnection = InternetConnect(hOpen, Server, INTERNET_INVALID_PORT_NUMBER, User, Password, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, 0)
Else
hConnection = InternetConnect(hOpen, Server, INTERNET_INVALID_PORT_NUMBER, User, Password, INTERNET_SERVICE_FTP, 0, 0)
End If

GetInternetConnect = hConnection

End Function

'=====================================================
'InternetGetLastResponseInfoラッパ
'=====================================================
Private Function GetInternetLastResponseInfo(ByVal lpdwResult As Long) As String

Dim lng_Length As Long
Dim str_Result As String
Dim byt_Buf() As Byte

Call InternetGetLastResponseInfo(lpdwResult, vbNullString, lng_Length)

If lng_Length = 0 Then Exit Function

ReDim byt_Buf(lng_Length - 1) As Byte

Call InternetGetLastResponseInfo(lpdwResult, byt_Buf(0), lng_Length)

str_Result = StrConv(byt_Buf(), vbUnicode)

If InStr(str_Result, Chr(0)) > 0 Then str_Result = Left(str_Result, InStr(str_Result, Chr(0)) - 1) 'NULLの除去

GetInternetLastResponseInfo = str_Result

End Function

'============================================================
'サーバへ接続
'============================================================
Public Function Connect(ByVal vServer As String, ByVal vUser As String, ByVal vPassword As String, Optional ByVal vPasvMode As Boolean = True, Optional ByVal vDirectory As String = "") As Boolean

Server = vServer
User = vUser
Password = vPassword
PasvMode = vPasvMode

If hOpen = 0 Then 'インターネットセッションオープン
If GetInternetOpen = 0 Then
MsgBox "インターネットセッションハンドルの取得に失敗しました", , "ftp client class error"
Exit Function
End If
End If

If GetInternetConnect = 0 Then 'コネクション確立
MsgBox "インターネットコネクションの確立に失敗しました", , "ftp client class error"
Exit Function
End If

If vDirectory <> "" Then
Connect = Me.Cd(vDirectory) 'ディレクトリの移動
Else
cDirectory = Me.Pwd 'カレントディレクトリの保存
Connect = True
End If

Connect = True

End Function

Public Sub Diconnect()
'============================================================
'現在の接続を切断、サーバ情報を破棄
'============================================================
If hConnection <> 0 Then InternetCloseHandle hConnection 'すでに接続がある場合、インターネットセッションを閉じる
If hOpen <> 0 Then InternetCloseHandle hOpen 'すでに接続がある場合、インターネットサービスを閉じる
hConnection = 0
hOpen = 0
End Sub

Private Sub Class_Terminate()
Call Me.Diconnect
End Sub

VBA 超高精度タイマークラス

VBAのパフォーマンス測るのにDateで引き算して...とかだと分解能は1sです。
APIのGetTickCountも分解能はmsです。

数分のかかる処理の時間を測るのにはこれでもいいんですが、
演算を多様するコードだともっと細かい時間を測りたくなります。

そこでQueryPerformanceCounterというAPIをつかってμsまで計測してみます。
この関数は環境依存の部分が多いので正確に数値がでない場合もあります。注意してください。

・クラス化したものです[Cl_QueryPerformance.cls]

Option Explicit

Private Declare Function QueryPerformanceCounter Lib "kernel32" (X As Currency) As Boolean
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (X As Currency) As Boolean

Private Ctr1 As Currency
Private Ctr2 As Currency
Private Freq As Currency
Private Overhead As Currency

Public Sub TimeClear()

QueryPerformanceFrequency Freq
QueryPerformanceCounter Ctr1
QueryPerformanceCounter Ctr2

Overhead = Ctr2 - Ctr1 ' determine API overhead

QueryPerformanceCounter Ctr1 ' time loop

End Sub

Public Function GetQueryPerformanceTime(Optional vFormat As String = "0.0000") As Double

Dim result As Currency

QueryPerformanceCounter Ctr2

result = (Ctr2 - Ctr1 - Overhead) / Freq 'APIオーバーヘッド分削除とか単位合わせたりとか

GetQueryPerformanceTime = Format(CDbl(result), vFormat) '指定フォーマットで返す

Call TimeClear 'タイマー初期化
End Function

Private Sub Class_Initialize()
Call TimeClear 'タイマー初期化
End Sub


・クラスを動かすサンプルモジュールです

Option Explicit

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

Public Sub QueryPerformanceTest()

Dim cls_QueryPerformance As New Cl_QueryPerformance

cls_QueryPerformance.TimeClear 'タイマー初期化

Call Sleep(500)

Debug.Print Format(cls_QueryPerformance.GetQueryPerformanceTime) '前回タイマー初期化時からの時刻。以降この関数実行時に同時にタイマー初期化がおこなわれる。

Call Sleep(750)

Debug.Print Format(cls_QueryPerformance.GetQueryPerformanceTime, "0.00")

cls_QueryPerformance.TimeClear '明示的にタイマーを初期化したい場合

Call Sleep(1000)

Debug.Print Format(cls_QueryPerformance.GetQueryPerformanceTime, "0.000000")

Call Sleep(1111)

Debug.Print Format(cls_QueryPerformance.GetQueryPerformanceTime, "0.00000000")

Set cls_QueryPerformance = Nothing

End Sub


結果は以下のようになりました。
0.5
0.75
1.000000
1.111300000

このようにかなり細かい数値まで取得できます。
VBAの浮動少数の扱いがよくわからないので数値は四捨五入されているかもしれません
が、少なくとも分解能は1ms以上ありそうなのでGetTickCountなんかを使うよりかはいいと思います。

クラスにしてるぶんそれだけで処理に時間を食います。
厳密に時刻を計算しなければいけないときはこのクラスを使わないか、計測したいコードに直接組み込んでください。

VBAからシステムアイコントレイを使う

Excel,Accessで動きます

WindowsXP Office2003で開発しました。
古いWindows APIを使っていますが、おそらく最新のものでも動きます。

システムアイコントレイとはタスクバーの右にあるアイコン達です。
なにかイベントをお知らせしてくれたり進捗状況をポップアップで表示します。
それをVBAで実現します。

ExcelはWindowsメッセージループが隠蔽されており完璧な動作はできませんが、
使い方によってはMsgBoxなどよりも手軽でユーザーの邪魔になりません。

・サンプルモジュール[Ml_NOTIFYICON.bas]
Option Explicit

Public Sub test()
    
    Dim cls_Icon As New Cl_NOTIFYICON 'NOTIFYICONオブジェクト
    
    Call cls_Icon.AddIcon(Application.hWnd, "アイコンてすと") 'システムトレイにアイコンを登録
    
    MsgBox "システムトレイにアイコンが追加されました"
    
    Call cls_Icon.ShowBalloon("バルーンメッセージてすと", "バルーンタイトル", NIIF_INFO, 10)       'バルーンチップの表示
    
    MsgBox "バルーンチップが表示されました"
    
    Call cls_Icon.ModifyIcon("C:\windows\system32\notepad.exe") 'アイコンの変更
    
    MsgBox "アイコンを変更しました"
    
    Call cls_Icon.DeleteIcon 'アイコンの削除
    
    MsgBox "アイコンを削除しました"
    
    Set cls_Icon = Nothing 'オブジェクト破棄、デストラクタの呼び出し

End Sub
・クラス[Cl_NOTIFYICON.cls]
Option Explicit

'user defined type required by Shell_NotifyIcon API call
'アイコン情報の構造体
Private Type NOTIFYICONDATA
    cbSize As Long
    hWnd As Long
    uID As Long
    uFlags As Long
    uCallbackMessage As Long
    hIcon As Long
    szTip As String * 128
    dwState As Long
    dwStateMask As Long
    szInfo As String * 256
    uTimeoutOrVersion As Long
    szInfoTitle As String * 64
    dwInfoFlags As Long
End Type

Public Enum NOTIFYICONINFO
    NIIF_NONE = &H0 'アイコンなし
    NIIF_INFO = &H1 '「情報」アイコン
    NIIF_WARNING = &H2 '「警告」アイコン
    NIIF_ERROR = &H3 '「エラー」アイコン
    NIIF_USER = &H4
    NIIF_ICON_MASK = &HF 'Version 6.0 以降: 予約されています。
    NIIF_NOSOUND = &H10 'Version 6.0 以降: 関連サウンドを鳴らさないようにします。バルーンツールチップにのみ適用されます。
End Enum

'constants required by Shell_NotifyIcon API call:
'アイコンに対する処理命令
Private Const NIM_ADD = &H0
Private Const NIM_MODIFY = &H1
Private Const NIM_DELETE = &H2

Private Const NIF_MESSAGE = &H1 'uCallbackMessage
Private Const NIF_ICON = &H2    'hIcon
Private Const NIF_TIP = &H4     'szTip
Private Const NIF_STATE = &H8   'Version 5.0 以降: dwState, dwStateMask
Private Const NIF_INFO = &H10   'Version 5.0 以降: szInfo, uTimeout, szInfoTitle, dwInfoFlags
Private Const NIF_GUID = &H20   'Version 6.0 以降: 予約されています。
      
'使用しない
'Private Const WM_MOUSEMOVE = &H200
'Private Const WM_LBUTTONDOWN = &H201     'Button down
'Private Const WM_LBUTTONUP = &H202       'Button up
'Private Const WM_LBUTTONDBLCLK = &H203   'Double-click
'Private Const WM_RBUTTONDOWN = &H204     'Button down
'Private Const WM_RBUTTONUP = &H205       'Button up
'Private Const WM_RBUTTONDBLCLK = &H206   'Double-click

'API宣言
Private Declare Function Shell_NotifyIcon Lib "shell32" Alias "Shell_NotifyIconA" (ByVal dwMessage As Long, pnid As NOTIFYICONDATA) As Boolean
Private Declare Function ExtractIconEx Lib "shell32.dll" Alias "ExtractIconExA" (ByVal lpszFile As String, ByVal nIconIndex As Long, phiconLarge As Long, phiconSmall As Long, ByVal nIcons As Long) As Long
Private Declare Function DrawMenuBar Lib "user32" (ByVal hWnd As Long) As Long

Private typ_Icon As NOTIFYICONDATA '自クラスで管理するアイコンのデータ(構造体)

Public Function AddIcon(ByVal hWnd As Long, Optional ByVal ToolTip As String = "Notifyicon", Optional ByVal IconResourceFile As String = "") As Long
'=========================================================================
'機能       :アイコンをタスクトレイに追加する。
'引き数     :hwnd(アイコンの親となるウィンドウハンドル。例:エクセルのウィンドウ、アクセスのフォーム)
'             ToolTip (アイコンの上にマウスを持ってきたときに表示されるメッセージ)
'             IconResourceFile(アイコンのアイコンとなるリソース。例:DLL,EXEファイル。~EXCEL.EXEなどがいいと思います)
'返り値     :Shell_NotifyIcon関数の戻り値
'=========================================================================

    Dim IconSmall As Long

    If IconResourceFile = "" Then IconResourceFile = Application.Path & "\" & "EXCEL.EXE" 'アイコンリソースの指定がない時は自アプリから取得
    
    Call GetExtractIcon(IconResourceFile, IconSmall) 'リソースからアイコンを取得

    With typ_Icon
        .cbSize = Len(typ_Icon)
        .hWnd = hWnd
        .uID = 0
        .uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE Or NIF_INFO
        .hIcon = IconSmall
        .szTip = ToolTip & vbNullChar
    End With
    
    DrawMenuBar hWnd
    
    AddIcon = Shell_NotifyIcon(NIM_ADD, typ_Icon)

End Function

Public Function ShowBalloon(ByVal Message As String, Optional Title As String = "Notifyicon", Optional ByVal BalloonIcon As NOTIFYICONINFO, Optional ByVal BalloonTimeOutSecond As Long = 10) As Long
'=========================================================================
'機能       :バルーンチップを表示する
'引き数     :Message(バルーンに表示するメッセージ)
'             Title (バルーンタイトル)
'             BalloonTimeOutSecond(バルーン表示がタイムアウトするまでの時間(秒)この機能は環境依存です)
'返り値     :Shell_NotifyIcon関数の戻り値
'=========================================================================

    With typ_Icon
        .cbSize = Len(typ_Icon)
        .uFlags = NIF_INFO
        .szInfoTitle = Title & vbNullChar
        .szInfo = Message & vbNullChar
        .uTimeoutOrVersion = BalloonTimeOutSecond * 1000 'タイムアウト時間(ms)
        .dwInfoFlags = BalloonIcon
    End With

    ShowBalloon = Shell_NotifyIcon(NIM_MODIFY, typ_Icon)
    
End Function

Public Function ModifyIcon(ByVal IconResourceFile) As Long
'=========================================================================
'機能       :アイコンのリソースを変更する
'返り値     :Shell_NotifyIcon関数の戻り値
'=========================================================================

    Dim IconSmall As Long
    
    Call GetExtractIcon(IconResourceFile, IconSmall)

    With typ_Icon
        .hIcon = IconSmall
        .uFlags = NIF_ICON
    End With
    
    ModifyIcon = Shell_NotifyIcon(NIM_MODIFY, typ_Icon)
    
End Function

Public Sub DeleteIcon()
'=========================================================================
'機能       :自クラスで作成したアイコンを削除する
'=========================================================================

    With typ_Icon
        .cbSize = Len(typ_Icon)
        .uFlags = NIF_ICON
        .uID = 0
    End With
    
    Call Shell_NotifyIcon(NIM_DELETE, typ_Icon) 'API実行
    
End Sub

Private Function GetExtractIcon(ByVal IconResourceFile As String, ByRef IconSmall As Long, Optional ByVal IconIndex As Long = 0) As Long
'=========================================================================
'機能       :ExtractIconEx関数のラッパ
'引き数     :IconResourceFile(アイコンリソースファイル)
'             IconIndex (リソースから取得するアイコンのインデックス)
'返り値     :ExtractIconEx関数の戻り値、関数内エラー発生時は-1
'=========================================================================

    Dim IconLarge As Long

    If Dir(IconResourceFile) = "" Then
        MsgBox IconResourceFile & "が見つかりません", , "GetExtractIcon関数エラー"
        GetExtractIcon = -1
        Exit Function
    End If
        
    GetExtractIcon = ExtractIconEx(IconResourceFile, IconIndex, IconLarge, IconSmall, 1)
    
End Function

Private Sub Class_Terminate()
    Dim typ_Dummy As NOTIFYICONDATA
    Call DeleteIcon
    typ_Icon = typ_Dummy
End Sub

VBAで月末日を取得

月末日を取得するコードです

月末日とは指定月の翌月の1日から一日を引いた日付ということになります。

VBAで祝日を取得する(アドオンを使わずに)

アドオン使えばできるんですが、企業で配布する場合すべてのエクセルにアドオン入れるなんて面倒です。

そこで祝日の日付データをクラス化でカプセル化して簡単に取得できるクラスを作りました。