Conditional Formatting Applying to a range instead of the individual cell - vba

The following code is intended to format the cell
if the value entered is No and
If the value of the cell in same row and some columns before is listed in the sshDevices array
Function sshProblem(rng As Range) As Boolean
Dim portStatus As String
portStatus = rng.Value
Dim deviceType As String
deviceType = Cells(ActiveCell.Row, 3).Value
Dim sshDevices As Variant
sshDevices = Array("linux", "vmw", "docker", "unix")
If StrComp(portStatus, "No") = 0 Then
If StrComp(deviceType, sshDevices(1)) = 0 Then
sshProblem = True
End If
End If
End Function
For now I'm just comparing the value of deviceType against the second element in the array. The formatting is working properly if the device type matches and if the content is No. The problem is that when the content is No and the device type does not match, ALL of the cells in the column loose their format. For example:
I enter "No" in row 8 and the cell is properly formatted with pink background:
Then I do the same with columns 9 and 10:
But when I get to 10, I enter "No" in the cell but now the value of previous cell "ubuntu" does not match the value of position 1 of the array, so i would expect that cell not to have a pink background, however all previous cells loose their background when I enter "No":
The conditional formatting settings look like this (the column letter is 'I'):
I dont know if my problem is stating (rng As Range) or if it is the way I set up the conditional formatting rule. Ideas?

If sshProblem is in a regular module then any Cells/Range will refer by default to the ActiveSheet, not necessarily the sheet you're calling the function from.
Also, ActiveCell is no use here - if you want to reference the cell from where the formula is being called, use Application.Caller
Finally, you can use Application.Match to test if a value is contained in an array.
Function sshProblem(rng As Range) As Boolean
Dim portStatus As String, sht As WorkSheet
Dim rw as Long
Set sht = rng.WorkSheet
portStatus = rng.Value
Dim deviceType As String
rw = Application.Caller.Row
deviceType = sht.Cells(rw, 3).Value
Dim sshDevices As Variant
sshDevices = Array("linux", "vmw", "docker", "unix")
If StrComp(portStatus, "No") = 0 Then
sshProblem = Not IsError(Application.Match(deviceType, sshDevices, 0))
End If
End Function

Related

Distributing the contents of a cell across a range

How would I go about distributing the contents of a cell in excel across a specified range based on a defined factor?
E.g. Cell A1 contains 1,2,3,4,5,6,7,8,9
The desired result is to spread that series over the range A1:A9
Or a more complex example: A1 contains apple, orange; 34; 67, "pod"
The desired result is to spread the contents separated by the differing punctuation over the range B1:F1
Thanks.
Use Data -> Text to Columns to do this:
http://www.excel-easy.com/examples/text-to-columns.html
In VBA
Option Explicit
Public Sub SpreadListsInColA()
Dim c As Range, itms As Variant
With Sheet1.UsedRange.Columns("A")
Application.ScreenUpdating = False
For Each c In .Cells
If Not IsError(c) Then
If Len(c.Value2) > 0 And InStr(c.Value2, ",") > 0 Then
itms = Split(c.Value2, ",")
c.Offset(, 1).Resize(, UBound(itms) + 1) = itms
End If
End If
Next
Application.ScreenUpdating = True
End With
End Sub
This UDF will almost deal with the first scenario. As it's a function then the original text will remain in cell A1 and this will distribute over the cells you've entered the formula in.
In A2:A10 or A2:I2 enter this array formula: =Transpose_Ext(A1,",")
Public Function Transpose_Ext(Source As Range, Delim As String) As Variant
With Application.Caller
If Source.Cells.Count = 1 Then
If .Rows.Count = 1 Then
Transpose_Ext = Array(Split(Source, Delim))
ElseIf .Rows.Count > 1 And .Columns.Count = 1 Then
Transpose_Ext = Application.WorksheetFunction.Transpose(Array(Split(Source, Delim)))
End If
End If
End With
End Function
To use multiple delimiters I'd add a paramarray as the last argument in the function to store an array of delimiters and then something to split the source value by each delimiter.
Edit: But, as already mentioned, text to columns and the Transpose function will do the job just as well.

Maintaining destination data format when copying style in VBA?

I am trying to copy the style from a specific column (formatted as Text starting from its second row) to another column (formatted as Date starting from its second row). Both columns stores values.
I am able to copy-and-paste the style to the destination column:
.Columns("A").Copy '# "A" is the starting column
.Columns(dest_col).PasteSpecial Paste:=xlPasteFormats '# dest_col is the destination column
but this code also formats it as a Text column, while I want to keep its original formatting (i.e. Date starting from the second row).
Is there any option I can use to prevent this behavior?
You may try to take only the values of the specific parameters, which you are interested in (E.g., style, Interior Color, Font Color etc.)
The following works only when the whole column has the same format, as far as I did not to loop through every cell:
Option Explicit
Sub TestMe()
Dim colFrom As Long
Dim colTo As Long
colFrom = 1
colTo = 5
CopyFullFontAndInterior colFrom, colTo
End Sub
Sub CopyFullFontAndInterior(colFrom As Long, colTo As Long, Optional wsN As Long = 1)
Dim copyFrom As Range
Dim copyTo As Range
With Worksheets(1)
Set copyFrom = .Range(.Cells(1, colFrom), .Cells(2 ^ 20, colFrom))
Set copyTo = .Range(.Cells(1, colTo), .Cells(2 ^ 20, colTo))
End With
copyTo.Style = copyFrom.Style
If copyFrom.Interior.Color > 0 Then copyTo.Interior.Color = copyFrom.Interior.Color
If copyFrom.Font.Color > 0 Then copyTo.Font.Color = copyFrom.Font.Color
End Sub
A possible workaround is to save the format of a given cell of the column in a variable and to use it after the .PasteSpecial:
Sub TestMe()
Dim saveOurFormat As String
saveOurFormat = Columns(5).Cells(2).NumberFormat
Columns("A").Copy
Columns(5).PasteSpecial Paste:=xlPasteFormats
Columns(5).NumberFormat = saveOurFormat
Application.CutCopyMode = False
End Sub

VBA: Offset based on column headers

Is it possible to offset to the right of a cell based on column headers? I have some code that loops through a range, and if it finds a specific value it will offset 12 columns to the right. Instead of saying Offset(,12), is there a way I can say offset to the right in that same row to the column with the header I want?
For example if column B is named "host" and my range is
rng = ws.range("B1:B20")
and column N is named "country", I don't want to write:
offset(,12).value = ...
Instead if there is something like:
offset(to column: country).value =...
The reason I ask for this is to not specific an offset number to make the code more resilient to any changes that may happen to my excel worksheet.
I hope the explanation is clear. thanks!
Try the Function below, will return the number of columns you need to Offset from your Rng to the "Header" you are looking for.
Option Explicit
Function OffesttoHeader(CurrentCol As Long, FindRng As Range, HeaderStr As String) As Long
Dim HeaderRng As Range
Set HeaderRng = FindRng.Find(what:=HeaderStr)
If Not HeaderRng Is Nothing Then
OffesttoHeader = HeaderRng.Column - CurrentCol + 1
Else
OffesttoHeader = -10000 ' raise to a large value >> as an error
End If
End Function
Test Sub Code (to test the function above):
Sub Test()
Dim ws As Worksheet
Dim Rng As Range
Dim NumberofCols As Long
Set ws = ThisWorkbook.Sheets("Sheet1") ' modify to your sheet's name
Set Rng = ws.Range("B1:B20")
' pass the following parameters:
' 1. Rng.column - in your case column B = 2
' 2. ws.Rows(1) - the Range to search for the Header, first row in ws worksheet
' 3. "Header" - the Header string you are searching for
NumberofCols = OffesttoHeader(Rng.Column, ws.Rows(1), "Header")
' raise an error message box
If NumberofCols = -10000 Then
MsgBox "Unable to find Header"
End If
End Sub
In order to obtain the solution you seek above, use the Range.Find Method.
'Column Number
Dim clmCountry as Integer
From here, we want to find the header by using the Range.Find Method
'to find the header
With ThisWorkbook.Sheets("SheetName")
'update the range if necessary
clmCountry = .Range("A1:Z1").Find("HeaderName").Column
End With
Once you've found the desired column, you may offset the following way:
... Offset(RowNum, clmCountry).Value = ...
I needed to get a column value out of a row defined as a Range.
Public Function ProcessOneLine(row As Range) As String
This works for me
row.Offset(0,2).Value2 ' returns the value in Column 3
row.Offset(1,Range("C1").Column).Value2 ' also returns the value in Column
So use something like this:
Dim srcColumn as String
Dim colPosn as Integer
srcColumn = "C"
colPosn = Range(srcColumn & "1").Column
cellValue = row.Offset(0,colPosn-1).Value2

Using Left() for variable

I'm new to VBA.
So i was trying to append prefixes to a set of values, depending on the first number of each value.
However i get a Type mismatch error on line 4. I suppose it is because I am trying to use Left() on a variable, and it is supposed to be used on a string (or something of that sorts?)
How do i accomplish that?
Thank you in advance
Sub test()
a = UsedRange.Rows.Count
Testvariable = Range("A1", "A" & a)
FirstNo = Left(Testvariable, 1)
For i= 1 To a
If FirstNo(i,1) = "1" Then
Cells(i,2) = "abc" & FirstNo(i,1)
Else Cells(i,2) = "def" & FirstNo(i,1)
End if
Next
End Sub
Trouble is you are trying to take the left of a Range object that happens to give you an array when assigned without the set keyword, when left wants a string.
This post here explains how to convert a range to a string, that you can then pass to left.
How can I convert a range to a string (VBA)?
Sub test()
dim a as long
a = UsedRange.Rows.Count
dim Testvariable as string
Testvariable = RangeToString(Range("A1", "A" & a))
dim FirstNo as string
FirstNo = Left(Testvariable, 1)
dim i as long
For i= 1 To a
If FirstNo(i,1) = "1" Then
Cells(i,2) = "abc" & FirstNo(i,1)
Else
Cells(i,2) = "def" & FirstNo(i,1)
End if
Next
End Sub
Function RangeToString(ByVal myRange as Range) as String
RangeToString = ""
If Not myRange Is Nothing Then
Dim myCell as Range
For Each myCell in myRange
RangeToString = RangeToString & "," & myCell.Value
Next myCell
'Remove extra comma
RangeToString = Right(RangeToString, Len(RangeToString) - 1)
End If
End Function
Also, be sure to always declare your variables correctly.
a = UsedRange.Rows.Count means create a variable called a of type variant and give it the value of UsedRange.Rows.Count
Testvariable = Range("A1", "A" & a) means create a variable called Testvariable of type variant and give it the .value of the range object (an array)
If these were declared properly
dim a as long
a = UsedRange.Rows.Count
dim Testvariable as string
Testvariable = Range("A1", "A" & a)
The assignment of Testvariable would have failed with a much more obvious error, informing you that you can't convert an array to a string.
Testvariable = Range("A1", "A" & a)
Testvariable is an array (unless a=1, which is not your case here)
Left is flexible enough to accept numbers in addition to strings, but NOT arrays.
If I follow your flow, I think this will get you what you are looking for.
Sub test()
Dim ws As Worksheet
Set ws = ActiveWorkbook.ActiveSheet
a = ws.UsedRange.Rows.Count
TestVariable = Range("A1", "A" & a)
For i = 1 To a
FirstNo = Left(TestVariable(i, 1), 1)
If FirstNo = "1" Then
Cells(i, 2) = "abc" & FirstNo(i, 1)
Else
Cells(i, 2) = "def" & FirstNo
End If
Next i
End Sub
I think the issue was, in addition to the other two answers, was that you need to loop through the cells and output something based on each cells value. Therefore, just throw your code just above your for loop inside and change as needed.
VBA does a lot of things behind your back, to "make things easier". The problem is that these things come back and bite you later.
Undeclared variables, for example. Without Option Explicit specified at the top of every module, VBA will happily compile code that uses variables that aren't declared. Sounds useful? It would be, if the types were inferred. But instead VBA declares an on-the-fly Variant, that holds whatever you put into it (and changes its type to accomodate whatever you're assigning).
a = UsedRange.Rows.Count
Here a is an implicit Variant/Long. Declare it as a Long integer:
Dim a As Long
Even better, give it a meaningful name:
Dim usedRows As Long
usedRows = UsedRange.Rows.Count
Now the fun part:
Testvariable = Range("A1", "A" & a)
Here Testvariable is an implicit Variant/Array. How so? The code that VBA sees looks something like this:
Testvariable = Range("A1", "A" & a).Value
And because a wouldn't be 1, that .Value is referring to more than one single cell - Excel's object model gives you an array that contains the values of all cells in the specified range, and that's what Testvariable contains.
If you wanted Testvariable to refer to a range rather than just their values, you would declare a Range object variable, and assign it with the Set keyword:
Dim testVariable As Range
Set testVariable = ActiveSheet.Range("A1:A" & a)
Notice the explicit ActiveSheet here: without it the code does exactly the same thing, so why put it in? Because you want to be as explicit as possible: unqualified Range, Cells, Rows, Columns and Names calls all implicitly refer to the active worksheet - and that's yet another source of bugs (just google up "range error 1004", you'll find hundreds of Stack Overflow questions about it).
If you meant testVariable to contain the values of every cell in that specified range, then you would declare and assign testVariable like this:
Dim testVariable As Variant
testVariable = ActiveSheet.Range("A1:A" & a).Value
And now you have an array that contains all values in range "A1:A" & a of the active worksheet: that's what your code does.. implicitly.
FirstNo = Left(Testvariable, 1)
So now we want the "first number", and we're reading it off a variant array.
The Left (or Left$) function is from the VBA.Strings module, and means to works with strings; it can work with other types too (with implicit type conversions), but if you give it an object reference or an array, it won't know how to convert it for you, and VBA will raise a run-time error.
The testVariable variant array contains Variant values: most cells will contain a Double floating-point value. Others will contain a String. Some cells can contain an error value (e.g. #N/A, #VALUE!, or #REF!) - and VBA will not be able to implicitly convert such error values either.
So before you read any cell's value, you need to make sure you can read it; use the IsError function for that:
If IsError(testVariable(1, 1)) Then
Exit Sub ' cell contains an error; cannot process
If Not IsNumeric(testVariable(1, 1)) Then
Exit Sub ' cell doesn't contain a number; cannot process
End If
' now that we KNOW our value is a numeric value...
Dim firstNumber As Double
firstNumber = testVariable(1, 1)
Notice that (1, 1)? That's because testVariable is a 1-based 2D array (without Option Base 1 implicitly sized arrays are always 0-based, except when you're getting one from a Range), so to read the value in the first row / first column, you need to read the value at index (1, 1).
But that's not what you're trying to do.
So I was trying to append prefixes to a set of values, depending on the first number of each value.
"Each value" means you need to iterate the values, so you have a loop there.
Dim i As Long
For i = 1 To a
'...
Next
It's not the "first number" we want, it's the firstDigit, of every cell we're looping over.
If FirstNo(i,1) = "1" Then
Here you've lost track of the types you're dealing with: FirstNo was assigned before the loop started, so its value will be constant at every iteration.
You mean to do this:
Dim values As Variant
values = ActiveSheet.Range("A1:A" & usedRows).Value
Dim i As Long
For i = 1 To usedRows
If Not IsError(values(i)) Then
Dim representation As String
representation = CStr(values(i))
Dim prefix As String
If Left$(representation, 1) = "1" Then
prefix = "abc"
Else
prefix = "def"
End If
ActiveSheet.Range("B" & i).Value = prefix & representation
End If
Next
Now that everything is explicit and properly indented, it's much easier to see what's going on... and now I'd seriously question why you need VBA to do that:
[B1] = IFERROR(IF(LEFT(A1, 1) = "1", "abc" & A1, "def" & A1), "")
And drag the formula down.

Loop through all cells in Range using Interop

I want to loop through all cells in a range.
Dim rngTop, rngAll as Excel.Range
'Set a cell
rngTop = DirectCast(_sheet.Cells(1, 2), Excel.Range)
'Set a range from the Top cell to its last cell in the cells column
rngAll = rngTop.End(Excel.XlDirection.xlDown)
For Each cell As Excel.Range In rngAll
If cell.Value2 = "x" Then
'Do stuff
End If
Next
cell.Value is underlined and gives me the compil error that cell.value2 is an object and I cannot use an operator (= in this case) on it. Could anyone help me get this task accomplished? Value2 should not be an object.
I also tried:
Dim cell As Excel.Range = Nothing
Dim i As Integer
For i = 1 To rngAll.Rows.Count
If DirectCast(rngAll.Cells(i, 5), Excel.Range).Value2 = "x" Then
'Do stuff
End If
next i
but have the same problem as above.
I guess I have the solution. The problem here is that vb doesnt know what type value2 will deliver so it delivers an object. That is why the code give the message that the =-Operator cannot be applied. Using
If CStr(cell.value2) = "x" then
...
works perfect. So the best would be to write a function to check the valuetype for every possible type and convert it.