Visual Basic excel, How to ask for letter colors - vba

I want to ask for a letter color in an If conditional:
string="asdfghjkl"
for i=1 to len(string)
letter = mid(string, i, 1)
input_letter = inputbox("Write a letter")
if letter = input_letter 'and letter.Font.Color = RGB(31,78,120)
'my code here
endif
next
The and letter.Font.Color = RGB(31,78,120) is not working. It says i need an object.
Is there any similar way to ask this? This RGB color is blue, and I am using this code to transform the entire sentence to blue (with the record macro excel setting)
With Selection.Font
.ThemeColor = xlThemeColorAccent1
.TintAndShade = -0.499984740745262
End With
Thanks

Regarding your question's problem:
The .Font.Color is a property of the class Range, but in your line of code:
if letter = input_letter 'and letter.Font.Color = RGB(31,78,120)
... you're trying to access this property in the variable letter, which is a String (you don't explicitly declare it as such, but it gets automatically declared when you execute letter = mid(string, i, 1) just above).
That is why you get an Object required exception: you're trying to access the property .Font.Color on something that is not a Range object (actually, not an Object at all).
Regarding your real need:
I'm not sure to understand what you're trying to do. Are you trying to reach a multi-colored text into a single cell in Excel? If I've got it right, you'll have a string:
string="asdfghjkl"
(please note: you can't call your variable String, that's a reserved keyword for the code. Think of calling it something else, though I guess you already do that in your real code or you wouldn't be able to execute it at all).
... and, for each letter of that string,
for i=1 to len(string)
... you want the user to give you a color. In that case, you can't do it in Excel. If not that, could you please express better your real need?

The code below comes closest to your OP logic and comment using the .Characters property of a cell Range (B11) containing your string value:
Code
Option Explicit
Sub test()
Dim blue As Long: blue = RGB(31, 78, 120)
Dim s As String: s = "asdfgh"
Dim letter As String
Dim input_letter As String
Dim i As Integer
Dim rng As Range
Set rng = ThisWorkbook.Worksheets("MySheet").Range("B11")
With rng
.Value = s
' color whole string
.Characters(1, Len(s)).Font.Color = blue
For i = 1 To Len(s)
letter = Mid(s, i, 1)
input_letter = InputBox("Write a letter")
If letter = input_letter And .Characters(i, 1).Font.Color = blue Then
'color found character
.Characters(i, 1).Font.Color = vbWhite
ElseIf input_letter = "" Then Exit For
End If
Next
End With
End Sub
Notes
Always use Option Explicitin your modules declaration head. So you would see that String isn't allowed as variable name as it's a function.
The extra color check in the If condition seems redundant, as characters so long a r e blue.
You seem to prefer repeated InputBoxes within the For - Next loop, could be reduced to a single call.

Related

VBA Word subscripts in Table

I am trying to add text as subscript in a Table Cell in a Word-Document using VBA.
I currently have this code, it is a part of the loop in which I insert my values into the table.
ActiveDocument.Tables(ActiveDocument.Tables.Count).Cell(i, j).Range.Font.Subscript = False
wordArray = Split(ws.Cells(i, j), "_")
For k = LBound(wordArray) To UBound(wordArray)
ActiveDocument.Tables(ActiveDocument.Tables.Count).Cell(i, j).Range.InsertAfter wordArray(k)
ActiveDocument.Tables(ActiveDocument.Tables.Count).Cell(i, j).Range.Font.Subscript = wdToggle
Next k
So I split the text that is in ws.Cells(i,j) on "_"
This can become an array of length 1,2 or 3
Only the second element of the array must be subscript.
My current code should do that, however, it writes the value into the cell based on the last value of Font.Subscript, so either fully normal or fully subscript.
So what I actually want in my table cell is the following
If the ws.Cells has for example a_b_c then b should be subscript and a and c normally written in the table cell. How do I need to change my code to accomplish that?
Since you haven't provided a mvce you'll need to adapt the following example to your needs - . It's main purpose is to demonstrate how to insert text and format it, which is done in the For loop.
Please note how to declare and instantiate a Table object - this is more reliable and more efficient than repeating ActiveDocument.Tables[index].
Use a Range object to write the data to the table cell. Important is "collapsing" the range so that the content is appended, rather than over-written. You need to write a separate range object for each formatting variation.
The code below checks whether the second member of the array is being written. If yes, it's formatted as subscript, if it's another member, then no subscript.
Sub CellContentWithSubscript()
Dim tbl As Word.Table
Dim rng As Word.Range
Dim wordArray '() As Variant
Dim data As String, k As Long
Set tbl = ActiveDocument.Tables(1)
data = "PartOne_Part two_Part three"
wordArray = Split(data, "_")
Set rng = tbl.Cell(1, 1).Range
rng.Collapse 0
rng.MoveEnd wdCharacter, -1
For k = LBound(wordArray) To UBound(wordArray)
rng.Text = wordArray(k)
If k = 1 Then
rng.Font.Subscript = True
Else
rng.Font.Subscript = False
End If
rng.Collapse 0
Next k
End Sub

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.

Vectorial formula for cell validation in Excel using VBA

I am writing a VBA formula to check that all characters in a cell "TestChars" are allowed, where allowed means that each character appears in a list defined by another cell "AllowedChars". To make things even harder, I would like this formula to work on ranges of cells rather than on a single cell.
The current code seems to work:
Option Explicit
Public Function AllCharsValid(InputCells As Range, AllowedChars As String) As Boolean
' Check that all characters in InputCells are among
' the characters in AllowedChars
Dim Char As String
Dim Index As Integer
Dim RangeTestChars As Range
Dim TestChars As String
For Each RangeTestChars In InputCells
TestChars = RangeTestChars.Value
For Index = 1 To Len(TestChars)
Char = Mid(TestChars, Index, 1)
If InStr(AllowedChars, Char) = 0 Then
AllCharsValid = False
Exit Function
End If
Next Index
Next RangeTestChars
AllCharsValid = True
End Function
I have the following questions:
The formula takes a range and returns a single boolean. I would prefer a vectorized function, where, given an input range, you get a corresponding range of booleans. It seems like built-in formulas like 'EXACT' can do this (those formulas where you have to press ctrl-shift-enter to execute them and where you get curly-brackets). Is there a way to do that with user-defined functions?
I am not new to programming, however I am completely new to VBA (I started literally today). Is there any obvious problem, weirdness with the above code?
Are there special characters, extremely long texts or particular input values that would cause the formula to fail?
Is there an easier way to achieve the same effect? Is the code slow?
when you start typing built-in formulas in excel you get suggestions and auto-completion. This doesn't seem to work with my formula, am I asking for too much or is it possible to achieve this?
I realize that this question contains several weakly related sub-questions, so I would be very happy also with sub-answers.
The following code will return a range of boolean values offset one column from the initial input range. Simply create a new tab in Excel and run testAllCharsValid and show the Immediate window in the IDE to see how it works.
Sub testAllCharsValid()
Dim i As Integer
Dim cll As Range, rng As Range
Dim allowedChars As String
' insert test values in sheet: for testing purposes only
With ActiveSheet ' change to Thisworkbook.Sheets("NameOfYourSheet")
Set rng = .Range("A1:A10")
For i = 1 To 10
.Cells(i, 1) = Chr(i + 92)
Next i
End With
' fill allowedChars with letters a to z: for testing purposes only
For i = 97 To 122
allowedChars = allowedChars & Chr(i)
Next i
' get boolean range
Set rng = AllCharsValid(rng, allowedChars)
' check if the returned range contains the expected boolean values
i = 0
For Each cll In rng
i = i + 1
Debug.Print i & " boolean value: " & cll.Value
Next cll
End Sub
' Check that all characters in InputCells are among
' the characters in AllowedChars
Public Function AllCharsValid(InputCells As Range, allowedChars As String) As Range
Dim BoolTest As Boolean
Dim Char As String
Dim Index As Integer
Dim RangeTestChars As Range, RangeBooleans As Range, RangeTemp As Range
Dim TestChars As String
For Each RangeTestChars In InputCells
BoolTest = True
TestChars = RangeTestChars.Value
For Index = 1 To Len(TestChars)
Char = Mid(TestChars, Index, 1)
If InStr(allowedChars, Char) = 0 Then BoolTest = False
Next Index
Set RangeTemp = RangeTestChars.Offset(0, 1) ' change offset to what suits your purpose
RangeTemp.Value = BoolTest
If RangeBooleans Is Nothing Then
Set RangeBooleans = RangeTestChars
Else
Set RangeBooleans = Union(RangeBooleans, RangeTemp)
End If
Next RangeTestChars
Set AllCharsValid = RangeBooleans
End Function
cf 2) If the length of the test string is zero, the function will return True for the cell in question, which may not be desirable.
cf 3) There is a limit to how many characters an Excel cell can contain, read more here. I suppose, if you concatenated some very long strings and sent them to the function, you could reach the integer limit of +32767, which would cause a run-time error due to the integer Index variable. However, since the character limit of Excel cells is exactly +32767, the function should work as is without any problems.
cf 4) None that I know of.
cf 5) This is not the easiest thing to achieve, but there is help to be found here.

Search cell for text and copy text to another cell in VBA?

I've got a column which contains rows that have parameters in them. For example
W2 = [PROD][FO][2.0][Customer]
W3 = [PROD][GD][1.0][P3]
W4 = Issues in production for customer
I have a function that is copying other columns into another sheet, however for this column, I need to do the following
Search the cell and look for [P*]
The asterisk represents a number between 1 and 5
If it finds [P*] then copy P* to the sheet "Calculations" in column 4
Basically, remove everything from the cell except where there is a square bracket, followed by P, a number and a square bracket
Does anyone know how I can do this? Alternatively, it might be easier to copy the column across and then remove everything that doesn't meet the above criteria.
Second Edit:
I edited here to use a regular expression instead of a loop. This may be the most efficient method to achieve your goal. See below and let us know if it works for you:
Function MatchWithRegex(sInput As String) As String
Dim oReg As Object
Dim sOutput As String
Set oReg = CreateObject("VBScript.RegExp")
With oReg
.Pattern = "[[](P[1-5])[]]"
End With
If oReg.test(sInput) Then
sOutput = oReg.Execute(sInput)(0).Submatches(0)
Else
sOutput = ""
End If
MatchWithRegex = sOutput
End Function
Sub test2()
Dim a As String
a = MatchWithRegex(Range("A1").Value)
If a = vbNullString Then
MsgBox "None"
Else
MsgBox MatchWithRegex(Range("A1").Value)
End If
End Sub
First EDIT:
My solution would be something as follows. I'd write a function that first tests if the Pattern exists in the string, then if it does, I'd split it based on brackets, and choose the bracket that matches the pattern. Let me know if that works for you.
Function ExtractPNumber(sInput As String) As String
Dim aValues
Dim sOutput As String
sOutput = ""
If sInput Like "*[[]P[1-5][]]*" Then
aValues = Split(sInput, "[")
For Each aVal In aValues
If aVal Like "P[1-5][]]*" Then
sOutput = aVal
End If
Next aVal
End If
ExtractPNumber = Left(sOutput, 2)
End Function
Sub TestFunction()
Dim sPValue As String
sPValue = ExtractPNumber(Range("A2").Value)
If sPValue = vbNullString Then
'Do nothing or input whatever business logic you want
Else
Sheet2.Range("A1").Value = sPValue
End If
End Sub
OLD POST:
In VBA, you can use the Like Operator with a Pattern to represent an Open Bracket, the letter P, any number from 1-5, then a Closed Bracket using the below syntax:
Range("A1").Value LIke "*[[]P[1-5][]]*"
EDIT: Fixed faulty solution
If you're ok with blanks and don't care if *>5, I would do this and copy down column 4:
=IF(ISNUMBER(SEARCH("[P?]",FirstSheet!$W2)), FirstSheet!$W2, "")
Important things to note:
? is the wildcard symbol for a single character; you can use * if you're ok with multiple characters at that location
will display cell's original value if found, leave blank otherwise
Afterwards, you can highlight the column and remove blanks if needed. Alternatively, you can replace the blank with a placeholder string.
If * must be 1-5, use two columns, E and D, respectively:
=MID(FirstSheet!$W2,SEARCH("[P",FirstSheet!$W2)+2,1)
=IF(AND(ISNUMBER($E2),$E2>0,$E2<=5,MID($W2,SEARCH("[P",FirstSheet!$W2)+3,1))), FirstSheet!$W2, "")
where FirstSheet is the name of your initial sheet.

How to extract specific text from a cell?

In this case, I want to extract the beginning text in a cell and leave the remainder intact.
e.g. a series of cells contain:
2nd Unit. Miami
3rd Production Staff. Toronto
1st Ad. San Francisco
I want to break this up without using Text to columns as previous rows are formatted differently and these last few rows are outliers that I want to handle.
I thought Regular Expressions might do it, but that seems a bit complex.
My algorithm idea is:
1. grab the wanted text (what function or custom sub would do that?)
2. Past the text to it's new location
3. Cut the text from the cell, leaving the remaining text.
Seems simple but I'm still wending my way through VBA forest, and at the rate I'm going it's going to end up faster doing it by hand. But this seems like a good opportunity to learn some VBA tricks.
TIA
Update:
I want to take the text up to the ".\ " and move it to a different column, keeping the remainder where it is.
VBA is unnecessary. To get the text after .\ in cell A1: =MID(A1,FIND(".\",A1,1)+2,LEN(A1)) to get the text before .\ in A1: =LEFT(A1,FIND(".\",A1,1)-1).
As additional information, Find returns the placement in the string where .\ appears. It is the equivalent of InStr in VBA. If .\ is not in the cell, it will display #VALUE, because I didn't bother to add error checking.
Since you seem to want to modify the cell text in place, VBA will be required.
Inside a loop that sets cl to the cell to be processed:
str = cl.value
i = Instr(str, ".\")
cl = Trim(Mid$(str, i + 2)) ' assuming you want to exclude the ".\"
cl.Offset(0, 1) Trim(Left$(str, i - 1)) ' Places the original first part one cell to the right
For the sake of anyone who had this same question, here is the fully tested, working code.
Function RE6(strData As String) As String
Dim RE As Object, REMatches As Object
Set RE = CreateObject("vbscript.regexp")
With RE
.MultiLine = False
.Global = False
.IgnoreCase = True
.Pattern = "[0-9][0-9][0-9][0-9]B"
RE6 = .test(strData)
End With
Set REMatches = RE.Execute(strData)
If REMatches.Count > 0 Then
RE6 = True
Else
RE6 = False
End If
End Function
Sub territory()
Dim strTest As String, str As String, cl As Range
strTest = ActiveCell.Value
Set cl = ActiveCell
If RE6(strTest) = True Then
str = cl.Value
i = InStr(str, ". ")
cl = Trim(Mid$(str, i + 2))
cl.Offset(0, 1) = Trim(Left(str, i - 1))
cl.Offset(0, 2) = "Instance"
MsgBox RE6(strTest)
End If
End Sub