IntroductionGetting StartedAll PostsNotesAbout The Author
Storing/Retrieving Data To/From A Text File
Monday, September 28, 2009 - 9:38 AM

This example of the code was for a shared comment history used in mutliple workbooks – also see "Requesting, Storing, And Retrieving User Settings" under the User Interfaces category of this site.


Option Explicit
Option Base 1


'the code in this module requires a reference to zaksCollection.dll
'the dll can be found here: http://www.zaks.demon.co.uk/code/cpts/coll/

Private Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" (ByVal nBufferLength&, ByVal lpBuffer$) As Long


Private Sub PopCommentGVs()
On Error Resume Next

    gvTestComments = 1

'    gvReportType = Entity
    gvCommentsFile = ThisWorkbook.Path & "\Report Files\Comments\CommentHistory.csv"
    gvCOBDate = Date - sStart.[DaysPriorForRoX] 'sStart.[DaysPriorForRoX]

    'find columns on 'T+1 (Values Only)' ws
    With wsDS(1)
        gvColNum1 = cMatch(ColName1, .Name)
        gvColNum2 = cMatch(ColName2, .Name)
        gvColNum3 = cMatch(ColName3, .Name)
        gvColNum4 = cMatch(ColName4, .Name)
        gvColNum5 = cMatch(ColName5, .Name)
        gvColNum6 = cMatch(ColName6, .Name)
        gvColNum7 = cMatch(ColName7, .Name)
        gvColNum8 = cMatch(ColName8, .Name)
    End With
    If (Application.Min(gvColNum1, gvColNum2, gvColNum3, gvColNum4, gvColNum5) <= 0) Then
        'this is just for development
        MsgBox ("Wrong sheet"): Stop
    End If

End Sub


Public Sub RunSaveComments(Optional CalledByVBA As Boolean)
 Dim subN&:  subN = SubStart(Updating, , , , "Saving Comments...", Not CalledByVBA)
On Error Resume Next
'don't need to close and re-open file during savecomments loop, but only writing to file so doesn't really matter

    Dim i As Byte, ws, tempInt& ', sStatusBar$

    ThisWorkbook.Activate

    If Not gvTestComments Then PopCommentGVs

    tempInt = myUBound(wsPendingComments)
    If CBool(tempInt) Then
        For i = 1 To tempInt
            SaveComments (i)
        Next
    Else
        SaveComments
    End If

    Call RefreshComments(True)

'    Application.StatusBar = sStatusBar

 SubEnd (subN)
End Sub


Private Sub SaveComments(Optional i As Byte)
On Error Resume Next

    If Not gvTestComments Then PopCommentGVs

    Dim GetCommentSheet As Worksheet, sUserName$:  sUserName = GetUserName()
    If (i > 0) Then Set GetCommentSheet = wsPendingComments(i) Else Set GetCommentSheet = ActiveSheet

    If (GetCommentSheet.Name <> sT1ValuesOnly.Name) Then
        'get column numbers for relevant ws
        With GetCommentSheet
            gvColNum1 = cMatch(ColName1, .Name)
            gvColNum2 = cMatch(ColName2, .Name)
            gvColNum3 = cMatch(ColName3, .Name)
            gvColNum4 = cMatch(ColName4, .Name)
            gvColNum5 = cMatch(ColName5, .Name)
            gvColNum6 = cMatch(ColName6, .Name)
            gvColNum7 = cMatch(ColName7, .Name)
            gvColNum8 = cMatch(ColName8, .Name)

        End With
        If (Application.Min(gvColNum1, gvColNum2, gvColNum3, gvColNum4, gvColNum5) <= 0) Then
            'this is just for development
            MsgBox ("Wrong sheet"): Stop

        End If
        gvTestComments = 0 'reset gvTestComments so column gvs will be reset before they are used again
    End If

    Dim iRow&, iLastRow&, sComment$, FileOpened As Boolean

    iLastRow = GetCommentSheet.Cells(1, 1).End(xlDown).Row
    If (iLastRow = 65536) Then Exit Sub

    For iRow = 2 To iLastRow
        sComment = GetCommentSheet.Cells(iRow, gvGetCommentCol)
        If TestLen(sComment) Then
TryAgain:
            If Not FileOpened Then
                On Error Resume Next
                Open gvCommentsFile For Append Lock Write As #1
                If (err.Number = 70) Then
                    Close #1
                    With Application
                        .StatusBar = "Comments file is locked by another user. Retrying in 5 secs..."
                        .Wait Now + TimeValue("00:00:05")
                    End With
                    GoTo TryAgain
                ElseIf (err.Number <> 0) Then
                    MsgBox "Failed to Open Comments File : " & gvCommentsFile & ". Please confirm you have persmissions to lock the file"
                    Exit Sub
                End If
            End If
            FileOpened = 1
            With GetCommentSheet
                Print #1, cq(.Cells(iRow, gvColNum1)) & "," & _
                    "'" & cq(.Cells(iRow, gvColNum2)) & "," & _
                    sUserName & "," & _
                    Round(.Cells(iRow, gvColNum3), 2) & "," & _
                    Round(.Cells(iRow, gvColNum4), 2) & "," & _
                    cq(DateValue(Format(Date, "mm/dd/yyyy"))) & "," & _
                    cq(.Cells(iRow, gvColNum5)) & "," & _
                    cq(.Cells(iRow, gvColNum6)) & "," & _
                    cq(.Cells(iRow, gvColNum7))
            End With
        End If
    Next
    If FileOpened Then Close #1

End Sub


Public Sub RefreshComments(Optional CalledByVBA As Boolean)
 Dim subN&:  subN = SubStart(Updating, Events, , Calculation, "Refreshing Comments...", Not CalledByVBA) 'need to disable events for DistributeComments sub
On Error Resume Next

    If Not gvTestComments Then PopCommentGVs

    ThisWorkbook.Activate

    Dim iRow&, lCommentHist$, lRange As Range
    Set zCommentHist = LoadComments(gvCommentsFile, Array(1, 2))
    Set lRange = RangeArea(wsDS(1).Cells(2, 1))
    For iRow = 1 To lRange.Rows.Count
        lRange.Cells(iRow, gvGetCommentCol) = ""
        lCommentHist = GetCommentsHist("'" & lRange.Cells(iRow, gvColNum1) _
                                        , "'" & lRange.Cells(iRow, gvAccountCol))
        With lRange.Cells(iRow, gvHistCommentCol)
            If TestLen(lCommentHist) Then
                .ClearFormats
                .Value = lCommentHist
                .WrapText = False
            Else
                .ClearFormats
                .Value = " " 'space is prevent new comments from spilling over into comment history column
            End If
        End With
'        'not doing this now:
'        lActiveComments = getActiveComments("'" & lRange.Cells(iRow, gvColNum1) _
'                          , "'" & lRange.Cells(iRow, gvColNum2) _
'                          , (lRange.Cells(iRow, gvColNum3) <> 0) _
'                          , lRange.Cells(iRow, gvColNum3))
'        lRange.Cells(iRow, gvColNum4) = lActiveComments
'        lRange.Cells(iRow, gvColNum5) = Left$(lActiveComments, 10)
    Next

    Call DistributeCommentsToOtherSheets

 SubEnd (subN)
End Sub


Public Function CheckForNewComments(Optional CalledByVBA As Boolean) As Byte
 Dim subN&:  subN = SubStart(Updating, Events, , , , Not CalledByVBA)
On Error Resume Next
'set wsPendingComments gv for sheets with pending comments
'and call RunSaveComments if any pending comments are found and user chooses to save them

    If Not wsGVTest Then PopEventGVs 'make sure aWSOut is set

    Dim c As Byte, ws
    Erase wsPendingComments
    For Each ws In aWSOut
        If CheckSheetForNewComments(ws.Name) Then
            c = c + 1
            ReDim Preserve wsPendingComments(c)
            Set wsPendingComments(c) = ws
        End If
    Next
    If (c > 0) Then
        If (MsgBox("There are pending comments. Save them now?", vbYesNo, "") = vbYes) Then
            Call RunSaveComments(True)
            CheckForNewComments = 1 'return 1
        Else
            CheckForNewComments = 2 'return 2
        End If
    End If
    Erase wsPendingComments

 SubEnd (subN)
End Function


Private Function CheckSheetForNewComments(sName$) As Boolean
On Error Resume Next

    Dim tempStr$
    With Sheets(sName)
        '(using string reference to avoid selecting ws)
        tempStr = ColumnLetter(cMatch(NewComment, sName))
        tempStr = tempStr & "2:" & tempStr & Application.Max(.UsedRange.Rows.Count, 2)
        If (Application.CountA(.Range(tempStr)) > 0) Then CheckSheetForNewComments = 1
    End With

End Function


Private Sub DistributeCommentsToOtherSheets()
On Error Resume Next

    If Not wsGVTest Then PopEventGVs

    Dim i&, s As Byte, CommentHistCol&, ColOut&(), NewCommentCol&(), tempStr$, tempVar ', sNames, aWSOut() As Worksheet
    Dim ActiveWS$:  ActiveWS = ActiveSheet.Name

    ReDim ColOut(UBound(aWSOut)), NewCommentCol(UBound(aWSOut))
    For i = 2 To UBound(aWSOut) 'skipping 'T+1 (Values Only)' ws
        With aWSOut(i)
            If (Application.CountA(.Columns(1)) > 1) Then 'make sure there's data on the ws
                ColOut(i) = cMatch(CommentHistory, .Name)
                NewCommentCol(i) = cMatch(NewComment, .Name)
                If (ColOut(i) = 0) Then Stop    'header not found
                'clear old comment data
                '(using string reference to avoid selecting ws)
                tempStr = ColumnLetter(ColOut(i))
                tempStr = tempStr & 2 & ":" & tempStr & Application.CountA(.Columns(1))
                .Range(tempStr).Clear
            End If
        End With
    Next

    With wsDS(1)
        CommentHistCol = cMatch(CommentHistory, .Name)
        For i = 2 To Application.CountA(.Columns(1)) 'skipping 'T+1 (Values Only)' ws
            tempStr = .Cells(i, CommentHistCol)
            If TestLen(tempStr) Then
                For s = 2 To UBound(aWSOut)
                    tempVar = Application.Match(.Cells(i, 1), aWSOut(s).Columns(1), 0)
                    If IsNumeric(tempVar) Then
                        If (tempVar > 1) Then 'just in case
                            With aWSOut(s).Cells(tempVar, ColOut(s))
                                .Value = tempStr
                                .WrapText = False
                            End With
                            aWSOut(s).Cells(tempVar, NewCommentCol(s)).ClearContents
                        End If
                    End If
                Next
            End If
        Next
    End With

    With Sheets(ActiveWS)
        If (ActiveSheet.Name <> ActiveWS) And .Visible Then .Select
    End With

End Sub


Private Function LoadComments(sFile$, aKeys, Optional aReorder) As zaksColl
On Error Resume Next
'read comments file and store data in a dictionary

    Dim zRes As New zaksColl, sStatusBar$:  sStatusBar = Application.StatusBar
    Dim aRow, sRow$, aRow2, i&, maxKey&, iRow&, sKey$, iMaxCols&

    For i = 1 To UBound(aKeys)
        If (aKeys(i) > maxKey) Then maxKey = aKeys(i)
    Next
    Dim fs As Scripting.FileSystemObject, a As Scripting.TextStream
    Set fs = CreateObject("Scripting.FileSystemObject")
    On Error GoTo FileNotFound
    Set a = fs.OpenTextFile(sFile, ForReading, False)
    iRow = 0
    Do While Not a.AtEndOfStream
        iRow = iRow + 1
        sRow = a.ReadLine()
        aRow = ParseRow(sRow, ",")
        If (iRow Mod 1000 = 0) Then Application.StatusBar = "Loading " & sFile & " row " & iRow
        If (UBound(aRow, 2) >= maxKey) Then
            sKey = ""
            For i = 1 To UBound(aKeys)
                sKey = sKey & Trim(aRow(1, aKeys(i)))
            Next
            If IsArray(aReorder) Then 'Reorder the columns
                iMaxCols = Application.Max(UBound(aRow, 2), UBound(aReorder))
                ReDim aRow2(1 To 1, 1 To iMaxCols)
                For i = 1 To iMaxCols
                    On Error Resume Next
                    If (aReorder(i) > 0) Then aRow2(1, i) = aRow(1, aReorder(i))
                Next
                aRow = aRow2
            End If
            If IsEmpty(zRes(sKey)) Then zRes(sKey) = New Collection
            zRes(sKey).Add aRow
        End If
    Loop
    a.Close
    Set LoadComments = zRes
    Application.StatusBar = sStatusBar

    Exit Function
FileNotFound:
    Application.StatusBar = sStatusBar
    MsgBox "File '" & sFile & "' does not exist. Please check the directory : " & err.Description
End Function


Private Function ParseRow(sRow$, sDelim$)
On Error Resume Next
'parse CSV line and create array of fields

    Dim iPos&, iPrePos&, iToken&

    ReDim aRes(1 To 1, 1 To 30)
    Do
        iPos = InStr(iPos + 1, sRow, sDelim)
        If (iPos > 0) Then
            iToken = iToken + 1
            aRes(1, iToken) = Mid(sRow, iPrePos + 1, iPos - iPrePos - 1)
            iPrePos = iPos
        Else
            iToken = iToken + 1
            aRes(1, iToken) = Right(sRow, Len(sRow) - iPrePos)
        End If
    Loop Until (iPos = 0)
    ReDim Preserve aRes(1 To 1, 1 To iToken)
    ParseRow = aRes

End Function


Private Function cq(s$)
    cq = WorksheetFunction.Substitute(s, ",", "|")
End Function


Private Function GetCommentsHist$(sSec, sAcct)
On Error Resume Next

    Dim sRet$, i&, sKey$, sTrimSec$, sTrimAcct$
    Dim lColl As Collection

    sTrimAcct = Mid(sAcct, 2, 1000)
    sTrimSec = Mid(sSec, 2, 1000)
    sKey = sTrimAcct & "'" & sTrimSec
'    sKey = Trim(sAcct) & Trim(sSec) 'this didn't work

    If IsEmpty(zCommentHist(sKey)) Then Exit Function

    Set lColl = zCommentHist(sKey)
    For i = 1 To lColl.Count
        sRet = PrintComment(lColl(i)) & vbCrLf & sRet
    Next
    If Right(sRet, 1) = Chr(10) Then sRet = Left(sRet, Len(sRet) - 1) 'remove extra line break
    GetCommentsHist = Trim$(sRet)

End Function


Private Function PrintComment$(aComment)
On Error Resume Next

    On Error GoTo ExitNow

    Dim sRet$
    sRet = Format(aComment(1, 6), "yyyy:mm:dd") 'date
    sRet = sRet & " " & Bullet & " " & aComment(1, 3)
    sRet = sRet & " " & Bullet & " " & aComment(1, 2)
    sRet = sRet & " " & Bullet & " " & aComment(1, 1)
    sRet = sRet & " " & Bullet & " " & aComment(1, 7)
    sRet = sRet & " " & Bullet & " " & aComment(1, 8)
    sRet = sRet & " " & Bullet & " " & aComment(1, 9)
    sRet = sRet & " " & Bullet & " " & aComment(1, 4)
    sRet = sRet & " " & Bullet & " " & aComment(1, 5)
    PrintComment = Trim$(sRet)
    PrintComment = Application.Substitute(PrintComment, "|", ",")

ExitNow:
End Function


Private Function GetUserName$()
On Error Resume Next

    Dim tempVar, tempStr$
    tempStr = Environ("USERNAME")
    tempVar = Application.VLookup(tempStr, sUserNames.[UserNames], 2, 0)
    If (VarType(tempVar) = vbString) Then GetUserName = tempVar Else GetUserName = tempStr

End Function


Private Function RangeRight(rngStart As Range) As Range
On Error Resume Next
'returns the rightmost cell from the start with continuous data
    If IsEmpty(rngStart) Or IsEmpty(rngStart.Offset(0, 1)) Then _
        Set RangeRight = rngStart Else Set RangeRight = rngStart.End(xlToRight)
End Function


Private Function RangeDown(rngStart As Range) As Range
On Error Resume Next
'return the downmost cell from the start with continuous data
    If IsEmpty(rngStart) Or IsEmpty(rngStart.Offset(1, 0)) Then _
        Set RangeDown = rngStart Else Set RangeDown = rngStart.End(xlDown)
End Function


Private Function RangeArea(rngStart As Range) As Range
On Error Resume Next
'get the area down and to the left of the start. End(xlDown) and xlLeft have problems if used on empty or single cells
    With rngStart
        Set RangeArea = Range(.Offset(0, 0), .Offset(RangeDown(rngStart).Row - .Row, RangeRight(rngStart).Column - .Column))
    End With
End Function

<< Navigate to Monday, September 28, 2009 Add New Comment
No records found        
Add New Comment
Your name   
Subject   
Content   
*Required fields

January, 2009
February, 2009
March, 2009
April, 2009
May, 2009
June, 2009
July, 2009
August, 2009
September, 2009
October, 2009
November, 2009
December, 2009
IntroductionGetting StartedAll PostsNotesAbout The Author