Excel convert URL to images (1004) - vba

I have an excel document linked to an SQL database which contains several columns of image URLs.
One of those URLs looks like this:
https://imissit.blob.core.windows.net/iris/596480cf967e0c990c37fba3725ada0c/814040e2-0ccb-4b05-bdb3-d9dc9cc798d9/texture.png
I found different approaches and methods on how to convert those URLs to images ( e.g.
Excel VBA Insert Images From Image Name in Column and https://superuser.com/questions/940861/how-can-i-display-a-url-as-an-image-in-an-excel-cell) within the excel document using macros. I tried those approaches but none of them works for my kind of URL. I tried other URLs (random images on the web, http and https and for those images it WORKS).
This is one of the snippets I tried, which works for other images:
Sub InstallPictures()
Dim i As Long, v As String
For i = 2 To 2
v = Cells(i, "O").Value
If v = "" Then Exit Sub
With ActiveSheet.Pictures
.Insert (v)
End With
Next i
End Sub
Anyway when trying it with my URL I get a runtime error 1004: Insert method of picture object cannot be executed(translated). Different approaches result in slightly different runtime errors (although 1004 is consistent).
Here are some image URLs I tried that work:
https://docs.oracle.com/cd/E21454_01/html/821-2584/figures/HTTPS_Collab_Sample.png
http://www.w3schools.com/css/paris.jpg
https://scontent.ftxl1-1.fna.fbcdn.net/v/t1.0-9/13043727_278733959131361_2241170037980408109_n.jpg?oh=bec505696c5f66cde0cc3b574a70547c&oe=58CC35C5
What is different from my URL to the others and why those methods don't work? What is the correct approach?

The problem (as far as I can tell) is not your device, but it's on the server that is hosting the image, and is failing to return the document. I'm not sure where Tim's comment above (pertaining to the 206 response code) comes from, but if that's the case, or if the URL is returning some error code, then your VBA would also fail and there is likely nothing you can do to resolve that if the problem is at the host.
I manually enter the URL today and download the file, no problem.
I check the response code and it's returning correctly a 200 (success).
The best you can do at this point is to simply trap the error, and flag it for later review.
In my test, I used some deliberatly bad URL just to ensure error handling is working as expected. These are the only ones that failed for me.
Here's the code I used, modified only slightly from yours and includes an error-handler to add a COMMENT to the cells which URLs return the error. This way you can later review manually and add those images if needed.
Sub InstallPictures()
Dim i As Long
Dim v As String
Dim cl As Range
Dim pic As Shape
Dim errors As New Collection
i = 2
Set cl = Cells(i, 15)
Do While Trim(cl.Value) <> vbNullString
v = Trim(cl.Value)
cl.ClearComments
With ActiveSheet.Pictures
On Error GoTo ErrHandler
Set p = .Insert(Trim(v))
On Error GoTo 0
' I added this code to resize & arrange the pictures
' you can remove it if you don't need it
p.TopLeftCell = cl.Offset(0, -1)
p.Top = cl.Offset(0, -1).Top
p.Left = cl.Offset(0, -1).Left
p.Height = Cells(i, 15).Height
p.Width = Cells(1, 15).Width
'''''''''''''''''''''''''''''
End With
NextCell:
i = i + 1
Set cl = Cells(i, 15)
Loop
If errors.Count > 0 Then
MsgBox "There were errors, please review the comments as some files may need to be manually downloaded"
End If
Exit Sub
ErrHandler:
Call ErrorNote(v, cl, errors)
Resume NextCell
End Sub
Private Sub ErrorNote(url$, cl As Range, ByRef errs As Collection)
' Adds an item to the errs collection and flags the offending
' cell with a Comment indicating the error occurred.
On Error Resume Next
errs.Add (url)
With cl
.ClearComments
.AddComment ("Error with URL: " & vbCrLf & url)
End With
End Sub

Related

Resume to a label not working as expected

I am having an issue using Resume and although I have found another solution by using an On Error GoTo, I am still confused as to why the code below doesn't work. The initial error occurs because the sheet name "Sheet_1" is already taken. This means that, in the watch window, err.number has a value of 1004 just before the Resume NameAgain is executed. Rather than clear the error and jump back up to the label, an error 20 occurs(resume without error), and the code moves to the End If line.
Given that there is an active error 1004, I can't understand why it acts as though there isn't an error. I have searched the site for Error 20 issues but nothing really resolved this for me or made me understand the logic behind it. Any help is much appreciated.
Sub ErrorTest()
Dim i as integer:i=1
NameAgain: On Error Resume Next
Worksheets("Main").Name = "Sheet_" & i
If Err.Number = 1004 Then
i = i + 1
Resume NameAgain
End If
End Sub
Update after paxdiablo comment:
The above was a poor attempt at trying to replicate but simplify a problem I was having. The section of code I am working with is below:
Activate CheckBook to use ActiveWindow
CheckBook.Activate
Set DestSheet = CheckBook.Worksheets.Add(After:=CheckBook.Sheets(1))
On Error Resume Next
v = 1
NameAgain: DestSheet.Name = ExpBookName & "_" & v
If Err.Number = 1004 Then
v = v + 1
Resume NameAgain
End If
On Error GoTo 0
ActiveWindow.DisplayGridlines = False
Set DestCell = DestSheet.Range("A2")
So the solution is to move the On Error Resume Next to the label line and use GoTo in place of Resume.
The resume statement is a means to, from within an error handler, go back to some point in your main (non-error-handling) code and resume execution.
In this case, you've explicitly stated you want to automatically resume next in the event of an error.
This is functionally equivalent to (VB-like pseudo-code):
line:
on error goto handler
cause error
resume line ' not in an error handler at this point '
handler:
resume next
So you're not technically in an error handler at the point where you try to resume to the label.
The right statement for what you're trying to do would be a simple goto rather than resume.
A more correct solution is to write code that does not deliberately generate errors or which does not use Goto.
Public Function GetNextSheetName(ByVal ipWb As Excel.Workbook, ByVal ipStemName As String) As String
Dim mySheet As Variant
Dim mySD As Scripting.Dictionary
Set mySD = New Scripting.Dictionary
For Each mySheet In ipWb.Sheets
' mySd.Count is a dummy entry to satisfy
' the Key and Item requirements for .Add.
' we are only interested in the Keys
' for use with the .Exists method later
mySD.Add mySheet.Name, mySD.Count
Next
Do
DoEvents
Dim myIndex As Long
myIndex = myIndex + 1
Dim myNextSheetName As String
myNextSheetName = ipStemName + "_" & CStr(myIndex)
Loop While mySD.Exists(myNextSheetName)
GetNextSheetName = myNextSheetName
End Function
Which now allows
Set DestSheet = checkbook.Worksheets.Add(After:=checkbook.Sheets(1))
DestSheet.Name = GetNextSheetName(checkbook, ExpBookName)
ActiveWindow.DisplayGridlines = False
Set DestCell = DestSheet.Range("A2")

.find() triggers run-time error 91 even though all variables are set VBA possibly due to bad references

I am writing code to create a template. This code populates a tab named "fullDistribution" from user-input on different tabs in the same wb. I have a working section of code that I wrote in a separate module (for testing) away from my master module. The code runs properly and executes completely when it is separate. When I pasted this section of code into my master module and ran it, I began receiving "Run-time error 91: object variable or with block variable not set" at the start of the newly-pasted code. I am not using any with blocks, and all of my variables are set. I made no changes in my code when I transferred it to my master module, and I carried over the new variables I created.
This is the selection of code that I wrote in a separate module:
Worksheets("bls2016").Activate
tcount = WorksheetFunction.CountA(Worksheets("detailedEntity").Range("D2:D" & Cells(Rows.Count, "D").End(xlUp).Row))
acount = WorksheetFunction.CountA(Worksheets("detailedEntity").Range("K2:K7"))
Application.ScreenUpdating = False
Dim h As Integer
Dim f As Integer
Dim blstate As Range
Dim bl As Range
Dim state As Range
Dim deat As Range
Dim agje As Range
Dim e As Integer
Dim r As Integer
Dim ii As Integer
Set blstate = Worksheets("bls2016").Range("D2:D" & Cells(Rows.Count, "D").End(xlUp).Row)
Set state = Worksheets("detailedEntity").Range("Q1")
Set deat = Worksheets("detailedEntity").Range("D2:D" & Cells(Rows.Count, "D").End(xlUp).Row)
Set agje = Worksheets("detailedEntity").Range("L2:M" & Cells(Rows.Count, "M").End(xlUp).Row)
h = Activecolumn
f = Activerow
r = 2
x = 120
For e = 1 To (acount * acount)
blstate.Find(state).Select
For ii = 1 To x
'ccnt = acst.Offset(0, 1)
ccgv = ActiveCell.Offset(0, 2)
acem = ActiveCell.Offset(0, 5)
Do While True
vl1 = Application.IfNa(Application.VLookup(Worksheets("fullDistribution").Cells(r, 2), deat, 1, False), 0)
If vl1 = 0 Then
Worksheets("fullDistribution").Cells(r, 4) = 0
Else:
vl2 = Application.IfNa(Application.VLookup(Worksheets("fullDistribution").Cells(r, 1), agje, 2, False), 0)
If ActiveCell.Offset(0, 1).Value = "Unknown Or Undefined" Then
Exit Do
Else:
If vl2 = ccgv Then
Worksheets("fullDistribution").Cells(r, 4) = acem
ElseIf vl2 <> ccgv Then
Worksheets("fullDistribution").Cells(r, 4) = ActiveCell.Offset(x + 1, 5)
Else:
End If
End If
End If
Exit Do
Loop
ActiveCell.Offset(f + 1, h).Select
r = r + 1
Next ii
Next e
The error triggers at the line "blstate.find(state).select" which tells excel to look in a dynamic range that contains the names of states and select the first instance of the state to use as the Activecell. Again, this works when it's run outside of the main module.
I believe this has something to do with a reference area. When this runs alone and finishes, I have to have a specific worksheet activated for it to run properly. If my excel workbook is open to a different tab, it will not run. My main module too only executes properly if it is run on a specific worksheet/tab.
If need be, I can edit my post and provide my whole master code.
It may be a problem of not fully referencing sheets, eg amend your blstate line to
with Worksheets("bls2016")
Set blstate = .Range("D2:D" & .Cells(.Rows.Count, "D").End(xlUp).Row)
end with
Then it might find the value and not error. You should look up how to use the Find method as your way is destined to cause you headaches.
blstate.Find(state).Select
Your code assumes that .Find finds what it's looking for. When Find doesn't find what it's looking for, the function returns Nothing, which is essentially a null object reference - and you can't make member calls on Nothing without getting run-time error 91.
Split it up:
Dim result As Range
Set result = blstate.Find(state)
If Not result Is Nothing Then
result.Select 'questionable anyway, but that's another issue
Else
MsgBox "Value '" & state & "' was not found in " & blstate.Address(External:=True) & "."
Exit Sub
End If
As for why it's not finding what you're looking for, Tim Williams already answered that:
Find recalls all settings used in the last call (even if you use the GUI to perform the Find), so make sure you specify the settings you want when you call it via VBA. If you don't do that, it may not work as you expect.... – Tim Williams 42 mins ago
My issue was very much related to incorrect referencing, however, I was able to resolve this issue by keeping the specific piece of code I was testing in a separate sub, and calling it from my main code, 'full distribution'.
Call test
'test' is the name of the sub with the tested code. This is a temporary fix to the solution, and if anyone struggles with referencing, try this.

Reading a barcode in excel to see if there is a match

I am working with Excel 2016. I have a smidgen of experience with VBA for applications, and some experience with programming.
I'm trying to take input from a barcode scanner, compare it to a column in a spreadsheet, and if there's a match, put a few characters and a date stamp in some cells (Initials and date, each in separate columns).
This question has a very similar use-case, and includes a code sample. I have tried the code sample and can't get it to work. At first, there was a problem with the array. Eventually I figured out you could do "C2:C8" and that seemed to work, though that's not documented anywhere (Probably part of a basics course/class, but not findable). There was an error about sub or function defined for Match(), so I enabled the Solver Add-in in the security center. That didn't fix it, so I found this forum post that explained Match wasn't a VBA function.
Now, I get an error after clicking the button "Run time error 1004, unable to get Match property of the WorksheetFunction class", clicking debug takes me to the same line.
Here is the code I have wound up with:
Private Sub CommandButton1_Click()
code = InputBox("Please scan a barcode and hit enter if you need to")
matchedCell = Application.WorksheetFunction.Match(code, Range("C2:C8"), 0)
matchedCell.Offset(0, 2) = Now
End Sub
This is incredibly frustrating because I thought this was a simple thing and already solved. Instead of working to solve the problem and build software, it seems I'm fighting syntax and/or the environment. What am I doing wrong?
two possibilities:
use Match() function of Application object
and store its returned value in a Variant variable to be checked for any error (if value not found)
Private Sub CommandButton1_Click()
Dim code As Variant
Dim matchedCell As Variant
code = InputBox("Please scan a barcode and hit enter if you need to")
matchedCell = Application.Match(code, Range("C2:C8"), 0)
If Not IsError(matchedCell) Then Range("C2:C8").Cells(matchedCell, 1).Offset(0, 2).Value = Now
End Sub
use Find() function of Range object
Private Sub CommandButton1_Click()
Dim code As Variant
Dim matchedCell As Range
code = InputBox("Please scan a barcode and hit enter if you need to")
Set matchedCell = Range("C2:C8").Find(what:=code, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=True)
If Not matchedCell Is Nothing Then matchedCell.Offset(0, 2).Value = Now
End Sub
Use Application.Match , and continue running your code only if there is a successful Match.
Option Explicit
Private Sub CommandButton1_Click()
Dim MatchRow As Variant
Dim code As Variant
Dim matchedCell As Range
code = InputBox("Please scan a barcode and hit enter if you need to")
' verify that there is a successful match in the searched range
If Not IsError(Application.Match(code, Range("C2:C8"), 0)) Then
MatchRow = Application.Match(code, Range("C2:C8"), 0) '<-- get the row number
Set matchedCell = Range("C" & MatchRow + 1) '<-- set the range (add 1 row since you are starting from row 2)
matchedCell.Offset(0, 2).Value = Now
'option 2: without setting the range
Range("C" & MatchRow).Offset(1, 2).Value = Now
End If
End Sub

Error runtime 91 and 429

This code feels like Schrodinger is executing it. If I open the project and run the code, I won't get any errors at all. If I view the code to edit or add anything, the first time I run the code, I get 'Run-time error 91'. If I try to run it a second time, making no changes, I get 'Run-time error 429' (ActiveX component can't create object).
What I'm trying to achieve is to find the row (BuildSel) in a range on worksheet (Ref) that has the same value as what's selected in a list on a userform (BuildList). Then once the row is found, to take data from that row and columns A and B, and put them in textbox's on my userform. Is my code right and ActiveX making the error? I apologize for terrible coding too.
EDIT: The listbox is on a multipage on my userform. I first noticed the issue today when I tried adding another listbox on a different page.
Private Sub BuildList_Click()
Dim Ref As Worksheet, BuildSel As Long
Set Ref = ThisWorkbook.Sheets("Ref")
BuildSel = Ref.Range("B2", Ref.Range("B" & Rows.Count).End(xlUp)).Find(BuildList.Value, lookat:=xlPart).Row
BuilderText.Value = Ref.Range("A" & BuildSel).Value
CompNameText.Value = Ref.Range("B" & BuildSel).Value
End Sub
Not sure why altering 'BuildSel' to variant makes it work, but the code as it stands has no error checking for when there is no matching list item to be found
The following code should be better suited for usage:
Private Sub BuildList_Click()
Dim Ref As Worksheet: Set Ref = ThisWorkbook.Sheets("Ref")
Dim BuildSel As Range
With Ref
Set BuildSel = .Range("B2", .Range("B" & Rows.Count).End(xlUp)).Find _
(BuildList.Value, lookat:=xlPart)
If Not BuildSel Is Nothing Then
BuilderText.Value = .Range("A" & BuildSel.Row).Value
CompNameText.Value = .Range("B" & BuildSel.Row).Value
Else
BuilderText.Value = ""
CompNameText.Value = ""
End If
End With
End Sub

On Error works in first and doesn't work in second instance. Bug?

I have a very strange problem here. Here's the code:
reqLang = "ENG"
Select Case reqLang
Case "CRO", "ENG"
'first loop -------------------------------------
On Error GoTo reqLangVisible
i = 1
'Loop until ccCROENG's are all hidden and then go to reqLangVisible.
Do
ActiveDocument.SelectContentControlsByTag("ccCROENG")(i) _
.Range.Font.Hidden = True 'hides all CCs
i = i + 1
Loop
reqLangVisible:
'second loop -------------------------------------
On Error GoTo langOut
i = 1
'Loop until ccreqLang's are all visible and then go to langOut.
Do
ActiveDocument.SelectContentControlsByTitle("cc" & reqLang)(i) _
.Range.Font.Hidden = False 'activates reqLang CCs
i = i + 1
Loop ' CAN'T GET OUT -----------------------------------
Case "CROENG"
i = 1
'Loop until ccCROENG's are all visible and then go to langOut.
Do
On Error GoTo langOut
ActiveDocument.SelectContentControlsByTag("ccCROENG")(i) _
.Range.Font.Hidden = False 'Shows all CCs
i = i + 1
Loop
End Select
langOut:
MsgBox "Success!" '------- yeah, not really.
Stop
I hope it's clear enough what it's trying to do (at least programming-wise). I have multiple ContentControls(CCs) with same titles and tags. The problem I end up with is marked with CAN'T GET OUT, because, you guessed it - I can't get of this second loop! I end up with the Out of range error because it runs out of CCs.
What's even weirder is that it actually did get out of the first loop which has the exact same On Error statement, thought pointing to a different section.
Is it me, or did I just - however unlikely - run onto a bug in VBA?
In any case, is there a solution or at least a workaround?
Typically you only use error handling for dealing with unexpected or unpredictable situations, such as not being able to access a drive, or finding you have no network access.
Error handling is not intended as a substitute for reasonable checks which could otherwise be done. i.e. collections have a Count property which you can use when looping over their items, so avoiding any error caused by trying to access Item(n+1) when there are only n items (and here you know n from Count). Alternatively, use a For Each loop.
Here's some sample code demonstrating use of two methods for looping over your controls:
Sub Tester()
Dim cc1 As ContentControls, cc2 As ContentControls
Dim c, i As Long
With ActiveDocument
Set cc1 = .SelectContentControlsByTag("tbTag")
Set cc2 = .SelectContentControlsByTitle("tbTitle")
End With
Debug.Print "cc1 has " & cc1.Count
Debug.Print "cc2 has " & cc2.Count
'use the Count property
For i = 1 To cc1.Count
Set c = cc1(i)
c.Range.Font.Hidden = True
Next i
'use a For Each loop
For Each c In cc2
c.Range.Font.Hidden = False
Next c
End Sub
This is the type of scenario for which this type of flow control is designed.
Applied to your original code:
Sub Tester2()
Dim reqLang, cc As ContentControls, c
reqLang = "ENG"
Select Case reqLang
Case "CRO", "ENG"
Set cc = ActiveDocument.SelectContentControlsByTag("ccCROENG")
SetTextHidden cc, True
Set cc = ActiveDocument.SelectContentControlsByTitle("cc" & reqLang)
SetTextHidden cc, False
Case "CROENG"
Set cc = ActiveDocument.SelectContentControlsByTag("ccCROENG")
SetTextHidden cc, False
End Select
MsgBox "Success!" '-- yeah really
End Sub
Sub SetTextHidden(cc As ContentControls, MakeHidden As Boolean)
Dim c
For Each c In cc
c.Range.Font.Hidden = MakeHidden
Next c
End Sub
So if you've read my comment, and to formally answer your question, it is not a bug.
You just need to use Error Handling Routines correctly.
What you're trying to do is somewhat like below. HTH.
Select Case reqlang
Case "CRO", "ENG"
On Error Resume Next '~~> ignores the error when encountered
'~~> Your loop which possibly creates the error goes here
On Error Goto 0 '~~> resets the actively triggered error handling
Case "CROENG"
On Error Resume Next '~~> ignores the error when encountered
'~~> Your loop which possibly creates the error goes here
On Error Goto 0 '~~> resets the actively triggered error handling
End Select
MsgBox "Success"
But as the link suggest, you need to handle errors and not simply disregard them. Try cheking on the actual error and find a way to correct it or avoid it.
You'll be surprise that you won't even be needing the Error Handling Routine.