Tuesday, February 02, 2010

image I’ve been playing recently with the amBX ambient effects library and devices.

Essentially, it’s an effects platform that allows you to easily create light, wind and rumble effects to coordinate with what’s going on on-screen, in a game, in media players, or what-have-you. The lighting effects are nothing short of fantastic. The rumble and fan effects…. meh. They’re interesting, but I’m not sure where that’ll go.

Regardless, the API for amBX is all C style objects, which means essentially an array of function pointers to the methods of the object; not really an API that VB.net likes to consume. Be sure to grab the amBX developer kit here. You’ll also need to core amBX software available from the main website. Finally, play around with the “Virtual amBX Test Tool”. It’ll allow you to experiment with all the amBX features and functions without having to buy anything at all. Of course, eventually you’ll want to get at least the starter kit, with the lights. But it’s not necessary to begin experimenting.

Hats off to Philips for making this possible!

image

Here’s an example of the structure definition from the amBX.h header file that comes with the API (I’ve clipped out comments and other bits):

struct IamBX {
    amBX_RESULT (*release) (struct IamBX * pThis);
    amBX_RESULT (*createLight) (struct IamBX * pThis,
                                amBX_u32 loc,
                                amBX_u32 height,
                                struct IamBX_Light** ppLight);
....

As you can see, each element in the structure is made up of a pointer to a function. The various functions all take, as their first argument, a pointer back to this structure. Under the covers, this is how virtually all object oriented languages function, it’s just the the compiler usually hides all this nasty plumbing so you don’t have to deal with it regularly.

But this presents a problem. VB.net is “managed” code, and as such, it likes things to be wrapped up in nice “managed” bits. There are plenty of good reasons for this, but these function pointers are decidedly not nice and tidy managed bits! So, what to do?

One solution is the approach Robert Grazz took here. It’s a solid solution, no doubt, and I learned a lot from looking at his approach, but, this being VBFengShui, I wanted that same functionality in native VB.net with no additional dll files hanging around!

Delegates to the Rescue!

A delegate in .net is essentially a managed function pointer. In VB.net, they’re most commonly used to handle events, but you wouldn’t really know it, because VB’s compiler does a good job of hiding all that plumbing from you. However, unlike VB6 and earlier, in VB.net, all the plumbing can, if you’re willing, be “brought into the daylight” and used however you want.

There are many, many discussions about delegates for VB.net out on the web. A really good Introduction to the concepts by Malli_S is on codeproject, so I won’t rehash the basics here.

The problem is, delegates in VB.net tend to be generated by using the AddressOf operator, and I already had the function addresses (they’re in that C structure). I just needed to get a delegate that would allow me to call it.

Some Googling turned up several good posts that provided pointers, including this one on StackOverflow. But it wasn’t exactly all the steps necessary.

Getting the C Structure

The first step toward getting something working with amBX is to retrieve the main amBX object. You do this through a standard Windows DLL call.

<DllImport("ambxrt.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)> _
        Public Shared Function amBXCreateInterface(ByRef IamBXPtr As IntPtr, ByVal Major As UInt32, ByVal Minor As UInt32, ByVal AppName As String, ByVal AppVer As String, ByVal Memptr As Integer, ByVal UsingThreads As Boolean) As Integer
        End Function

Assuming you have the ambxrt.dll file somewhere on your path or in the current dir, the call will succeed, but what exactly does that mean?

Here’s an example of a call to it in C:

    if (amBXCreateInterface(
            &pEngineHandle, 
            majorVersion, minorVersion, 
            (amBX_char*)cAppName.ToPointer(), (amBX_char*)cAppName.ToPointer(),
            nullptr, false)
        != amBX_OK)

Essentially, you pass in the Major and minor version numbers of the Version of amBX you need, and two strings indicating the name of your app and the version of your app. No problems with any of that. But that &EngineHandle is the trick.

It means that this call will return a 4 byte pointer to a block of memory that contains the amBX object structure, which is, itself, an array of 4 byte function pointers to the various methods of the amBX object. Therefore, in VB.net, that argument is declared ByRef IamBXPtr as IntPtr.

Now, we need a place to store that “structure of pointers.” Going through the ambx.h file, I ended up with a structure that looks like this:

        <StructLayout(LayoutKind.Sequential)> _
        Private Structure IamBX
            Public ReleasePtr As IntPtr
            Public CreateLightPtr As IntPtr
            Public CreateFanPtr As IntPtr
            Public CreateRumblePtr As IntPtr
            Public CreateMoviePtr As IntPtr
            Public CreateEventPtr As IntPtr
            Public SetAllEnabledPtr As IntPtr
            Public UpdatePtr As IntPtr
            Public GetVersionInfoPtr As IntPtr
            Public RunThreadPtr As IntPtr
            Public StopThreadPtr As IntPtr
        End Structure

That StructLayout attribute is particularly important. It tells the compiler that the structure should be layed out in memory JUST AS it’s declared in source code. Otherwise, the compiler could possibly rearrange elements on us. With Managed Code, that’d be no problem, but when working with unmanaged C functions, that would not be a good thing!

And finally, we need a way to retrieve that function pointer array and move it into the structure above, so that it’s easier to work with from VB.net. That’s where the System.Runtime.InteropServices.Marshal.PtrToStructure function comes in. This routine will copy a block of unmanaged memory directly into a managed structure.

Private _IamBX As IamBX
Private _IamBXPtr As IntPtr
...

amBXCreateInterface(_IamBXPtr, 1, 0, “MyAppName”, “1.0”, 0, False)

_IamBX = Marshal.PtrToStructure(_IamBXPtr, GetType(IamBX))

And presto, you should now have the _IamBX structure filled in with the function pointers of all the methods of this C “Object”.

Calling the C Function Pointer

At this point, we’ve got everything necessary to call the function pointer. Take the CreateLight function. The C prototype for this function is shown at the top of this page. As arguments, it takes a pointer back to the amBX structure, 2 32bit integers describing the location and height of the light source, and it returns are pointer to another structure, in this case, an amBX_Light structure, which contains another set of function pointers, just like the amBX structure described above.

First, we need to declare a delegate that matches the calling signature of the CreateLight C function we’ll be calling:

<UnmanagedFunctionPointer(CallingConvention.Cdecl)> _
Private Delegate Function CreateLightDelegate(
         ByVal IamBXPtr As IntPtr, _
         ByVal Location As Locations, _
         ByVal Height As Heights, _
         ByRef IamBXLightPtr As IntPtr) As amBX_RESULT

The key points here are:

  1. Make sure you have the CallingConvention attribute set right. Most C libraries will be CDECL, but some libraries are STDCALL.
  2. Make sure your parameter types match, especially in their sizes. Not doing this will lead to corrupted called or system crashes.

Now, create a variable for the delegate and use the Marshal.GetDelegateFromFunctionPointer function to create a new instance of the delegate, based on the applicable function pointer. Since the function pointer for the CreateLight function is stored in the CreateLightPtr field of the _IamBX structure, we pass that in as the pointer argument.

IMPORTANT NOTE: There are 2 overloads for the Marshal.GetDelegateFromFunctionPointer  function. Be sure to use the one what requires a second argument of the TYPE of the delegate to create. Using the other one will fail at runtime because it won’t be able to dynamically determine the type of delegate to create.

Next, call the function via the delegate, just as if it was a function itself.

Finally, use Marshal.PtrToStructure again to copy the array of function pointers returned into another structure, this one formatted to contain the function pointers of the Light object that just got created.

Dim d As CreateLightDelegate = Marshal.GetDelegateForFunctionPointer(_IamBX.CreateLightPtr, GetType(CreateLightDelegate))
Dim r = d(_IamBXPtr, Location, Height, _IamBXLightPtr)
_IamBXLight = Marshal.PtrToStructure(_IamBXLightPtr, GetType(IamBX_Light))

The amBX library is much, much larger than just this little bit I’ve shown here. Once I get the entire thing coded up, I’ll be presenting it here as well.

But in the meantime, if you have a need to interface with C style function pointer objects, don’t let anyone tell you it can’t be done in VB.net

Final Note

As you may or may not have guessed, amBX is a 32bit library and its interfaces, functions and arguments all live in 32bit land. If you’re working with VS2008, BE SURE to set your project to the x86 platform, and NOT “Any CPU” or “x64”! The default, for whatever reason is “Any CPU”, which means things will work properly when run under 32bit windows, but things won’t work well at all if run under a 64bit OS.

posted on Tuesday, February 02, 2010 10:37:20 AM (Central Standard Time, UTC-06:00)   •  # •  Comments [1] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, January 28, 2010

At one point a while back (while I was still working for my previous company), we had “support for x64” handled down as a request for an interim version.

I thought, “No sweat”. Grab a few alternative prerequisite MSIs, determine the bitness of the processor you’re on in the install (InstallShield has the functionality built in), and just fire off the appropriate preqs. Our actual application is 32bit, and we had no intention or need to compile as 64bit.

Well. A few clicks later and the install was built and deployed to a test machine.

Crash.

Initial sample database didn’t get deployed properly.

Ok, why not. Well. Long story short, the application couldn’t find a registry key setting. But I hadn’t changed anything about the registry.

Lots of digging through verbose install logs later, and I discovered that the installer was, on a 64bit OS creating registry keys under the key  HKEY_CURRENT_USER\Software\Wow6432Node whereas when my application when to get the registry key, it was looking in the normal HKEY_CURRENT_USER\Software\ key.

Huh?!

First, what the hell was this Wow6332Node? Obviously it’s some kind of “compatibility” bit for running 32bit apps under a 64bit OS. And that’s exactly what it is.

Ok, my Installer is InstallShield, and it’s a 32bit app (even if it can install 64bit packages), so that explains why it was writing the key to that Wow6432Node, but why wasn’t my application reading it from there?

A little digging later, and I found this option buried in the “Advanced Compiler Settings” panel of the “Compile Options” tab of the Project’s properties.

image

See that AnyCPU setting? When set to AnyCPU, the JIT compiler will dynamically compile your .net dll as either a 32bit or 64bit dll, depending on the process that loaded it.

Obviously, on a 32bit OS, all the processes will be 32bit, so everything’s 32bit.

BUT, on a 64bit OS, things get nastier.

If the DLL runs under a native code 32bit process, it’ll get compiled as 32bit x86 code and run under the Wow32 (Windows On Windows) layer. This explains why the DLL that acts as an AddIn to Word worked just fine. Word is a native 32bit process, so the DLL was ending up executing as 32bit too.

However, the little utility app that creates our initial database was a .net application, and it was set to AnyCPU.

That meant that when IT loaded our DLL, the DLL ended up JITed to 64bit code (because the utility app was set to AnyCPU, so it was JITed to 64bit code because it was a base process and was running under a 64bit OS.

Case closed.

Short version of the story. Since tracing all this down, I’ve learned that for VS2008, the .net team decided to change the default of that option from x86 to AnyCPU, and then, in general, that has been regarded as a bad thing to have done.

The default will be changed BACK to x86 in 2010 apparently, but until then, unless you REALLY need to compile code for AnyCPU, be sure to switch this option back to x86!

posted on Thursday, January 28, 2010 2:06:36 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Tuesday, January 05, 2010

If you haven’t already checked it out, definitely head over to Tom’s hardware for their article on setting up “GodMode” in Windows 7 (apparently it works in Vista too, dang, wish I’d known that).

Basically, you create a folder somewhere, then rename it to

GodMode.{ED7BA470-8E54-465E-825C-99712043E01C}

When you do, that folder becomes a “virtual folder” full of (on my system anyway) 278 links to virtually every administrator type function you might ever want to jump to on a Windows Machine. Here’s a sample screenshot:

image

Very, very cool indeed.

posted on Tuesday, January 05, 2010 11:18:15 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, December 31, 2009

If you haven’t checked it out already, DirectShowLib.net is pretty much THE way to get at direct show functionallity in VB.net (that is unless you want to spend $$$). Thing is, it’s only a paper thin wrapper over the DirectShow COM api stuff, so there ain’t no hand holding here.

I’d mucked with it for a while and with the help of some of the sample code, got video playing in a form in VB.net fairly easily, but, I needed to flip and/or rotate that video under some circumstances.

After many googles, I finally came across the IVMRMixingControl9 interface that the VideoMixingRenderer9 exposes, but, no matter what I did, I could not cast from a VMR9 to the MixingControl, like so:

Dim Mixer = DirectCast(VMR9, IVMRMixerControl9)

I kept getting an “Interface not implemented”. Then I happened across a post about a wholly different problem, but buried within it was a comment about needing to set the VMR into “mixing mode” in order for it to implement that IVMRMixingControl9 interface. Ugh! DirectShow is nothing if not interfaces. And odd dynamically implemented interfaces at that. Oh well.

A little more digging, and it turns out to be quite easy. Just obtain the IVMRFilterConfig9 interface from your VMR9 object, and call SetNumberOfStreams on it (with and argument 1 or more).

The end result is code that looks like this (remember, this is with a reference to DirectShowLib.net):

DIM FGM = New FilterGraph
Dim VMR9 As IBaseFilter = New VideoMixingRenderer9

FGM.AddFilter(VMR9, "Video Mixing Renderer 9")
Dim FC As IVMRFilterConfig9 = VMR9
FC.SetRenderingMode(VMR9Mode.Windowed)
FC.SetNumberOfStreams(1)

Dim Mixer = DirectCast(VMR9, IVMRMixerControl9)
Mixer.SetOutputRect(0, New NormalizedRect(1, 1, 0, 0))
Mixer.SetAlpha(0, 0.2)

Note that in this case, the SetOutputRect is reversing the output rectangle, so the video is going to end up flipped and upsidedown, exactly what I was needing. I’ve also set the alphachannel to .2, meaning the video is somewhat transparent.

Unfortunately, there doesn’t appear to be any way to rotate the video using the MixingControl, so that is my next topic of research.

posted on Thursday, December 31, 2009 12:11:22 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [1] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Sunday, November 22, 2009

A long (long) time ago, if you wanted your users to be able to enhance your product by add coding logic, you pretty much had two choices:

  • Dig into Lex and Yacc or....
  • Roll your own parser and scripting execution engine.

Then, around the time VB 6 was released, Microsoft introduced VBA. It worked, but it was expensive and a god awful complicated mess to integrate with your app, so outside of Microsoft Office (which even as of Office 2010, still supports it!), very few applications actually made use of it.

Around the same time, the Microsoft Scripting Control was released. This was an ActiveX control you could use from any COM compatible language to embed VBScript. Now, of course, this is VBScript, not a "real" language like C or even VB. But, it was free, easy to obtain, and very easy to use.

As the years have rolled by, .NET has steadily grown in capability and while the Windows Scripting Host exe is still shipped with Windows, the old VBScript and JScript scripting has largely been supplanted by either:

  • ASP and more recently ASP.NET
  • Powershell
  • Dynamically compiled .NET code
  • and likely lots more options I'm not familiar with

Now, all these options are good, and each definitely has it's place, but, for many purposes, good ol' VBscript still fits the bit quite nicely.

But can you use it from .NET? And if so, how?

The Tempest in the Teapot

Before I continue, I should point out that any time you start talking about allowing users to add their own code into your application, you're opening up a huge can of worms that you'll need to deal with. Things like properly catching exceptions from badly written user code (yeah, that never happens), adding watchdog timers to deal with hung and infinitely looping user code, security concerns, and the like all will have to factor into the decision. You have been warned \:\-\) .

So Simple It Hurts

I had a plugin project recently where I wanted to give the user the ability to write some very simple logic to expand the functionality of the application.

Of course, I wanted to build a full plugin API, but I also wanted to provide a lighter-weight option for those users that didn't want to dive head-first into a full on .NET project. VBScript seemed like the obvious choice.

A few Google searches later and most everything I found was talking about how to dynamically compile .NET code, which is cool indeed, but not what I was really after. Other articles and posts insisted that users should convert their VBScript code to .NET (ok, yeah, but that's not really the point now is it?).

Then I did a Google Desktop search of my own system and turned up some VB6 code I'd written ages ago to experiment with the Microsoft Scripting Control.

Now, I know that .NET languages support accessing ActiveX Controls and COM objects in general, but this was pretty old stuff. Would it actually still work?

I cobbled up a dirt simple VB.net project:

Public Sub Main()
    Dim Script = New MSScriptControl.ScriptControl
    Script.Language = "VBScript"
    Script.AddCode("sub Main" & vbCrLf & "MsgBox ""This is a Test"" " & vbCrLf & "End Sub")
    Script.Run("Main")
End Sub

Be sure to add a reference in the VB.net project to the COM object "Microsoft Scripting Control".

Run it, and lo and behold, I get the "This is a Test" message box, straight from VBScript!

Obviously, there's lots more to making use of the Scripting Control than just this, but, clearly, VBScript is still very much a choice for adding limited programmable logic to your application.

posted on Sunday, November 22, 2009 11:31:44 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Monday, July 20, 2009

image I've been working with .NET and Office interoperability for some time now, and, other than a few minor hiccups here and there, things have generally been very smooth.

But a colleague recently ran into a problem that required a good deal of hunting to resolve. Long story short, even though .NET interoperability with Office objects is virtually foolproof, releasing your references has some hidden dangers lurking amongst the weeds.

As a general rule, it's safe to let the .NET garbage collector (GC) do it's thing while your application is running.

BUT, when you're app shuts down, you need to take a few extra steps to make sure everything's cleaned up before quitting.

The details are laid on in this article on MSDN. It's from 2005, but, from what I can tell, it's just as applicable now as it was then.

Essentially, the trick boils down to this:

When you're preparing to shut your application down, you need to make SURE you've released all your references to any Office objects. This means setting all your various references to Nothing, or, more typically, letting the GC do its thing.

Unfortunately, the GC doesn't necessarily do its thing during a shutdown, so you have to force the issue. And that requires code essentially like this:

WordApp.Quit() 
WordApp = Nothing 
GC.Collect() 
GC.WaitForPendingFinalizers() 
GC.Collect() 
GC.WaitForPendingFinalizers() 

The article noted above doesn't really go into why it's necessary to call Collect and WaitForPendingFinalizers twice, but it does appear to be required. I'm guessing it's due to the way the Finalizer sweeps object references; it could be possible for it to release objects in a certain order, causing it to skip some objects. But that's just a guess.

And as a final note, if your .NET code uses any COM interoperability, it might be a good idea to do some final cleanup along these lines as well.

posted on Monday, July 20, 2009 9:32:52 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Saturday, July 11, 2009

I'm trying to automate the building of an MSI file using InstallShield's "Standalone Build Manager" via MSBuild, and one nicety I was hoping to implement was integrating the output of the InstallShield build log into the results of MSBuild.

Originally, I'd used an MSBuild EXEC task to fire up ISCmdBld.exe to perform the build, which works just fine, but which writes the log file to a more or less arbitrary location within the InstallShield project folder structure.

After a bit of Googling, I noticed that the Microsoft.SDC.Tasks MSBuild Extension Library had an InstallShield task as a part of it. Great! Just use that to perform the compilation, and I'm all set!

Unfortunately, that was not quite the case.

Once I'd downloaded the files, extracted them to the proper locations, and modified the Microsoft.Sdc.Common.Tasks file to point to the right place, I was still getting an error when I attempted a build:

Message              = Object reference not set to an instance of an object.

After some investigation, including browsing the source on the codeplex site, I figured out at least part of the problem:

        protected override void InternalExecute()
        {
            #region Execute code

            Directory.CreateDirectory(buildpath);

            RegistryKey installShieldKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\InstallShield\Developer\7.0");

The registry subkey referenced in the task points to an InstallShield 7 key, and I'm using IS 2009. <Sigh>

At this point, I'm back to using the EXEC task directly. Not optimal, but it does work.

posted on Friday, July 10, 2009 11:02:39 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, July 10, 2009

I've build working a lot recently with MSBuild, specifically in relation to automating the build process for our companies products. In my case, our product is mostly .NET, with some VB6 projects still hanging around, and of course some database bits and InstallShield and DemoShield projects to wrap things up for a disc image. Pretty typical commercial product build process.

At any rate, I've been experimenting with an MSBuild project type that I could embed in my solution to control all this, not only from the IDE, but also automatically get hooked in with the TFSBuild process, when I really started needing a way to the the diagnostic logging level of the build process when MSBuild is run from within Visual Studio to perform the build.

Normally, VS runs MSBuild with a minimal logging level, so you really don't see that much in the Visual Studio Output window. I wanted to alter that.

I'd been looking pretty fruitlessly until I came across Sara Ford's blog here. Turns out, there's an option in Visual Studio's Options Window to do just this! It's not Project specific, unfortunately, so if you set it to diagnostic and then compile a large project, the Output Window will be huge.

But still, nice to know it's there.

posted on Friday, July 10, 2009 10:59:53 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Monday, October 27, 2008

I ran into a chunk of code today that threw me.

The code was contained within a particular class definition.

Every member (properties and methods) were marked Shared.

Public Shared Sub MyMethod()
blah....
End Sub

The net effect was a class that, even if you could instantiate it, wouldn't have any purpose as in instance. Its sole use was to provide essentially a dumping ground for a bunch of general purpose library routines.

At first, I just wrote this off as odd and continued on.

But, on further reflection, this just doesn't seem right. As Scott Hanselman might say, it's got a rather nasty Code Smell.

So, I did what any reasonable person would do; I googled it.

Apparently, there are more than a few people out there contemplating the same thing. What's scary though, is that usually, the arguments end up along these lines:

  1. Modules in VB.Net are a throwback, a hack, and a nod to compatibility and should be avoided.
  2. Modules can't be accessed from other .NET languages.
  3. Other .Net Languages don't have modules, so you're better off using shared members on a class definition.
  4. Modules aren't OOP. Classes are. Use classes.

It's already been proven that, internally, VB.Net modules compile to exactly the same code as a Class with shared methods. Further, the modules have been documented as a first class feature of VB.Net, the language, not some soon-to-be-deprecated nod to VB6 compatibility. And Modules CAN be accessed from other languages. So I'm not going to bother with any of those bits again.

Personally, I think in comes down to two simple concepts, syntactics and semantics.

The Syntactics

Syntactically, defining members in a Module allows them to be accessed without  qualifying the member with the module name. So, given this Module definition:

Public Module MyModule
   Public Shared Sub MyMethod()
      blah....
   End Sub
End Module

I can access the method MyMethod by simply invoking the name MyMethod(); I don't have to qualify it, as in MyModule.MyMethod().

This is a minor issue, except that it effectively allows you to extend the VB language. You can add your own RIGHT() function or LEFT() function, etc. This can be a fantastic aid to code readability when used appropriately.

The Semantics

However, I believe the even bigger argument is semantics. A class, is, by its very definition, the definition of an object that can be instantiated. Even singleton classes can be instantiated at least once. But a class of shared methods is a class that has no reason to be instantiated. And that really isn't a class, it's just a set of loosely related, if even related at all, functions. And that is the definition of a Module!

Do you regularly create an integer type variable when what you really need is a DATE? Then why collect a bunch of shared functions in a class when a module is specifically intended for such purposes?

Discoverability

The only rational I can come up with is discoverability. Forcing developers to prefix shared methods with the class name provides a means (through Intellisense) of discovering the capabilities of that class. But, you can optionally prefix module members in exactly the same way, so again, shouldn't we be using the features of the language as they're intended?

Why Shared Members at all?

Given this, what benefit is there of using shared members at all?  I've seen a few examples for defining singleton objects where you retrieve the single instance via a shared member of the class. That certainly makes logical sense, but from a coding perspective, it seems ugly to me. Why ask the class for the single available instance of itself? Shouldn't you ask something else for that instance? It also makes for some odd-reading code:

Dim TheOneInstance = MySingletonClass.GetTheOneInstance

And other than singletons, what other purpose is there for shared members?

(UPDATE 10/29/08: After further experiments and reading, it turns out that, as far as I can tell, the ONLY way to truly get a singleton object is to use a shared method within the object itself. Nasty, but that appears to be the only way to do it).

I know the .NET framework uses them extensively, but are they used effectively?

For example, take the String class. It exposes quite a few shared members, such as Compare and Copy. But in practice, using those shared members doesn't quite make sense. If I'm comparing two strings, the object-oriented thinker in me naturally gravitates to:

If OneString.Compare(OtherString) Then

Not

If String.Compare(OneString, OtherString) then

The bottom line is, if I'm comparing two strings or copying a string, I'll always be working with an instance of a string, so why shouldn't those methods be instance methods and not shared methods?

I suppose String.Format() is a passable example, but even that would seem to make more sense as:

dim x = "Insert a parameter here {0}".Format(Arg0)

Do you have a good use for shared methods that you'd like to, uh, share?

Let me know!

posted on Monday, October 27, 2008 9:22:45 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Tuesday, September 16, 2008

image I really liked the idea of a built-in configuration management framework with .NET. That is, until I actually tried to use it.

I wrote about the configuration functions in .NET here. My specific words were:

If your sideline utility needs to save a few settings, use the .NET configuration...

Ugh, I'm sorry I said that...

What's Wrong

The .NET configuration namespace is powerful, no doubt about that. But, that comes at a huge cost. The thing is enormous. And the available documentation and examples just aren't that good.

Honestly, though. The docs don't bother me that much. What bothers me is that even though the existing framework will probably do everything I want it to, discovering how is just too dang hard and non-intuitive.

For instance:

  • Why the hell should I have to create a new ExeConfigurationFileMap object just to change the path of where the system reads its config file? Sorry, but that's about as intuitive as a car you accelerate by yodeling.
  • What on earth were they smoking when they came up with those ludicrous guid/hash/hex based folder names where your user level config files are stored by default? Facilitate upgrades? Yeah, maybe, but pretty much nothing else. That's one that I guarantee will go down with the registry and DCOM as a bad idea.
  • Registering ConfigurationSection handlers? Huh? Why should I have to write the full class names of classes internal to my application into my configuration file so that .NET can read them?

What Would Be Nice

What I was looking for was a simple way to create a class like so:

Public Sub MySettingClass
   Public Setting1 as string = ""
   Public Setting2 as Integer = 0
End Class

No need to explicitly use properties unless you need them. No need for attributes. Heck, you shouldn't even have to declare the class <Serializable>, though that's a minor point.

In lieu of coexisting within the My.Settings space, it should be able to persist itself to a config file, and then de-persist itself back when asked, like this:

Settings = New MySettingsClass
Settings.Load
...
Settings.Save

Accessing your settings should be completely early bound, with all that sweet Intellisense goodness baked right in:

x = Settings.Setting1
y = Setting.Setting2

Further, I should be able to easily persist sub-objects or collections made accessible off this root settings object. For example, to save a form's current position and size, and then restore it, should take code similar to:

Settings.FormPositions.Save(MyForm)
Settings.FormPositions.Restore(MyForm)
Debug.print "Form position is " & Settings.FormPositions(0).Position

And a few additional requirements.

  • First, having no initial configuration file shouldn't be a problem. The entire collection of settings should easily default to some "built in" default values when no config file exists.
  • Second, I should be able to go from 0-60 in no time. In other words, I should be able to take the .VB file for a settings base class, drop it in my application, add a settings class with the properties I need to persist, as well as a .LOAD and a .SAVE at the appropriate points in my project, and be off. No "presetting" my config file, no tweaks to anything, no registering this or that, mucking with the GAC, etc, etc.

Research

The available Microsoft documentation on the configuration system was so confusing, I believe I knew less about it after I finished reading the docs than I did when I started.

I did turn up a very good article on CodeProject by Jon Rista called Cracking the Mysteries of .NET 2.0 Configuration. Definitely worth a read if you're diving into this stuff.

While Jon gives several samples of code, nothing really illustrated exactly what I was looking for. However, there was more than enough info in the article to kick-start things for me.

Long story short, it turns out that the Configuration system in .NET is, in typical MS fashion, more than capable but ultra-overkill for many small-app type scenarios.

My Solution

While this is definitely still a work in progress, it's proved quite useful so far, so I thought others might find it handy too.

The system consists of one file, SettingsBase.vb. It defines two classes, SettingsBase and SettingsBaseDictionary.

SettingsBaseDictionary is just a simple extension to the normal generic Dictionary class that allows it to be serialized. This is something I found on the web and is so handy with respect to settings that I just include it directly in the file.

SettingsBase is a MustInherit class, meaning it's abstract. To use it, you must create your own settings class (call it whatever you like) that inherits from SettingsBase:

Public Class Settings
   Inherits SettingsBase

   Public Name As String = ""
   Public Phone As String = ""
End Class

When you want to load your settings, just instantiate your Settings object and invoke Settings.Load. To change settings, set the object's properties as you normally would.

To save your settings, invoke Settings.Save.

Finally, you'll need to add a reference to System.Configuration. Directly accessing the ConfigurationManager and EXEConfigurationFileMap objects requires it.

The sample project I've zipped up shows several examples of this, from ridiculously simple to moderately sophisticated. I even threw in a really simple example of DataBinding to a setting property (in this case, a Dictionary of contacts).

The Code

If you don't want to download the sample, I've included the source to the SettingBase.VB file here. Note that this also includes the source to the SettingBaseDictionary, but if you don't want it, you can simply delete it.

Imports System.Configuration
Imports System.IO
Imports System.Text
Imports System.Xml
Imports System.Xml.Serialization


''' <summary>
''' Base Class that will allow you to easily persist
''' a "settings" object via the .net configuration management framework
''' 
''' By Darin Higgins
''' Sept 2008
''' You are free to use this class in your own projects.
''' But please, keep the attributions as to the source.
''' </summary>
''' <remarks>
''' Be sure to add a reference to System.Configuration
''' </remarks>
''' <editHistory></editHistory>
Public MustInherit Class SettingsBase
#Region " Constants"
   '---- just some names of constants used in the class
   Private Const DEFAULTSETTINGFILENAME = "Settings.config"
   Private Const ROOTSECTION = "general"
   Private Const ROOTITEM = "settings"
#End Region


#Region " Properties"
   Private rFilename As String = DEFAULTSETTINGFILENAME
   ''' <summary>
   ''' The Settings filename (name only, no path)
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   Public Property FileName() As String
      Get
         Return rFilename
      End Get
      Set(ByVal value As String)
         rFilename = value
      End Set
   End Property


   Private rCompanyName As String = My.Application.Info.CompanyName
   ''' <summary>
   ''' The CompanyName used when creating a path to the settings store
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   Public Property CompanyName() As String
      Get
         Return rCompanyName
      End Get
      Set(ByVal value As String)
         rCompanyName = value
      End Set
   End Property


   Private rAppName As String = My.Application.Info.ProductName
   ''' <summary>
   ''' The AppName used when creating a path to the settings store
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   Public Property AppName() As String
      Get
         Return rAppName
      End Get
      Set(ByVal value As String)
         rAppName = value
      End Set
   End Property


   ''' <summary>
   ''' Retrieves the full path and filename to the settings store
   ''' Normally \Docs and Settings\All Users\Application Data\CompanyName\AppName\Settings.config
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   Private ReadOnly Property pAppConfigFilename() As String
      Get
         '---- Don't use My.Computer.FileSystem.SpecialDirectories 
         '     because the commonappdata folder returned will always have
         '     the version in it and it will automatically be created
         '     but I don't want that here
         Dim path = System.Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)

         '---- as is pretty standard practice, our app settings
         '     go in a CompanyName\Appname folder in the CommonAppData folder
         If Me.CompanyName.Length > 0 Then
            path = System.IO.Path.Combine(path, Me.CompanyName)
            If Not My.Computer.FileSystem.DirectoryExists(path) Then
               My.Computer.FileSystem.CreateDirectory(path)
            End If
         End If
         If Me.AppName.Length > 0 Then
            path = System.IO.Path.Combine(path, Me.AppName)
            If Not My.Computer.FileSystem.DirectoryExists(path) Then
               My.Computer.FileSystem.CreateDirectory(path)
            End If
         End If

         Dim Filename = System.IO.Path.Combine(path, Me.FileName)
         Return Filename
      End Get
   End Property


   ''' <summary>
   ''' Creates an ExeConfigurationFileMap object to properly
   ''' locate the config files we'll use
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   Private ReadOnly Property pAppConfigMap() As ExeConfigurationFileMap
      Get
         Dim filemap = New ExeConfigurationFileMap
         filemap.ExeConfigFilename = Me.pAppConfigFilename
         Return filemap
      End Get
   End Property


   ''' <summary>
   ''' Creates a Configuration object mapped to 
   ''' the proper settings files
   ''' Sets up several internal setting elements and sections
   ''' so we can persist the host object
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   Private ReadOnly Property pConfig() As Configuration
      Get
         Static cfg As Configuration

         '---- cache the config object so we can reuse it
         If cfg Is Nothing Then
            cfg = ConfigurationManager.OpenMappedExeConfiguration(Me.pAppConfigMap, ConfigurationUserLevel.None)
         End If
         If Not cfg.HasFile Then
            '---- force a file to be created
            '     This settings is just general purpose placeholder
            '     not really intended to be used
            cfg.AppSettings.Settings.Add("version", My.Application.Info.Version.ToString)
            cfg.Save()
         End If

         Dim bDirty As Boolean = False
         If cfg.HasFile Then
            '---- no need for groups in this case
            '     but this sample code illustrates how you'd create a ConfigGroup if necessary
            'If cfg.SectionGroups(ROOTSECTION) Is Nothing Then
            '   cfg.SectionGroups.Add(ROOTSECTION, New ConfigurationSectionGroup)
            '   bDirty = True
            'End If
            'If cfg.SectionGroups(ROOTSECTION).Sections("options") Is Nothing Then
            '   cfg.SectionGroups(ROOTSECTION).Sections.Add("options", New ClientSettingsSection)
            '   bDirty = True
            'End If
            Dim sect As ClientSettingsSection = cfg.Sections(ROOTSECTION)
            If cfg.Sections(ROOTSECTION) Is Nothing Then
               sect = cfg.Sections(ROOTSECTION)
               cfg.Sections.Add(ROOTSECTION, sect)
               bDirty = True
            End If
            Dim element = sect.Settings.Get(ROOTITEM)
            If element Is Nothing Then
               element = New SettingElement(ROOTITEM, SettingsSerializeAs.Xml)
               sect.Settings.Add(element)
               bDirty = True
            End If
            If element.Value.ValueXml Is Nothing Then
               '---- make sure element contains something
               element.Value.ValueXml = New Xml.XmlDocument().CreateElement("value")
               bDirty = True
            End If

            If bDirty Then cfg.Save()
         End If
         Return cfg
      End Get
   End Property


   ''' <summary>
   ''' If you would like to use the built in ConfigurationStringsSection
   ''' just add a readonly property that exposes this property
   ''' Doing it this way, you can expose this property anywhere you want
   ''' in your Setting object heirarchy.
   ''' 
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   Protected ReadOnly Property ConnectionStrings() As ConnectionStringsSection
      Get
         Return pConfig.ConnectionStrings
      End Get
   End Property


   ''' <summary>
   ''' Since this is essentially a key/value pair collection
   ''' there's not much benefit to exposing it, but I've included it
   ''' for completeness.
   ''' 
   ''' These kinds of sections are particularly useful when config merging
   ''' is used heavily, but that's not the point of this base class
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   Protected ReadOnly Property AppSettings() As AppSettingsSection
      Get
         Return pConfig.AppSettings
      End Get
   End Property
#End Region


#Region " Methods"
   ''' <summary>
   ''' Persist the host object to the configuration file
   ''' </summary>
   ''' <remarks></remarks>
   Public Sub Save()
      With Me.pConfig
         '---- not using configgroups right now, but keeping this for reference
         'Dim sect = TryCast(.SectionGroups(ROOTSECTION).Sections("options"), ClientSettingsSection)
         'If sect.Settings.Get(ROOTITEM) Is Nothing Then
         '   element = New SettingElement(ROOTITEM, SettingsSerializeAs.Xml)
         '   element.Value.ValueXml = New Xml.XmlDocument().CreateElement("value")
         '   sect.Settings.Add(element)
         'Else
         '   element = sect.Settings.Get(ROOTITEM)
         'End If
         Dim sect = TryCast(.Sections(ROOTSECTION), ClientSettingsSection)
         Dim element = sect.Settings.Get(ROOTITEM)

         '---- Create a serializer to serial our superclass
         Dim s = New XmlSerializer(Me.GetType)
         Using ms = New MemoryStream
            '---- serialize it
            s.Serialize(ms, Me)
            '---- rewind and convert the stream to a string
            ms.Seek(0, SeekOrigin.Begin)
            Dim myutf As UTF8Encoding = New UTF8Encoding()
            '---- load it up into an xml doc
            Dim xml = New XmlDocument
            xml.LoadXml(myutf.GetString(ms.GetBuffer()))
            '---- and push into the settings "value"
            '     stripping out the XML header stuff
            element.Value.ValueXml.InnerXml = xml.DocumentElement.OuterXml
         End Using

         '---- Force the save, because we won't otherwise trigger
         '     the dirty condition
         sect.SectionInformation.ForceSave = True
         .Save()
      End With
   End Sub


   ''' <summary>
   ''' Reload the superclass's properties from configuration
   ''' </summary>
   ''' <remarks></remarks>
   Public Sub Load()
      With Me.pConfig
         Dim sect = TryCast(.Sections(ROOTSECTION), ClientSettingsSection)
         Dim element As SettingElement = sect.Settings.Get(ROOTITEM)

         '---- if we've got the required element...
         If Len(element.Value.ValueXml.InnerXml) Then
            '---- deserialize the xml
            Dim s = New XmlSerializer(Me.GetType)
            Dim myutf As UTF8Encoding = New UTF8Encoding()
            Using ms = New MemoryStream(myutf.GetBytes(element.Value.ValueXml.InnerXml))
               '---- just get a generic object
               '     and we'll use reflection to 
               '     update the properties and fields of THIS object
               Dim o As Object = Nothing
               Try
                  o = s.Deserialize(ms)
               Catch ex As Exception
                  Debug.Print("Problem")
               End Try

               If o IsNot Nothing Then
                  '---- now need to refresh our values from 
                  '     this deserialized object
                  For Each Field In Me.GetType().GetFields
                     If Field.IsPublic Then
                        '---- BaseSettings has no fields, so 
                        '     we don't need to check if the field
                        '     is defined the the base object
                        Try
                           Field.SetValue(Me, Field.GetValue(o))
                        Catch
                        End Try
                     End If
                  Next

                  '---- now copy over any properties
                  For Each Prop In Me.GetType().GetProperties
                     '---- first, check that the property
                     '     isn't one of the BaseSettings properties
                     '     we don't want to depersist those!
                     Dim n = Prop.Name
                     Dim query As IEnumerable(Of System.Reflection.PropertyInfo) = Me.GetType.BaseType.GetProperties.Where(Function(Prop2) Prop2.Name = n)
                     If query.Count = 0 Then
                        '---- this is not a property on BaseSettings
                        If Prop.CanWrite Then
                           If Prop.GetIndexParameters.Count = 0 Then
                              '---- handle non-indexed properties
                              Try
                                 Prop.SetValue(Me, Prop.GetValue(o, Nothing), Nothing)
                              Catch
                              End Try
                           Else
                              '---- not handling indexed properties yet
                           End If
                        End If
                     End If
                  Next
               End If
            End Using
         End If
      End With
   End Sub
#End Region


#Region " SettingsBaseDictionary"
   ''' <summary>
   ''' A simple serializable dictionary class I pulled off the web.
   ''' 
   ''' Originally at http://www.playswithcomputers.com/SGDCollection.aspx
   ''' 
   ''' I've included it here because very often settings collections
   ''' need to be keyed for access, and lists/bindinglists (which will
   ''' persist just fine) don't make that especially straightforward
   ''' like a dictionary.
   ''' 
   ''' However, you can also use a generic List or BindingList for properties
   ''' and they seem to be persisted just fine, they just aren't quite 
   ''' as easy to perform keyed lookups on.
   ''' 
   ''' </summary>
   ''' <remarks></remarks>
   ''' <editHistory>
   ''' </editHistory>
   <XmlRoot("dictionary", IsNullable:=True)> _
   Public Class SettingsBaseDictionary(Of TKey, TValue)
      Inherits Generic.Dictionary(Of TKey, TValue)
      Implements IXmlSerializable

      Private Const ITEMNAME = "item"
      Private Const KEYNAME = "key"
      Private Const VALUENAME = "value"

      Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements IXmlSerializable.GetSchema
         Return Nothing
      End Function


      Public Sub New()
         MyBase.New()
      End Sub
      Public Sub New(ByVal capacity As Integer)
         MyBase.New(capacity)
      End Sub
      Public Sub New(ByVal comparer As System.Collections.Generic.IEqualityComparer(Of TKey))
         MyBase.New(comparer)
      End Sub
      Public Sub New(ByVal capacity As Integer, ByVal comparer As System.Collections.Generic.IEqualityComparer(Of TKey))
         MyBase.New(capacity, comparer)
      End Sub
      Public Sub New(ByVal dictionary As Generic.IDictionary(Of TKey, TValue))
         MyBase.New(dictionary)
      End Sub
      Public Sub New(ByVal dictionary As Generic.IDictionary(Of TKey, TValue), ByVal comparer As System.Collections.Generic.IEqualityComparer(Of TKey))
         MyBase.New(dictionary, comparer)
      End Sub
      Public Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext)
         MyBase.New(info, context)
      End Sub


      ''' <summary>
      ''' Read a Serialized XML Dictionary of generic objects
      ''' </summary>
      ''' <param name="reader"></param>
      ''' <remarks></remarks>
      Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements IXmlSerializable.ReadXml
         Dim keySerializer As XmlSerializer = New XmlSerializer(GetType(TKey))
         Dim valueSerializer As XmlSerializer = New XmlSerializer(GetType(TValue))
         Dim wasEmpty As Boolean = reader.IsEmptyElement
         reader.Read()
         If wasEmpty Then Return

         Do While (reader.NodeType <> System.Xml.XmlNodeType.EndElement)
            reader.ReadStartElement(ITEMNAME)
            reader.ReadStartElement(KEYNAME)

            Dim key As TKey = DirectCast(keySerializer.Deserialize(reader), TKey)
            reader.ReadEndElement()

            reader.ReadStartElement(VALUENAME)
            Dim value As TValue = DirectCast(valueSerializer.Deserialize(reader), TValue)
            reader.ReadEndElement()

            Me.Add(key, value)

            '---- finish reading this element and move to the next
            reader.ReadEndElement()
            reader.MoveToContent()
         Loop
         reader.ReadEndElement()
      End Sub


      ''' <summary>
      ''' Write the XML Serialization of a dictionary of generic objects
      ''' </summary>
      ''' <param name="writer"></param>
      ''' <remarks></remarks>
      Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements IXmlSerializable.WriteXml
         Dim keySerializer As XmlSerializer = New XmlSerializer(GetType(TKey))
         Dim valueSerializer As XmlSerializer = New XmlSerializer(GetType(TValue))

         For Each key As TKey In Me.Keys
            writer.WriteStartElement(ITEMNAME)

            writer.WriteStartElement(KEYNAME)
            keySerializer.Serialize(writer, key)
            writer.WriteEndElement()

            writer.WriteStartElement(VALUENAME)
            valueSerializer.Serialize(writer, DirectCast(Me(key), TValue))
            writer.WriteEndElement()
            writer.WriteEndElement()
         Next
      End Sub
   End Class
#End Region

End Class

Points of Interest

  • Your settings file will, by default, be written to the CommonApplicationData folder, and in there, in a folder named the same as the CompanyName in your assembly information screen, and in there, in a folder named the same as the Application Name in the assembly information screen.

image 

So, for the above app, under Windows XP, you'll find the default setting file in

c:\Documents and Settings\All Users\Application Data\One Nifty Company\SettingsTest\Settings.config

and under Vista

c:\Program Data\One Nifty Company\SettingsTest\Settings.config

This is pretty standard practice for config files, but if you want to change it, you've got the source\:\-\) .

  • You can change the setting filename, the Company Name and the Product Name used by simply changing the associated  properties of your setting object (these properties are all inherited from the SettingsBase object).
  • Although you can use fields in your base Settings object, you won't be able to use any DataBinding support with them. This is a limitation of the .NET Databinding support, and not with the Settings class. Quite frankly, it sucks. Sometimes full blown properties make sense, but in this case, properties are just a lot more work for the same net effect.
    If you want to use Databinding with your settings class, you're probably best off declaring all persistent elements of your settings class as properties to begin with, and just avoid using fields at all.
  • The SettingsBase object doesn't even try to provide access to configuration "sections" or "section groups". You can easily break settings down into groups or sections by using "sub objects" off your main settings object (the one that inherits from SettingsBase). I illustrate this is the sample app.
  • There's no merging (at least not intentionally anyway). I'm still not completely sure of how that works, and I haven't needed it yet. 
  • As I indicated earlier, much of this could be accomplished by creating a custom ConfigurationSection class, and registering it in your config file, but from what I can tell so far, that mean you have to manually add gunk to your config file. To me, that's got a code smell akin to that guy in the next cube that burns patchouli all day. It doesn't stink, per se, but it sure makes your eyes water if you're around it long enough.
<configSections>
    <section name="sampleSection"
    type="System.Configuration.MySectionHandler" />
</configSections>

 

What's Next

  • Really, this should be wrappable in a custom ConfigurationSection handler. My only gripes with that approach is that it doesn't resolve the folder naming problem, and it requires the goofy registration of the section handler. I'm betting there's ways around both those issues, though. I just haven't found them yet.
  • In the same vein, I'd really love to figure a way to accommodate what I'm looking for, AND still use the My.Settings namespace as it's currently automatically defined by Visual Studio. Again, I'm working on that, but this simple approach works for the short and sweet utility apps I've needed to turn out recently.

Check it out and let me know what you think!

posted on Tuesday, September 16, 2008 8:39:54 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, August 14, 2008

We recently got into a discussion where I work about the best way to explain to a user why a control is disabled.

One person argued that it made sense to leave the "disabled" controls enabled, and, when a user tried to click/use the control, pop up a messagebox explaining why it won't work in this instance.

To me, that seemed to go against everything I'd ever learned about UI design and controls, namely, if a control is disabled, it ought to look disabled on screen and it shouldn't do anything if you poke at it.

Still, I'd had plenty of experience myself with apps where controls were disabled and I had no idea why they were disabled, much less how to go about getting that functionality enabled. That can certainly make for a frustrating time.

Then someone suggested a tooltip or a status bar message. If you tried to click the disabled control, or hovered your mouse over it, you'd get a little, innocuous message somewhere telling you why that control was disabled.

Awesome idea!

Only one problem.

Tooltips don't work for disabled controls. Actually, they do for menus, toolbars and likely a few others, but that's another story.

And you don't get MouseOver events on disabled controls.

Sigh.

Well, I couldn't just walk away from this.

A quick google turned up a few bits:

  • There was this from Roy Auchterlounie, but it's MFC.
  • But then there was this nugget from a post by Linda Lui, apparently with MS Support. It's in C#, but it's relatively easy to translate to VB.

The only thing was, Lui's solution was not exactly what I'd call an encapsulated solution.  As Scott Hanselman would say, dropping snippets of code like this all over my forms just doesn't have a wonderfully fragrant code smell.

I'd messed around with Control Extenders under ASP.NET some time ago, and this seemed like the perfect excuse to try it out on a good ol' WinForms app.

A little refactoring later, and I've ended up with the "Disabled Tool Tip" Extender Control. It directly inherits from the out-of-the-box tooltip in VS2008. As a result, there's not a lot of code here. Also, it should work with VS2005, but I'm not guaranteeing as much.

Add this class to your project, recompile, then drop one onto a form.

Zip! Boom! Pow! Every control on your form should now have a "ToolTip on DisabledToolTip" property. You simply set this new property to the tooltip you want to show when the control is disabled and you're done.

image

(my test rig, she is much fine, no?\:\-\) )

A few notes about this class.

One significant issue I ran into immediately, was how do you retrieve a reference to the containing form if you're a component sited on that form. All the obvious stuff didn't work. There's gotta be a more straightforward way to do it, but I failed to find it, at least with respect to a Component type control (one of those that isn't actually sited ON the form, but rather in that little area at the bottom of the designer).

I ended up caching an instance of some control during the SetToolTip method, since this method is called during the form initialization by any control on the form that had a tooltip set for it via the designer.

   Public Shadows Sub SetToolTip(ByVal control As Control, ByVal caption As String)
      MyBase.SetToolTip(control, caption)

      '---- if we don't have the parent form yet...
      If rParentForm Is Nothing Then
         '---- attempt to get it from the control
         rParentForm = control.FindForm
         '---- if that doesn't work
         If rParentForm Is Nothing Then
            '---- cache the control for use a little later
            rControl = control
         End If
      End If
   End Sub

But, you can't use the FindForm method here, necessarily, because if the form is still being initialized, you'll get back nothing.

So, I ended up implementing the ISupportInitialize interface, and, during the EndInit method, if I have a cached control reference, I use it at this point to retrieve the parent form via FindForm.

   Public Sub EndInit() Implements ISupportInitialize.EndInit
      '---- if we weren't able to retrieve the form from the control
      '     before, we should be able to now
      If rControl IsNot Nothing Then
         rParentForm = rControl.FindForm
      End If
   End Sub

Roundabout, yes, but it seems to work in a very stable way, and it means I don't have to resort to MFC style subclassing and the like. I can just monitor events on the parent form via a simple WithEvents variable reference.

Anyway, the full code for the class is here. It's short enough that I'm not going to bother with a ZIP file at this point.

And finally, as with any code you pick up off the net, I'm making no guarantees of any sort. If it works for you, great. If not. Well, I'll certainly do my best to help if you let me know. It works for me, but it is necessarily full on, battle tested, bulletproof stuff? Uh. No.

Enjoy! If you see any improvements to be made, please share!

And please, if you post it elsewhere, give me (and Linda Lui) proper credit!

Imports System.ComponentModel

''' <summary>
''' Custom ToolTip Component that is based on a normal tooltip component but tracks tips 
''' for disabled controls
''' Note that the because this is a separate extender, all the controls on a form
''' can have an "Enabled" tip (as normal) AND a "disabled" tip.
''' 
''' By Darin Higgins 2008
''' Based on a code example by Linda Lui (MSFT)
'''
''' </summary>
''' <remarks></remarks>
''' <editHistory></editHistory>
Public Class DisabledToolTip
   Inherits ToolTip
   Implements ISupportInitialize

   '---- hold onto a reference to the host form
   '     to monitor the mousemove
   Private WithEvents rParentForm As System.Windows.Forms.Form

   Private _rbActive As Boolean = True
   ''' <summary>
   ''' Active for the Disabled ToolTip has a slightly different meaning
   ''' than "Active" for a regular tooltip
   ''' </summary>
   ''' <value></value>
   ''' <remarks></remarks>
   <DefaultValue(True)> _
   Public Shadows Property Active() As Boolean
      Get
         Return _rbActive
      End Get
      Set(ByVal value As Boolean)
         If _rbActive <> value Then
            _rbActive = value
         End If
      End Set
   End Property



   '---- hold on to a control temporarily while we wait for things to 
   '     settle
   Private rControl As Control

   ''' <summary>
   ''' Shadow the settooltip function so we can intercept and save a control
   ''' reference. NOTE: the form MIGHT not be setup yet, so the control
   ''' might not know what it's parent is yet, so we cache the the first control
   ''' we get, and use it later, if necessary
   ''' </summary>
   ''' <param name="control"></param>
   ''' <param name="caption"></param>
   ''' <remarks></remarks>
   Public Shadows Sub SetToolTip(ByVal control As Control, ByVal caption As String)
      MyBase.SetToolTip(control, caption)

      '---- if we don't have the parent form yet...
      If rParentForm Is Nothing Then
         '---- attempt to get it from the control
         rParentForm = control.FindForm
         '---- if that doesn't work
         If rParentForm Is Nothing Then
            '---- cache the control for use a little later
            rControl = control
         End If
      End If
   End Sub


   Public Sub BeginInit() Implements ISupportInitialize.BeginInit
      '---- Our base tooltip is disabled by default
      '     because we don't want to show disabled tooltips when
      '     a control is NOT disabled!
      MyBase.Active = False
   End Sub


   ''' <summary>
   ''' Supports end of initialization phase tasks for this control
   ''' </summary>
   ''' <remarks></remarks>
   Public Sub EndInit() Implements ISupportInitialize.EndInit
      '---- if we weren't able to retrieve the form from the control
      '     before, we should be able to now
      If rControl IsNot Nothing Then
         rParentForm = rControl.FindForm
      End If
   End Sub


   Public Sub New(ByVal IContainer As IContainer)
      MyBase.New(IContainer)
   End Sub


   ''' <summary>
   ''' Monitor the MouseMove event on the host form
   ''' If we see it move over a disabled control
   ''' Check for a tooltip and show it
   ''' If the cursor moved off the control we're displaying
   ''' a tip for, hide the tip.
   ''' </summary>
   ''' <param name="sender"></param>
   ''' <param name="e"></param>
   ''' <remarks></remarks>
   Private Sub rParentForm_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles rParentForm.MouseMove
      Static ctrlWithToolTip As Control = Nothing

      Dim ctrl = rParentForm.GetChildAtPoint(e.Location)

      If ctrl IsNot Nothing Then
         If Not ctrl.Enabled Then
            If ctrlWithToolTip IsNot Nothing Then
               If ctrl IsNot ctrlWithToolTip Then
                  '---- if we're not over the control we last showed
                  '     a tip for, close down the tip
                  Me.Hide(ctrlWithToolTip)
                  ctrlWithToolTip = Nothing
                  MyBase.Active = False
               End If
            End If
            If ctrlWithToolTip Is Nothing Then
               Dim tipstring = Me.GetToolTip(ctrl)
               If Len(tipstring) And Me.Active Then
                  '---- only enable the base tooltip if we're going to show one
                  MyBase.Active = True
                  Me.Show(tipstring, ctrl, ctrl.Width / 2, ctrl.Height / 2)
                  ctrlWithToolTip = ctrl
               End If
            End If

         ElseIf ctrlWithToolTip IsNot Nothing Then
            '---- if we're over an enabled control
            '     the tip doesn't apply anymore
            Me.Hide(ctrlWithToolTip)
            ctrlWithToolTip = Nothing
            MyBase.Active = False
         End If
      ElseIf ctrlWithToolTip IsNot Nothing Then
         '---- if we're not over a control at all, but we've got a
         '     tip showing, hide it, it's no longer applicable
         Me.Hide(ctrlWithToolTip)
         ctrlWithToolTip = Nothing
         MyBase.Active = False
      End If
   End Sub
End Class
posted on Thursday, August 14, 2008 9:45:42 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Saturday, August 09, 2008

I blogged about fonts quite a while back here.

But I thought I'd add a reference to a new font I just stumbled across by DamienG on his blog, called Envy Code R.

I especially like his italics as bold trick that enables you to have Italics comments in Visual Studio even though VS only has an option for bold, even in VS2008.

image Envy Code R Pretty good, but a bit compressed, note the small M. To me, the verticals are so close together, they blur a bit. It's also a tad taller than Consolas.

Even if you don't care for the font, his VS color schemes, particularly Humane, are definitely worth a try.

Very nice.

Consolas with his bold-italics trick would be a very nice VS2008 font indeed. Hmmm.

posted on Saturday, August 09, 2008 12:45:06 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, August 07, 2008

Way back in the wild west days of VB6, if you wanted a long running utility to ALSO pop up a progress bar, be cancelable, and be nicely encapsulated in a reusable class, you didn't have a lot of options.

The first was to actually use VB6's multithreading support via a thread per object ActiveX exe. It worked, but it wasn't a trivial exercise.

One trick I used on a number of occasions was the modal form callback.

Essentially, it works like this.

  • You call into a class to "start" the long running process.
  • The class opens an associated progress form, and passes itself to the form.
  • The Progress form stores a WithEvents reference to the class, so it can be notified of progress as the class does its work.
  • Then, the form shows itself modally, and in the Activate event, calls back into the class to actually start the processing.
  • The class then starts it's long running processing, raising progress events as appropriate, and yielding via DOEVENTS at appropriate times.

It's a tad complex, to be sure, but certainly less complex than some of the alternatives and it still yields a nicely responsive UI that the user can easily cancel out of.

Fast forward to today. I had a similar requirement for a little VB.NET utility, so I figured, what the hey, might as well use that old trick.

After a bit of coding, I run the app and am rewarded with this:

image

Huh? I wasn't "Attempting to call into managed code without transitioning out first." Heck, there wasn't even any API calls in this mix, Much less "Low Level extensibility points"!

Of course, the VS help for this message proved utterly useless, so I start googling.

Equally useless. Surely, I'm not the only guy out there to get this treat, right?

Anyway, eventually, I decided that maybe, just maybe, something about the reentrancy into the class from a modal dialog was causing the problem, though I still don't have concrete proof of that.

I reworked the class to cooperate with the BackgroundWorker component (a nifty little bit of new .NET goodness), and everything's back on track.

The only bit of nastiness left is that my background worker class raises several events, which my progress form monitors and presents, but doing anything within those events directly resulted in an exception about accessing UI objects from a thread other than the one they were created on.

The VS Help was helpful for this though, and adding code like this to the event handlers...

   Delegate Sub ScanningFileCallback(ByVal FileName As String, ByVal Count As Integer)
   Private WithEvents MyClass As MyClass

   Private Sub MyClass_ScanningFile(ByVal FileName As String, ByVal Count As Integer) Handles MyClass.ScanningFile
      If Me.prgProgress.InvokeRequired Then
         Dim d As New ScanningFileCallback(AddressOf MyClass_ScanningFile)
         Me.Invoke(d, New Object() {FileName, Count})
      Else
         me.prgProgress.Maximum = MyClass.FileCount
         me.prgProgress.Value = Count
      End If
   End Sub

worked around that problem nicely, although it'd be nice if the background worker class could somehow handle this transition itself. Hmmm....

posted on Thursday, August 07, 2008 10:11:18 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, July 24, 2008

I've always been a big fan of the MZTools addin for VB6, so it was a little disappointing, if not understandable, that for his .NET foray, he's wanting to get paid a little something for it.

Not that that's bad, I just haven't been able to commit to MZTools for .NET just yet.

In the meantime, I stumbled across a very slick little VS addin called DPack.

It's actually a small set of different addins. Doesn't look like you get the source, which is unfortunate, but the features are pretty slick, plus they work in VB, C++ and even C# and Ruby.

The Code Browser alone is worth the download. With one keystroke, you get a window like this:

image

It's nicely sorted, browsable, and with one other click, takes you directly to the given code element. Even better, you can assign direct keystrokes to filter only methods, classes, properties, etc. By default, ALT-M shows just the methods in the current file, for instance. Reminds me of the old F2 (Function list) key that used to be in older VB's. I missed that key<sob>.

Also particularly nice is the "Surround Selection With..." feature. You can't customize the surrounding text but they give you the most common items (surround with TRY CATCH, FOR NEXT, DO WHILE, REGION, etc, etc.), so that's not too bad.

It seems very quick and stable. And the best part is, it's completely free!

posted on Thursday, July 24, 2008 9:46:10 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Sunday, April 20, 2008

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.

posted on Sunday, April 20, 2008 3:24:11 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, March 13, 2008

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!

posted on Thursday, March 13, 2008 8:25:11 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, February 01, 2008

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.

posted on Friday, February 01, 2008 7:25:33 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Saturday, November 03, 2007

I've been trying to make a point of reading more .NET code lately.

Maybe it's Scott Hanselman's Weekly Source Code.

Or maybe it's that Code Project gadget on my Google home page.

At any rate, I just ran across a project there call LinFU (don't ask what it stands for), but...wow.

It reminds me of the Black Belt columns by Matt Curland ages ago in Visual Basic Pro (or whatever it was called then, now Visual Studio Magazine). There hasn't been articles like his in that rag for years. (and btw, if you still do VB6 and don't have his Advanced Visual Basic 6, I'd highly recommend getting a copy).

Anyway, it's stuff like Philip Laureano's LinFU that really is making .NET seem more and more attractive.

posted on Friday, November 02, 2007 11:10:14 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Wednesday, September 05, 2007

What's with this?

I've seen this term bandied about, usually referring to some new feature in VB, and usually in a disparaging way.

Why?

I thought pretty much everything about a 3 or 4GL was syntactic sugar. Without that sugar, we'd be coding in assembler or hex opcodes. I mean, an IF THEN ELSE construct is just syntactic sugar for a bunch of JNEs or JEQs, right? Hell, object oriented code is nothing but syntactic sugar for an array of type structures and function calls with an index as their first argument (the hidden ME pointer in all OO compiler code).

Sounds more like just so much L337ism to me.

I say bring it on. As long as it makes sense, expands the language in useful ways, and isn't required to make use of the language, the more the merrier. My only concerns would be:

  • Any additional syntax that's required in order to successfully use the language. That's VB.NET's biggest problem now, with respect to adoption by users of VB.
  • Compatibility breaking changes

Still, I think ANDALSO is a tad corny, nice, but corny. So shoot me.

posted on Wednesday, September 05, 2007 9:44:58 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [5] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, August 24, 2007

So, how useful do you find code snippets to be in VS2005? I mean, beyond the "I don't know how to do something, aha! here's an example!" use that would be better served up in expanded help file examples.

On the surface, I suppose they could be handy in some circumstances. For instance, setting up TRY CATCH blocks maybe, or....um... ok, having a hard time coming up with other examples. I suspect snippets are one of those gizmos that make you "feel" more productive without actually making anyone truly more productive. They seem like Stephen Colbert's answer to developer performance.

My problem with these kinds of macro code "generators" is that they just seem like so much "cut and paste" programming. If you've every had to work on code that was built by cut and paste, you know it's more fun to steam your eyeballs out of their sockets.

If a piece of code warrants being "set up" as a snippet, it probably ought to be encapsulated into its own function/sub/method/whatever, where it can be reused properly.

Here's a sample snippet for "iterating through a collection":

      ' Iterate through a collection
        For Each name As String In names

        Next

Now maybe I'm just being a curmudgeon here, but that comment, I'd have to redo, and the rest of it, I can type in less time than it'd take to find the snippet, esp with intellisense. Sure, I could also set up a shortcut for it, but even then, does this sort of thing really save time? Or rather, does it save real time?

You might make the argument that the snippets for creating properties can save time, but if I'm creating that many properties, that consistently, I'm guessing I'm going to use something like MyGeneration instead.

And in the end, a snippet might save a few keystrokes here and there, but, when you look at the entire life span of a project, I'd wager that even if you snorted snippets and peed the Quake physics engine, you'd be lucky to save yourself a few thousandths of a percentage point of the total time you spent coding.

Makes me wonder what the Freakonomics guys would say about them.

posted on Friday, August 24, 2007 7:27:53 AM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, August 16, 2007

The VS 2008 VB will support nullable types.

Simply put, a nullable type is a standard type, like, say, integer or string that can ALSO contain a null.

This can become very important in database work, because a null value in a field means that no value has been assigned to that field vs a common case where you might use 0 in a numeric field to indicate no value, or you might use a null string (ie a string with no length) in a string field.

Obviously, using 0 to indicate no value works much of the time, but, there often comes a point when you actually do need to use a 0 value for that field.  There's a number of other, more arcane reasons that using one of the values from the range of possible values of a type to indicate null is usually not a good idea.

Hence, nullable types. With a nullable type, I can have an integer variable that can be a 31bit positive or negative number, or 0, and STILL have yet another "state" of that variable that indicates it's null.

This will likely be incredibly handy for many many uses when working with database data.

But the syntax?

Dim a As Integer?

or

Dim a? As Integer

No, I'm not making that up.

What the hell? A question mark?

Why not just:

Dim a As Nullable Integer

I find this to be an unbelievably hypocritical decision given the almost overwhelming philosophical rants delivered by Microsoft about not using the traditional VB type specifier characters in .NET anymore (you know, $ for strings, % for integers, those bad boys). Heck, check page 42 of "Framework Design Guidelines" by Krzysztof Cwalina and Brad Abrams of Microsoft:

DO NOT use Hungarian notation (their bold, not mine)

Now, to be fair, the authors in this particular instance are discussing publicly available interfaces, but I've seen the same directive applied to internal coding as well. So, we're not suppose to use hungarian, and we're not supposed to use type characters, so we're left to simply scan the code to information about how a variable is used or what it likely contains. Jeez. How convenient.

posted on Thursday, August 16, 2007 4:33:15 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [3] • 
Kick it •  Add to del.icio.us •  View blog reactions;