VBA not handling "bad" characters when inserting values into spreadsheet - vba

I'm using the following code to insert values from my result set into my spreadsheet:
Rst.MoveFirst
Do While Rst.EOF = False
For i = 0 To Rst.Fields.Count - 1
ActiveCell.Value = Rst.Fields(i).Value 'insert value into cell
ActiveCell.Offset(0, 1).Activate 'move to next cell for next value
Next i
ActiveCell.Offset(1, -i).Activate 'move to next row for next record
Rst.MoveNext
Loop
The problem I'm running into is that some users are using = as the first character in their input, and excel does not like that ("Application-defined or object-defined error").
I've seen the workaround for Entering '=' as the first character in a cell but I don't want to apply the ' to every field, ideally only if it starts with =.
I need a solution that also works with Null values. I've tried a bunch of different combinations (IsNull, IsDBNull), but I can't seem to find one that works.

I'm sitting on a Linux box right now so I can't test this but from memory...
If you set the Number format of the cell to "#" before you assign the value then that should mean that it takes values as literal strings
This also means that "0131" won't be changed to 131
ActiveCell.NumberFormat = "#" ' This Cell will hold text
ActiveCell.Value = Rst.Fields(i).Value 'insert value into cell
You could put the formating within an If Statement based on Rst.Fields(i).Type if you only want to do this for text fields

As #timwilliams suggested, I used this answer to set the format of the range (seems more efficient than #tompage's cell-by-cell approach) to text.

Related

Comparing values of cells in a column with ComboBox value input by user

I am not able to compare the values of a cells in a column with combobox value input.
I have 2 workbooks tests(contains ComboBox2) and test1(contains a column whose cells are compared with ComboBox2.value)
I have a for loop to achieve this.
For i = 1 To LastRow
If wkbSource.Worksheets(sheet_no).Cells(i, 1) = ComboBox2.Value Then
'do something
End If
Next i
I have debugged the code and I understood that if statement doesn't execute even after a match.
How can I fix it ?
EDIT :
Also I would like to know how you can add two cell values because directly adding it is showing incorrect output.
For example
wkbSource.Worksheets(sheet_no).Cells(i, 1) + wkbSource.Worksheets(sheet_no).Cells(i, 3)
This was due (once again) to the Variant Comparison Curse. See in particular the "UPDATE 4" of that question.
If wkbSource.Worksheets(sheet_no).Cells(i, 1) = ComboBox2.Value Then
This compares two Variants. But, when the cell contains a number, and is not explictly formatted as Text, not preceded by ' when entered. Excel will consider it as a number and so it's .Value will be a number Variant. On the other hand, Combobox2.Value retuned a text Variant, so the comparison failed!
When comparing two Variant variables, these operations will fail:
2 = "2" ' False
3 > "2" ' False
Therefore, the solution in your particular situation is to force comparing texts, using the .Text properties of the control and the cell. Here's how you would - for example - sum up cells that match your query:
For i = 1 To LastRow
If Trim(wkbSource.Worksheets(sheet_no).Cells(i, 1).Text) = Trim(ComboBox2.Text) Then
'do something
if IsNumeric(wkbSource.Worksheets(sheet_no).Cells(i, 1).Value2) Then _
mySum = mySum + wkbSource.Worksheets(sheet_no).Cells(i, 1).Value2
End If
Next i

Excel VBA script filters header unexpectedly

Introduction
I have some spreadsheets like the following.
Here the header is on rows 16 and 17. There is a "header" to the left (not shown) among the earlier rows and columns that includes a picture, some non-tabular data, a legend, etc., that is unimportant here. Header text on row 16 is obfuscated because reasons. Data marked in bold red indicates that that sample point has undergone some process. Here is the code snippet from the script that highlights those data points in bold red.
' Traverse columns applying redding until hitting the row end, Comment, or SpGr: whichever comes first
For currIndex = abcDateCol + 1 To lastCol
' Check for exit conditions:
If Cells(abcDateRowDesc, currIndex).Value() = "Comments" Then Exit For
If Cells(abcDateRowDesc, currIndex).Value() <> "" Then
If Cells(abcDateRowDesc, currIndex + 1).Value() = "process" Then
' Looks like we have a column of something Red-able
Columns(ColumnLetter(currIndex) & ":" & ColumnLetter(currIndex + 1)).Select
Selection.AutoFilter ' Turn on autofiltering (hopefully)
Selection.AutoFilter Field:=2, Criteria1:="=1", Operator:=xlOr, Criteria2:="=e"
Selection.Font.ColorIndex = 3
Selection.Font.Bold = True
Selection.AutoFilter ' Turn off autofiltering
Columns(ColumnLetter(currIndex + 1) & ":" & ColumnLetter(currIndex + 1)).EntireColumn.Delete Shift:=xlToLeft
End If
End If
Next currIndex
Context
Here, abcDateCol refers to column AE, lastCol refers to column AQ, abcDateRow (not shown, but available) and abcDateRowDesc refer to the header rows 16 and 17 respectively, and the ColumnLetter function is a user-defined function that returns the human-readable column letter(s) given a column number; this is common functionality you may have seen elsewhere, or even made yourself.
Let's Continue
Never mind that the condition in If Cells(abcDateRowDesc, currIndex).Value() = "Comments" is never satisfied because of an oversight (I'm assuming) -- two different rows, guaranteed.
Let's take a look at what the spreadsheet looks like before this script is executed.
So, the script takes pairs of columns, and for each pair of columns it marks data cells bold red if a data cell's right-adjacent cell has a 1 (or an "e"?) (as a boolean; answers the question, "Has this sample point undergone whatever process?") and then trashes the "process" column.
The Problem
A client wants the gratuitous header gone, so they may more easily import the spreadsheet into whatever solution they have. Delete rows 1 through 15, and this is what I get.
What in the bleepity-bleep happened to the header? I don't understand how this first row gets highlighted. It seems too perfectly weird. Now, let's revisit the very first spreadsheet.
I've filled the "header" with some dummy text after the script executed. Wow, there's the first row reddened again, this time ad infinitum! So, this problem has always existed. Oh, and the first column, too! And, it magically stops right above the proper header so we would never see it.
The Questions
Why is this script unexpectedly reddening the first row and column? Can this be easily solved, or am I looking at some sort of a rewrite? If so, please point me in the general direction.
It helps to mention that these spreadsheets are generated from a Windows application and their scripts executed before a user has a copy of their spreadsheet. Also, regarding the second picture (the spreadsheet with the "process" columns shown), this spreadsheet is not something that normally exists. I generated it for the sake of this post by skipping the script's for loop. The application uses a chosen spreadsheet template, that looks the same minus the data, fills in the sample data, and then executes several scripts over the data.
I considered using conditional formatting, but there are a few dozen spreadsheet templates. Even if I just change the one I need, I can't change the fact that these common scripts run over it. I feel my best option is to correct the script. And, I wouldn't change the script to account for my edge case. The whole ecosystem feels flaky, but that's just subjective.
Note
I am not the author of this script (or any of my company's VBA!). I'm considering this an inheritance tax levied upon me.
*Update
I was asked if I traced through this code. I apologize that I didn't include that information in my original post. Here is what I know. Selection.Font.ColorIndex = 3 turns the cells in the selection that satisfy the autofilter plus the first row (two cells as only two columns are selected at a given time), and Selection.Font.Bold = True makes the same cells bold in the same manner. I suspect it has something to do with the autofilter, so I'm going to take a look at the answers now.
This edit should fix your problems, hopefully (they did for my remake of your spreadsheet, but we won't know til you try on the real thing)
' Traverse columns applying redding until hitting the row end, Comment, or SpGr: whichever comes first
For currIndex = abcDateCol + 1 To lastCol
' Check for exit conditions:
If Cells(abcDateRowDesc, currIndex).Value() = "Comments" Then Exit For
If Cells(abcDateRowDesc, currIndex).Value() <> "" Then
If Cells(abcDateRowDesc, currIndex + 1).Value() = "process" Then
' Looks like we have a column of something Red-able
'Columns(ColumnLetter(currIndex) & ":" & ColumnLetter(currIndex + 1)).Select
With Range(Cells(abcDateRowDesc, currIndex), Cells(abcDateRowDesc, currIndex + 1).End(xlDown))
.AutoFilter 2, "=1", xlOr, "=e"
' Don't format header
With .Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count) .Font.ColorIndex = 3
.Font.Bold = True
.AutoFilter ' Turn off autofiltering
End With
End With
Columns(currIndex + 1).Delete xlShiftLeft
End If
End If
Next currIndex
This all starts with a quirk of how the code is choosing its range to autofilter. The selected area is the full column, instead of the area you actually want to format (row 18 to the last entry). It seems that autofiltering on a full column with empty top rows automatically sets the first nonempty row as the header row. So the header is left unfiltered/unhidden by the statement, and it gets colored in as part of the full column selection. So that's why your headers are getting colored.
Now, if you tried to test this by putting data in the above empty rows like "a", those values would become the first ones in the column and would be selected as the headers - meaning those values get colored. Whatever is in the first nonempty row of your columns will be the autofilter header and will get colored.
But that should only affect the columns you explicitly colored, not the entirety of the first row, right? The problem here is that Excel likes to make assumptions about data in order to save time. So if you have a whole row full of red, bold "a"'s and right next to them you put in another "a" to test whether that cell is formatted or not... well, it automatically gives you a red, bold "a" despite the cell being previously unformatted! And if you keep going down the row in this way, it'll appear like your whole row got formatted. But, if you were to jump over a few columns (say, 5-ish) and enter in another "a", voila, it's unformatted, and any "a"s you put in near it will be too. You can also check what Excel by deleting an unformatted "a" in a far off column, then continuing to enter "a"'s all the way down until you reach that same cell - this time, the "a" will be red and bold because all of the others in the row were, too, even though we just checked that this was an unformatted cell!
Basically, having the wrong range for your autofilter made things act very unexpectedly, then trying to test the formatting issue by entering in values just made everything less clear. The code I've provided just autofilters the relevant area (row 17 to the last contiguous row), fixing the core issue.
here's a (commented) refactoring of your code that should do:
Option Explicit
Sub main()
Dim abcDateCol As Long, lastCol As Long, abcDateRow As Long, abcDateRowDesc As Long, currIndex As Long
abcDateCol = 31
lastCol = 43
abcDateRow = 16 '<--| you can change it to 1 for the last "scenario"
abcDateRowDesc = 17 '<--| you can change it to 2 for the last "scenario"
For currIndex = abcDateCol + 1 To lastCol '<--| loop through columns
With Cells(abcDateRow, currIndex) '<--| refer to cell in current column on row abcDateRow
If .Value = "Comments" Then Exit For '<--| Check for exit conditions on row 'abcDateRow'
If .Offset(1).Value <> "" And .Offset(1, 1).Value = "process" Then '<--| Check for processing conditions on row 'abcDateRowDesc'
With .Resize(.Offset(, 1).End(xlDown).Row - .Row + 1, 2) '<-- consider the range from current referenced cell 1 column to the right and down to last 'process' number/letter
.AutoFilter Field:=2, Criteria1:="=1", Operator:=xlOr, Criteria2:="=e" '<--| filter on "process" field with "1" or "e"
If Application.WorksheetFunction.Subtotal(103, .Cells.Resize(, 1)) > 1 Then '<--| if any values match...
With .Offset(2).Resize(.Rows.Count - 2, 1).SpecialCells(xlCellTypeVisible).Font '<--|... consider only filtered values skipping headers (2 rows), and apply formatting
.ColorIndex = 3
.Bold = True
End With
End If
.AutoFilter '<-- reset autofilter
.Resize(, 1).Offset(, 1).EntireColumn.Delete Shift:=xlToLeft '<-- delete the "2nd" column (i.e. one column offsetted to the right)
End With
End If
End With
Next currIndex
End Sub
there were two faults in your "inherited" code:
If Cells(abcDateRowDesc, currIndex).Value() = "Comments" Then Exit For was to be referred to abcDateRow index row instead
the formatting would be applied to all cells, were they filtered (matching) or not

Clear all cells in column who = ""?

So, I have a script that is plotting data points for me, and I'm running into an issue where there are several blank spots in the data. Of of my columns is calculated, and I have a formula that sets it equal to "" if more than 0 cells that are used in the calculation are blank. The plots that use the blank cells work fine to show gaps in the data, but Excel doesn't evaluate a cell that has a formula that results in "" as blank.
Therefore, I need to set up some code that can search the entire column of data and clear the formula out of the cells whose value equal "", thereby making them blank and displaying as gaps in the plot.
I know I can use the Find and What commands to find the first cell that is evaluated as "", but how can I use it to find all the cells in the column?
The row range for the data is always constant, between 4 and 243, and the column number I am searching (within a loop) evaluates as 3*(iCounter - 1) + 2, if that helps anyone.
(The range I am searching is equal to Range(Cells(4, 3*(iCounter - 1) + 2), Cells(243, 3*(iCounter - 1) + 2))
Click on any cell in the column you wish to cleanup and run this:
Sub ClearThem()
Dim BigR As Range, r As Range
Set BigR = Intersect(ActiveCell.EntireColumn, ActiveSheet.UsedRange)
For Each r In BigR
If r.Value = "" Then r.Clear
Next r
End Sub

VBA Go to last empty row

I have a project on excel macro, I need to highlight the next last row that has an empty value. example cell A1:A100 have data and the next cell is A101 is empty.
when user click a button it should highlight the cell A101...
If you are certain that you only need column A, then you can use an End function in VBA to get that result.
If all the cells A1:A100 are filled, then to select the next empty cell use:
Range("A1").End(xlDown).Offset(1, 0).Select
Here, End(xlDown) is the equivalent of selecting A1 and pressing Ctrl + Down Arrow.
If there are blank cells in A1:A100, then you need to start at the bottom and work your way up. You can do this by combining the use of Rows.Count and End(xlUp), like so:
Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Select
Going on even further, this can be generalized to selecting a range of cells, starting at a point of your choice (not just in column A). In the following code, assume you have values in cells C10:C100, with blank cells interspersed in between. You wish to select all the cells C10:C100, not knowing that the column ends at row 100, starting by manually selecting C10.
Range(Selection, Cells(Rows.Count, Selection.Column).End(xlUp)).Select
The above line is perhaps one of the more important lines to know as a VBA programmer, as it allows you to dynamically select ranges based on very few criteria, and not be bothered with blank cells in the middle.
try this:
Sub test()
With Application.WorksheetFunction
Cells(.CountA(Columns("A:A")) + 1, 1).Select
End With
End Sub
Hope this works for you.
This does it:
Do
c = c + 1
Loop While Cells(c, "A").Value <> ""
'prints the last empty row
Debug.Print c

Autofilter not working as intended

I'm trying to delete all rows that contain a specific value in column with autofilter. For some reason, it doesn't work as intended and only deletes some of the rows with the specified value, and I'm determined to find out WHY
My code is as simple as this:
Function GetRowRange(sheet, column, value) As Range
'check for a valid section column
sheet.AutoFilterMode = False
sheet.UsedRange.AutoFilter Field:=column, Criteria1:=value
Set GetRowRange = sheet.AutoFilter.Range.SpecialCells(xlCellTypeVisible)
sheet.AutoFilterMode = False
End Function
With GetRowRange(importsheet, importsheet.UsedRange.Find("stato_4").column, "0")
.Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count).Delete
End With
Update
it makes no sense. I try to change my delete function to:
For Each Row In GetRowRange(importsheet, importsheet.UsedRange.Find("stato_4").column, "0").Offset(1, 0).Rows
Row.EntireRow.Delete
iront = iront + 1
Next
MsgBox iront
and it will recognize that there are 42 rows in the range (iront = 42), but none of them get deleted
I think if you have less than 8192 rows (pre Excel 2010), you could delete them using Special Cells?
Add a formula column that throws an error if the row contains the value you want to test for.
TRY THIS:
' Now, this formula will put an #N/A in each row where the column matches the criteria.
Range("C1:C" & Range("A1").End(xlDown).Row).FormulaR1C1 = "=IF(C[-2]=criteria,NA(),"""")"
'Then use SpecialCells with Error code xlErrors to return the range containing the rows to be deleted.
Range("C1:C" & Range("A1").end(xldown).row).SpecialCells(xlCellTypeFormulas,xlErrors).entirerow.delete
There is more on that on Oxgrid
HTH
Philip
Not sure UsedRange is reliable. Try using something like Range("A1").CurrentRegion