Fundamental Excel VBA Classes & Objects - vba

There is something I am not understanding and I guess it must be pretty basic.
Can someone please be so kind to explain to me the relationship between Classes and Objects enough so that I can understand what is happening below?
Both Location and Name are properties of the PivotTable Class and return strings.
Why do the first statements work but the last 4 give the error
"Object doesn't support this action (Error 445)"?
?ActiveWorkbook.ActiveSheet.PivotTables.Count
3
?ActiveWorkbook.ActiveSheet.PivotTables(1).Name
PivotTable12
?ActiveWorkbook.ActiveSheet.PivotTables(2).Name
PivotTable3
?ActiveWorkbook.ActiveSheet.PivotTables(3).Name
PivotTable2
?ActiveSheet.PivotTables(1).Location
?ActiveSheet.PivotTables(2).Location
?ActiveSheet.PivotTables(3).Location
?ActiveSheet.PivotTables("PivotTable12").Location
[Location Def][1]
[Immediate Window][2]
[Error][3]

Some objects have properties or methods which either aren't available in every version, or in every situation. Seemingly most tasks in VBA can be accomplished in multiple ways; this, it's important to be comfortable with looking for "alternative means of accomplishing the same thing."
In this case my next step would be to check out which properties/methods are available for my object in my scenario by Setting it to a variable:
Dim p As PivotTable
Set p = ActiveSheet.PivotTables(1)
Stop
Run that code, and when execution breaks at the Stop, double click the variable p to select it, right-click it, click Add Watch... and click OK.
This will open the Watches. Use the ⊞ to open and explore the tree to see what's available in this context.
Location likely says "Application Defined error..." however perhaps you can find the location with:
?p.DataBodyRange.Cells.Address
or
?p.DataLabelRange.Cells.Address
...see what the Watches window shows as available within the context you're using.

Related

How do I access the value of a textbox control on a subform where the subform itself is just a control on another subform of the main form

I've just assumed responsibility for a database written ages ago in MS Access and I need to make some changes but unfortunately, even though I used to be a very experienced Access developer some time ago, I haven't really used it to an advanced level for about 15 years and I just can't work out how to do what I need.
Specifically, I have a main form called frmOrders.
On that form I have a subform called frmJobCard.
But frmJobCard is itself a form with a subform called frmSFJCOrders.
So, when someone clicks a particular button on frmOrders, I need to be able to read the value of a textbox on frmSFJCOrders and take action based on that value.
I've so far read through dozens of posts (both on StackOverflow and other techy sources) all of which touch upon different aspects of reading from controls on subforms and I've spent over a day trying to get this right but it never actually works so I'd be grateful if someone could tell me how I just read this value.
I think I need something along the lines of...
If Forms.frmOrders.frmJobCard.Form.frmSFJCOrders.[Estimate QTY] = 0 Then
...but I just can't make it work.
I would expect the code above to evaluate to 0 then take the actions below but it just always fails on the evaluation with
Run-time error '2465':
Application-defined or object-defined error
Any help is greatly appreciated - thanks in advance :)
It should be close to this:
If Me!frmJobCard.Form!frmSFJCOrders.Form![Estimate QTY].Value = 0 Then
Note that frmJobCard and frmSFJCOrders must be names of the subform controls which may differ from the names of the forms.
Consider this Access MVPs resource which I bookmarked in my early Access programming days. Specifically you need ! to delineate within objects and . to delineate between parent/child objects with Form. qualifier for every form object.
If Forms!frmOrders!frmJobCard.Form!frmSFJCOrders![Estimate QTY] = 0 Then
Alternatively, use a functional version:
If Forms("frmOrders").Controls("frmJobCard") _
.Controls("frmSFJCOrders") _
.Controls("[Estimate QTY]") = 0 Then

Can I find out if an AppleScript object has a certain property?

I'm attempting to script BBEdit to make me feel more at home in coming from TextMate. One thing I need to be able to do is see if an object I have a reference to has a particular property.
For instance:
tell application "BBEdit"
tell front window
get selected items
end tell
end tell
This will succeed on a project window, but not on a disk browser window, because the latter does not have a 'selected items' property. How do I see if there is such a property in the object?
Please note: I know how to inspect an object in Script Editor (get properties) to see what properties it has, but I need to know at runtime what they are.
What about the class?
tell application "BBEdit"
if class of window 1 is disk browser window then
# ...
else
# ...
end if
end tell
I don't have bbedit so I can't check, but if different types of windows exist, and each type of window has different properties, then can't you just check the window type first? Then you would know what type of properties you can get. There must be some basic property of a window that tells you its type or kind or whatever that would help you make the decision.
The only solution I have so far is to wrap it in an error handler:
try
set sel to selected items
on error errMsg number errNum
if errNum is -1700 then
-- Code that handles no selected items attribute
return
end
error errMsg number errNum
end try
-- Code that handles when selected items attribute exists
There is a difference between documents and windows in BBEdit. Windows are an element of documents, but only windows have the selection property, so you can check the type of window first and avoid catching errors entirely (and make for cleaner code as a result).
Also, try using the selection property, which is hard property in BBEdit as opposed to "selected items" because selection will always return a usable object, even if only an insertion point.

How would one detect a property of type "InvalidOperationException" within a collection?

Consider the following vb.net code for an office add-in (for access):
td = db.TableDefs(objectName)
For Each fld In td.Fields
For Each fldprp In fld.Properties
Debug.Print(fldprp.Value.ToString())
Next
Next
the variable "db" is a .net representation of the access vba return result from "Application.CurrentDB()". "td" is of type "DAO.TableDefClass".
This code throws an exception of type "InvalidOperationException" when the value of the fldprp.value property cannot be determined (in Visual Studio, it shows the value as {"Invalid Operation."} in the watch window). fldprp.name, however, is available.
There are only a few properties which this occurs on. I'd like to be able to loop through all the fld.properties and output the values, but ONLY if it is not an exception.
I am pretty sure why it is happening (certain properties are not available in this context). What I need to know is how to detect this at run-time so i can skip the property.
I can't seem to find a solution that will work. Help would be greatly appreciated.
Inside the body of the inner loop, put this at the top:
If TypeOf fldprp Is InvalidOperationException Then Continue
http://msdn.microsoft.com/en-us/library/0ec5kw18%28VS.80%29.aspx
I don't know your specific situation, but what I would suggest is using an explicit filter for the object types you want to include instead of filtering out the ones you don't want to include.

Why can't I get properties from members of this collection?

I've added some form controls to a collection and can retrieve their properties when I refer to the members by index.
However, when I try to use any properties by referencing members of the collection I see a 'Could not set the ControlSource property. Member not found.' error in the Locals window.
Here is a simplified version of the code:
'Add controls to collection'
For x = 0 To UBound(tabs)
activeTabs.Add Item:=Form.MultiPage.Pages(Val(tabs(x, 1))), _
key:=Form.MultiPage.Pages(Val(tabs(x, 1))).Caption
Next x
'Check name using collection index'
For x = 0 To UBound(tabs)
Debug.Print "Tab name from index: " & activeTabs(x + 1).Caption
Next x
'Check name using collection members'
For Each formTab In activeTabs
Debug.Print "Tab name from collection: " & formTab.Caption
Next formTab
The results in the Immediate window are:
Tab name from index: Caption1
Tab name from index: Caption2
Tab name from collection:
Tab name from collection:
Why does one method work and the other fail?
This is in a standard code module, but I have similar code working just fine from within form modules. Could this have anything to do with it?
Edited to add
formTab was declared as a Control, but I find that if it is declared as an Object then the code works.
This will probably solve my problem, but in the interests of furthering my knowledge I would be grateful for any explanation of this behaviour, particularly with regard to the difference in running the code in the different types of module.
This is a really great question. Your edit at the end of the post reveals a lot about how VBA works and what's going on here. I'm not 100% this what's going on, but I'll explain what I think is happening.
A Collection in VBA (and VB6, for that matter; same code base) is not strongly typed. This means that everything in a collection is technically an "object." In the .NET world (as of .NET 2.0), it's possible to have strongly typed collections so that you could say "everything in this collection is a Control object." In VBA, this isn't possible with a Collection.
In your first iteration, where you are referring to the item indexed in the activeTabs collection, activeTabs(x + 1) is referring to an object. When you tell VBA to look up .Caption of that object, it doesn't know what the underlying type is (I think), so it has to simply look to see if the underlying object type contains a property or method called Caption. As you can see, Tab controls do in fact contain a property called Caption.
In your second interation, where you are doing a For Each loop, I think the problem is that the Control type probably doesn't have a property called Caption, though different types of controls probably do. For example, a text box control probably doesn't have a Caption property whereas as label control does have a Caption property.
You have a few options to fix your second loop. 1) You could declare formTab as a Tab control (I'm not sure exactly what it's called). The Tab control should have a Caption property. 2) If every control in activeTabs is not specifically a Tab control (in which case, you should probably call it activeControls instead of activeTabs), you could check within your loop to see if the formTab is actually a Tab control. If it is, cast it as a Tab control and then call .Caption. Until you cast it as a Tab control, VBA won't know that it has a Caption property since a regular Control object doesn't have a caption property.
In the end, you can get away with using objects as in your first loop and letting the runtime figure out what to do, but that can give really bad performance. In general, it's better to work with your specific types in a strongly-typed language. It also helps to show in your code that you know specifically what you're working with rather than leaving it to the runtime to decide what properties and methods you can work with.

The data set name is missing in the data region 'DataSetName'

I added an additional, new DataSet to my report and have been getting this cryptic error ever since.
The issue was that when the report had elements setup using the first data set I'd defined when the report was created. Adding an additional data set reset the DataSetName value to be blank. In this case for my Table but it could be for a List, etc.
To correct:
Open the report in SQL Server Business Intelligence Development Studio (AKA Visual Studio)
View the object details in the Properties Window (View > Properties Window or press F4)
Check the DataSetName value (under the Data section)
Update the value to point to the correct Data Set
Examine your RDLC file, open it in a XML editor. Most specifically, take a look at the Dataset section. See if there are some old ones that are still there. You can edit this file directly, but be careful what you do.
You can also attempt to run the RDLC file through a XML validator, and see if it comes up with any errors. Make sure to validate against the RDLC file's schema. (http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition/ReportDefinition.xsd)
I had a bit of trouble finding the correct properties window that contained this value, so I will add the following:
On the Layout Tab, press F4 to bring the properties box up.
In the dropdown at the top of the properties box, find your table and select it.
You should now see that data section about halfway down, along with the DataSetName property the error is complaining about.
I know this is a decade late, but this is to possibly help the next guy that is searching the internet for answers (like me).
In SSRS, right-click any unused/white area of the report then choose Select-> and the Group level (not group level content) that you are getting the error about.
In the properties window (Menu->View->Check Properties) for the Group Level, scroll down to General the DataSetName. Select the drop down arrow and choose the original dataset.