Consulting

Page 2 of 3 FirstFirst 1 2 3 LastLast
Results 21 to 40 of 50

Thread: A Data Cleaning Macro - Clean, Trim and CHAR(160)

  1. #21
    VBAX Sage
    Joined
    Apr 2007
    Location
    United States
    Posts
    8,728
    Location
    A few other ideas / thoughts to consider (not tested, but at least no errors)

    1. There was a lot of duplicate code, so you could restructure a bit
    2. I wouldn't rely on Activesheet. You could use RangeIn.Parent.Name to get the sheet name for the range
    3. I like to use Exit Sub to avoid a lot of nested If statement if possible
    4. Good idea in your sub to have DoEvents and Statusbar updates. You could combine both and what I like to do is update the statusbar whenever I have a full percent change (9% to 10% to 11% ...)


    Option Explicit
     
     ' An enumeration to allow for bitwise option selection - uses base 2, aka: binary
    Enum CleanType
        ReplaceOnly = 0
        Size = 1
        TextToNum = 2
        Trim = 4
        Clean = 8
        Proper = 16
    End Enum
      
    Sub TrimAll(RangeIn As Range, Optional CleaningMode As CleanType = ReplaceOnly, Optional ReplaceOnly As Boolean = False, _
        Optional Length As Long = 255, Optional ReplacementCode As Long = 32, Optional bCompleteStatus As Boolean = True)
         
        Dim Cell As Range, IntersectRng As Range
        Dim CodesToReplace() As Long, i As Long     ' Index is VBA word PH
        Dim CurrentProgressValue As Double, NumCells As Long, NumCellRanges As Double
        Dim CheckValue As Long
         
         
        ' Initialise character codes to be replaced
        CodesToReplace = Array(127, 129, 141, 143, 144, 157, 160)
         
        For i = LBound(CodesToReplace) To UBound(CodesToReplace)
             
             ' Display progress information in status bar
            Application.StatusBar = "Currently on worksheet: '" & RangeIn.Parent.Name & _
                "'  -  Replacement in Progress: " & i & " of " & UBound(CodesToReplace) & _
                ": " & Format(i / UBound(CodesToReplace), "Percent") & _
                "'  -  Macro Is Still Running!"
             
            RangeIn.Replace What:=Chr(CodesToReplace(i)), Replacement:=Chr(ReplacementCode), LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False
        Next i
         
        If CleaningMode = ReplaceOnly Then Exit Sub
         
        Set IntersectRng = Intersect(RangeIn, RangeIn.SpecialCells(xlConstants, xlTextValues))
            
        If IntersectRng Is Nothing Then Exit Sub
        
        NumCellRanges = IntersectRng.Cells.Count
         
        CurrentProgressValue = 0
        CheckValue = 0
        
        For Each Cell In IntersectRng
            With Cell
                If CleaningMode And Size Then .Value = Left(.Value, Length)
                If CleaningMode And TextToNum Then If IsNumeric(.Value) Then .Value = .Value * 1
                If CleaningMode And Trim Then .Value = Application.WorksheetFunction.Trim(.Value)
                If CleaningMode And Clean Then .Value = Application.WorksheetFunction.Clean(.Value)
                If CleaningMode And Proper Then .Value = Application.WorksheetFunction.Proper(.Value)
                
                'maybe only update on a full percent change
                If CheckValue <> CLng(100 * CurrentProgressValue / NumCellRanges) Then
                    CheckValue = CLng(100 * CurrentProgressValue / NumCellRanges)
                 
                    Application.StatusBar = "Currently on worksheet: '" & _
                        RangeIn.Parent.Name & "'  -  Cleaning in Progress: '" _
                        & CurrentProgressValue & " of " & NumCellRanges & ": " & _
                        Format(CheckValue, "#0\%") & "'  -  Macro Is Still Running!"
                    DoEvents
                End If
                 
                 ' Iterate the progress counter
                CurrentProgressValue = CurrentProgressValue + 1
            End With
        Next Cell
         
    End Sub

    Like I said, not tested with real data, but maybe some ideas you could use. When you're done, post the final result so I can include it in my cleanup

    Paul

  2. #22
    Righto, I'll have a look at some of those suggestions in due course and get back to you.

    Thanks again for your help Paul

    EDIT: Okay, so, I still have a lot to learn based on how much you whittled that code down by!

  3. #23
    So I bounced into another problem, that is, creating violations of Data Validation rules as VBA goes about it's cleaning and replacing etc, thus resulting in an overflow error.
    I've decided the best approach was to calculate a complement set from the range object representing the constants, and the range of object containing the cells that have validation (each of which is calculated as per prior code, that is, from the intersection of RangeIn with .specialcells [constants,text] and RangeIn with specialcells [all validation] property).
    These two threads have proved fruitful regarding how to calculate a complement set (given VBA's lack of a native function):
    http://stackoverflow.com/questions/1...10237#17510237
    http://dailydoseofexcel.com/archives...-and-subtract/

    So far I've just pilfered the code from the post below and implemented it:
    http://stackoverflow.com/a/17510237/2724551

    I haven't tested it rigorously but it seems to work. If anyone wants to throw in some advice on the efficiency of calculating a complement set it would be appreciated, as crunching even just the original intersect using the native function takes a significant portion of the total time...

    The general scheme is to use a simple boolean flag passed as an argument, that determines whether cells with validation are excluded from calculating the range that will have cleaning operations conducted on it.

  4. #24
    Argh, the overflow errors are more general, they're coming from something to do with applying excels clean function to numbers that have been stored as text.... Any ideas?
    The overflow error comes when .trim is called to act on a cell that was a number stored as text, but the prior .clean operation causes it to display as ############# and seems to have deleted the leading zero...

    In my case I don't explicitly need to convert these numbers stored as text (they're mobile phone numbers) to numbers, but how can i avoid the overflow error?
    Would the answer lie in storing the cell format prior to cleaning and then reapplying it between each cleaning operation and afterwards, or some such? (that seems horribly computationally inefficient, but I'm clutching at straws)....

  5. #25
    Knowledge Base Approver VBAX Wizard
    Joined
    Apr 2012
    Posts
    5,645
    Since you didn't post a sample workbook nor indciated which code you are using it's purely guessing...
    How can we be able to help you in such a case ?

  6. #26
    Fair point. I can't publish the workbook unfortunately, but I can probably recreate the problem in an example. The code is still evolving, I'll post it along with the example workbook once it's created.

    EDIT: I worked out the problem, its not the data validation (though I now have a way round that should I ever need it) that was just a coincidence .
    The problem is that after either the trim or clean operation has been completed, the cell format is being converting those numbers with a leading zero from text, back to a 'number'.
    That is then giving the next function call (either trim or clean) a big headache. All other non-text constant cells get filtered out at the start when calculating the range, the problem is these ones are getting dynamically created part way through cleaning and then subsequent functions error trying to work on a cell that is not a constant with a text value.
    I haven't actually worked out what I'm going to do about it as yet, as I actually want to keep these as numbers stored as text, so that means skipping these types of cells, but ID'ing them isn't straight forward (at least no computationally efficient method that I can think of).
    Last edited by Simple_One; 08-28-2013 at 05:46 AM.

  7. #27
    Okay, sorted it out.
    Basically, for reasons that are unknown to me, cells that were 'numbers stored as text' but formatted in Excel as 'Dates' rather than as 'Text'; were still getting picked up by the following bit of code:
    RangeIn.SpecialCells(xlConstants, xlTextValues)
    When one of these cells was encountered, as soon as it was trimmed or cleaned, it would automatically revert to being a 'number' rather than 'numbers stored as text'; naturally this caused errors with the following function calls on a cell that is no longer an xlConstants, xlTextValues cell.
    My solution isn't elegant, basically what it does is change the format of all cells within the calculated IntersectRng, to text (via IntersectRng.NumberFormat = "@"). The rest of the code then works again. This may or may not be acceptable for others, but that's where I have left it at.

    I've left in the functionality to skip data validation cells, though it doesn't actually seem to be a problem to clean and trim them (as I originally thought). Note that if you skip cells with data validation, this is dependent on other code that I sourced from the previous links (the codes provides a function to calculate a complement range when given two range arguments). I have reposted this code below, but it's nothing of mine, I just whacked it in and used it.
    In summary: you only need the extra functions if you set the SkipValidation argument to True; if that is left as False, the extra functions are not called. The extra functions are:
    Public Function Complement
    Public Function Union

    So here is the main cleaning code as it currently stands:
    Option Explicit
    
    ' An enumeration to allow for bitwise option selection - uses base 2, aka: binary
    Enum CleanType
        ReplaceItOnly = 0
        SizeIt = 2 ^ 0
        TextToNumIt = 2 ^ 1
        TrimIt = 2 ^ 2
        CleanIt = 2 ^ 3
        ProperIt = 2 ^ 4
        PrefixCharIt = 2 ^ 5
        FormatIt = 2 ^ 6
    End Enum
    
    
    
    
    Public Sub TrimAll(RangeIn As Range, Optional CleaningMode As CleanType = 76, Optional Length As Integer = 255, Optional ReplacementCode As Integer = 42, Optional SkipValidation As Boolean = False)
    
    
        Dim Cell As Range, IntersectRng As Range, ValidationRng As Range, TextConstRng As Range
        Dim i As Integer
        Dim CurrentProgressValue As Double, CountCellsInRanges As Double, CheckValue As Double, PercentChange As Double
        Dim CodesToReplace() As Variant
        Dim temp As String
    
    
        On Error GoTo STOPPER
    
    
                ' Initialise character codes to be replaced
                CodesToReplace() = Array(127, 129, 141, 143, 144, 157, 160)
    
    
                For i = LBound(CodesToReplace) To UBound(CodesToReplace)
                    ' Display progress information in status bar
                    Application.StatusBar = "Currently on worksheet: """ & RangeIn.Parent.Name & """  -  Replacement in Progress: """ & i & " of " & UBound(CodesToReplace) & ": " & Format(i / UBound(CodesToReplace), "Percent") & """  -  Macro Is Still Running!"
                    ' Convert all extra codes to the ReplacementCode character for trimming by other functions or other means in Excel
                    RangeIn.Replace What:=Chr(CodesToReplace(i)), Replacement:=Chr(ReplacementCode), LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False
    '                On Error Resume Next 'in case no text cells in selection
                Next i
    
    
                ' If no further cleaning has been specified
                If CleaningMode And ReplaceItOnly Then Exit Sub
    
    
                ' Display progress information in status bar
                Application.StatusBar = "Currently on worksheet: """ & ActiveSheet.Name & """  -  Calculating which cells are text values out of the " & Format(RangeIn.Cells.count, "#,##0") & _
                                        " that were passed for further processing  -  This might take some time, but THE MACRO IS STILL RUNNING!"
    
    
                ' Set the intersection range, deciding whether or not to include cells with validation that may be violated.
                If SkipValidation Then
                    Set ValidationRng = Intersect(RangeIn, RangeIn.SpecialCells(xlCellTypeAllValidation))
                    Set TextConstRng = Intersect(RangeIn, RangeIn.SpecialCells(xlConstants, xlTextValues))
                    Set IntersectRng = Complement(TextConstRng, ValidationRng)
                Else
                    Set IntersectRng = Intersect(RangeIn, RangeIn.SpecialCells(xlConstants, xlTextValues))
                End If
    
    
                ' No cells were found containing constants of type a text value type, so exit
                If IntersectRng Is Nothing Then Exit Sub
    
    
                ' Set all cell formats to confirm they will stay as text throughout
                ' .SpecialCells(xlConstants, xlTextValues) seems to be selecting cells that are formatted as 'Date'
                If CleaningMode And FormatIt Then IntersectRng.NumberFormat = "@"
    
    
                ' Initialise the required counter variables
                CountCellsInRanges = IntersectRng.Cells.count
                CurrentProgressValue = 0
                CheckValue = 0
    
    
                For Each Cell In IntersectRng
    
    
                    With Cell
                        ' Perform the CleaningMode operations.
                        If CleaningMode And SizeIt Then .Value = Left(.Value, Length)
                        If CleaningMode And PrefixCharIt Then .Value = .Value
                        If CleaningMode And TextToNumIt Then .Value = .Value * 1
                        If CleaningMode And TrimIt Then .Value = Application.WorksheetFunction.Trim(.Value)
                        If CleaningMode And CleanIt Then .Value = Application.WorksheetFunction.Clean(.Value)
                        If CleaningMode And ProperIt Then .Value = Application.WorksheetFunction.Proper(.Value)
                    End With
    
    
                    ' Only update on a full percent change
                    PercentChange = 100 * CurrentProgressValue \ CountCellsInRanges
                    If CheckValue <> PercentChange Then
                        CheckValue = PercentChange
                        Application.StatusBar = "Currently on worksheet: '" & RangeIn.Parent.Name & "'  -  Cleaning in Progress: '" _
                                                & CurrentProgressValue & " of " & CountCellsInRanges & ": " & _
                                                Format(CheckValue, "#0\%") & "'  -  Macro Is Still Running!"
                    End If
                    
                    ' Stop every 10% for system event execution
                    If CheckValue Mod 10 = 0 Then
                        DoEvents
                    End If
    
    
                    ' Iterate the progress counter
                    CurrentProgressValue = CurrentProgressValue + 1
    
    
                Next Cell
    
    
    STOPPER:
        MsgBox "Error encountered, debugging needed."
        Exit Sub
    
    
    End Sub
    
    
    
    
    Public Sub Call_TrimAll_Selection()
    'uses bitwise (base 2) enumerated CleanType data type, so options can be added together
    
    
        Dim bStatusBar As Boolean
    
    
        ' store state of status bar, and then show it so progress of function can be seen
        bStatusBar = Application.DisplayStatusBar
        Application.DisplayStatusBar = True
    
    
        ' prep for long macro to run
        Application.Cursor = xlWait
        Application.Calculation = xlCalculationManual
        Application.ScreenUpdating = False
    
    
        Call TrimAll(Selection)
    
    
        'Clear the status bar and restore its original state.
        Application.StatusBar = False
        Application.DisplayStatusBar = bStatusBar
    
    
        ' Restore after long macro
        Application.ScreenUpdating = True
        Application.Cursor = xlDefault
        Application.Calculation = xlCalculationAutomatic
        Application.CalculateFullRebuild
    
    
    End Sub
    
    
    
    
    Public Sub Call_TrimAll_Workbook()
    'uses bitwise (base 2) enumerated CleanType data type, so options can be added together
    
    
        Dim Cell As Range, OriginalCell As Range, WSCell As Range
        Dim CodesToReplace() As Integer, i As Integer, CurrentProgressValue As Integer
        Dim WS As Worksheet, OriginalWS As Worksheet
        Dim bStatusBar As Boolean
    
    
        ' store state of status bar, and then show it so progress of function can be seen
        bStatusBar = Application.DisplayStatusBar
        Application.DisplayStatusBar = True
    
    
        ' prep for long macro to run
        Application.Cursor = xlWait
        Application.Calculation = xlCalculationManual
        Application.ScreenUpdating = False
    
    
        ' Store the original position prior to running
        Set OriginalCell = ActiveCell
        Set OriginalWS = ActiveSheet
    
    
        For Each WS In Worksheets
    
    
            WS.Activate
            Set WSCell = ActiveCell
            Range("A1").Select
            Range(Selection, Selection.SpecialCells(xlLastCell)).Select
    
    
            Call TrimAll(Selection)
    
    
            WSCell.Select
    
    
            ' Allow system to do events, then get VBA to wait for some number of seconds to aid stability
            DoEvents
            Application.Wait (Now() + CDate("00:00:01"))
    
    
        Next WS
    
    
        OriginalWS.Activate
        OriginalCell.Select
    
    
        'Clear the status bar and restore its original state.
        Application.StatusBar = False
        Application.DisplayStatusBar = bStatusBar
    
    
        ' Restore after long macro
        Application.ScreenUpdating = True
        Application.Cursor = xlDefault
        Application.Calculation = xlCalculationAutomatic
        Application.CalculateFullRebuild
    
    
    End Sub
    And here are the extra functions (not mine originally, or even modded by me) to allow for the SkipValidation functionality:
    '(needed by the 2nd function)Public Function Union(ByRef rng1 As Range, _
                          ByRef rng2 As Range) As Range
        If rng1 Is Nothing Then
            Set Union = rng2
            Exit Function
        End If
        If rng2 Is Nothing Then
            Set Union = rng1
            Exit Function
        End If
        If Not rng1.Worksheet Is rng2.Worksheet Then
            Exit Function
        End If
        Set Union = Application.Union(rng1, rng2)
    End Function
    
    
    
    
    
    
    Public Function Complement(ByRef rng1 As Range, _
                               ByRef rng2 As Range) As Range
        Dim rngResult As Range
        Dim rngResultCopy As Range
        Dim rngIntersection As Range
        Dim rngArea1 As Range
        Dim rngArea2 As Range
        Dim lngTop As Long
        Dim lngLeft As Long
        Dim lngRight As Long
        Dim lngBottom As Long
    
    
        If rng1 Is Nothing Then
            Exit Function
        End If
        If rng2 Is Nothing Then
            Set Complement = rng1
            Exit Function
        End If
        If Not rng1.Worksheet Is rng2.Worksheet Then
            Exit Function
        End If
        Set rngResult = rng1
        For Each rngArea2 In rng2.Areas
            If rngResult Is Nothing Then
                Exit For
            End If
            Set rngResultCopy = rngResult
            Set rngResult = Nothing
            For Each rngArea1 In rngResultCopy.Areas
                Set rngIntersection = Application.Intersect(rngArea1, rngArea2)
                If rngIntersection Is Nothing Then
                    Set rngResult = Union(rngResult, rngArea1)
                Else
                    lngTop = rngIntersection.row - rngArea1.row
                    lngLeft = rngIntersection.Column - rngArea1.Column
                    lngRight = rngArea1.Column + rngArea1.Columns.count - rngIntersection.Column - rngIntersection.Columns.count
                    lngBottom = rngArea1.row + rngArea1.Rows.count - rngIntersection.row - rngIntersection.Rows.count
                    If lngTop > 0 Then
                        Set rngResult = Union(rngResult, rngArea1.Resize(lngTop, rngArea1.Columns.count))
                    End If
                    If lngLeft > 0 Then
                        Set rngResult = Union(rngResult, rngArea1.Resize(rngArea1.Rows.count - lngTop - lngBottom, lngLeft).Offset(lngTop, 0))
                    End If
                    If lngRight > 0 Then
                        Set rngResult = Union(rngResult, rngArea1.Resize(rngArea1.Rows.count - lngTop - lngBottom, lngRight).Offset(lngTop, rngArea1.Columns.count - lngRight))
                    End If
                    If lngBottom > 0 Then
                        Set rngResult = Union(rngResult, rngArea1.Resize(lngBottom, rngArea1.Columns.count).Offset(rngArea1.Rows.count - lngBottom, 0))
                    End If
                End If
            Next rngArea1
        Next rngArea2
        Set Complement = rngResult
    End Function

  8. #28
    Knowledge Base Approver VBAX Wizard
    Joined
    Apr 2012
    Posts
    5,645
    I'd be more interested in a sample workbook.

  9. #29
    Okay attached is a small example that produces the errors I'm aware of, with some comments on the results.
    The code is slightly updated from what I posted above.

    The data cleaning code is in one module, and the range complement is in another if people want to play around with skipping data validation.
    Cell D3 has data validation on it, but both of the cells with red fill produce an overflow error due to their format as 'date'. As mentioned, my solution is change their format to text.

    One obvious thing to resolve is implement some sort of error handling if it encounters an empty worksheet, at the moment it errors with 'no cells were found'. It needs some of way of just exiting the sub and then carrying on with the next sheet if there is one in the calling sub.

    EDIT: I suppose you could do some conditional checking in the calling sub to skip empty worksheets, rather than passing it to the cleaning sub to deal with....
    Attached Files Attached Files

  10. #30
    Moderator VBAX Sage SamT's Avatar
    Joined
    Oct 2006
    Location
    Near Columbia
    Posts
    7,814
    Location
    @ Not so Simple_One

    When you get this project polished, why don't you submit it to Potential KB Entries
    I expect the student to do their homework and find all the errrors I leeve in.


    Please take the time to read the Forum FAQ

  11. #31
    Moderator VBAX Sage SamT's Avatar
    Joined
    Oct 2006
    Location
    Near Columbia
    Posts
    7,814
    Location
    @ Not so Simple_One

    When you get this project polished, why don't you go to the VBAX Code Submissions forum and submit it to Potential KB Entries sub forum.
    I expect the student to do their homework and find all the errrors I leeve in.


    Please take the time to read the Forum FAQ

  12. #32
    Hi SamT, I can do that. Still a fair bit of work to go to make it robust and as fast as possible though.

    @Paul_Hossler
    I had a specific query for you. The reason I didn't adopt your method of looping areas (which sounds like it should be faster than looping cells as I have done), is because when I use your code from page 1, whenever it encounters a range (rather than a single cell) it overwrites the entire range with the value from the first cell.
    Is this a problem you have encountered before, or have I managed to do something strange to your code that I'm playing with?

  13. #33
    Knowledge Base Approver VBAX Wizard
    Joined
    Apr 2012
    Posts
    5,645
    I managed to 'clean' your example using:

    Sub M_snb()
       
       With Sheets("Example").UsedRange
           .Replace Chr(10), "", xlPart
           .Replace Chr(160), "", xlPart
           
           .Name = "snb_001"
           .Value = [index(trim(snb_001),)]
           .Value = [if(snb_001="","",if(column(snb_001)=4,if(isnumber(snb_001),text(snb_001,"'0000000000"),snb_001),snb_001))]
        End With
       
    End Sub

  14. #34
    Thanks, snb, I'll have a look.

    Any idea's about how to:
    1. Detect pivot tables in a worksheet, and then skip to the next worksheet?
    2. Detect a worksheet that contains no .SpecialCells(xlConstants, xlTextValues), and then skip to the next worksheet?

    Thanks.

  15. #35
    Knowledge Base Approver VBAX Wizard
    Joined
    Apr 2012
    Posts
    5,645
    sub M_snb()
      on error resume next
    
      for each sh in sheets
        if sh.pivottables.count=0 then 
          n=sh.specialcells(2).count
          if err.number =0 then
            sh.Columns(4).NumberFormat = "@"
    
            With Sh.UsedRange
              .Replace Chr(10), "", xlPart
              .Replace Chr(160), "", xlPart
           
              .Name = "snb_001"
              .Value = [index(trim(snb_001),)]
            End With
          end if
          err.clear
        end if
      next
    End Sub
    NB. The 'problems' in column D were caused by the incorrect numberformatting.
    Last edited by snb; 08-29-2013 at 01:59 AM.

  16. #36
    Okay, thanks. Error handling is something I'm new to, but in your code above, does it essentially work by saying:

    On Error Resume Next >>> Just keep going and execute the next line of code
    {this means you can do anything that won't be affected by whatever the error may have been}
    When the error might be relevant, you check Err.Number and then decide to skip some code...(or run different code etc)
    Then you clear the error on your way out.

    Is that about the gist of it?

  17. #37
    Knowledge Base Approver VBAX Wizard
    Joined
    Apr 2012
    Posts
    5,645
    There's probably a fine explanation in the VBEditor's helpfiles.

  18. #38
    Hmmm, never realised VB's help was different to Excels
    I'll give it a look see.

  19. #39
    Knowledge Base Approver VBAX Wizard
    Joined
    Apr 2012
    Posts
    5,645
    It's a different program.

  20. #40
    That it is, thanks for pointing that out, the explanation on error handling was helpful.
    Thanks also for the index idea, I'm playing around with using that do things by area as Paul suggested, rather than by cell and that seems to avoid the problem of overwriting values in a range with that of only the first cell. It does seem slow though, i presume that's down to the overheads of how index works....

    Question: In your short piece of code for the example sheet
    Sub M_snb()     
        With Sheets("Example").UsedRange
            .Replace Chr(10), "", xlPart
            .Replace Chr(160), "", xlPart
             
            .Name = "snb_001"
            .Value = [index(trim(snb_001),)]
            .Value = [if(snb_001="","",If(column(snb_001)=4,If(isnumber(snb_001),text(snb_001,"'0000000000"),snb_001),snb_001))]
        End With
         
    End Sub
    If you reverse the sequence of the replacement (replace char 160 and then char10), you get a slightly different result. Your arrangement works perfectly, but doing 160 first then 10 results in the data in the cell being slightly offset from centre, even after the trim and clean function have run. Any idea what's going on there?

    Sub M_snb()     
        With Sheets("Example").UsedRange
            ' inverse sequence of original code
            .Replace Chr(160), "", xlPart
            .Replace Chr(10), "", xlPart
             
            .Name = "snb_001"
            .Value = [index(trim(snb_001),)]
            .Value = [if(snb_001="","",If(column(snb_001)=4,If(isnumber(snb_001),text(snb_001,"'0000000000"),snb_001),snb_001))]
        End With
         
    End Sub

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •