VBA search function works only if triggered from another sheet - excel

This function loops through all the textboxes in the workbook to find and highlight text + textbox that contain the result.
The problem is: if I trigger it from a specific sheet, all the textboxes in that sheet that contain the result will NOT be highlighted (text is found though, so it works halfway).
If I trigger it from a worksheet that does not contain a textbox with a result, then everything works.
Dim shp As Shape
Dim Color As String
Dim ColorIndexobj As String
Dim Sizeobj As Integer
Dim sFind As String
Dim sFind2 As String
Dim sTemp As String
Dim iPos As Integer
Dim sTemp2 As String
Dim iPos2 As Integer
Dim Response
Dim sourceSheet As Worksheet
Set sourceSheet = ActiveSheet
sFind = InputBox("Search for?")
If Trim(sFind) = "" Then
MsgBox "Nothing entered"
Exit Sub
End If
For Each ws In ActiveWorkbook.Worksheets
ws.Select
For Each shp In ws.Shapes
If shp.Type = msoTextBox Then
sTemp = shp.TextFrame.Characters.Text
If InStr(LCase(sTemp), LCase(sFind)) <> 0 Then
shp.Select
With shp.Line
Color = shp.Line.ForeColor.RGB
Weight = shp.Line.Weight
.ForeColor.RGB = vbRed
.Weight = 5
End With
sFind2 = LCase(sFind)
sTemp2 = LCase(shp.TextFrame.Characters.Text)
iPos2 = InStr(sTemp2, sFind2)
If iPos2 > 0 Then
With shp.TextFrame.Characters(Start:=iPos2, _
Length:=Len(sFind2)).Font
Sizeobj = .Size
.Size = 35
End With
End If
Set sourceSheet = ActiveSheet
Response = MsgBox( _
"Do you want to continue?", _
Buttons:=vbYesNo, Title:="Continue?")
If Response = vbYes Then
With shp.Line
.ForeColor.RGB = Color
.Weight = Weight
End With
With shp.TextFrame.Characters(Start:=iPos2, _
Length:=Len(sFind2)).Font
.Size = Sizeobj
End With
End If
If Response = vbNo Then
With shp.Line
.ForeColor.RGB = Color
.Weight = Weight
End With
With shp.TextFrame.Characters(Start:=iPos2, _
Length:=Len(sFind2)).Font
.Size = Sizeobj
End With
Exit Sub
End If
End If
End If
Next
Next
Call sourceSheet.Activate
End Sub

The problem is that the change that you are doing is undone before the window is ever repainted.
Here is a kludge (from this answer):
Immediately after
With shp.Line
Color = shp.Line.ForeColor.RGB
Weight = shp.Line.weight
.ForeColor.RGB = vbRed
.weight = 5
End With
Put the line:
ActiveWindow.SmallScroll 0
Exactly what causes a window to repaint during a running macro isn't clearly documented. The rules are evidently different as they apply to the active sheet, which would explain the behavior that you are observing. There isn't any simple RePaint method of a worksheet, hence the kludge, which works since scrolling triggers a repaint, even if the distance scrolled is zero.
From some reason, using DoEvents rather than this kludge doesn't seem to work.

Related

Adding a note to a cell based on another cell

I am trying to add a specific note to a cell based on the cell value to explain what the cell contents are. I am trying to use the code below to do this but I get a run-time error '1004' on the following line:
Target.Cells.Comment.Text Text:=Comment_E
Private Sub Worksheet_Change(ByVal Target As Range)
'Defining what column is being changed
If Target.Column = 3 Then
'Adding a comment to the cell
Dim Status_Col As String
Dim NA As String
Dim i As Integer
'Types
Dim Comment_T As String
'Explainations
Dim Comment_E As String
i = 4
Comment_T = Target.Cells.Value
For i = 4 To 10 ' checking the list of types
If Cells(i, 14).Value = Comment_T Then
Comment_E = Cells(i, 15).Value
End If
Next i
Target.Cells.Select
Target.Cells.AddComment
Target.Cells.Comment.Visible = False
Target.Cells.Comment.Text Text:=Comment_E
Selection.ShapeRange.ScaleHeight 0.48, msoFalse, msoScaleFromTopLeft
Selection.ShapeRange.ScaleWidth 3.32, msoFalse, msoScaleFromTopLeft
End If
End Sub
You need to account for the case where a cell already has a comment, and the case where multiple cells are updated (eg fill down).
Few fixes/suggestions:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim v, cmt As Comment, rng As Range, c As Range
'any changes in Col3?
Set rng = Application.Intersect(Target, Me.Columns(3))
'optionally set some limit for the size of change you want to handle
If rng.Cells.CountLarge > 100 Then Exit Sub
'now process each cell in the col3 range
For Each c In rng.Cells
v = Application.VLookup(c.Value, Me.Range("N4:O10"), 2, False)
If Not IsError(v) Then
Set cmt = c.Comment 'already has a comment?
If cmt Is Nothing Then
Set cmt = c.AddComment() 'no comment so add one
With cmt 'formatting...
.Visible = False
.Shape.Height = 30 'fixed height/width is easier
.Shape.Width = 100
End With
End If
cmt.Text v 'set/replace text
End If
Next c
End Sub

VBA Excel automatic image resize & border

I would like to have my image properly resized and bordered with the black line, thickness 1.
My situation looks like this:
and when I used this code:
Sub ResizeCivilsA()
SizeToRange Sheets("Civils 1").Pictures("Picture 29"), Range("B3:L46")
End Sub
Function SizeToRange(s, Target As Range)
s.Left = Target.Left + 10
s.Top = Target.Top - 5
s.Width = Target.Width
s.Height = Target.Height
End Function
, everything was adjusted fine, but:
It have been done only for the specified shape id, which is "Picture 29"
It was without the borders
So I tried then:
Sub ResizeCivilsA()
Dim shp As Shape
For Each shp In ThisWorkbook.Worksheets
If shp.Name Like "*Picture*" Then
SizeToRange shp, Range("B3:L46")
End If
Next
and finally I am getting error:
Type mismatch, with debugger pointing the line:
For Each shp In ThisWorkbook.Worksheets
Regarding the border around the image I found the common solution here:
https://learn.microsoft.com/en-us/office/vba/api/Excel.Range.BorderAround
However after appliance into my work:
Worksheets("Civils 1").Shape("Picture 29").BorderAround _
ColorIndex:=3, Weight:=xlThick
it wasn't enough since I had to remove the _ and got nothing afterward.
Is there some way to have the possibility for instant resizing the image and making the border around it for ANY attached image, which as default is called "Picture..."?
Try this code.
Read code's comments and adjust it to fit your needs
EDIT: The code checks if picture is within target range ad then adjusts its properties.
Code:
Option Explicit
Public Sub ResizeAllShapesInSheet()
Dim targetSheet As Worksheet
Dim targetRange As Range
Dim targetShape As Shape
' Define the sheet that has the pictures
Set targetSheet = ThisWorkbook.Worksheets("Civils 1")
' Define the range the images is going to fit
Set targetRange = targetSheet.Range("B3:L46")
' Loop through each Shape in Sheet
For Each targetShape In targetSheet.Shapes
' Check "picture" word in name
If targetShape.Name Like "*Picture*" Then
' Call the resize function
SizeToRange targetShape, targetRange
End If
Next targetShape
End Sub
Private Sub SizeToRange(ByVal targetShape As Shape, ByVal Target As Range)
If Not (targetShape.Left >= Target.Left And _
targetShape.Top >= Target.Top And _
targetShape.Left + targetShape.Width <= Target.Left + Target.Width And _
targetShape.Top + targetShape.Height <= Target.Top + Target.Height) Then Exit Sub
' Adjust picture properties
With targetShape
' Check if next line is required...
.LockAspectRatio = msoFalse
.Left = Target.Left + 10
.Top = Target.Top - 5
.Width = Target.Width
.Height = Target.Height
End With
' Adjust picture border properties
With targetShape.Line
.Visible = msoTrue
.ForeColor.RGB = RGB(0, 0, 0)
.ForeColor.TintAndShade = 0
.ForeColor.Brightness = 0
.Transparency = 0
.Visible = msoTrue
.Weight = 6
End With
End Sub
Let me know if it works
Initial read looks like your For Each is looking for Shape objects, but you are giving it a collection of Sheet objects.
For Each sht In ThisWorkbook.Worksheets
For Each shp In sht.Shapes
If shp.Name Like "*Picture*" Then
Set r1 = shp.TopLeftCell
Set r2 = r1.Offset(10, 43)
SizeToRange shp, Range(r1.Address & ":" & r2.Address)
End If
Next shp
Next sht
Hope that helps!
EDIT: Updated with relative address.

Excel - VBA - Function detecting image in cell

I wanted to write a function that detects if there is an Image in the cell. When not, it goes to the next row. The column stays the same.
My Function doesnt work.
Public Function HasImage(ByVal Target As Range) As Boolean
Dim bResult As Boolean
Dim shp As Shape
bResult = False
For Each shp In ActiveSheet.Shapes
If shp.TopLeftCell.Address = Target.Address _
And shp.BottomRightCell.Address = Target.Address Then
bResult = True
End If
Next shp
HasImage = bResult
End Function
I may be wrong but does your BottomRightCell have to be offset (1,1) from the topleft when image sized to one cell?
In my testing your code seemed to fail because of the BottomRightCell.Address, with pic sized to one cell, being a row below and a column to the right.
I tested with the following code where I adjusted BottomRightCell.Address with:
And shp.BottomRightCell.Address = Target.Offset(1, 1).Address
Code:
Option Explicit
Public Sub test()
Dim mypic As Object
Set mypic = ActiveSheet.Pictures(1)
PositionPic mypic
MsgBox HasImage([A1])
MsgBox mypic.BottomRightCell.Address
End Sub
Public Function HasImage(ByVal Target As Range) As Boolean
Dim bResult As Boolean
Dim shp As Shape
bResult = False
For Each shp In ActiveSheet.Shapes
If shp.TopLeftCell.Address = Target.Address _
And shp.BottomRightCell.Address = Target.Offset(1, 1).Address Then
bResult = True
End If
Next shp
HasImage = bResult
End Function
Public Sub PositionPic(ByVal mypic As Object)
With mypic
.Left = ActiveSheet.Cells(1, 1).Left
.Top = ActiveSheet.Cells(1, 1).Top
.Width = ActiveSheet.Cells(1, 1).Width
.Height = ActiveSheet.Cells(1, 1).Height
End With
End Sub

Adjust Listbox.Height problems

I have a userform with a textbox and a listbox with the following plan:
Users input text in Textbox1.
Every time Textbox1.Text changes, a search with the following features is performed:
Textbox1.Text is searched in a specific range in a worksheet.
Textbox1.Text can be found more than once.
Listbox1 is populated with the results of the search.
So far so good. Due to having large set of data, the list can get many items. In this case the list reaches out of the screen and I had to limit Listbox1.Height. This is the code for the above:
Private Sub TextBox1_Change()
Dim srchWord As String, firstAddress As String
Dim srchRng As Range, cell As Range
Dim maxRow As Integer
ListBox1.Clear
If TextBox1.Value = "" Then
ListBox1.Height = 0
Else
With ThisWorkbook.Worksheets(1)
maxRow = .Cells(.Rows.Count, 2).End(xlUp).Row
Set srchRng = .Range("A2:A" & maxRow)
End With
srchWord = TextBox1.Value
Set cell = srchRng.Find(srchWord, LookIn:=xlValues, lookat:=xlPart)
With ListBox1
If Not cell Is Nothing Then
firstAddress = cell.Address
Do
If Not cell.Value Like "*(*" Then
.AddItem (cell.Value)
Select Case .ListCount
Case Is < 2
.Height = 17
Case Is < 21
.Height = 15 * .ListCount
Case Else
.Height = 272.5
End Select
Me.Height = 500
End If
Set cell = srchRng.FindNext(cell)
Loop While Not cell.Address = firstAddress
End If
End With
End If
End Sub
The problem was in Case Else when scroll was enabled I couldn't reach the last item of the list. By searching on the net I found some potential solutions:
set Listbox1.IntegralHeight = False set the height and then set again Listbox1.IntegralHeight = True
set Listbox1.MultiSelect = fmMultiSelectSingle and then set again Listbox1.MultiSelect = fmMultiSelectExtended.
do both of the above.
Application.Wait (Now + TimeValue("0:00:01") * 0.5) and then set the height.
None of these worked.
To be able to scroll to the last item, this worked:
Listbox1.IntegralHeight = False
Listbox1.Height= x
Listbox1.IntegralHeight = False
Listbox1.Height= x
but this also set the Listbox1.Height to this of one single item. (with arrows at the right)
Does anybody know how on earth am I going to control the Listbox1.Height without all this unwanted behaviour? Also if somebody can suggest another structure that could follow the plan mentioned at first, I 'm willing to discard the listbox.
This seems to be a not completely explored behaviour.
In my experience just redefine some listbox arguments.
Try the recommended sets of .IntegralHeight to False and True.
Another possible measure can help in some cases: try to choose heights for your listbox that come close to the following multiplication:
listbox height = (font size + 2 pts) * (maximum items per page)
Insert the following code after With ListBox1:
With ListBox1
.Top = 18 ' << redefine your starting Point
.Font.Size = 10 ' << redefine your font size
.IntegralHeight = False ' << try the cited recommendation :-)
Insert the following code before End With:
.Height = .Height + .Font.Size + 2
.IntegralHeight = True
End With
Hope that helps.
Link
See another faster approach to filter listboxes at How to speed up filling of listbox values on userform excel
#T.M.: Thank you for your quick response and for your time. Your answer gave me exactly what I wanted and that's why I'm marking it as such. I'm posting this just for future reference.
What I finaly did to implement the plan.
First of all I inserted:
this
With ListBox1
.Top = 18
.Font.Size = 10
.IntegralHeight = False
and this
.Height = .Height + .Font.Size + 2
.IntegralHeight = True
End With
and I linked .Height with .Font.Size as you suggested. As long as there was no need to assign absolute values to the height, there was no need to have a Select Case statement in my code.
Moreover I realized that there was no need to change the height every time an item was added but only at the end of the process, so I took it out of the loop.
Finally I added a piece of code that would make the list invisible when Textbox1 was empty. The code is now like this:
Final Userform code:
Option Compare Text
Option Explicit
Private bsdel As Boolean 'indicates if backspace or delete keys have been hit.
Private Sub ListBox1_Click()
Dim cell As Range
Dim maxRow As Integer
With ThisWorkbook.Worksheets(1)
maxRow = .Cells(.Rows.Count, 2).End(xlUp).Row
Set cell = .Range("A1:A" & maxRow).Find(UserForm11.ListBox1.Text, LookIn:=xlValues, lookat:=xlWhole)
If Not cell Is Nothing Then
cell.Select
'do other stuff also.
End If
End With
End Sub
Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
bsdel = False
If KeyCode = 8 Or KeyCode = 46 Then _
bsdel = True
End Sub
Private Sub TextBox1_Change()
Dim srchWord As String, firstAddress As String
Dim srchRng As Range, cell As Range
Dim maxRow As Integer
ListBox1.Clear
ListBox1.Visible = True
If bsdel And TextBox1.Value = "" Then
ListBox1.Visible = False
Me.Height = 130
Else
With ThisWorkbook.Worksheets(1)
maxRow = .Cells(.Rows.Count, 2).End(xlUp).Row
Set srchRng = .Range("A1:A" & maxRow)
End With
srchWord = TextBox1.Value
Set cell = srchRng.Find(srchWord, LookIn:=xlValues, lookat:=xlPart)
With ListBox1
'.Top = 84 'test made: deleting this made no difference.
'.Font.Size = 10 'test made: deleting this made no difference.
.IntegralHeight = False
If Not cell Is Nothing Then
firstAddress = cell.Address
Do
If Not cell.Value Like "*(*" Then 'this range includes notes within parenthesis and I didn't need them.
.AddItem (cell.Value)
End If
Set cell = srchRng.FindNext(cell)
Loop While Not cell.Address = firstAddress
If .ListCount < 21 Then 'the size is adjusted.
.Height = (.Font.Size + 2) * .ListCount
Else 'the size stays fixed at maximum.
.Height = (.Font.Size + 2) * 20
End If
End If
Me.Height = .Height + 130
.Height = .Height + .Font.Size + 2
.IntegralHeight = True
End With
End If
bsdel = False
End Sub
Private Sub UserForm_Activate()
TextBox1.SetFocus
End Sub
Private Sub UserForm_Initialize()
ListBox1.Visible = False
End Sub

Skip first worksheet of the workbook in VBA

I want to have a number of worksheet on each of worksheets in a workbook skipping the first one and do some formatting as well, however i want this vba code to skip the first worksheet (name can differ but always is going to be first). Thus the question is how should i do that?
Sub ex2()
Dim kl As Worksheet
Dim Ws_Count As Integer
Dim a As Integer
Ws_Count = ActiveWorkbook.Worksheets.Count
For a = 2 To Ws_Count
With Rows("2:2")
.RowHeight = 20
.Interior.Color = RGB(150, 250, 230)
End With
With Range("B2")
.Value = "Sheet Number" & " " & a
.Font.Size = 12
.Font.Bold = True
.Font.Underline = True
End With
Next a
End Sub
Your code was good, you were only missing a single line, checking the current sheet kl.Index.
Code
Option Explicit
Sub ex2()
Dim kl As Worksheet
For Each kl In Worksheets
' not the first worksheet
If kl.Index > 1 Then
With kl.rows("2:2")
.RowHeight = 20
.Interior.Color = RGB(150, 250, 230)
End With
With kl.Range("B2")
.Value = "Sheet Number" & " " & kl.Index - 1
.Font.Size = 12
.Font.Bold = True
.Font.Underline = True
End With
End If
Next kl
End Sub
Try this:
Sub ex2()
Dim Ws_Count As Integer
Dim a As Integer
Ws_Count = ActiveWorkbook.Worksheets.Count
For a = 2 To Ws_Count
With Worksheets(a)
'rest of your code
End With
Next a
End Sub
With the posted code, the end result would be:
Sub ex2()
Dim Ws_Count As Integer
Dim a As Integer
Ws_Count = ActiveWorkbook.Worksheets.Count
For a = 2 To Ws_Count
With Worksheets(a)
Worksheets(a).Activate
With Rows("2:2")
.RowHeight = 20
.Interior.Color = RGB(150, 250, 230)
End With
With Range("B2")
.Value = "Sheet Number" & " " & worksheets(a).Index - 1
.Font.Size = 12
.Font.Bold = True
.Font.Underline = True
End With
Next a
End Sub
you were almost there since you only missed worksheet specification
you could either add a either add a Worksheets(a).Activate statement right after For a = 2 To Ws_Count one or, which is much better, wrap your formatting code in a With Worksheets(a) ... End With block, adding dots (.) before every range reference and have them refer to the currently referenced worksheet, as follows
Sub ex2()
Dim a As Integer
For a = 2 To Worksheets.Count
With Worksheets(a) '<--| reference current index worksheet
With .Rows("2:2") '<--| reference current worksheet row 2
.RowHeight = 20
.Interior.Color = RGB(150, 250, 230)
End With
With .Range("B2") '<--| reference current worksheet cell "B2"
.Value = "Sheet Number" & " " & a
.Font.Size = 12
.Font.Bold = True
.Font.Underline = True
End With
End With
Next a
End Sub
So, no need for any If statement that would have worked only once: although it wouldn't affect performance significantly in this case it would be very inefficient from a purely coding point of view
Loop through your worksheets like this, and check the index property (which stores the worksheets location) to make sure it's not the first.
Public Sub test()
For Each ws In Worksheets
If ws.Index > 1 Then
'Formatting goes here
End If
Next
End Sub

Resources