My code pastes a shape onto Sheet1 and tries to resize the width, getting an error on this line below.
Worksheets("Global Quarterly").Shapes(Picture1).Width = 1607.76
GlobalFile.Activate
Sheets("Sheet1").Select
ActiveSheet.Paste Destination:=Range("B25")
Selection.Name = "Picture1"
Worksheets("Global Quarterly").Shapes(Picture1).Width = 1607.76
Range("A1").Select
Application.ScreenUpdating = False
Option Explicit is likely missing, making Picture1 an undeclared variable, so at run-time you're passing Variant/Empty to the Worksheet.Shapes collection's Item property getter - and that's neither an index nor the name of a shape, so you get a... well an arguably confusing run-time error: a more instinctive error to throw would have been error 5 here, for "invalid parameter or procedure call" - it's the Shapes.Item member (implicit) that's throwing; execution never even gets to the Shape.Width member call.
Specify Option Explicit at the top of every module; that will make your code uncompilable until all variables are declared, which helps prevent typos and forces a minimal rigor in variable usage.
If the name of the shape is "Picture1" (note the string delimiters / double-quotes), then you need to do as Lodi hinted:
Worksheets("Global Quarterly").Shapes("Picture1").Width = 1607.76
Related
I am getting an error while running this code, cannot understand what's wrong with this:
i = ActiveCell.End(xlUp).Offset(0, 1).Value
ActiveCell.Offset(0, 1).Value = Application.WorksheetFunction.VLookup(r, source, WorksheetFunction.Match("i", msource, 0), 0)
Application.WorksheetFunction invokes worksheet functions, but in the context of VBA code: normally in VBA, when a function throws an error, it comes in the form of a run-time error, and that's exactly what these functions do.
So you have two options.
One, handle (or swallow) VBA runtime errors:
On Error Resume Next
ActiveCell.Offset(1, 0).Value = Application.WorksheetFunction.VLookup(...)
On Error GoTo 0
Like this, when the right-hand side of the assignment throws an error (i.e. when the lookup fails), the left-hand side won't be affected and the target cell keeps whatever value it had before. Note the On Error GoTo 0, which restores runtime errors. This is critically important; without it On Error Resume Next will have your code running in some unhandled error state, and that is the single best way to hide bugs and make them pretty much impossible to diagnose later.
Two, use the late-bound version.
When invoked directly against Application, worksheet functions are late-bound. You don't get compiler assistance so watch out for any typos and make sure the parameters are good (you don't get a tooltip with a parameters list for late-bound member calls).
ActiveCell.Offset(1, 0).Value = Application.VLookup(...)
It's the same VLookup function, but now it behaves like it would on a worksheet, returning an error value instead of throwing a run-time error like a VBA function would. That makes the target cell value hold a #N/A worksheet error when the lookup fails.
Same applies to the inner Match function; now while nesting worksheet functions is how we do things in a worksheet cell's formula, doing that in code makes everything much harder than necessary - split things up, evaluate the Match separately, validate whether it returned an error value, then pass it to VLookup only if it didn't.
Dim matchResult As Variant
matchResult = Application.Match(i, msource, 0)
If IsError(matchResult) Then Exit Sub
Note that your code is passing the literal string value "i" to the Match function; you probably intend to pass the value of the i variable: you must remove the double quotes around it to do that.
I am trying to create multiple instances of the same modeless UserForm in excel-VBA, with parameters, through a Sub.
I can make it work with two of the three parameters I want to assign, but the third one keeps returning me
"Run-time Error '91': Object variable or With block variable not set"
and I can't figure out why.
It may be an obvious typo that I didn't see, but I really can't point out the problem.
Here is my code:
Sub AskToClose(targetWksht As Worksheet, targetRow As Integer, test As String)
Dim newInstanceOfMe As Object
Set newInstanceOfMe = UserForms.Add("MOVE_TO_CLOSED") 'MOVE_TO_CLOSED is the name of my UserForm
newInstanceOfMe.targetWksht = targetWksht 'If I comment this line it works just fine, otherwise Run-time error 91
newInstanceOfMe.targetRow = targetRow
newInstanceOfMe.test = test
newInstanceOfMe.Show vbModeless
End Sub
_____________________________________________________________________
Sub test()
Dim openWksht As Worksheet
Set openWksht = Worksheets("OPEN WO") 'The worksheet exists and works just fine everywhere else
Call AskToClose(openWksht, 2, "test 2")
Call AskToClose(openWksht, 3, "test 3")
Call AskToClose(openWksht, 4, "test 4")
Set openWksht = Nothing 'I tried to comment this line just in case, same result...
End Sub
_____________________________________________________________________
'My MOVE_TO_CLOSED UserForm parameters
Public targetWksht As Worksheet
Public targetRow As Integer
Public test As String
newInstanceOfMe.targetWksht = targetWksht
This statement produces an error-level code quality inspection result for Value Required inspection in Rubberduck (an open-source VBIDE add-in project I manage). The inspection explains the situation as follows:
Object used where a value is required
The VBA compiler does not raise an error if an object is used in a place that requires a value type and the object's declared type does not have a suitable default member. Under almost all circumstances, this leads to a run-time error 91 'Object or With block variable not set' or 438 'Object doesn't support this property or method' depending on whether the object has the value 'Nothing' or not, which is harder to detect and indicates a bug.
There are two types of assignments in VBA: value assignment (Let), and reference assignment (Set). The Let keyword is redundant/optional/obsolete for value assignments:
Dim foo As Long
foo = 2 + 2
Let foo = 2 + 2 '<~ exactly equivalent to the above
So unless the Set keyword is present, VBA attempts to make a value assignment. If the object has a default member, that might just work - the VBA specs define how let-coercion mechanisms make that happen. That's how you can assign a Range to a value:
Sheet1.Range("A1") = 42
That's implicitly assigning to Range.Value, via an implicit member call to Range.[_Default], a hidden property of the Range class.
If the right-hand side of the assignment was also an object, then let-coercion would be happening on both sides of the operator:
Sheet1.Range("A1") = Sheet1.Range("B1") '<~ implicit default member calls galore!
Sheet1.Range("A1").Value = Sheet1.Range("B1").Value
But we're not looking at a Range here, we're looking at a UserForm, and a UserForm does not have a default member, so let-coercion can't happen... but the compiler won't validate that, so the code gets to run anyway... and blows up at run-time instead.
So, we're looking at a Let assignment with both sides holding an object reference, for a class type that doesn't define a default member.
Something.SomeObject = someOtherObject
But VBA doesn't care that there's no default member - because there's no Set keyword, it tries as hard as it can to do what you told it to do, and coerce these objects into values... and fails, obviously.
If Something.SomeObject (left-hand side) is Nothing, then the let-coercion attempt will try to invoke the inexistent default member -- but since the object reference is Nothing, the call is invalid, and error 91 is raised.
If Something.SomeObject is already holding a valid object reference, then the let-coercion attempt will go one step further, and fail with error 438 because there's no default member to invoke.
If Something.SomeObject has a default member (and the reference isn't Nothing), then the value assignment succeeds, no error is raised... but no object reference was assigned, and this might be a subtle bug!
Adding a Set keyword makes the assignment a reference assignment, and now everything works fine.
The following code works fine on latest Excel Windows and also on Excel 16.28 on Mac. But on the latest Excel for Mac (16.29 and 16.30) it generates this error: "Compile error: Method or data member not found" on the code line MyShape.Select.
I assume that there is an alternative way to do what I want that the compiler will approve of, but I don't know what it would be. As an alternative, I tried not selecting the shape and just referring to it, but then I get the same error but on the With MyShape.ShapeRange.Fill line.
Dim MyShape As Shape
'Other stuff
Set MyShape = ActiveSheet.Shapes.AddShape(msoShapeRectangle, 400, 400, DistanceBetweenCells, LineWidth)
MyShape.Select
With Selection.ShapeRange.Fill
'stuff here
End With
I'm hoping that a newer version of Mac Excel, when released, will revert to the older version in allowing the above, but assuming that's not the case, any workarounds?
I like that you're explicitly referring to ActiveSheet, kudos!
The problem is that ActiveSheet is an Object, wich means the compiler is helpless: ActiveSheet.Shapes compiles, but so will ActiveSheet.Shapess - even with Option Explicit specified. The entire expression is evaluated at run-time.
Let's fix that first:
Dim sheet As Worksheet
Set sheet = ActiveSheet
Now sheet.Shapes gets intellisense and compile-time validation, along with subsequent the .AddShape member call. You even get parameter tooltips as you type up the argument list!
What happens next is interesting: you declared MyShape as a Shape, but it's not a Shape you're looking at - the Shape class doesn't have a ShapeRange property, so... where does MyShape.ShapeRange come from then?
If you break execution (F9 to set a breakpoint) after the MyShape.Select call, and then bring up the immediate pane (Ctrl+G), the answer appears:
?typename(selection)
Rectangle
If you press Shift+F2 on the word Rectangle...
Dim myRectangle As Excel.Rectangle '<~ here
...the VBE doesn't seem to figure it out ("identifier under cursor is not recognized"). So we press F2, then right-click somewhere and tick the "Show hidden members" option - and sure enough, there it is:
So your code says "let's use the Shape interface", but works with a Rectangle object. And since that works, it means a Rectangle "is a" Shape: the two interfaces simply describe the same object through different lens, so either works... but then Shape.ShapeRange doesn't look quite right, since the Shape class doesn't define that member and that's the interface we explicitly said we were going to be working with.
If we want to invoke the members of Rectangle, we can - and since we're now showing hidden members in the object browser, intellisense displays the hidden types and members too. If the entire With block is early-bound, everything makes much more sense:
With myRectangle.ShapeRange.Fill
...and explains how the late-bound code off ActiveSheet would work at run-time to resolve the member call, and now the compiler needs a completely other strategy to compile the VBA code: maybe that could shake things up enough to get it to work, maybe it won't. At least the type ambiguities and ignored-by-compiler statements are all gone :)
The thing that's surprising here, is that you can't do that with VBA user code. If you made a MyShape class with a DoSomething method:
'#ModuleDescription "A metaphorical Shape"
Option Explicit
Public Sub DoSomething()
MsgBox TypeName(Me)
End Sub
And then a MyRectangle class that implements MyShape and exposes a member on its own public interface, that yields a MyShape object reference:
'#ModuleDescription "A metaphorical Rectangle"
Option Explicit
Private sh As MyShape
Implements MyShape
Public Property Get Thing() As Object
Set Thing = sh
End Property
Private Sub Class_Initialize()
Set sh = New MyShape
End Sub
Private Sub MyShape_DoSomething()
MsgBox TypeName(Me)
End Sub
And now in any standard module, we can test this - first, all early-bound, and we'll have a factory method that returns a MyShape, to mimick Shapes.CreateShape:
Public Sub WorksMaybe()
Dim r As MyShape
Set r = CreateRect
r.Thing.DoSomething
End Sub
Private Function CreateRect() As MyShape
Set CreateRect = New MyRectangle
End Function
So we run this (on Windows), and I expected, the code doesn't compile:
Late binding however...
Public Sub WorksMaybe()
Dim r As Object
Set r = CreateRect
r.Thing.DoSomething
End Sub
Private Function CreateRect() As MyShape
Set CreateRect = New MyRectangle
End Function
...works? Nope:
Are we not looking at a MyRectangle object? No: we're looking at the limits of late-binding polymorphism in VBA - we created a New MyRectangle, but to the compiler CreateRect returns a MyShape object reference. If we place a breakpoint on End Function, run it, and then type ?TypeName(CreateRect) in the immediate pane (Ctrl+G) when the breakpoint is hit, then despite the declared type being MyShape, the runtime type is clearly MyRectangle.
And it should work - but it doesn't. Error 438, member not found: the late-bound/run-time equivalent of the "method or data member not found" compile error.
And if we use the interface we really mean to work with...
Public Sub WorksMaybe()
Dim r As MyRectangle
Set r = CreateRect
r.Thing.DoSomething
End Sub
Private Function CreateRect() As MyShape
Set CreateRect = New MyRectangle
End Function
...then everything "just works":
Now, I'm not running this on a Mac, but this code compiles for me...
Option Explicit
Const DistanceBetweenCells As Long = 50
Const LineWidth As Long = 2
Public Sub WorksMaybe()
Dim r As Excel.Rectangle
Set r = CreateRect
r.ShapeRange.Fill.BackColor.RGB = vbRed
End Sub
Private Function CreateRect() As Excel.Shape
Set CreateRect = Shapes.AddShape(msoShapeRectangle, 40, 40, DistanceBetweenCells, LineWidth)
End Function
...and systematically raises run-time error 13 as soon as CreateRect returns and the Shape reference gets assigned to a Rectangle - error 13 being "type mismatch". In other words, a Rectangle is not a Shape (!!?!??). Proof, if we make CreateRect return a Excel.Rectangle, we now get the type mismatch error as soon as we try to assign the function's return value, and nothing makes sense anymore: there's something weird going on, and, well, I'm out of ideas - there doesn't appear to be any way to work early-bound with a Rectangle, despite what TypeName(Selection) claims the type is (the class is hidden/undocumented for a reason after all!), which... pretty much destroys all hope, especially if neither With Selection.Fill nor With MyShape.Fill work (it does work perfectly fine here on my Windows box though).
Sending a frown with some repro code through the user feedback feature should get you heard from the product team at Microsoft. I doubt they removed anything from anywhere - but it's not impossible something broke how interfaces are resolved, somewhere deep down in some seemingly unrelated piece of internal API :)
Ok, I think it is figured out. In Mac Excel version 16.29 Microsoft has deleted certain class members for, at least, Shapes. For instance "Fill" and "Select" are no longer available. So any code that refers to them will generate an error.
I'm not sure how extensive this is or any other ramifications, but I do know that the code works fine in version 16.28, and also that the member list in 16.28 shows both "fill" and "select" but not in 16.29. Thanks to Mathieu Guindon above for the input and also to a poster who deleted his thread - both of these people really helped. I've reported the issue to Microsoft.
Answer from Steve Rindsberg (MVP):
Iterate through the .ShapeRange collection:
For x = 1 To .ShapeRange.Count
With .ShapeRange(x)
'...stuff....
End With
Next
I've had a few similar weird ones on Mac where perfectly good code (on
Windows) errors or sometimes make the Mac app go POOF! And disappear.
Iterating the collections this way has been the fix.
I'm in Access 2007 VBA, trying to return an #ERROR value from a function, as shown in the code below. But I've just discovered the largest number which VBA.CVErr(expression) will accept is 2^15-1, aka an Integer vartype; not a Long.
This seems incomprehensible, since the VBA constant vbObjectError is a Long. Other error functions work with longs; for instance: VBA.Error(vbObjectError) works fine.
In light of this issue, what suggestions are there to properly make use of vbObjectError to return user-defined errors as error objects from user-defined functions..?
Public Sub TesUDE()
Dim v As Variant
v = UDE()
Debug.Print TypeName(v), VBA.CStr(v)
End Sub
Public Function UDE() As Variant
On Error GoTo ErrorHandler
err.Raise 2 ^ 15 - 1 , , "This is a user-defined error." 'Works.
err.Raise 2 ^ 15 , , "This is a user-defined error." 'Overflow.
err.Raise vbObjectError, , "This is a user-defined error." 'It laughed at me.
ErrorHandler:
UDE = VBA.CVErr(err.Number)
End Function
The vbObjectError constant is useful to ensure your custom error numbers never colliding with a "built-in" error number, which makes error handling more robust in a way: it ensures error e.g. #91 consistently means "object reference not set", for example.
It implies custom errors are thrown/raised and handled, though - not returned.
Don't get me wrong: returning an Error-type value does have legitimate uses; like when you're writing a user-defined worksheet function and need Excel to distinguish between e.g. "an invalid reference was provided" (#REF!), "no match was found for the specified value" (#N/A), or "I've no idea what you're talking about" (#NAME?); in the Excel type library each of these errors have a corresponding XlErrXxxxx global constant defined, with an underlying value in the low 2000's.
It's entirely possible there's a similar use case in Access (I'm not all that familiar with Access), meaning the caller receiving the error is the Access query engine, much like the caller of a UDF in Excel is Excel's calculation engine.
Otherwise (i.e. if the caller is other VBA code), returning an error amounts to using the Error type for flow control, and making things return Variant meaning "this function might return a meaningful value of some type, or some error, maybe"... generally makes the code harder to read/follow.
So in use cases where the caller isn't your own VBA code, for error codes you mean to return as Error-type values (which is much cleaner than returning some magic non-zero Long number with the same meaning), you will want to skip the vbObjectError part.
Think of vbObjectError errors as "internal errors" that your VBA code handles, and Error/CVErr errors as "user-facing errors" that your VBA code returns. As come sort of self-inflicted convention =)
If the error you mean to expose is an actual custom VBA error code that you handle elsewhere in your VBA project, you'll want to "map" it to a finite-set of "user-facing" error codes - probably by defining constants, or enums for them:
Private Const ERR_CUSTOM_ERROR_1 = vbObjectError + 42
Public Enum UserFacingError
ErrFooWasNotBarred = &H7E1
ErrSomething
ErrSomethingElse
End Enum
'...
Public Function DoSomething(ByVal foo As Long) As Variant
On Error GoTo ErrHandler
'..."happy path"...
Exit Function
ErrHandler:
Select Case Err.Number
Case 5 'Invalid procedure call/argument
DoSomething = CVErr(ErrFooWasNotBarred)
Case ERR_CUSTOM_ERROR_1
DoSomething = CVErr(ErrSomething)
Case Else
DoSomething = CVErr(ErrSomethingElse)
End Select
End Function
Trying to use a form to create a row that stringes together two values in a concat using a string. Upon clicking the finish button the macro checks several values to determine if it can place in the information. One of these rules I am trying to set is detecting if the string/value already exists.
ID = txtStory.Value & "." & txtTask.Value
If Range("A7:A98").Cells.Find(what:=ID, LookAt:=xlWhole) > 0 Then
MsgBox "Story ID already exists.", vbExclamation, "Duplicate Found"
Exit Sub
End If
The code operates correctly when the information violates the rule. However, if the result is false (the values are not duplicate with anything in the column) then I receive "Run-time error '91': Object variable or With block variable not set"
What needs to be adjusted to fix the issue?
Find returns a Range object reference. When nothing matches the criteria, the function returns Nothing - a null reference.
This is indeed very very close to the linked would-be-duplicate, with the following nuance - this:
Range("...").Cells.Find(...) > 0
Is really doing this:
Range("...").Cells.Find(...).Value > 0
You're implicitly calling into the Range object's default member, which points to its Value.
It's that implicit member call that's throwing runtime error 91, because Find returned Nothing so you have no object to get a value from, to perform the > 0 comparison.
The solution for the error 91 is, as in the linked Q&A, to first verify that Find returns a valid object reference.
The solution to avoid similar bugs in the future and in many other circumstances, is to avoid implicit default member calls - i.e., write code that means what it says and that says what it means.
Set result = Range("...").Find(...)
If Not result Is Nothing Then
'.Find call was successful
If result.Value > 0 Then '<< explicit Range.Value member call
'...
End If
Else
'.Find call failed
End If
Note that the .Cell member call is redundant.