Saturday, 8 August 2015

AET Data Checker - New Features

I've added some new stuff to my AET Data Checker addin.

First, I added some labels so you can see what the Main Column and Look At Column are referring to (not just the Column letter)



The assumption is you will have your data set up with column headers in the first row.

Next up, you can now select the Main Criteria from a combo box (dropdown list). Or you can still type it in like before. Plus you can use wildcards like * and ?.



With all combo boxes, you can use the up and down arrow keys to rapidly change columns and criteria.

You'll also notice some new checkboxes.



What do they do? This!



When used, the highlighted labels make it easy to see what values matter. Use the square button at the top to turn all highlighting off or the checkboxes to toggle it individually.

Download the new version here.

See some examples of it's use here.

See you next time.

Friday, 31 July 2015

Sortable File Picker

A month or so ago, I blogged about a file picker (and a folder picker) that I made.

This is the 2nd version of my file picker.



It's similar to my former version but you can sort ascending/descending by file name, save date, file type and file size.

You can also choose to view Excel files only.

In an endeavour to make the code run faster on network drives, I've tried to use Dir as much as possible, and do not use File System Object. There is still a small lag (on my work PC) but it does appear to run faster than before.

Here's the link if you want try it out.

AET Data Checker
By the way, I also made some changes to my Data Checker. Please download the improved version if you use it. (same link as before)

See you next time.

Sunday, 19 July 2015

Find and Highlight Text

You probably know how you can use Ctrl + F to open the Find dialog box within Excel. Then click the Find All button, then Ctrl + A to select all cells that have the text you want to find.



(If you didn't, you do now)

Or you can give my AET Find and Replace tool a whirl.


Here's some code that based on my addin. (Run the "Test" subroutine)
Private sFindText As String
Private rFoundCells As Range

Private Sub Test()
    Dim rHighlightCell As Range
    
    If TypeName(Selection) <> "Range" Then
        
        MsgBox "Please select a range.", , "Find and Highlight  Text"
        
        Exit Sub
        
    End If
    
    Selection.Font.ColorIndex = xlAutomatic
    
    sFindText = InputBox("Enter the text to find", "Find and Highlight text")
    
    If sFindText = vbNullString Then Exit Sub
    
    Call FindCellsWithValue(False) 'Match Case? Use True or False
    
    If Not rFoundCells Is Nothing Then
        
        For Each rHighlightCell In rFoundCells.Cells
            
            Call HighlightFoundText(rHighlightCell, sFindText, vbRed)
            
            'HighlightFoundText Subroutine
            '------------------------
            'rHighlightCell = Cell to highlight
            'sFindText = Text to highlight if found
            'Colour to use (RGB colours etc are okay too)
            
        Next rHighlightCell
        
    Else
        
        MsgBox Chr(34) & sFindText & Chr(34) & " was not found.", , "Find and Highlight Text"
        
    End If
    
    Set rFoundCells = Nothing
    
End Sub

Private Sub FindCellsWithValue(bMatchCase As Boolean)
    Dim sFirstAddress As String
    Dim rFindCell As Range
        
    With Selection
        
        Set rFindCell = .Find(sFindText, LookIn:=xlValues, MatchCase:=bMatchCase)
        
        If Not rFindCell Is Nothing Then
            
            sFirstAddress = rFindCell.AddressLocal
            
            Do
                
                If rFoundCells Is Nothing Then
                    
                    Set rFoundCells = rFindCell
                    
                Else
                    
                    Set rFoundCells = Union(rFindCell, rFoundCells)
                    
                End If
                
                Set rFindCell = .FindNext(rFindCell)
                
            Loop While Not rFindCell Is Nothing And rFindCell.Address <> sFirstAddress
            
        End If
        
    End With
        
    Set rFindCell = Nothing
        
End Sub

Private Sub HighlightFoundText(rCell As Range, sHighlightText As String, varHighlight As Variant)
    Dim iInstances As Integer
    Dim iTimesToHighlight As Long
    Dim iMidStart As Integer
    
    sHighlightText = UCase(sHighlightText)
    
    iInstances = (Len(UCase(rCell.Value)) - Len(Replace$(UCase(rCell.Value), _
        sHighlightText, vbNullString))) / Len(sHighlightText)
    
    iMidStart = 1
    
    For iTimesToHighlight = 1 To iInstances
        
        rCell.Characters(InStr(iMidStart, UCase(rCell.Value), sHighlightText), _
            Len(sHighlightText)).Font.Color = varHighlight
        
        iMidStart = InStr(iMidStart, UCase(rCell.Value), sHighlightText) + 1
        
    Next iTimesToHighlight
    
End Sub

Note that rather loop through all cells in the selection, the code actually replicates how the Find dialog box works, and selects only cells that have the text to find within them. The FindCellsWithValue subroutine is pretty standard code in this sense, and you can find similar examples here and there on the web. (My AET Find and Replace tool uses a version of this technique). The advantage is speed. Much faster to set up a secondary "selection" of cells where the text exists, before running the HighlightFoundText subroutine if you have a lot of cells selected. And the original selection will still be selected once the highlighting has finished. :-)

See you next time.

Saturday, 11 July 2015

VBA Code Indenter and Highlighting VBA Code

This week I was looking at my VBA code indenter addin.

Attention: The VBA code indenter has recently been replaced by AET VBE Tools. Download them here.

First thing I did was make some improvements to the menu code. Some people have mentioned that they have trouble getting the menu to show, and I think I found the reason and fixed it.

I also added an option to indent single procedures. Just place your cursor in the procedure you want to indent, and select the appropriate option.



I don't know how useful this is (why not just indent the whole module?), but I guess there are legitimate reasons. And I also did it because I wanted to see if I could! A productive learning experience in learning more VBE related code to be sure. To those interested, the project is password protected, but use "password" to unlock it and have a look at the code if you want.

You can download the new version here.

Now regarding the Visual Basic Editor itself. You can make various changes to make your code more visible on the Editor Format tab on the Options dialog box (click Tools, Option on the top menu of the Visual Basic Editor). The forecolour of your Comment Text is probably set to green by default. Another change I like is to make Keywords Text blue. Keywords include Sub, Function, If, Then, etc.



Another thing I'd really like to do though is to dynamically highlight "matching" text, and everything in between. What do I mean by this? Pictures are probably an easy way to explain.

Last time I looked at indenting VBA code in cells instead of the Visual Basic Editor.

As far as I know, you can't change the colour of VBA code in the VBE, apart from Tools, Options, Editor Format mentioned above. (Am I wrong? Let me know!).

But code in a worksheet is a different situation. You can use a Worksheet_SelectionChange event to do all kinds of things!

Before


After


Useful? Maybe, if you have lots of code and it's driving you crazy to see which code matches what. You might have lots of nested code, or code off screen. Yes, we can break code down into smaller subroutines or functions, but that's a job in itself, and how about if the code is not ours to change? I think highlighting code like this is a possibly useful curiosity in any case.

Here's the highlight code. In the VBE, paste it into the Sheet module that corresponds with the sheet where your code is to be highlighted.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    Dim rCodeCheck As Range
    Dim rCell As  Range
    Dim lIndentSpacesCount As String
    Dim sFirstWord As String
    Dim sKeyWordsRGB As String
    Dim sOtherTextRGB As String
    
    Cells.Interior.ColorIndex = xlNone
    
    sKeyWordsRGB = RGB(255, 242, 204) 'Change to suit
    
    sOtherTextRGB = RGB(221, 235, 247) 'Change to suit
    
    With Target
        
        If .Column <> 1 Then Exit Sub
        
        If .Cells.Count > 1 Then Exit Sub
        
        If .Value = vbNullString Then Exit Sub
        
        Set rCodeCheck = Range(Cells(1, 1), Cells(Rows.Count, 1).End(xlUp))
        
        If Intersect(Target, rCodeCheck) Is Nothing Then
            
            Set rCodeCheck = Nothing
            
            Exit Sub
            
        End If
        
        lIndentSpacesCount = Len(.Value) - Len(LTrim$(.Value))
        
        If InStr(LTrim$(.Value), Chr(32)) > 0 Then
            
            sFirstWord = Left$(LTrim$(.Value), InStr(LTrim$(.Value), Chr(32)) - 1)
            
        Else
            
            sFirstWord = vbNullString
            
        End If
        
        Select Case sFirstWord
            
            Case "Select", "For", "Do", "With"
            
            .EntireRow.Interior.Color = sKeyWordsRGB
            
            For Each rCell In rCodeCheck.Cells
                
                If rCell.Row > .Row Then
                    
                    With rCell
                        
                        If Len(.Value) - Len(LTrim$(.Value)) = lIndentSpacesCount Then
                            
                            .EntireRow.Interior.Color = sKeyWordsRGB
                            
                            Exit For
                            
                        Else
                            
                            If .Value <> vbNullString Then .EntireRow.Interior.Color = sOtherTextRGB
                            
                        End If
                        
                    End With
                    
                End If
                
            Next rCell
            
            Case "If", "#If"
            
            If Right(.Value, 4) = "Then" Then
                
                .EntireRow.Interior.Color = sKeyWordsRGB
                
                For Each rCell In rCodeCheck.Cells
                    
                    If rCell.Row > .Row Then
                        
                        With rCell
                            
                            If Len(.Value) - Len(LTrim$(.Value)) = lIndentSpacesCount Then
                                
                                .EntireRow.Interior.Color = sKeyWordsRGB
                                
                                If Left$(LTrim$(.Value), 6) = "End If" Then Exit For
                                
                                If Left$(LTrim$(.Value), 7) = "#End If" Then Exit For
                                
                            Else
                                
                                If .Value <> vbNullString Then .EntireRow.Interior.Color = sOtherTextRGB
                                
                            End If
                            
                        End With
                        
                    End If
                    
                Next rCell
                
            Else
                
                .EntireRow.Interior.Color = sOtherTextRGB
                
                For Each rCell In rCodeCheck.Cells
                    
                    If rCell.Row > .Row Then
                        
                        With rCell
                            
                            If .Value <> vbNullString Then
                                
                                If Len(.Value) - Len(LTrim$(.Value)) = lIndentSpacesCount Then
                                    
                                    If Left$(LTrim$(.Value), 6) = "Select" Then Exit For
                                    
                                    If Left$(LTrim$(.Value), 3) = "For" Then Exit For
                                    
                                    If Left$(LTrim$(.Value), 2) = "Do" Then Exit For
                                    
                                    If Left$(LTrim$(.Value), 4) = "Loop" Then Exit For
                                    
                                    If Left$(LTrim$(.Value), 4) = "With" Then Exit For
                                    
                                    If Left$(LTrim$(.Value), 2) = "If" And Right$(.Value, 4) = "Then" Then Exit For
                                    
                                    If Left$(LTrim$(.Value), 3) = "#If" And Right$(.Value, 4) = "Then" Then Exit For
                                    
                                    .EntireRow.Interior.Color = sOtherTextRGB
                                    
                                Else
                                    
                                    Exit For
                                    
                                End If
                                
                            End If
                            
                        End With
                        
                    End If
                    
                Next rCell
                
            End If
            
            Case "End", "#End", "Loop"
            
            'Do nothing
            
            Case Else
            
            .EntireRow.Interior.Color = sOtherTextRGB
            
            For Each rCell In rCodeCheck.Cells
                
                If rCell.Row > .Row Then
                    
                    With rCell
                        
                        If .Value <> vbNullString Then
                            
                            If Len(rCell.Value) - Len(LTrim$(.Value)) = lIndentSpacesCount Then
                                
                                If Left$(LTrim$(.Value), 6) = "Select" Then Exit For
                                
                                If Left$(LTrim$(.Value), 3) = "For" Then Exit For
                                
                                If Left$(LTrim$(.Value), 2) = "Do" Then Exit For
                                
                                If Left$(LTrim$(.Value), 4) = "Loop" Then Exit For
                                
                                If Left$(LTrim$(.Value), 4) = "With" Then Exit For
                                
                                If Left$(LTrim$(.Value), 2) = "If" And Right$(.Value, 4) = "Then" Then Exit For
                                
                                If Left$(LTrim$(.Value), 3) = "#If" And Right$(.Value, 4) = "Then" Then Exit For
                                
                                .EntireRow.Interior.Color = sOtherTextRGB
                                
                            Else
                                
                                Exit For
                                
                            End If
                            
                        End If
                        
                    End With
                    
                End If
                
            Next rCell
            
        End Select
        
    End With
    
    Set rCodeCheck = Nothing
    
End Sub

Now I have to say something. The Visual Basic Editor has seen little change over a long period of time. It certainly doesn't have the rich interface functionality of other code editors. And trying to find code samples to manipulate the interface is not an easy task.

Will this change? There's been many changes to the front end of Excel and other Office applications. The Visual Basic Editor is wanting, at least as the interface is concerned.

Microsoft, I'm sure there's many programmers besides myself who would like to see improvements. Our numbers may pale in comparison compared to ordinary users, but Office is not cheap. I would definitely like to see something better, coming soon.

Opinions? I'd like to see your feedback in the comments. (and like I wrote above, I'd also like to see a way of highlighting code within the VBE if it can be done.)

Sunday, 5 July 2015

Indenting VBA Code In Cells

There's a few addins out there to indent VBA code. I should know this because I made one of them.

But let's say for argument's sake that you can't use any of them. Perhaps you're using a computer somewhere that won't allow you to install what you want. You still have access to the VBE, right?

Here's some code not yet indented.



If you look at the sub's name in the image provided, you might guess what's coming. Yep, I've pasted the code into cells and that's where I'me going to indent it.

And here is the code to indent the code for indenting ;-)

Private Sub IndentCodeInCells()
   Dim rCell As Range
   Dim sCodeString As String
   Dim sCodeStringOffset As String
   Dim sFirstWord As String
   Dim sIndent As String
   Dim sTab As String

   If TypeName(Selection) <> "Range" Then

      MsgBox "Please select a range."

      Exit Sub

   End If

   If Selection.Columns.Count > 1 Then

      MsgBox "Please select a single column."

      Exit Sub

   End If

   sTab = WorksheetFunction.Rept(Chr(32), 3) 'Change number of characters to suit

   For Each rCell In Selection.Cells

      With rCell

         If Not .Value = vbNullString Then

            .Value = LTrim$(.Value)

            sCodeString = CStr(.Value)

            sCodeStringOffset = CStr(LTrim$(.Offset(1).Value))

            Select Case sCodeString

               Case "End Sub", "End Function", "End Type", "End Enum", "End Property"
               sIndent = ""

               Case "End If", "#End If", "End Select", "End With", "Else", "#Else", "Loop"
               sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            End Select

            If Left$(sCodeString, 4) = "Next" Then sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            If Left$(sCodeString, 10) = "Loop Until" Then sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            If Left$(sCodeString, 10) = "Loop While" Then sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            If Left$(sCodeString, 4) = "Wend" Then sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            If Right$(sCodeString, 1) = ":" Then

               .Value = .Value

            Else

               .Value = sIndent & sCodeString

            End If

            If Left$(sCodeString, 4) = "Sub " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 12) = "Private Sub " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 11) = "Public Sub " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 9) = "Function " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 17) = "Private Function " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 16) = "Public Function " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 5) = "Type " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 13) = "Private Type " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 12) = "Public Type " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 5) = "Enum " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 13) = "Private Enum " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 12) = "Public Enum " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 9) = "Property " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 17) = "Private Property " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 16) = "Public Property " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 4) = "Else" Then sIndent = sIndent & sTab

            If Left$(sCodeString, 5) = "#Else" Then sIndent = sIndent & sTab

            If Left$(sCodeString, 2) = "Do" Then sIndent = sIndent & sTab

            If InStr(sCodeString, Chr(32)) > 0 Then

               sFirstWord = Left$(sCodeString, InStr(sCodeString, Chr(32)) - 1)

            Else

               sFirstWord = vbNullString

            End If

            Select Case sFirstWord

               Case "For", "With", "Select"
               sIndent = sIndent & sTab

            End Select

            If sFirstWord = "If" And Right$(sCodeString, 4) = "Then" Then sIndent = sIndent & sTab

            If sFirstWord = "#If" And Right$(sCodeString, 4) = "Then" Then sIndent = sIndent & sTab

            If sFirstWord = "If" And Right$(sCodeString, 1) = "_" And Right$(sCodeStringOffset, 4) = "Then" Then sIndent = sIndent & sTab

            If sFirstWord = "#If" And Right$(sCodeString, 1) = "_" And Right$(sCodeStringOffset, 4) = "Then" Then sIndent = sIndent & sTab

         End If

      End With

   Next rCell

End Sub

And here's how it looks after. (Indenting your code makes it a lot easier to read for yourself, and anybody else who may refer to it)



Now I simply copy the indented code and paste it back into the VBE.

Okay, how did I indent the code for this blog post then?

With this. (Note the code comments to remove "<br>" if required.)

Private Sub IndentCodeWithHTMLSpacesInCells()
   Dim rCell As Range
   Dim sCodeString As String
   Dim sCodeStringOffset As String
   Dim sFirstWord As String
   Dim sIndent As String
   Dim sTab As String

   If TypeName(Selection) <> "Range" Then

      MsgBox "Please select a range."

      Exit Sub

   End If

   If Selection.Columns.Count > 1 Then

      MsgBox "Please select a single column."

      Exit Sub

   End If

   sTab = WorksheetFunction.Rept("&nbsp;", 3) 'Change number of characters to suit

   For Each rCell In Selection.Cells

      With rCell

         If Not .Value = vbNullString Then

            .Value = LTrim$(.Value)

            .Value = Replace(.Value, "", vbNullString)

            sCodeString = CStr(.Value)

            sCodeStringOffset = CStr(LTrim$(.Offset(1).Value))

            Select Case sCodeString

               Case "End Sub", "End Function", "End Type", "End Enum", "End Property"
               sIndent = ""

               Case "End If", "#End If", "End Select", "End With", "Else", "#Else", "Loop"
               sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            End Select

            If Left$(sCodeString, 4) = "Next" Then sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            If Left$(sCodeString, 10) = "Loop Until" Then sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            If Left$(sCodeString, 10) = "Loop While" Then sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            If Left$(sCodeString, 4) = "Wend" Then sIndent = Left$(sIndent, Len(sIndent) - Len(sTab))

            If Right$(sCodeString, 1) = ":" Then

               .Value = .Value

            Else

               .Value = sIndent & sCodeString & "<br>" 'Remove "& <br>" if required

            End If

            If Left$(sCodeString, 4) = "Sub " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 12) = "Private Sub " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 11) = "Public Sub " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 9) = "Function " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 17) = "Private Function " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 16) = "Public Function " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 5) = "Type " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 13) = "Private Type " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 12) = "Public Type " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 5) = "Enum " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 13) = "Private Enum " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 12) = "Public Enum " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 9) = "Property " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 17) = "Private Property " Then sIndent = sIndent & sTab

            If Left$(sCodeString, 16) = "Public Property " Then sIndent = sIndent & sTab


            If Left$(sCodeString, 4) = "Else" Then sIndent = sIndent & sTab

            If Left$(sCodeString, 5) = "#Else" Then sIndent = sIndent & sTab

            If Left$(sCodeString, 2) = "Do" Then sIndent = sIndent & sTab

            If InStr(sCodeString, Chr(32)) > 0 Then

               sFirstWord = Left$(sCodeString, InStr(sCodeString, Chr(32)) - 1)

            Else

               sFirstWord = vbNullString

            End If

            Select Case sFirstWord

               Case "For", "With", "Select"
               sIndent = sIndent & sTab

            End Select

            If sFirstWord = "If" And Right$(sCodeString, 4) = "Then" Then sIndent = sIndent & sTab

            If sFirstWord = "#If" And Right$(sCodeString, 4) = "Then" Then sIndent = sIndent & sTab

            If sFirstWord = "If" And Right$(sCodeString, 1) = "_" And Right$(sCodeStringOffset, 4) = "Then" Then sIndent = sIndent & sTab

            If sFirstWord = "#If" And Right$(sCodeString, 1) = "_" And Right$(sCodeStringOffset, 4) = "Then" Then sIndent = sIndent & sTab

         Else

            .Value = "<br>" 'Remove this line if required

         End If

      End With

   Next rCell

End Sub

If you run the code, you will see the indenting is done with "&nbsp;" which is used instead of normal spaces in HTML. Simple copy the code and paste it in HTML when and where necessary.

See you next time.