calling functions in a global template

  • Thread starter Raymond Bissonnette
  • Start date
R

Raymond Bissonnette

Hi,

How can I call a function in a global template (loaded from the startup
menu) from other document templates?

Any help is appreciated.

Raymond
 
M

Malcolm Smith

I would just reference the global template from your document template and
then call the routine as normal.

I would avoid Application.Run if at all possible.

- Malc
 
J

Jonathan West

Malcolm Smith said:
I would just reference the global template from your document template and
then call the routine as normal.

That is all very well if you are only using the macros on your own PC. If
you plan to distribute the global template, then problems start because the
reference had-codes the full pathname of the global template, and so nothing
will work if the template is placed in a different location on another PC.
I would avoid Application.Run if at all possible.

Ho hum. I've never had a problem with Application.Run. Do you have any
particular reason for offering that advice?
 
M

Malcolm Smith

If you are sending the templates out on a firm wide basis then you make
sure that all the templates all live in the same location on the hard
drive.

Not hard to do and I have had zero problems with massive rollouts in the
past.

Are you particularly trying to wind me up, Jonathon? Because you know how
it's done properly -- it's been discussed here before.


As for the last question; now come on, surely...

- Malc
 
J

Jonathan West

Malcolm Smith said:
If you are sending the templates out on a firm wide basis then you make
sure that all the templates all live in the same location on the hard
drive.

Not hard to do and I have had zero problems with massive rollouts in the
past.

That depends to some extent on the company and the extent to which it has
standardised its existing installations. That is not something which can be
guaranteed. I thought it worth making the comment.
Are you particularly trying to wind me up, Jonathon? Because you know how
it's done properly -- it's been discussed here before.

No, I'm no trying to wind you up. You have a habit of sometimes making
stronger statements than the facts necessarily warrant, and it does nobody
any harm for you to explain why you give the advice you do if somebody asks.
As for the last question; now come on, surely...

No answer I see. OK. If you don't want to explain it, you don't have to.
 
M

Malcolm Smith

Jonathon

Okay, I'll get off my high horse. :)

If one is rolling out a series of documents to more than user then one
should have the stuff standardised in terms of using fixed locations for
the start-up templates.

Otherwise it will be hell to get updates if one doesn't know where the
heck to update them to.

If the locations of the start-up templates are not known (gulp!) then the
simple answer is that the routines in the start-up templates should not be
there but in an Active-X DLL which is then installed on the user's machine
and then called like another library routine.


Application.Run? Let me get back to you on this one after the day's
racing; some of us have to be kept in the manner in which we're
accustomed... :)

- Malc
www.dragondrop.com
 
M

Malcolm Smith

Here we go...


In my start-up template I created three routines:


Public Sub TestOne()
MsgBox "Test One", vbOKOnly, "<no caption>"
End Sub


Public Sub TestTwo(sCaption As String)
MsgBox "Test Two", vbOKOnly, sCaption
End Sub


Public Function TestThree() As Long
TestThree = Documents.Count
End Function


Nothing clever there nor nothing wrong with this code neither.


In my document template I created three pairs of calling routines. Each
pair, such as T1() and T1a() do the same thing. T1() is using
Application.Run and the T1a() calls the routine via the reference.

This is the code:



Option Explicit


Sub T1()

Application.Run "TP.modTP.TestOne"
MsgBox "Done!", vbOKOnly, "Calling routine"

End Sub


Sub T1a()

TP.modTP.TestOne
MsgBox "Done!", vbOKOnly, "Calling routine"

End Sub


Sub T2()

' Calling via Application.Run

Dim sParameter As String

sParameter = "Hello, Malc"

Application.Run "TP.modTP.TestTwo", sParameter
MsgBox "Done!", vbOKOnly, "Calling routine"

End Sub


Sub T2a()

' Calling via the Reference

Dim sParameter As String

sParameter = "Hello, Malc"

TP.modTP.TestTwo sParameter
MsgBox "Done!", vbOKOnly, "Calling routine"

End Sub


Sub T3()

' Calling via Application Run

Dim nDocuments As Long

' ???

End Sub


Sub T3a()

' Calling via the Reference

Dim nDocuments As Long
nDocuments = TP.modTP.TestThree()
MsgBox "There are " & Trim$(Str$(nDocuments)) & " document(s) visible.",
vbOKOnly

End Sub



Let's go through them one pair at a time.

T1() and T1a() work as expected. They both call the routine in the
start-up template and then return back to the calling routine. Nothing
difficult there.


T2() and T2a() does the same thing, but with a difference that the called
routine, TestTwo() requires a parameter. I can get it to work with T2a()
but having a hell of a time getting to work with T2().


T3() and T3a() is very straightforward. TestThree is a function which
returns the number of open documents. T3a() works as expected but I can't
even figure out the syntax in which I need to get T3() to run.


I was going to do a test with a public created object in the start-up
template, but since Word 2000 came out we can't do that any more. A shame
otherwise we could see problems here with Application.Run here as well.


I have also seen problems with errors in the called routine in the past
with Application.Run (no, I am not going to spend hours reproducing it!)
and the passing of the error handling did not pass back to the calling
routing up the call stack. Not only that, the rest of the calling
routine didn't run.

This I have seen many times before. Of course I don't see it any more
because I expunge Application.Run on sight. If Application.Run is
present then there must be a design fault at play.


Hope that this starts to explain my reluctance to advise people on using
Application.Run -- there is no longer any need to do so.

Cheers
- Malc
www.dragondrop.com
 
M

Malcolm Smith

Jonathon

Another test.


In the start-up template have the following code:


Public Sub TestFour()

ActiveDocument.Bookmarks("bmkThisBookmarkDoesNotExist").Range.Select

End Sub



In the calling template have these two routines. The first calls the
routine via the Application.Run call and the second does it via the
reference:


Sub T4()

On Error Resume Next

Application.Run "TP.modTP.TestFour"
MsgBox "Done!", vbOKOnly, "Calling routine"

End Sub


Sub T4a()

On Error Resume Next

TP.modTP.Testfour
MsgBox "Done!", vbOKOnly, "Calling routine"

End Sub



Run them both and you will find that the error in the TestFour() routine
is not passed up the call stack as it would be expected. The call in
T4a() works as expected.


Another good reason not to use Application.Run .


Need I explain more?

- Malc
www.dragondrop.com
 
M

Malcolm Smith

So, further from this last test we come across a grave problem.

Sure, we could have trapped the error in the called routine but if we did
then how would we know in the calling routine that there was an error?

Let us say that the start-up template contained code to access a Document
Management System, a not unreasonable scenario, and if there is an error
when saving a new document how will the calling routine (a) know and (b)
handle it?

Bear in mind that we can't rely on the call stack as the Application.Run
effectively creates a new call stack and since we can't return error codes
or error objects from the start-up template we are starting to get quite
stuffed.

Of course we could have the start-up template try to force a call back to
the original template with another Application.Run but that way leads to
madness and ought to be dismissed straight away.

So, back to the start of the thread; please, Raymond, don't consider
Application.Run -- it just isn't worth it in the long run.

- Malc
www.dragondrop.com
 
M

Martin Seelhofer

Hello NG
So, back to the start of the thread; please, Raymond, don't consider
Application.Run -- it just isn't worth it in the long run.

I agree with you in that Application.Run shouldn't be used to
connect different parts of the same macro-application. The
main reason for this is the loss of error propagation which
you mentioned in your postings...

In my opinion, however, there are situations, where Application.Run
makes perfect sense. Since you don't need to add a reference to the
global template, you can distribute your application throughout
- lets say - a company without running into problems with the
hard coded template path (created by adding the reference). So,
if you are just going to call a macro (in the sense of a "sub withouth
parameters") in a delegating way, Application.Run is in fact a very
easy way to decouple your applications...

NB: A discussion of the advantages and disadvantages of using
Application.Run will result in much the same arguments which
come into play when talking about early- and late-binding (using
New or CreateObject in VBA) with the exception of the mentioned
error-propagation-problem.


Cheers,

Martin
 
M

Malcolm Smith

Martin

If one doesn't know where the start-up templates are placed on someone's
hard drive so that one can't get to them then two things spring to mind.

1. If it's a firm-wide situation then someone, somewhere, has messed up
with the overall strategy of the roll out.

2. If one is wanting a return value from one of these calls into the
Unknown then surely the best thing is not to use Application.Run but to
have the called routines shoved into an Active-X DLL or Exe and then call
that library.

After all, with use of the COM interface one doesn't need to know the
location of the library and also one can have return values or objects
coming back to the calling routine.


If, for example, one has a Document Management System (DMS) controlled by
the start-up templates. Wouldn't you want to know if the document was
saved and where it was saved to, or at least get a Document Number back so
that one can locate the thing again?


In all honesty, I cannot see a single reason for using Application.Run in
this day and age. Perhaps in Word95 days (when it really was a macro
language) then I would totally agree.


Anyway, I am sure that countless number of others will happily flame me
for this comments and thoughts.


Best wishes
- Malc
www.dragondrop.com
 
K

Klaus Linke

[...] In all honesty, I cannot see a single reason for using
Application.Run in this day and age. Perhaps in Word95
days (when it really was a macro language) then I would
totally agree.

Anyway, I am sure that countless number of others will
happily flame me for this comments and thoughts.



Hi Malcolm,

No flames from me ;-)

I just scanned my macros for "Application.Run". I found one application
where I wouldn't know how to do without it (since I don't see another way to
pass a macro as an argument).

I got tired to rewrite the (rather compliated) code to loop all story ranges
again and again.
So I wrote "LoopStoryRanges" once, that does contain the code to loop the
story ranges, and write all my other macros so they work on some range.

Private Sub LoopStoryRanges(myMacro As String)
Dim myStoryRange As Range
For Each myStoryRange In ActiveDocument.StoryRanges
Application.Run myMacro, myStoryRange
While Not (myStoryRange.NextStoryRange Is Nothing)
Set myStoryRange = myStoryRange.NextStoryRange
Application.Run myMacro, myStoryRange
Wend
Next myStoryRange
End Sub

Private Sub myTest(myRange As Range)
' Check what StoryRanges are present in the doc
MsgBox myRange.StoryType,,"StoryType:"
End Sub

Sub myTestLoopStoryRanges()
' runs "myTest" on each StoryRange
LoopStoryRanges "myTest"
End Sub

Now you're probably calling this a dirty trick, and you're right.
But I call on LoopStoryRanges in quite a few of my macros, and would hate to
have to do without it.

Regards,
Klaus
 
M

Martin Seelhofer

Hi Malcolm
1. If it's a firm-wide situation then someone, somewhere, has messed up
with the overall strategy of the roll out.

This might be true for small or medium sized companies. But in large
companies (1000+), you always have a heterogenous environment
up to some extent, no matter what policies/strategies you have. You
cannot switch 5000 users to Office 2003 immediately, a migration
always takes time (during which all users should be able to work
as usual)...

The (software-)world is getting better, but it is still far from being
perfect ;-)
2. If one is wanting a return value from one of these calls into the
Unknown then surely the best thing is not to use Application.Run but to
have the called routines shoved into an Active-X DLL or Exe and then call
that library.

After all, with use of the COM interface one doesn't need to know the
location of the library and also one can have return values or objects
coming back to the calling routine.

In all honesty, I cannot see a single reason for using Application.Run in
this day and age. Perhaps in Word95 days (when it really was a macro
language) then I would totally agree.

Agreed, but only up to a certain extent. In the very low-level view, COM
supports two ways of accessing an object's functionality. The first is the
use
of the IUnknown-interface and a call to its QueryInterface method, the other
is the use of IDispatch and a call to Invoke-method. While the first one
corresponds to the early-binding approach, the second one corresponds
to late-binding, where an object does not have to know *anything* about
the destination object but the name of the method to call or the property to
set/get. VBA can only work the way it does because of the IDispatch-
mechanism. Well, although VBA hides this situation mostly from the
programmer,
there are some points where the concept bubbles up to the surface.
Application.Run is such a thing, it corresponds almost 1:1 to the Invoke-
method of the IDispatch-interface.

The reason why I don't entirely agree with your position is that I've
seen lots of examples of problems with the VBA-reference-approach.
I'm sure that a lot of "corporate" VBA-programmers even nowadays run
into situations where a project referencing an ActiveX-component works
perfectly on one installation (e.g. Office 97 on WinME) but has a problem
with
finding the referenced component on another (e.g. Office XP on WinXP).
Personally, I ran into such situations various times. An example is the cool
CalendarControl installed with Access. Every single time, switching from the
static reference-approach to late-binding saved the day...

There is another point, though. While the reference-way always forces
the programmer to concentrate on specific members of objects, late-binding
with e.g. Application.Run lets you reuse the same code for different
purposes. Although the following example does not make much sense,
I hope you see what my point is:

Consider the following situation where you have 2 different functions
for calculating the area of a triangle (e.g. CalcArea3) and a rectangle
(e.g. CalcArea4), both with two parameters. There is a variable (edges),
which holds the number of edges. The standard way to calculate
the area would be an if-statement like this:

If edges = 3 Then
area = CalcArea3(value1, value2)
ElseIf edges = 4 Then
area = CalcArea4(value1, value2)
End If

Now, consider the solution using Application.Run:

area = Application.Run("CalcArea" & edges, value1, value2)

This might not seem to be spectacular, but when the number of
different but related functions rises, this results in a considerable
decrease of code lines. (Note that error handling and/or bounds
checking and stuff still needs to be addressed, though)

Note that I don't want to push people to using Application.Run (beware!!!).
I'm just saying that you might not want to restrict yourself to never using
it at all...
Anyway, I am sure that countless number of others will happily flame me
for this comments and thoughts.

No need for flaming, just expressing different points of view which makes
the whole newsgroup-thing so interesting ;-)


Cheers,

Martin
 
M

Malcolm Smith

This might be true for small or medium sized companies. But in large
companies (1000+), you always have a heterogenous environment
up to some extent, no matter what policies/strategies you have. You
cannot switch 5000 users to Office 2003 immediately, a migration
always takes time (during which all users should be able to work
as usual)...

Exactly, and I agree whole heartedly. Hence the need for a decent
strategy.

And, I must say, an excellent discussion on Application.Run as well.

- Malc
 
W

Word Heretic

G'day (e-mail address removed) (Malcolm Smith),

FWIW, faced with the "app.run" option to support a
larger-than-user-group-might-require template set, I ducked out the
back door past the smokers, set up a reference from a) to b) and set
up my commands from global template a)'s commandbars direct through to
global template b)'s commands then removed the reference.

If the target isnt present you just get a macro not found error when
you attempt to access it.

Even then though, a lot of the time I was able to work around through
better design of toolbars - the above example is easily worked-around
by having the button being added by the second template's presence as
its def'd in that template's bars.

Result? Have never, will never, use App.Run :)


(e-mail address removed) (Malcolm Smith) was spinning this yarn:
Exactly, and I agree whole heartedly. Hence the need for a decent
strategy.

And, I must say, an excellent discussion on Application.Run as well.

- Malc

Steve Hudson

Word Heretic, Sydney, Australia
Tricky stuff with Word or words for you.
wordheretic.com

If my answers r 2 terse, ask again or hassle an MVP,
at least they get recognition for it then.
Lengthy replies offlist require payment.
 
J

Jonathan West

Malcolm Smith said:
Jonathon

Okay, I'll get off my high horse. :)

:)

I've been busy with other things, which is why I've taken a while to respond
on this. Apologies for that.
If one is rolling out a series of documents to more than user then one
should have the stuff standardised in terms of using fixed locations for
the start-up templates.

Ideally yes, but in a mixed or multi-site environment, that ideal cannot
always be achieved. It can't even be approached for sotware which is sold
commercially into locations you can't control.
Otherwise it will be hell to get updates if one doesn't know where the
heck to update them to.

If the locations of the start-up templates are not known (gulp!) then the
simple answer is that the routines in the start-up templates should not be
there but in an Active-X DLL which is then installed on the user's machine
and then called like another library routine.

There are alternatives. For instance it is always possible for an installer
to open Word using automation, interrogate to find the paths it needs and
then close Word before copying files to the appropriate places. I've used
that technique myself several times.
 
J

Jonathan West

Hi Malcolm

I'm going to handle the responses to your next 3 messages in one. If I've
understood you correctly, the problems you described can be summarised as
follows.

1. Error information is not passed back up the call stack from a command
called using Application.Run

2. Calling subroutines with parameters doesn't behave

3. You can't call functions using Application.Run and get a return value.

I would disagree with items #1 & #2. As an example of expected behaviour for
both of these, I placed the following code in the startup module

Sub TestMessage(str As String)
On Error Resume Next
MsgBox str
Err.Raise 55, "test", "this is a test"
End Sub

And the following code in a module in the attached template

Sub TestRun()
Application.Run "TestMessage", "This is a test"
Debug.Print Err.Number, Err.Description
End Sub

The code ran as expected, in that it diaplayed a message box saying "This is
a test", and it printed the error number and description to the immediate
window.

I agree with item 3, the Run syntax does not handle functions. However, that
doesn't entirely prevent you from getting a return value back, you can pass
it back in a parameter passed ByRef. Try this

In the startup template, you have the following routine

Sub SplitFirstWord(StrIn As String)
If InStr(StrIn, " ") > 0 Then
StrIn = Left$(StrIn, InStr(StrIn, " ") - 1)
End If
End Sub

In the attached template, you have this

Sub TestPassParamBack()
Dim strTest As String
strTest = "This is a test"
Application.Run "SplitFirstWord", strTest
MsgBox strTest
End Sub

The routine runs as expected, displaying just "Test" in the messagebox.

Now, I'll admit that there are few occasions whan Application.Run is
actually needed, but there is one I do use regularly. I've deiagned a batch
processing routine which allows any arbitrary user-selected macro to be run
on all the documents in a folder. Its a fairly straightforward routine, and
the user selection process for the macro is as follows

With Dialogs(wdDialogToolsMacro)
If .Display Then
strMacro = .Name
End If
End With

So I have a string containing the name of the macro the user selected.
Application.Run is the simplest way to run that macro on each file in turn.
It doesn't matter what the macro is called or in which template or add-in it
is stored. It is available and can run.

If you choose to remove Application.Run from all your code, that is fine.
I'm not sure that the facts can justify branding it is evil and never to be
used though.

--
Regards
Jonathan West - Word MVP
http://www.multilinker.com
Please reply to the newsgroup
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top