I have a VBA function in Excel returns a concatenated string of text from cells selected by users.
This works as I require, however if there are hidden cells in the selection, the value of the hidden cell is included, which is undesirable. An example of when this issue occurs is when a table is filtered.
Is there a way to amend my function to check if the cell that is being read is visible?
Sub ConcatEmialAddresses()
Dim EmailAddresses As String
ActiveSheet.Range("C3").Value = combineSelected()
ActiveSheet.Range("C3").Select
Call MsgBox("The email address string from cell ""C3"" has been copied to your clipboard.", vbOKOnly, "Sit back, relax, it's all been taken care of...")
End Sub
Function combineSelected(Optional ByVal separator As String = "; ", _
Optional ByVal copyText As Boolean = True) As String
Dim cellValue As Range
Dim outputText As String
For Each cellValue In Selection
outputText = outputText & cellValue & separator
Next cellValue
If Right(outputText, 2) = separator Then outputText = Left(outputText, Len(outputText) - 2)
combineSelected = outputText
End Function
To determine if a Range has an hidden cell, I would check that the height/width of each row/column is different from zero:
Function HasHiddenCell(source As Range) As Boolean
Dim rg As Range
'check the columns
If VBA.IsNull(source.ColumnWidth) Then
For Each rg In source.Columns
If rg.ColumnWidth = 0 Then
HasHiddenCell = True
Exit Function
End If
Next
End If
' check the rows
If VBA.IsNull(source.RowHeight) Then
For Each rg In source.rows
If rg.RowHeight = 0 Then
HasHiddenCell = True
Exit Function
End If
Next
End If
End Function
Sub UsageExample()
If HasHiddenCell(selection) Then
Debug.Print "A cell is hidden"
Else
Debug.Print "all cells are visible"
End If
End Sub
I used this
Function areCellsHidden(Target As Range)
areCellsHidden = False
If (Target.Rows.Hidden = True) Then
areCellsHidden = True
ElseIf (Target.Columns.Hidden = True) Then
areCellsHidden = True
ElseIf (Target.Count > 1) Then
If _
Target.Count <> Target.Columns.SpecialCells(xlCellTypeVisible).Count _
Or Target.Count <> Target.Rows.SpecialCells(xlCellTypeVisible).Count _
Then
areCellsHidden = True
End If
End If
End Function
Related
is there I way for searching a value on the next sheet (ActiveSheet.Next.Activate) without jumping on to it?
Here the whole Code:
the problem is, it jumps to the next sheet even if there is no searched value.
Dim ws As Worksheet
Dim Loc As Range
Dim StrVal As String
Dim StrRep As String
Dim i As Integer
Private Sub CommandButton1_Click()
i = 1
Call Replacing
End Sub
Private Sub CommandButton2_Click()
i = 2
Call Replacing
End Sub
Public Sub Replacing()
StrVal = Userform1.Textbox1.Text
StrRep = Me.Textbox1.Text
if Trim(StrVal) = "" Then Exit Sub
Dim fstAddress As String
Dim nxtAddress As String
For Each ws In ThisWorkbook.Worksheets
With ws
Set Loc = .Cells.Find(what:=StrVal)
fstAddress = Loc.Address
If Not Loc Is Nothing Then
If Not StrRep = "" And i = 1 Then
Loc.Value = StrRep
Set Loc = .Cells.FindNext(Loc)
ElseIf i = 2 Then Set Loc = Range(ActiveCell.Address)
Set Loc = .Cells.FindNext(Loc)
nxtAddress = Loc.Address
If Loc.Address = fstAddress Then
ActiveSheet.Next.Activate '****Here it should jump only if found something on the next sheet****
GoTo repeat
nxtAddress = Loc.Address
End If
If Not Loc Is Nothing Then Application.Goto ws.Range(nxtAddress), False
End If
i = 0
End If
End With
Set Loc = Nothing
repeat:
Next ws
End Sub
the variable "i" which switches between the values 0, 1 and 2 is bound to two buttons. these buttons are "Replace" and "Skip (to next found value)".
This code asks on each occurrence of StrVal whether you want to replace the value or skip it.
I found a problem checking if Found_Address = First_Found_Address:
If you've replaced the value in in First_Found_Address it won't find that address again and miss the starting point in the loop.
Also the original source of the code stops at the last value using Loop While Not c Is Nothing And c.Address <> firstAddress. The problem here is that if the value in c is being changed eventually c will be Nothing but it will still try and check the address of c - causing an error (Range Find Method).
My solution to this is to build up a string of visited addresses on the sheet and checking if the current address has already been visited using INSTR.
I've included the code for calling from a button click or from within another procedure.
Private Sub CommandButton1_Click()
FindReplace Userform1.Textbox1.Text, 1
End Sub
Private Sub CommandButton2_Click()
FindReplace Userform1.Textbox1.Text, 1, Me.Textbox1.Text
End Sub
Sub Test()
FindReplace "cd", 1, "ab"
End Sub
Sub FindReplace(StrVal As String, i As Long, Optional StrRep As String = "")
Dim ws As Worksheet
Dim Loc As Range
Dim fstAddress As String
Dim bDecision As Variant
For Each ws In ThisWorkbook.Worksheets
'Reset the visited address list on each sheet.
fstAddress = ""
With ws
Set Loc = .Cells.Find(what:=StrVal, LookIn:=xlValues, LookAt:=xlWhole, SearchDirection:=xlNext)
If Not Loc Is Nothing Then
Do
fstAddress = fstAddress & "|" & Loc.Address
Loc.Parent.Activate 'Activate the correct sheet.
Loc.Activate 'and then the cell on the sheet.
bDecision = MsgBox("Replace value?", vbYesNo + vbQuestion, "Replace or Select value?")
If bDecision = vbYes Then
Loc = StrRep 'Raise the blade, make the change.
'Re-arrange it 'til it's sane.
End If
Set Loc = .Cells.FindNext(Loc)
If Loc Is Nothing Then Exit Do
Loop While InStr(fstAddress, Loc.Address) = 0
End If
End With
Next ws
End Sub
I am trying to write something like: If the value "O" is found in this range (M3:Q3) then fill all the cells in the range (M3:Q3) not containing the "O" with "X".
Here is what I have so far. I have had a lot of trouble getting the function to populate a value in anything other than the cell the function is being called.
Function positive(range_data As range)
Dim display As String
display = ""
Dim positiveValue As Boolean
For Each Item In range_data
If Item.Value = "O" Then
positiveValue = True
End If
Next
If positiveValue = True Then
For Each Item In range_data
If Item.Value = "" Then
Worksheets("Sheet1").Cells(Item.row, Item.Column).Value = "X"
End If
Next
End If
'positive = range_data
'positive = display
End Function
Thank you for your help!
your code seems to work fine, I just turned it into a Sub
Sub positive(range_data As Range)
Dim display As String
display = ""
Dim positiveValue As Boolean
For Each Item In range_data
If Item.Value = "O" Then
positiveValue = True
End If
Next
If positiveValue = True Then
For Each Item In range_data
If Item.Value <> "O" Then
Worksheets("Sheet1").Cells(Item.Row, Item.Column).Value = "X"
End If
Next
End If
'positive = range_data
'positive = display
End Sub
Sub MAIN()
positive Range("A1:C4")
End Sub
The function can work if called from a Sub:
Function positive(range_data As Range)
Dim display As String
display = ""
Dim positiveValue As Boolean
For Each Item In range_data
If Item.Value = "O" Then
positiveValue = True
End If
Next
If positiveValue = True Then
For Each Item In range_data
If Item.Value = "" Then
Worksheets("Sheet1").Cells(Item.Row, Item.Column).Value = "X"
End If
Next
End If
positive = "whatever"
End Function
Sub MAIN()
x = positive(Range("M3:Q3"))
End Sub
But there is no reason to use a function since the function's output is not used.
In the main subroutine, I have to get two user inputs ((1) range address (e.g., A1:C50), (2) Name String (e.g., James)), and call the function subroutine (by passing the inputs as arguments), and printout the result through Message Box as to whether the name exists or doesn't exist in the range.
Both the search range and the name should be input from the users. How do I write the Function subRoutine and the calling sub? This is what I have so far.
Function NameExists(name As String, area As Range) As Boolean
If name = area.Value Then
NameExists = True
Else
NameExists = False
End If
End Function
Sub Main()
Dim NameExists As Boolean
Dim name As String
name = InputBox("Enter a Name")
area = InputBox("Enter a Range")
If NameExists = True Then
MsgBox name & " Has Been Found"
Else
MsgBox name & " Has Not Been Found"
End If
End Sub
You need to actually call the Function that checks if it exists and pass the name and area variables that you've had the user input. Here is a somewhat crude example:
Sub Main()
Dim nm As String
Dim ar As String
nm = InputBox("Enter a Name")
ar = InputBox("Enter a Range")
If NameExists(nm, ar) = True Then
MsgBox nm & " Has Been Found"
Else
MsgBox nm & " Has Not Been Found"
End If
End Sub
Private Function NameExists(name As String, area As String) As Boolean
Dim myRange As Range
Set myRange = Range(area)
For Each myCell In myRange
If myCell.Value = name Then
NameExists = True
Exit For
End If
Next
Set myRange = Nothing
End Function
This is an extract form an excel 2010 macro I’m working on in VBA, the function I’m having issues with is DelStrikethroughs
Whenever the value in a cell is "TRUE", "FALSE", or "#N/A" (when its "#N/A" the macro crashes) the function returns a null string. Upon further investigation it looks like the variable x.text is always blank and has the error "Unable to get the text property of the characters class" when i try to debug it.
Any ideas on how to fix this? (I'm happy for the function to return the original text if it cant remove the strike through text, but a proper solution is preferred)
Below is the code sample:
Sub testx()
Dim testRange As Range
Set testRange = selection
Call DelStrikethroughs(testRange.Cells(1, 1))
End Sub
Function DelStrikethroughs(Cell As Range) As String
'Returns the text value of a cell with strikethrough characters removed
Dim NewText As String
Dim iCh As Integer
For iCh = 1 To Len(Cell)
Dim x As Characters
Set x = Cell.Characters(iCh, 1)
On Error Resume Next '"On Error" is here to deal with blank characters
If x.Font.Strikethrough = False Then
NewText = NewText & x.text
End If
If Err.Number = 0 Then
NewText = NewText
Else
NewText = NewText & x.text
End If
Next iCh
DelStrikethroughs = NewText
End Function
Try this:
Sub testx()
Dim testRange As Range, c As Range
Set testRange = Selection
For Each c In testRange
c.Offset(0, 1).Value = DelStrikethroughs(c)
Next c
End Sub
Function DelStrikethroughs(Cell As Range) As String
'Returns the text value of a cell with strikethrough characters removed
Dim NewText As String
Dim iCh As Long, l As Long, ch As Characters
On Error Resume Next
l = Cell.Characters.Count
On Error GoTo 0
If l = 0 Then
NewText = Cell.Text
Else
For iCh = 1 To l
Set ch = Cell.Characters(iCh, 1)
NewText = NewText & IIf(ch.Font.Strikethrough, "", ch.Text)
Next iCh
End If
DelStrikethroughs = NewText
End Function
If all you want to do is return the text in the cell without any strikethrough, then try:
Function DelStrikethroughs(Cell As Range) As String
DelStrikethroughs = Cell.Text
End Function
In a VBA Word macro, I'd like to get a Field-object for the field which contains the cursor.
The obvious try fails:
Private Sub Try1()
MsgBox Selection.Fields.Count
End Sub
The array is empty. Then I tried:
Private Sub Try2()
Dim oRange As Range
Set oRange = Selection.GoTo(What:=wdGoToField)
MsgBox oRange
End Sub
The cursor does not move, the message is empty.
I can iterate over ActiveDocument.Fields, compare the ranges and find the containing fiels. But probably there is a simple direct way?
My current production code with iteration over Document.Fields:
Sub Test()
Dim oField As Field
Set oField = FindWrappingField(Selection.Range)
If oField Is Nothing Then
MsgBox "not found"
Else
MsgBox oField
End If
End Sub
Private Function FindWrappingField(vRange As Range)
Dim oField As Field
Dim nRefPos As Long
' If selection starts inside a field, it also finishes inside.
nRefPos = vRange.Start
' 1) Are the fields sorted? I don't know.
' Therefore, no breaking the loop if a field is too far.
' 2) "Code" goes before "Result", but is it forever?
For Each oField In vRange.Document.Fields
If ((oField.Result.Start <= nRefPos) Or (oField.Code.Start <= nRefPos)) And _
((nRefPos <= oField.Result.End) Or (nRefPos <= oField.Code.End)) Then
Set FindWrappingField = oField
Exit Function
End If
Next oField
Set FindWrappingField = Nothing
End Function
The following function determines whether the selection spans or is within a field.
Function WithInField(Rng As Word.Range) As Boolean
' Based on code by Don Wells: http://www.eileenslounge.com/viewtopic.php?f=30&t=6622
' Approach : This procedure is based on the observation that, irrespective of _
a field's ShowCodes state, toggling the field's ShowCodes state _
twice collapses the selection to the start of the field.
Dim lngPosStart As Long, lngPosEnd As Long, StrNot As String
WithInField = True
Rng.Select
lngPosStart = Selection.Start
lngPosEnd = Selection.End
With Selection
.Fields.ToggleShowCodes
.Fields.ToggleShowCodes
' Test whether the selection has moved; if not, it may already have been _
at the start of a field, in which case, move right and test again.
If .Start = lngPosStart Then
.MoveRight
.Fields.ToggleShowCodes
.Fields.ToggleShowCodes
If .Start = lngPosStart + 1 Then
WithInField = False
End If
End If
End With
End Function
You can use the function with code like:
Sub TestWithInField()
Dim Rng As Word.Range, c As Word.Range, StrRslt As String
Set Rng = Selection.Range
For Each c In Rng.Characters
StrRslt = StrRslt & c.Text & ",WithInField:" & WithInField(Rng:=c) & vbCr
Next
Rng.Select
MsgBox StrRslt
End Sub
I had the same problem and I solved with the code below:
Sub Test()
NumberOfFields = Selection.Fields.Count
While NumberOfFields = 0
Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend
NumberOfFields = Selection.Fields.Count
Wend
End Sub
Of course, I have to know that the cursor is in a field.
Apparently, when you select a range extending to the right, at some moment the field will be selected. The end of the range doesn't count (it not acuses a field range)
I use this code
Sub GetFieldUnderCursor()
Dim NumberOfFields As Integer
Dim oFld As Field
Dim TextFeld As String
Dim Typ As Integer
Dim pos As Integer
Dim NameOfField As String
'update field. Cursor moves after the field
Selection.Fields.Update
'select the field
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend
'check if there is a field
NumberOfFields = Selection.Fields.Count
If NumberOfFields = 0 Then
MsgBox "No field under cursor"
Exit Sub
End If
Set oFld = Selection.Fields(1)
TextFeld = Trim(oFld.Code.Text)
Typ = oFld.Type '85 is DOCPROPERTY, 64 is DOCVARIABLE
If Typ = 85 Or Typ = 64 Then
pos = InStr(15, TextFeld, " ")
If pos > 0 Then
NameOfField = Trim(Mid(TextFeld, 12, pos - 11))
MsgBox NameOfField
End If
End If
End Sub