Concerned about code falling over in scenarios I haven't thought of during testing and so wanted to understand the difference between these two approaches, which appear to accomplish exactly the same thing:
ActiveWindow.Selection.ShapeRange(1)
ActiveWindow.Selection.ShapeRange.Item(1)
Context is: Identifying the shape in which the active cursor position is, or if whole shapes selected, the first shape in the selected items (i.e. if there's an active cursor inside a shape, it returns that shape; if the user has selected multiple shapes, it returns the first shape).
Looking at MSDN:
For first option, appears you can add the index in the brackets to access whatever shape number in selected shapes https://learn.microsoft.com/en-us/office/vba/api/powerpoint.shaperange
But looking at "Item", appears to do exactly the same thing by definition "Returns a single Shape object from the specified ShapeRange collection."
https://learn.microsoft.com/en-us/office/vba/api/powerpoint.shaperange.item
What is the difference and in what situations can it lead to erroring out?
TIA
That link posted by Tim Williams gives a good explanation of what's going on - well worth a read.
In answer to your specific questions:
There's no difference in the two lines of code. Since .Item is the default property of a collection object, ShapeRange(1) is doing the same as ShapeRange.Item(1). Coding style is a matter of personal preference, but most VBA developers avoid implicit references. Having said that, I'd say most of us do leave out .Item when referencing a collection, eg ThisWorkbook.Worksheets("Sheet1"). If you're interested, I do find .Item useful for code that is wrapped in a With ... End With block:
With ThisWorkbook.Worksheets
.Add After:=.Item(.Count)
End With
No, your code won't fail. The only reason it might fail would be if Microsoft changed its default property for the Collection object, and you're more likely to be eaten by a shark while being struck by lightening on the same day your lottery numbers came up.
Related
Possible stupid question here...
But is it possible in MS-Access to programmatically manipulate text label captions in such a way that that a different caption will appear in the header of each report section?
Ie., The design view shows a text label object in the GroupLevel zero header, with a default caption of "blah"....but upon execution of a Report_Load() sub, the actual text displayed is different for each section in the report? Say, simply "Section 1", "Section 2', "Section 3" and so on?
My suspicion is that this is not possible, but just wondering if anyone has some creative ideas how to make it work.
I realize that there are other/better ways of accomplishing the same thing...but is such a thing possible using VBA and Label objects specifically (at the moment, this is an external constraint and one that I cant change).
EDIT: https://drive.google.com/open?id=1PID58qMyp_rNxv9tsQk38-Co9sDOFgzY
EDIT 2: Original post specifically designated LABELS as the only object for an acceptable solution. Edit to include LABELS and TEXT BOXES...which of course makes the question nearly trivial. Apologies to #peakpeak for my lack of clarity!
You can change the caption with
Me.<name of header>.Caption = "whatever"
Select Properties for the header in design view and find out and/or change the Name property. Me assumes that the VBA code is located under Microsoft Access Class Objects in the form you want to manipulate.
Actually, thank you for your help, but I have found something that appears to function as a decent work-around within the constraints I described above. Strictly speaking, this does not satisfy the terms of the question as originally asked (-1 to me for lack of clarity, and apologies to #peakpeak, who admittedly had essentially zero chance of answering this question as asked), but it this is close enough that it solves my immediate problem:
Change the object intended to contain my dynamic text from a label to a text box (duh!), keep all formatting settings, etc. the same so that the graphical presentation is unchanged.
Set the Control Source of the new text box to a public function, where the argument of the function is the name of a relevant field in the underlying query ("tName" in the linked example), so that the dynamic text box has a control source "=GetText([tName])" and the GetText() function is defined in the appropriate module for the report, and defines the text as desired, e.g.:
GetText(tName as String) as string
SELECT Case tName
Case "Albert"
GetText = "Section 1"
Case "Barry"
GetText = "Section 2"
Case Else
GetText = "Section 3"
'and so on
End Select
`
End Function
Sometimes when developing you find that typical property names show up in lower case.
TempVars.Item("abc")
Might appear like this.
TempVars.item("abc")
Or
tbxMyTextbox.Value
Shows up as
tbxMyTextbox.value
The question is why does this happen and how do you fix it?
I've asked myself this question several times and seen others ask it in SO and elsewhere. In most cases the question comes up when searching for answers on other coding errors. Sometimes the developer (me too) wonders if there's something wrong that they're missing that causes this lower case issue.
A while back I ran across an answer (I'll add the original reference when I run across it again) that made the most sense and actually allowed me to correct this nagging issue.
Apparently it happens when you use a variable in lower case that has a name that's the same as a property.
So
Dim value as string
will result in
myObject.Value
appearing as
myObject.value
Solution?
Because VBE apparently considers this variable across the entire IDE -- apparently without regard to scope; the way to revert to the proper case is to temporarily create the variable in upper case and avoid naming variables with the same name as existing properties in your application.
So the solution for the .value / .Value issue above would be to temporarily include the line
Dim Value as string
in a module within your app. Run the code that includes this line.
Afterwards, remove this temporary line of code.
Then as hoped, .value properties will again appear as .Value and not .value.
Of course, we should avoid those reserved words in the first place, but if at some point that wasn't done the above should fix the issue.
Here are some reserved word links:
Microsoft - Access 2002 & later
Allen Browne's bad word list
Reserved Word search tool (various languages)
Thanks to #ScottCraner for the nudge about avoiding reserved words.
Try something like this:
CallByName(myObject, "value", VbGet)
I'm using the trick described here - !A1 - to get the the range of cells up to and including the current one (typically for a Rows() function). This works great for day to day usage, and eliminates a lot of errors that I get when moving ranges around when I previously had to use an adjacent set of rows.
Unfortunately, my formulas need to be evaluatable from VBA. With __THISCELL__ as my !A1 cell, and the cell housing the formula as $Z$100 the following evaluates to an error:
Application.Evaluate(rngCell.formula)
And the following evaluates to $A$1:$Z$50
rngCell.Worksheet.Evaluate(rngCell.formula)
Obviously an approach is to replace __THISCELL__ with rngCell.Address(External:=True) prior to evaluation, but here's the kicker: I'd like to be able to execute my formula parser in a workbook which uses, say THIS_CELL, THISCELL or __THISCELL safely, and I'd also like to be able to safely execute my code in a workbook with a name like __NOT__THIS_CELL__.
All I need for this is a mechanism to evaluate relative references relative to a specific cell address - which since people do use R1C1 references in VBA a fair bit, I imagine must be around. However, I don't know it. Can anyone help?
NB: I like to avoid fiddling with ActiveCell, Selection, etc. where possible, since those smell like the excel equivalent of SendKeys - who knows what the user is doing when you access them. Even then, though, I'm not certain I'll get the right answer, because for the Worksheet.Evaluate approach, I'm not positioned in cell $A$1!
If I understand your question, I believe you're looking for the Range().Offset method.
Range().Offset(rOffset, cOffset) refers to a range that is rOffset lower and cOffset to the right of the given range (negative values for up and left are allowed). Also, .Offset can access and set all of the properties of the range, just like you would do with .Range.
The approach I've taken for the time being is implicit in the question: when a named range is detected, store the current selection and worksheet, select the one which we use as the evaluation context, and then use Evaluate. This seems to work, provided the cell being evaluated is inside the activesheet.
I don't like jumping the selection all over the place - feels dirty - but short of a more elegant solution, it does work.
I have a macro workbook with a number of worksheets that exist permanently, which are constantly cleared, updated, etc. Since they are referred to in various subroutines, I have made each corresponding worksheet object a pseudo-global variable in the following manner, for example for the "Main" sheet:
Function MAIN() As Worksheet
Set MAIN = ThisWorkbook.Sheets("Main")
End Function
By doing so, I can then refer to each sheet in the other subroutines, for example:
MAIN.Cells.ClearContents
I have also defined some pseudo-global constants which are located in a fixed place on the "Main" sheet in a similar way, for example:
Function NumLines() As Integer
NumLines = MAIN.Range("C3").Value
End Function
In this way, I use "NumLines" just like any variable throughout the code.
I expect that there is a more efficient way to manage globally accessed variables like these and was wondering, what would be a better way to accomplish this?
For reliable sheet reference I would suggest to use Sheet.CodeName Property. Each sheet has its unique CodeName which you could find in the place marked yellow on the picture below.
For quick reference to cell value I would suggest to use Range name. After you select you C3 cell you need to put unique name in the box marked yellow below. All Range names are unique in the workbook.
As a result you can use sheet and cell reference as presented below in each of your subroutines in your project.
Sub Test_Macro()
Debug.Print MAIN.Name '>> result: Sheet1
Debug.Print Range("CellC3").Value '>> result: 100
End Sub
I expect that there is a more efficient way to manage globally accessed variables like these and was wondering, what would be a better way to accomplish this?
When I use global variables in VBA, I do three things.
I always preface global variables with a g_ prefix. It seems often that a global variable in VBA is useful. But I've also spent far too long trying to track down "what variables are global or not?" in other people's code. Keeping a very clear naming convention will save you and whoever looks at your code a TON of hassle in the future.
This is even more important if you are less experienced as a developer. Avoiding globals is hard in VBA, and the less experience you have, the more likely it is you will use globals. For others to help or maintain the code this becomes so important.
If you are going to be using even a small number of global variables, you must use Option Explicit unless you want to cause nightmares in maintaining code. It's hard enough to track down these errors when you wrote code let alone months or years later.
I always create a module which is called "GlobalVariables" or something similar. That module contains all of the global declarations in one location. For larger code bases this can become longer but it has always paid off for me because I know exactly where all my globals are defined. None of the "which file is this variable actually being defined in?" game.
Just an unrelated note, too, in your first example - I would use the code name rather than that function. Each VBA worksheet has a sheet name ("Main" in your case) as well as a codename, which you can set in VBA and remains the same. This prevents users from changing the name of "Main" and breaking code.
You can also refer directly to them similar to how you are using MAIN.Cells. KazJaw has a good example of this.
I have a report that is generated in PowerPoint, and underneath many of the graphs, there is text that tells the reader to refer to pages in the appendix. I would like to be able to dynamically reference these slides.
For example, under a graph I might have the text "Please see appendix page 54", but I need the 54 to be linked to a slide so that if I insert another slide it will say 55.
Is this possible to do in VBA? I do not expect somebody to write my code for me, I would just like to know if this is a reasonable thing to do before I spend hours attempting to do it.
Side note: I feel horrible asking a question about MS Office on here, but since I believe it would need to be implemented in VBA (I don't think this functionality is built in by default) I think that it is a relevant question.
No need to feel horrible asking this here.
How one might do this:
In PPT, shapes, slides and even the presentation itself can have an associated tag collection; named string values. For example, assuming a reference to the shape in oSh, you can do:
oSh.Tags.Add "AssociatedSlideId", "293"
In this case, you'd apply this tag to your graph; the 293 would be the SlideID of the slide you want to reference. Each slide has a unique SlideID assigned when it's created; the SlideID won't change when you move the slide around/add/delete slides.
To read the tag from the shape:
Debug.Print oSh.Tags("AssociatedSlideId")
In this case, that'd return "293". Feed that to FindBySlideID to get the SlideIndex of the slide (ie, the ordinal position of the slide in the presentation). Or ask it for SlideNumber if you want the number of the slide that'll appear in number placeholders (usually, but not always the same as slide index).
Debug.Print ActivePresentation.Slides.FindBySlideID(clng("293")).SlideIndex
You might also tag the textbox or other shape that you want to use to hold the reference, then write a function along the lines of:
Function ShapeTaggedWith(oSl as Slide, sTagName as String, sTagValue as String) as Shape
This would iterate through the shapes on slide oSl looking for one with a tag named sTagName, value = sTagValue and return it to the caller if found.
Now you can find the shape that's nominated as your caption for the graph, let's call it, and change its text to match the SlideIndex (or SlideNumber) of the slide the chart's supposed to reference.
Hope that's all moderately clear; if not, that's why the StackOverflow gods gave us comments.