Change Part of text font in Excel cell using vba - vba

Hi I am trying to create a function to calculate milliohms (mOhms)
my function is
Function mOhms(Current, Voltage)
mOhms = Format((Voltage / Current) * 1000, "00.00 m") & Chr(87)
End Function
with results being
40.00 mW
(if cell values are 24 and 1 respectivly)
How do i get the W as (Ω) ohms symbol
if i change the cell font style to Symbol m changes to micro (μ) symbol
i have tried paying with
With ActiveCell.Characters(Start:=Len(ActiveCell) - 1, Length:=1).Font
.FontStyle = "Symbol"
End With
Which results in "Circular reference error"s
Need some help to resolve this

Try using Unicode in place of the Chr(87)
Function mOhms(Current, Voltage)
mOhms = Format((Voltage / Current) * 1000, "00.00 m") & ChrW(&H2126)
End Function

should you want to stick with Characters object you have to:
use Name property, instead of FontStyle one
set its Start parameter to the last character of the range text, instead of the second to last one
so you may want to code like follows:
Sub main()
With Range("G1") '<--| change this to any valid Range reference
.Value = mOhms(24, 1) '<--| set the referenced range value
FormatOhm .Cells '<--| format the referenced range value last character
End With
End Sub
Function mOhms(Current, Voltage)
mOhms = Format((Voltage / Current) * 1000, "00.00 m") & Chr(87)
End Function
Sub FormatOhm(rng As Range)
With rng
.Characters(Start:=Len(.Value), Length:=1).Font.name = "Symbol"
End With
End Sub
a possible enhancement of which could be the handling of "W" character actual position in the string, should it not always be the last character
then you could add the following function:
Function GetCharacter(rng As Range, char As String) As Long
Dim i As Long
With rng
For i = 1 To .Characters.Count
If .Characters(i, 1).Text = char Then
GetCharacter = i
Exit For
End If
Next i
End With
End Function
that returns a Long with the passed character position inside the passed range value or 0 if no match occurred
in this case you'd have to slightly change FormatOhm() function to have it handle the actual character position:
Sub FormatOhm(rng As Range, iChar As Long)
If iChar = 0 Then Exit Sub '<--| exit if no character matching occurred
With rng
.Characters(Start:=iChar, Length:=1).Font.name = "Symbol"
End With
End Sub
and your "main" code would then get to:
Sub main()
With Range("G1") '<--| change this to any valid Range reference
.Value = mOhms(24, 1) '<--| set the referenced range value
FormatOhm .Cells, GetCharacter(.Cells, "W") '<--| format the referenced range value character corresponding to "W", if any
End With
End Sub
of course what above could be further both improved and made more robust, for instance handling char parameter length in GetCharacter() and correspondingly in FormatOhm()

Related

Excel cell content validation with use of VBA code

I am looking for a solution to validate and highlight my cell in case false.
I tried the most promising solution: Regex. But still can not find the pattern I need.
My latest attempt was this pattern: "[A-Z-0-9_.]" This works only if the cell contains only a symbol and nothing else, if the symbol is part of a string it does not work.
Problem is that it does not catch cells that have an odd character in a string of text: Example C4UNIT| or B$GROUP.
Specification Cell can contain only capital characters and two allowed symbols Dash - and Underbar _
This is my complete code:
Function ValidateCellContent()
Sheets("MTO DATA").Select
Dim RangeToCheck As Range
Dim CellinRangeToCheck As Range
Dim CollNumberFirst As Integer
Dim CollNumberLast As Integer
Dim RowNumberFirst As Integer
Dim RowNumberLast As Integer
'--Start on Column "1" and Row "3"
CollNumberFirst = 1
RowNumberFirst = 3
'--Find last Column used on row "2" (Write OMI Headings)
CollNumberLast = Cells(2, Columns.count).End(xlToLeft).Column
RowNumberLast = Cells(Rows.count, 1).End(xlUp).Row
'--Set value of the used range of cell addresses like: "A3:K85"
Set RangeToCheck = Range(Chr(64 + CollNumberFirst) & RowNumberFirst & ":" & Chr(64 + CollNumberLast) & RowNumberLast)
Debug.Print "Cells used in active Range = " & (Chr(64 + CollNumberFirst) & RowNumberFirst & ":" & Chr(64 + CollNumberLast) & RowNumberLast)
For Each CellinRangeToCheck In RangeToCheck
Debug.Print "CellinRangeToCheck value = " & CellinRangeToCheck
If Len(CellinRangeToCheck.Text) > 0 Then
'--Non Printables (Space,Line Feed,Carriage Return)
If InStr(CellinRangeToCheck, " ") _
Or InStr(CellinRangeToCheck, Chr(10)) > 0 _
Or InStr(CellinRangeToCheck, Chr(13)) > 0 Then
CellinRangeToCheck.Font.Color = vbRed
CellinRangeToCheck.Font.Bold = True
'--Allowed Characters
ElseIf Not CellinRangeToCheck.Text Like "*[A-Z-0-9_.]*" Then
CellinRangeToCheck.Font.Color = vbRed
CellinRangeToCheck.Font.Bold = True
Else
CellinRangeToCheck.Font.Color = vbBlack
CellinRangeToCheck.Font.Bold = False
End If
End If
Next CellinRangeToCheck
End Function
Try this:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
'we want only validate when cell content changed, if whole range is involved (i.e. more than 1 cell) then exit sub
If Target.Cells.Count > 1 Then Exit Sub
'if there is error in a cell, also color it red
If IsError(Target) Then
Target.Interior.ColorIndex = 3
Exit Sub
End If
'validate cell with our function, if cell content is valid, it'll return True
'if it i s not valid, then color cell red
If Not ValidateText(Target.Value) Then
Target.Interior.ColorIndex = 3
End If
End Sub
Function ValidateText(ByVal txt As String) As Boolean
Dim i As Long, char As String
'loop through all characters in string
For i = 1 To Len(txt)
char = Mid(txt, i, 1)
If Not ((Asc(char) >= 65 And Asc(char) <= 90) Or char = "-" Or char = "_") Then
'once we come upon invalid character, we can finish the function with False result
ValidateText = False
Exit Function
End If
Next
ValidateText = True
End Function
I've originally assumed you wanted to use RegEx to solve your problem. As per your comment you instead seem to be using the Like operator.
Like operator
While Like accepts character ranges that may resemble regular expressions, there are many differences and few similarities between the two:
Like uses ! to negate a character range instead of the ^ used in RegEx.
Like does not allow/know quantifiers after the closing bracket ] and thus always matches a single character per pair of brackets []. To match multiple characters you need to add multiple copies of your character range brackets.
Like does not understand advanced concepts like capturing groups or lookahead / lookbehind
probably more differences...
The unavailability of quantifiers leaves Like in a really bad spot for your problem. You always need to have one character range to compare to for each character in your cell's text. As such the only way I can see to make use of the Like operator would be as follows:
Private Function IsTextValid(ByVal stringToValidate As String) As Boolean
Dim CharValidationPattern As String
CharValidationPattern = "[A-Z0-9._-]"
Dim StringValidationPattern As String
StringValidationPattern = RepeatString(CharValidationPattern, Len(stringToValidate))
IsTextValid = stringToValidate Like StringValidationPattern
End Function
Private Function RepeatString(ByVal stringToRepeat As String, ByVal repetitions As Long) As String
Dim Result As String
Dim i As Long
For i = 1 To repetitions
Result = Result & stringToRepeat
Next i
RepeatString = Result
End Function
You can then pass the text you want to check to IsTextValid like that:
If IsTextValid("A.ASDZ-054_93") Then Debug.Print "Hurray, it's valid!"
As per your comment, a small Worksheet_Change event to place into the worksheet module of your respective worksheet. (You will also need to place the above two functions there. Alternatively you can make them public and place them in a standard module.):
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ValidationRange As Range
Set ValidationRange = Me.Range("A2:D5")
Dim TargetCell As Range
For Each TargetCell In Target.Cells
' Only work on cells falling into the ValidationRange
If Not Intersect(TargetCell, ValidationRange) Is Nothing Then
If IsTextValid(TargetCell.Text) Then
TargetCell.Font.Color = vbBlack
TargetCell.Font.Bold = False
Else
TargetCell.Font.Color = vbRed
TargetCell.Font.Bold = True
End If
End If
Next TargetCell
End Sub
Regular Expressions
If you want to continue down the RegEx road, try this expression:
[^A-Z0-9_-]+
It will generate a match, whenever a passed-in string contains one or more characters you don't want. All cells with only valid characters should not return a match.
Explanation:
A-Z will match all capital letters,
0-9 will match all numbers,
_- will match underscore and dash symbols.
The preceding ^ will negate the whole character set, meaning the RegEx only matches characters not in the set.
The following + tells the RegEx engine to match one or more characters of the aforementioned set. You only want to match your input, if there is at least one illegal char in there. And if there are more than one, it should still match.
Once in place, adapting the system to changing requirements (different chars considered legal) is as easy as switching out a few characters between the [brackets].
See a live example online.

How do I check whether value in active cell contains any letter or not?

For example cell "A1" is linked to cell "B1", so in formula bar for cell "A1" we have:
=B1
How can I check whether value in cell "A1" contains letter B?
I tried the following:
Dim Criteria_3 As Boolean
Dim Value As Range
Set Value = Selection
Dim x As Variant
Set x = Cells
Dim text As String
For Each x In Value
If IsNumeric(x) Then
Criteria_3 = VBA.InStr(1, x.Formula, text) > 0
As soon as value of "Text" is "" it does not work and I really struggle to fined the right solution.
your question is not really conclusive, so here are two options:
To check wheter the value contains B:
blnCheck = 0 < InStr(1, rngCell.Value, "B")
To check wheter the Formula contains B:
blnCheck = 0 < InStr(1, rngCell.Formula, "B")
Regarding your null string problem:
As soon as value of "Text" is "" it does not work and I really struggle to fined the right solution.
That's because you're using VBA.InStr(1, x.Formula, text) and in this case 1 is an invalid index on a string of length 0. You can omit that, or you can code around it like:
If Len(Trim(x.Formula)) = 0 Then
'## Do nothing
Else
Criteria_3 = VBA.InStr(1, x.Formula, text) > 0
End If
To your specific question of identifying when a value contains any alpha character(s):
You can use a function like this to test whether a value contains any letter, by evaluating the Ascii code for each character, and break when True:
Function ContainsAnyLetter(val) As Boolean
Dim ret As Boolean
Dim str$, ch$
Dim i As Long
str = LCase(CStr(val))
For i = 1 To Len(str)
ch = Mid(str, i, 1)
If 97 <= Asc(ch) And Asc(ch) <= 122 Then
ret = True
Exit For
End If
Next
ContainsAnyLetter = ret
End Function
In your code, you could call it like:
Criteria_3 = ContainsAnyLetter(x.Value) '## or x.Formula, depending on your needs
You can use LIKE
https://msdn.microsoft.com/en-us/library/swf8kaxw.aspx
Something like if rngCell.value like "*B*" then
if your goal is to check whether the cell contains any valid range reference, then you could go like this
Option Explicit
Sub main()
Dim cell As Range
For Each cell In Worksheets("Sheet001").Range("A1:A20") '<== jus a test range, set it as per your needs
MsgBox IsCellReference(cell.Formula)
Next cell
End Sub
Function IsCellReference(text As String) As Boolean
On Error Resume Next
IsCellReference = Not Range(Replace(text, "=", "")) Is Nothing
End Function

Knowing the assigned name of a cell instead of the "A1" name

Context:
I have several lists in my sheet (1 column wide, 1-10 rows long). When I right click a cell in these lists, I can do several options, that all work well. I have given a name to the cell at the top of each of these lists (ex. Cell A1 has been given the name cell_1, B10 is names cell_2, etc).
I would like to know if the cell I am right clicking on is the one at the top of the list; is it named "cell_(number)"? If it is not, it checks the cell on top of that one. Does it have a name that starts with "cell_"? If not, check the one on top, etc. Until I can figure out the user clicked on an element of WHICH list.
TL;DR The actual question
I can use ActiveCell.Address, which gives me something like "A1" whether or not I have assigned a name to that cell. ActiveCell.Name gives "Sheet1!A1", so it's not much better. Any idea how to get it to return the name I have assigned instead?
Create a UDF to test the application names, it's less efficient but contains error handling within the function itself:
Sub SO()
'// Example how to call function
Debug.Print GetCellName(Range("A1"))
End Sub
Function GetCellName(myCell As Excel.Range) As Variant
Dim nameCheck As Variant
For Each nameCheck In Application.Names
If Replace(Replace(Replace(nameCheck, "=", ""), "'", ""), "!", "") = _
CStr(myCell.Parent.Name & myCell.Address) Then
GetCellName = CStr(nameCheck.Name)
Exit Function
End If
Next
GetCellName = CVErr(Excel.xlErrName)
End Function
Note you can also use this function in a worksheet cell like so:
=GetCellName(A1)
Perhaps this would work. This function returns the names assigned to a cell (or bigger range for that matter). If there's more than one name, it returns it as an array for array formula...or the user can supply an index to return only the desired name position
Public Function CellIsInRangeNames(sheetname As String, checkRange As Range, Optional itemNumber As Variant) As Variant
Dim oNM As Name
Dim oSht As Worksheet
Dim isect As Range
Dim namesCollection() As Variant
Set oSht = Worksheets(sheetname)
Dim i As Integer
i = -1
For Each oNM In oSht.Names
Set isect = Application.Intersect(Range(oNM.Name), checkRange)
If Not isect Is Nothing Then
i = i + 1
ReDim Preserve namesCollection(0 To i)
namesCollection(i) = CStr(oNM.Name)
End If
Next oNM
If i = -1 Then
'didn't find any
CellIsInRangeNames = xlErrName
ElseIf Not IsMissing(itemNumber) Then
'user wanted this instance only
If (itemNumber - 1 > UBound(namesCollection)) Or (itemNumber - 1 < LBound(namesCollection)) Then
CellIsInRangeNames = xlErrValue
Else
CellIsInRangeNames = namesCollection(itemNumber - 1)
End If
Else 'here's the list as an array
CellIsInRangeNames = namesCollection
End If
End Function

How do I find a value in a row and return the column number with VBA?

My excel sheet is filled with zeroes except for one cell in every row.
I want to find that cell and return the column.
For example: In the cell T616 is a value other than 0. May it be -15400.
I want to find that cell(T616) based on the row(616) and have the column returned(T). May it even be in a MsgBox.
This is my result of many tries and long Google-sessions:
Public Function find_Column(lRange As Range, lValue As String) As Integer
Dim vCell As Range
For Each vCell In lRange.Cells
If vCell.Value = lValue Then
find_Column = vCell.Column
MsgBox (find_Column)
Exit Function
End If
Next vCell
End Function
I found this code somewhere and modified it a little bit, but I can't remember where. So thanks to the creator!
How do I search for a number other than 0?
I'm relatively new to VBA and don't really have an idea what I am doing. Sorry for my bad English (foreigner). I'd appreciate any help. Thank you!
Try this:
Public Function findNonZeroValueInColumn(lRange As Range) As Integer
Dim vCell As Range
For Each vCell In lRange.Cells
If vCell.Value <> 0 Then
find_Column = vCell.Column
Exit Function
End If
Next vCell
End Function
Sub ShowValue()
call MsgBox(findNonZeroValueInColumn(Range("A:A")))
End Sub
Remember that Functions are supposed to return values. Subs (Procedures) do not return values.
The <> symbol is the "Not Equal" Comparison. So if you wanted to check if a cell didn't equal 0, you would write
If vCell.Value <> 0 Then
... ' rest of code here
In your problem:
Public Function find_Column(lRange As Range) As Integer
Dim vCell As Range
For Each vCell In lRange.Cells
If vCell.Value <> 0 Then
find_Column = vCell.Column
MsgBox (find_Column)
Exit Function
End If
Next vCell
End Function
Also since you are only checking against 0, you wouldn't need the extra lValue As String argument.
As the other part of your question has been answered while I was typing, if you want to return the column letter instead of the number...
find_Column = Replace(Replace(vCell.Address, vCell.Row, ""), "$", "")
If this is what you want the function to return, you would have to change the type your function is returning to string

Get one cell from passed column/row/range VBA Excel

i'm writing a user defined function for excel in VBA.
User may pass a whole column/row into the function instead of one cell. How do you get cell that is in the same row (for column case) and in the same column (for row case), where the function is.
For example, when you are writing in Excel in cell, say, C3 the formula "=A:A*B:B" it calculates A3*B3 in fact. I want to have the same behaiviour in my UDF.
Let's assume function that returns passed argument for simplicity reasons.
This code does not work (returns #VALUE! for columns/rows/ranges):
Public Function MyTestFunction(ByVal arg1) As Variant
MyTestFunction = arg1
End Function
My option is as follows, but I am concerned about performance and the fact that user may want to pass a value to the formula instead of Range.
Public Function MyTestFunction2(ByVal arg1 As Range) As Variant
If arg1.Count = 1 Then
MyTestFunction2 = arg1.Value
Else
' Vertical range
If arg1.Columns.Count = 1 Then
MyTestFunction2 = arg1.Columns(1).Cells(Application.Caller.Row, 1).Value
Exit Function
End If
' Horizontal range
If arg1.Rows.Count = 1 Then
MyTestFunction2 = arg1.Rows(1).Cells(1, Application.Caller.Column).Value
Exit Function
End If
' Return #REF! error to user
MyTestFunction2 = CVErr(xlErrRef)
End If
End Function
How do you solve this problem?
Thanks to valuable comments code has been slightly updated and now can be used in other formulas to filter input values.
Public Function MyTestFunction2(ByVal arg1) As Variant
If Not TypeName(arg1) = "Range" Then
MyTestFunction2 = arg1
Exit Function
End If
If arg1.Count = 1 Then
MyTestFunction2 = arg1.Value
Else
' Vertical range
If arg1.Columns.Count = 1 Then
' check for range match current cell
If arg1.Cells(1, 1).Row > Application.Caller.Row Or _
arg1.Cells(1, 1).Row + arg1.Rows.Count - 1 < Application.Caller.Row Then
' Return #REF! error to user
MyTestFunction2 = CVErr(xlErrRef)
Exit Function
End If
' return value from cell matching cell with function
MyTestFunction2 = arg1.Worksheet.Columns(1).Cells(Application.Caller.Row, arg1.Column).Value
Exit Function
End If
' Horizontal range
If arg1.Rows.Count = 1 Then
' check for range match current cell
If arg1.Cells(1, 1).Column > Application.Caller.Column Or _
arg1.Cells(1, 1).Column + arg1.Columns.Count - 1 < Application.Caller.Column Then
' Return #REF! error to user
MyTestFunction2 = CVErr(xlErrRef)
Exit Function
End If
' return value from cell matching cell with function
MyTestFunction2 = arg1.Worksheet.Rows(1).Cells(arg1.Row, Application.Caller.Column).Value
Exit Function
End If
' Return #REF! error to user
MyTestFunction2 = CVErr(xlErrRef)
End If
End Function
In the first code snippet change MyTestFunction = arg1 to Set MyTestFunction = arg1. Also add a small mechanism that recognizes the TypeName() of the arg1 and make sure that the function is receiving a Range type object.
Public Function MyTestFunction(ByVal arg1) As Variant
Set MyTestFunction = arg1
End Function
example
Then, if you get to your spreadsheet and type in =MyTestFunction(A:A) on any row and you'll receive the equivalent value from the column you're passing to the function that sits on the same row.
And your second idea about getting a similar behaviour as =A:A*B:B you can achieve with
Public Function MyTestFunction2(ParamArray arr() As Variant)
MyTestFunction2 = arr(0)
End Function
example
I think you need to use Application.ThisCell property to do it. According to MSDN:
Application.ThisCell- Returns the cell in which the user-defined
function is being called from as a Range object.
Let me present how to use it on simple example.
Imagine we have data as presented below in column A:B and we want to achieve results which comes from =A*B for each row separately.
In such situation you need the function below and put it next in C column in this way: =MyTestFunction(A:A,B:B)
Function MyTestFunction(rngA As Range, rngB As Range)
Dim funRow As Long
funRow = Application.ThisCell.Row
MyTestFunction = rngA(funRow) * rngB(funRow)
End Function
Please keep in mind that Application.ThisCell will not work if you call your function from other VBA procedure.