Category Archives: VB Feng Shui

Internationalizing Applications

0
Filed under Languages, VB Feng Shui

image A recently posted article on CodeProject had this to say about Visual Studio’s support for multi-lingual applications:

Conclusion: Visual Studio .NET does not offer any multilanguage support which is worth thinking about it.
You will only waste your time with Microsoft’s approach!

Check the whole article/project out here.

I can’t verify the author’s credentials that would justify such a claim, but from the sound of it, he did at least start down the MS sanctioned path, and I have to agree with him.

Way back in the dark ages, pre-internet, around 1990 or so, I was managing development of a CRM system (customer relationship management software).

We’d picked up some resellers oversees and needed to get the product internationalized. This was really at the very beginning of Windows even (Win 3.1 and WFW, if you remember those!). Our internationalization efforts had to apply equally well to our DOS app and our Windows version.

Several of the developers and myself went to a conference, the precursor to VBits, (I don’t remember exactly what it was called back then) and I got a chance to talk with one of the MS internationalization engineers directly.

I’d played with the whole “separate resources for each language” technique and found it workable, but so labor intensive, that I couldn’t imagine anyone but the largest shops actually doing it that way.

The MS guy verified that suspicion. He said (and I’m paraphrasing), “The core team finishes up the project, and ships it, and then the whole project base is ‘thrown over the wall’ and each internationalization team then takes over and internationalized the project into their respective languages, re-tests, etc.”

Ouch.

Now, I’m sure times have changed at MS, but if the comment from Elmue in the article on CodeProject is any indication, they haven’t changed that much.

The internationalization functions I helped build way back then was based on 3 simple concepts:

  1. The original english strings had to remain in the code
  2. Those strings has to actually be used by the code
  3. Those strings had to be easily searchable (say, by a GREP or similar utility) for extraction and translation

Why those first two points?

Because if the english strings remain in the code, the code remained relatively easy to debug and maintain for the programmers.

Further, if the strings are actually used by the code, that meant that during dev and alpha/beta testing, we wouldn’t have any disconnect issues with resources not matching what was needed in the code itself. This is akin to the age old concept of eating your own dog food. The idea of socking strings away in resources and just having a comment in the code as to what the string contained just scared the hell out of me.

Also, it also meant that if the translatable resources are lost for whatever reason, the program would still be able to run based on the “compiled in” English strings. Not ideal, but better than simply throwing errors or displaying blanks.

We accomplished all these goals by embedding all translatable strings (including those in dialogs, etc), into a function call. Something like:

MyString$ = TX$(“This is the english version”, stringcode, groupcode)

Where stringcode and groupcode were optional arguments that indicated, basically, the resource ID of the string and an arbitrary group ID of the string.

Originally, when you were writing or working on code, you’d never even bother entering the stringcode or groupcode args, so your call would look like:

MyString$ = TX$(“This is the english version”)

But, because it was trivially easy to scan for TX$(), when our scanner was run on the code, it could

  1. Extract the strings
  2. Give then ID’s
  3. Rewrite the source code with the appropriate string and group codes as necessary.
  4. Generate a “translator’s file” that contained the string, ID’s and potentially developer comments that would indicate the context of the string and intention (for use by translators to assist with the translation).

Nowadays, with OO, extension methods, reflection, and all the other .NET goodies, seems like this whole process could be vastly more efficient than even what we did back then.

But in the end, we translated the product into 8 or more languages in just a few months with this technique, using no additional developers, and a few native speaker volunteer translators. And it didn’t require any code rewrites and was just as efficient to debug as if you’d left the strings in the code and did nothing about translation at all.

Now, granted, there’s a lot more to internationalizing an app that translating text. You have to worry about icons, input method (when applicable), date, time, and number formats, and even subtle things like color choices, but it was a huge timesaver for an otherwise arduous task.

Closing comment: Why TX$() you might ask<g>? Basically, it was because we didn’t want a huge function name taking up tons of space in the code when it would be used as often as it was. That’s all. As I recall, it is about the only two letter function I’ve ever authored in code. I was never a big fan of the BASICA 2 letter name restriction!

Any translation war stories out there? How have you translated applications?

Being a Good Windows Citizen

0
Filed under Installations, Rants, VB Feng Shui

I’m constantly amazed by some of the goofy things that application installations do to my system without even giving me the option.

Obviously, there’s the whole spyware/adware problem, but that’s just blatant.

There’s an entirely other, more subtle, level to the problem, where applications do little things that are difficult to undo, are annoying and that you aren’t given the option of not “doing” in the first place.

Here’s one example. Now, I like the CodeJock products, and I’m not really picking on them here, it’s just that they give me a good example:

image

This is the “Right Click – New” menu that you get from Explorer when you right click in a folder and select “New”.

Now, I’m a pretty busy developer, and I like a nice shortcut just as much as the next guy, but honestly, I can’t imagine EVER creating so many “Command Bars Designer Documents” that I’d want an entry for it on my New Items menu like this. As you can see, there’s already enough junk on this menu. I wonder how many people out there create a new contact, bitmap image or briefcase so regularly that it belongs on this menu. Hell, I’ve always created new Word docs by opening Word, starting my document and then “File-Save”-ing them wherever, never by the “New Office Word Document” menu, but then, maybe that’s just me.

A few others:

  • Unrequested shortcuts on the QuickLaunch space or the Desktop.
  • Unrequested Tray Icons.
  • Adding Vista Sidebar gadgets without permission.
  • Stealing file extension associations without asking and not restoring them on uninstall.

Any other bits of application rudeness out there anyone has run into?

Choices in Utility Function Implementation

0
Filed under .NET, VB Feng Shui

One thing I’ve begun to notice about VB.NET is that there are a pile of options as to how you might implement a particular piece of functionality. I’m still not convinced that this is necessarily a good thing, but it is hard to argue with having options.

For instance, I recently needed a function to retrieve the number of elements defined in an enumeration. Now, I realize that enumerations can be sparse, and that creating an array to be indexed by the enumeration values will not work with all enumerations, but that’s a topic for another post.

The first option I thought of was to implement it as a plain ol’ function in a module:

Debug.print GetEnumItemCount(GetType(MyEnum))

    Public Function GetEnumItemCount(ByVal typ As Type) As Integer
        If typ.IsEnum Then
            Return System.Enum.GetNames(typ).Length
        Else
            Return 0
        End If
    End Function

Not terribly interesting and it’s perfectly functional, but it just doesn’t seem particularly .NET-ish. Also, I don’t really care for the “double function” approach (ie calling GetType() to retrieve the enum type descriptor object to pass into the function).

So, any way to improve this?

One way is to get rid of the extra GetType function call. Unfortunately, the only way I’ve seen to do this so far is to pass in one of the enumeration values instead of the enumeration itself (or its type).

Debug.print GetEnumSiblingCount(MyEnum.Value1)

    Public Function GetEnumSiblingCount(ByVal e As [Enum]) As Integer
        Return System.Enum.GetNames(e.GetType).Length
    End Function

I’ve renamed the function GetEnumSiblingCount because that’s exactly what this new function does, return the total number of siblings to the passed in enumeration element.

Unfortunately, this approach still seems a little a lot odd.

My next thought was, why not add an extension method to the enumeration itself, so you could do something like:

Debug.print MyEnum.Count

Unfortunately, since MyEnum is a type and not considered an actual object in any sense, you can’t invoke a method on it, or even define one on it for that matter.

You can, however, get close, by defining an extension method on the Type object itself:

Debug.Print GetType(MyEnum).Count

Module EnumExtender
    <extension ()> _
    Public Function Count(ByVal typ As Type) As Integer
        If typ.IsEnum Then
            Return System.Enum.GetNames(typ).Length
        Else
            Return 0
        End If
    End Function
End Module

The downside here is that we’re extending not just enum types but all types, and extending anything other than enums with this function doesn’t make much sense.

Interestingly, there’s yet another option; extend the enum elements themselves.

Debug.print MyEnum.Value1.SiblingCount

Module EnumExtender
    <extension ()> _
    Public Function SiblingCount(ByVal anEnum As [Enum]) As Integer
        Return System.Enum.GetNames(anEnum.GetType).Count
    End Function
End Module

However, this also has the same problem as a previous try in that it just doesn’t feel right to ask an enumeration element for some property of it’s containing type.

Ok, how about just changing up the definition of the enumeration itself:

    Public Enum MyEnum
        Value1 = 0
        Value2 = 1
        Value3 = 2
        Count = 3
    End enum

Short, sweet, no compiler tricks or reflection.

But. It doesn’t work with existing, already defined Enumerations, and since the Count is part of the enumeration, the actual count of the enum is 4, not 3, which would seem to be confusing in the long run.

I’m sure there’s many more possibilities as well, and, hopefully, I’ve missed a better one and someone out there will let me know it!

Long story short, my first inclination, the plain ol’ function-in-a-module GetEnumItemCount(), seems the best solution for this particular piece of functionality.

In reality, the best solution for most of these sorts of generic, application-wide, stateless utility functions seems to be the plain ol’ function-in-a-module.

My Entries on the List of Things to Hate about VB.Net

0
Filed under Rants, VB Feng Shui

First, I should say I’m a pretty ardent supporter of VB, even VB.net. Heck, that should be obvious from the name of this blog<g>

But, as I’m getting more and more into the “.net” of VB, I’m finding more and more things to dislike about it.

There’s plenty already written on this subject and maybe I’m a tad late to the party, but I haven’t seen these particular things mentioned before, so here’s my additions to the topic.

  • Sluggish IDE. Good god. I’ve got a dual core Core2 rig with ultrafast video and 4gb of ram, and the VS2008 IDE feels like trudging through a Valdez-soaked beach compared to working with VB6. I understand this isn’t specifically a VB problem. The IDE applies to all .NET languages. But that doesn’t change the fact that if you program in VB, you use the IDE, and it’s a dog.
  • Intellisense. I like Intellisense, the concept, but there’s just something about the VB.NET implementation that is so much more “in the way” that VB6’s. It feels a bit like that brother-in-law that you can’t get off the couch soon enough…
  • MDI style IDE. While there are definitely some improvements to the UI of the IDE, and just maybe I’m a relic, but I MUCH prefer the VB6 (and before) “SDI” floating windows style IDE to this monolithic, MDI style environment. To make good use of it, you have to maximize it on-screen, which hides everything else onscreen. Granted, a nice dual or triple monitor setup minimizes that impact, but still, I just find it much more cumbersome and “in the way” than the old environment.
  • Uninitialized strings variables. This one just floors me. Take this code:
        dim s as string
        debug.print len(s)
        debug.print s.length
    The len(s) line works like you’d expect (you get 0), but the s.length line fails with an error! What the hell? An uninitialized object? This is like a car manufacturer coming out with the new year model and swapping the gas and brake pedals. I don’t care whether they told me about it before or not, it’s just plain stupid. Why on earth have two functions that do exactly the same thing but one works as any longtime VB programmer would expect and one does not? And no, “That’s just the CLR/it’s necessary for C#/Blah Blah” just doesn’t cut it.

As I read through many of the posts, I have to admit I’ve started to wonder myself as to whether this is all some low-level ultra-subtle manipulations on the part of MS to nudge people further and further away from VB. Yes, it starts to sound like conspiracy theory, but surely, all this couldn’t be completely unintentional, could it?

Even so, there are ways around some of this. And I still much prefer VB, the language, to anything even resembling C and it’s ilk.

Still, if no one complains, things won’t change (or they’ll get worse<sigh>).

Fixing System.String

0
Filed under .NET, Languages, Rants, VB Feng Shui

String manipulation is a very different beast in VB.NET than in VB6. In fact, I’d wager that it’s the one area that trips up programmers new to VB.NET more than any other aspect of the language.

I’ve talked about some annoying aspects of System.String before, but this go round, I’m going to concentrate on shared methods.

Shared Methods are methods that are invoked via the class type itself, and not via an instance of the class.

For instance, with strings, you have a nicely named IsNullOrEmpty  function that you’d expect would return a true/false indicating whether the string in question is null or empty. Unfortunately, you’d be only half right.

Bring up the Object browser and find the String class, then highlight the IsNullOrEmpty method and you’ll see this:

image

Notice the Shared keyword. That indicates that this method IS NOT an instance method, thus you can’t invoke it via a specific instance of a string; rather, you must invoke it via the String class directly.

So, you can’t do this:

Dim s as string
If s.IsNullOrEmpty Then

But you can do this:

Dim s as string
If String.IsNullOrEmpty(s) Then

Now, it makes perfect sense, from a purely technical perspective, that invoking an instance method on an uninitialized object wouldn’t work anyway, so a method like IsNullOrEmpty wouldn’t make sense to be an instance method, since attempting to invoke it via a variable that hadn’t actually already been initialized would result in an “Object not Set” error.

However, this is VB, not some ivory tower exercise in theoretical language design. If I’m going to invoke a method like IsNullOrEmpty, I’m expect to be able to do so against an instance. Having to invoke it via the String class is just so utterly unintuitive, it defies all reason.

Oddly, the very argument that I note above in favor of using a shared method for IsNullOrEmpty is violated by another string property, Length. Here’s a property that is definitely an instance property, but causes VB code to fail (with the Object Not Set error) when invoked on a variable that hasn’t actually been set to value yet.

Is this just an arbitrary oversight, a design flaw, or an intentional “feature” of the language? I can’t answer that.

But, realistically speaking, I can say that it’s utterly frustrating to have elements of a language, such as these, behave so drastically different from one version to another. It doesn’t matter that the syntax is different (x.Length vs Len(x), for instance), there is an expectation there that simply is no longer met and does nothing but confuse.

Fortunately, with VB 2008, there is a relatively trivial way to correct these problems, and likely a host of other similar issues.

It’s called “extension methods”.

To create an IsNullOrEmpty that works like any reasonable person would expect it too, just put this in a Utility module somewhere in your project:

Imports System.Runtime.CompilerServices

Module StringExtensions

    ''' <summary>
    ''' Returns True if the current string instance is nothing or a null string
    ''' </summary>
    ''' <param name="aString"></param>
    ''' <returns></returns>
    <extension ()> _
    Public Function IsNullOrEmpty(ByVal aString As String) As Boolean
        Return String.IsNullOrEmpty(aString)
    End Function
End Module

The Imports System.Runtime.CompilerServices is only used during compilation. You can actually continue to target the .NET runtime v2.0, even if you use this code (however, you still have to compile the code from VS2008, it won’t work in VS2005).

You tag the new version of IsNullOrEmpty with the <extension()> attribute to  mark it as an extension method.

The first parameter of an extension method is required and is an argument of the type of class that you’re extending, in this case the String class.

You can have additional arguments if necessary, but you don’t need any for this method.

This trick takes advantage of the fact that even though the String class already has a method named IsNullOrEmpty, the function signature is not the same as this one (since ours has the implicit first argument). This is effectively an overload and it allows VB to know to call the new method if invoked against an instance, and the old one if invoked against the String class directly (which is exactly what’s being done within the method itself!).

There are several other “shared” methods on the string class that can similarly be extended to more intuitive instance methods in this way, for instance:

  • Compare
  • Concat
  • Format
  • Join

Length could also be added to this list but you can’t quite treat it the same, since it’s a property, and the Extension attribute can’t be applied to properties.

Finally, extension methods can be unbelievably useful for associating functionality with specific classes that you can’t extend in any other way, but, as always, you need to be careful with how much you extend a  class.

For example, it might be tempting to extend the String class with all sorts of path/file/folder manipulation and parsing logic, so that you could do something like this:

dim s as string = "c:\myFolder"
debug.print s.DriveLetter

but doing so could quickly clutter the String object and Intellisense.

As usual with these sorts of techniques, use judiciously.

Fun with the Clipboard

0
Filed under Utilities, VB Feng Shui

Have you ever clipped something to the clipboard and wished you could immediately “do something” with it?

For instance, if I clip a URL, it’d be nice to simply be able to immediately “open” that URL.

Or if a clip a phone number, how about dialing that phone number (intelligently, of course, adding or removing 1+ and dealing with area codes, prefixes and the like). Esp useful for WebEx or Gotomeeting conference calls that require not only dialing a number but dialing the meeting access code as well.

Or how about immediately jumping to Google Maps to highlight a place if you just clipped an address?

I’m building a little open source example utility to do these very things, but was wondering if there wasn’t something else it could do as well (within that general idea) that I’m just not seeing.

Two or three actions just doesn’t seem like enough to warrant a whole utility.

Or is there already a utility to do this?

Any thoughts?

One Big Control or Lots of Little Ones (redux)

0
Filed under .NET, ASP, Rants, VB Feng Shui

Some time ago, (or here in the wonderfully virtual web world), I questioned the whole aspect of all this pile of controls in ASP.NET. Of course, you’ve got the grids and listviews, and you also have all the radio buttons, checkboxes. But then you’ve got a separate validator for each kind of validation you want to use, and even validators to let you combine other validators in “AND” and “OR” logical relations.

My thoughts were “Jeez. this is madness.”

Wouldn’t a single control that you could adapt to different display behaviors, validation rules etc be much easier to work with and create a more maintainable solution?

Well, apparently someone at Microsoft had the same bright idea, at least with the new ListView control in ASP.NET 3.5.

Take a read of Fritz Onion’s article in the latest MSDN magazine.

He describes the new ListView as a control that can “literally replace all other databinding controls in ASP.NET. That’s right. Every last one.”

Now, if they’d do the same for that validation nonsense, we might be getting somewhere!

A Grousing About Properties in .NET

0
Filed under .NET, Rants, VB Feng Shui

I’ve started playing more and more with .NET lately. There’s a lot to like, a lot to dislike, and a few things that left me scratching my head.

One is this new fascination with moving regional (or class scoped variables) up to right on top of the properties that are wrapping them.

You’ve no doubt seen the pattern.

In old school VB6, you’d have something like this:

    {top of class}
    Private _MyPropVar As Boolean

    {lots of code here}

    Public Property Get MyProp() As Boolean
        Return _MyPropVar
    End Get

    Public Property Let MyProp(Byval NewValue as Boolean)
       _MyPropVar = value
    End Set
    {code continues}

And in the New Order, you have this:

    {other code}

    Private Shared _MyPropVar As Boolean = True
    Public Shared Property MyProp() As Boolean
        Get
            Return _MyPropVar
        End Get
        Set(ByVal value As Boolean)
            _MyPropVar = value
        End Set
    End Property

    {code continues}

Now, the pundits out there claim that the ability to move the definition of the variable that stores the property value down in the code to where the property is actually defined is a good thing.

And it very well might be.

But if that variable is used elsewhere in the class (it is, after all, visible to the entire class), then it makes more sense to me for it to be grouped with other such class scoped variables at the top of the class, and not buried within the class.

What’s most frustrating about this, though, is that the language designers had the perfect opportunity to solve this problem elegantly, and didn’t.

The simple introduction of a property scope would have done the trick. Then for those variables that are ONLY used to track the contents of a property, you’d have this (note the _MyPropVar definition has moved to INSIDE the property declaration):

    {other code}

    Public Shared Property MyProp() As Boolean
        Private _MyPropVar As Boolean = True
        Get
            Return _MyPropVar
        End Get
        Set(ByVal value As Boolean)
            _MyPropVar = value
        End Set
    End Property

    {code continues}

Now there may be significant ramifications involved that prevented this change, and I’m certainly no expert at compiler design or implementation (though I have implemented a pseudo-code compiler scripting language used in a commercial product).

But this just doesn’t seem like all that big a deal to have implemented and would have cleanly resolved where to put variables of both persuasions (ie those that are used by ONLY a single property, and those that are truly class scoped and used my multiple properties/methods).

Personally, I think this was a bit of a “good enough” decision.

Practically, I think I’ll stick with locating truly class-scoped variables at the top of the class, and ONLY bury variables that really are only accessed by a single method/property.

Public Interfaces

0
Filed under Rants, Software Architecture, VB Feng Shui

image I’m guessing that the vast majority of VB apps (VB6, or .NET), have no public interface. After all, VB is the prototyping language, not an actual project development language, right? </sarcasm>

Well, if you do eventually find yourself in need of exposing a public interface from your application, what’s the right way to do it?

First, a definition, though.

By public interface, I’m talking about some manner of exposing functionality within your application to the outside world. What you do with your BAS files, and private CLS files, not to mention FRM’s, or the all-encompassing  VB.NET *.VB file is your own business. But what the world sees, or, more appropriately, has to deal with, is another matter altogether.

There’s a pile o’ ways to expose functionality from an app, especially a VB app.

First, a few old school methods….

  • DDE – Yeah, it’s still alive. Want proof? Just boot up a copy of Vista, load REGEDIT and search for DDEEXEC in the HKCR hive. But seriously, you don’t want to go there.
  • Windows Messages – SendMessage/PostMessage. They’re not just for subclassing and peeping toms. DDE and even COM relies on the Window Messaging subsystem to work. Your apps can leverage it too. But really, that’s a whole lot of pain for not a lot of gain, so why bother?
  • DLL Entry Points – There are several tools out there that make exposing DLL entry points from a VB application relatively easy to do. In some cases, this is how you have to expose functionality (as in when some other app only makes calls to DLL entry points, and you want that app to talk to yours). But using this technique to expose your own functionality to the world just seems so, well, reagan-omic.

And the more common techniques….

  • COM – The classic VB PUBLIC class. All you gotta do is change your class’s interface type to PUBLICNOTCREATABLE, MULTIUSE, or GLOBALMULTIUSE and biff boom pow! Anyone can use your class, right?
  • .NET Assemblies – Same as COM, but shinier

OK, COM (or .NET assemblies) it is (I’m going to consider the two the same for now, what sacrilege!)

So how do you do it?

There’s piles of books on the mechanics of the task, so I’m not going there. Just look at Kurata’s Doing Objects in Visual Basic 2005 or Dan Appleman’s various books on ActiveX techniques for those kinds of details.

What I’m talking about is which interface design is most appropriate.

Kinds of Interfaces

Generally, I’ve seen these designs gather into 3 camps:

  • The massive wad of hierarchically related objects. This is the Outlook, Word or Excel object models.
  • The single public class containing all the functions you could ever want. Not terribly common; I see it most is 3’rd party utility libraries.
  • And finally, the single class with a single (or at most very limited set of) functions that serve as a “command line” of sorts where you might pass in a parameter that is the “operation” and that additional parameters that are the “arguments for the operation”. The GroupWise API is a great example of this.

The Massive Hierarchical Wad

Pros

  • Easiest to program against. Intellisense, separate objects, etc speed working with your model.

Cons

  • Hardest to keep compatible. Even minor changes from one version to the next of your application can render it incompatible with previous versions of your application, causing loads of headaches for your users.

The Single Class

Pros

  • Relatively easy to program against. Intellisense will help. So does consistent naming of all those methods and properties.
  • Works well for relatively minor amounts of exposed functionality.

Cons

  • Generally considered bad practice.
  • Suffers from compatibility headaches much like the Massive Wad.

The Command Line Class

Pros

  • Essentially immune from direct interface compatibility issues, since the lone interface is flexible enough initially to never have to be changed.

Cons

  • More difficult to code against. Intellisense only helps with the command line function itself, not with each of the commands that can be sent.
  • Moves compatibility issues to the user. If new functions are introduced, it’s the users responsibility to test for the version of the application and react appropriately.

So, what’s the final word?

As usual, there’s really no right answer.

Personally, I tend to stay away from the “Single Class”, simply because it has all the difficulties associated with the Massive Wad, and none of the immunity of the Command Line Class; the worst of both worlds.

The Command Line Class is most useful when your exposed function points number in the 10-100’s. Any more than that, and it starts to become unwieldy, though it’s still perfectly workable.

The Massive Wad is the most traditional, most familiar style of public interface to those who’ve likely used public interfaces of other systems. That’s not to say it’s the best option, just that’s it’s the most typical.

How about it? What kinds of public interfaces have you built and put out there? Is there a style I’ve missed. Maybe something better than any of these?

Reimagining MsgBox

0
Filed under Code Garage, Software Architecture, VB Feng Shui

Here’s a little trick I’m not sure many people know about, but that I’ve used for so long, I’d basically forgotten about it.

I wrote about overriding the ERR object in VB here, and also wrote a far more involved article about it.

But did you know you can also override the MSGBOX function?

Just create a function with the following signature in a BAS module of your choice:

Public Function MsgBox(ByVal Prompt$, Optional ByVal Buttons As VbMsgBoxStyle = vbOKOnly, Optional ByVal Title$ = "", Optional ByVal HelpFile$ = "", Optional ByVal Context As Long = 0) As VbMsgBoxResult

Put the appropriate code in it, and presto, custom msgbox.

Why, you might ask?

  • Personally, I like the option of logging every msgbox that’s displayed by my app. Errs are handier, but msgbox’s can be useful.
  • In addition, you have the option of calling the MessageBox API call directly yourself, which gives you access to additional flags that you can’t use with the VB version alone.
  • You might also decide that the MSGBOX proper just doesn’t look good, and craft up a modal, nicely skinned form to stand in its place.
  • And finally, in a particular app I worked on, I needed to call a function BEFORE and a different function AFTER displaying every msgbox. I certainly didn’t want to alter every call to MSGBOX and add those extra lines. Overriding it made that a trivial task.

Please let me know if you come up with any good uses for this trick.