A family of Microsoft spreadsheet software with tools for analyzing, charting, and communicating data.
A lot of bedtime reading for you. Hope I have explained everything OK and not made it too boring but please get back to me if you require any more explanation.
Q1. I thought of using COUNTA/Resize to check if the user inputs a invalid number. In such case, there would be no entries in the column. So COUNTA will be nothing/zero excluding the column heading.
A1. Perfectly valid method. In the code example below I have commented out the code I used and inserted the COUNTA method. Note the syntax where VBA needs to be told it is a WorksheetFunction and I used VBA ListObject variables for the range to count and input variable for the value to count. Have included 2 methods of syntax (one commented out) using the ListObject. One uses column number and the other uses the column header name.
Q2. How does it switch between Source and Output workbooks without using windows activate
A2. Generally it is not necessary to activate any workbooks, worksheets, ranges etc if the code fully references the desired location with the workbook name, sheet name and range etc. By assigning a workbook to a workbook variable then assign the worksheet to a worksheet variable by nominating both the workbook and worksheet then assigning a range to a range variable from the worksheet variable. If the objects are properly assigned in order then the last variable which is usually the range contains the full reference to workbook, worksheet and range.
Copy the following code into a blank new workbook and run the code. (to run code from within the VBA editor just click anywhere in the sub and press F5). You will see in the MsgBox that rng.Address returns the complete reference including the workbook, sheet and range. This is a great advantage of using object variables.
Sub VariableExample()
Dim wb As Workbook
Dim ws As Worksheet
Dim rng As Range
Set wb = ThisWorkbook ***'ThisWorkbook is the workbook containing this VBA code***
'Set wb = Workbooks("My Workbook") ***'Alternative syntax. "My Workbook" can be ThisWorkbook or a different Workbook.***
Set ws = wb.Worksheets("Sheet1")
Set rng = ws.Range("A1:C30")
MsgBox rng.Address(External:=True) ***'Returns the full reference to workbook, sheet and range***
Worksheets("Sheet3").Activate 'NOT the sheet with the assigned rng
rng.Value = "My Test" 'This will output to Sheet1 event though Sheet3 is the Active sheet (NOTE: it populates the entire range with the same value)
End Sub
Q3. What is a list object. I have checked it in the object browser ListObject is part of Worksheet and TableObject class. I don't see any syntax.
A3. Yes a ListObject is a Table. many moons ago they were called Lists in the worksheet but the name got changed to Table but the VBA has never been changed. If you search the internet for VBA code associated with List Objects you will probably find quite a few examples of the syntax. It is possible to use different syntax. eg I used the column number of the DataBodyRange. You can also use the column Id (Header Name)
Q4. Is vbCrLf same as vbNewLine? I guess it is.
A4. You guessed correctly. See the following link for some more information on these. All of the posts at the link are probably relevant. Which one to use is mostly matter of choice so if you come across any instances where you get strange results like 2 line feeds etc then try an alternative.
https://stackoverflow.com/questions/37273817/vba-vbnewline-but-with-no-extra-space
Q5. I see no variables being passed or sub being called. Function Validate is public. It jumps to function validate procedure to validate myInp while debugging.
A5. See following line in the code.
If Validate(lstObj.DataBodyRange.Columns(1), myInp) Then
Validate is the name of the UDF and the call passes lstObj.DataBodyRange.Columns(1) which populates the variable rngToSearch.
It also passes myInp which populates the variable varToFind
Q6. What is rngToFind?
A6. rngTofind is a range variable and the cell where the value is found is assigned to it. It will be a single cell range.
Q7. If rngToFind = Nothing.
A7. Firstly, rngTofind by itself never equals anything. The rngToFind.Value will equal something and is the value in the cell/range. The terminology is: If rngToFind is Nothing. ie. Nothing has been assigned to it. In the code I have used the opposite as follows.
If Not rngToFind Is Nothing
If it is NOT Nothing then it is Something so it has been assigned to a range meaning that the value being searched has been found and the cell where found is assigned to the range variable.
Q8. In the Find arguments what is xlValues, xlWhole, xlByRows, xlNext is default I see in syntax MatchCase False implies?
A8. xlValues is look in the values returned by formulas or values that are entered directly in a cell. Oposite is xlFormulas and is normally used in conjunction with xlPart (see next part of answer) to find cells that contain a particular formula. eg find COUNTA with xlFormulas will find a cell containing that COUNTA function.
xlWhole refers to the entire contents of the cell must match; not just part of the cell contents.
Opposite is xlPart. eg. cell contains OssieMac. Search for Ossie and use xlPart and the cell will be found but if xlWhole Ossie will not be found.
xlByRows means to search 1st row of range to search, then 2nd row, 3rd row etc. Opposite is xlColumns means to search 1st column then 2nd column, 3rd column etc.
xlNext means to search forward through the range from the first cell. Opposite is xlPrevious which will search backwards through the range from the last cell.
Interesting thing here is if xlNext is used then if the first cell of the range is a match then it is not found until after it finishes at the bottom of the range and loops around to the top of the range again. Find alwys loops around so code needs to be used to halt the code if FindNext is used when searching for multiple instances of a value.
If you perform a Find in the interactive mode on the worksheet and in the find dialog select the Options then you will be able to associate most of the VBA arguments to the Interactive Mode.
(xlPrevious is not supported in the Interactive mode)
Q9. However, I face issue with all boarders. The template I use has conditional formatting so if I use Clear instead of Clear.Contents conditional formatting is gone.
Clear.contents leave the boarders and when I run the macro with a different input number now the boarders are beyond the last row.
A9. See the code example below where I clear the borders below the data and to right of data without affecting Conditional formatting.
Also see where I have populated the date cells directly with the value from the VBA Date function and it is not necessary to Copy -> PasteSpecial -> Values.
See how you go with it and again feel free to get back to me if any more questions or problems.
Amended Code Example.
Sub MCR()
Dim wbSource As Workbook
Dim wbOutput As Workbook
Dim wsOutput As Worksheet
Dim wsFull As Worksheet
Dim lstObj As ListObject
Dim strPrompt As String
Dim myInp As Variant
Dim lngLastRow As Long
Dim strCity As String
Dim strRowRef As String
strCity = "New York"
Set wbSource = Workbooks("ML.xlsx") 'Assign workbook to workbook variable
Set wsFull = wbSource.Worksheets("Full-View") 'Assign the worksheet to worksheet variable
Set lstObj = wsFull.ListObjects("Table1") 'Assign the Table (List Object) to a List Object variable
'Setting the variables as per the previous 3 lines of code, lstObj now contains the full
'infomation about the Table including the Workbook name, Worksheet name and Table name
Set wbOutput = Workbooks("MCR Macro.xlsm")
Set wsOutput = wbOutput.Worksheets("Sheet1") 'Edit "Sheet1" to the worksheet name for the output
'By using a variable for the prompt, the prompt can be altered if invalid input by User
strPrompt = "Enter the number" & vbCrLf \_
& "Cancel to exit and terminate processing."
Do
'By using default in following line if User errors in the Inlut then
'User can see what was entered and correct it otherwise it will be blank
myInp = Trim(VBA.Interaction.InputBox(Prompt:=strPrompt, Title:="MCR Macro", Default:=myInp))
If myInp = "" Then 'If user cancels then myInp will be zero length string
MsgBox "User cancelled. Processing terminated." 'Optional. Can delete this line and simply Exit
Exit Sub
End If
'######################################################################################################
'Following line counts the number of instances of the input value in the first column of data
'If WorksheetFunction.CountA(lstObj.DataBodyRange.Columns(1), myInp) > 0 Then 'OptionUsing column number
If WorksheetFunction.CountA(lstObj.ListColumns("Serial").DataBodyRange, myInp) > 0 Then 'Option Using column header name
Exit Do
Else
'Change Input prompt with message re invalid input and loop back to InputBox
strPrompt = "Invalid entry. Please edit the number" & vbCrLf \_
& "Cancel to exit and terminate processing."
End If
'#######################################################################################################
'\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
'Code between asterisk lines replaced with alternative code above between hash lines
'If at least one value found in the column to Filter then there will be some output.
'If Validate(lstObj.DataBodyRange.Columns(1), myInp) Then
' Exit Do 'Go to past Loop command if Valid Input
'Else
'Change Input prompt with message re invalid input and loop back to InputBox
' strPrompt = "Invalid entry. Please edit the number" & vbCrLf \_
' & "Cancel to exit and terminate processing."
'End If
'\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
Loop 'If Invalid Input will loop back to Do for User to edit Input.
lstObj.Range.AutoFilter Field:=1, Criteria1:=myInp
'DataBodyRange is range under the column headers and Column 8 is the same as offset 7 columns.
lstObj.DataBodyRange.Columns(8).SpecialCells(xlCellTypeVisible).Copy
wsOutput.Range("A4").PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks \_
:=False, Transpose:=False
lstObj.AutoFilter.ShowAllData 'Optional: Cancel filtering on the Table
Application.CutCopyMode = False 'Optional: Clear copy from clipboard
Application.Goto wsOutput.Range("A4") 'Optional: To Activate the worksheet with Output
With wsOutput
'Next line: Call the UDF to find the last used row on the worksheet
lngLastRow = LastRow(.Cells) '.Cells is entire range of wsOutput.
.Range(.Cells(4, "O"), .Cells(lngLastRow, "O")) = strCity
.Range(.Cells(4, "P"), .Cells(lngLastRow, "P")) = Date 'Date is the current date in VBA
.Range(.Cells(4, "P"), .Cells(lngLastRow, "P")).NumberFormat = "dd-mmm-yyyy"
.Range(.Cells(4, "Q"), .Cells(lngLastRow, "Q")) = "C"
***'Remove all borders below the last row of data***
strRowRef = lngLastRow + 1 & ":" & .Rows.Count
.Rows(strRowRef).Borders.LineStyle = xlNone
***'Remove all borders from column after last used column to right extremity of worksheet***
***'Next line just used the actual column reference from next column***
***'past data to maximum columns because usually known range.***
.Columns("S:XFD").Borders.LineStyle = xlNone
End With
End Sub
Function LastRow(rng As Range) As Long
***'Finds the last used row in a worksheet. Essential if not all columns have data to bottom***
Dim rngToFind As Range
With rng
Set rngToFind = .Find(What:="\*", \_
LookIn:=xlFormulas, \_
LookAt:=xlPart, \_
SearchOrder:=xlRows, \_
SearchDirection:=xlPrevious, \_
MatchCase:=False)
End With
If Not rngToFind Is Nothing Then
LastRow = rngToFind.Row
Else
LastRow = 1 'If nothing found then set row one as last row
End If
End Function
Function Validate(rngToSearch As Range, varToFind As Variant) As Boolean
***'This function not required when COUNTA function used in lieu***
Dim rngToFind As Range
With rngToSearch
Set rngToFind = .Find(What:=varToFind, \_
LookIn:=xlValues, \_
LookAt:=xlWhole, \_
SearchOrder:=xlByRows, \_
SearchDirection:=xlNext, \_
MatchCase:=False)
If Not rngToFind Is Nothing Then
Validate = True
Else
Validate = False
End If
End With
End Function