Category Archives: .NET

Controlling MSBuild Verbosity Within the Output Window In Visual Studio

0
Filed under .NET, MSBuild

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.

Multi-Line String Literals

0
Filed under .NET, VB Feng Shui, XML

VB has long been my favorite development language, but, as is typical, even it can definitely be improved in innumerable ways.

One aspect of VB that has always bothered me is string literals. Since VB treats EOL as significant, you can’t have strings that wrap across multiple lines in the environment. This is especially problematic when you deal with SQL queries, report formatting strings, and the like. For instance, this is generally what you see when you have to deal with long SQL queries in VB code.

Dim q as String = "Select *" & vbcrlf
q &= "From MyTable" & vbcrlf
q &= "Where MyColumn = 1"

Now, you can do a few formatting tricks and clean it up a little, but even then, it’s going to be cumbersome to work with.

One workaround has always been to embed these long, multi-line literals in a Resource file. The VS 2008 IDE even goes so far as to allow you to enter multi-line strings in the resource editor, although you have to use Ctrl-Enter to get a new line:

image

If you look at the RESX file for the above resource, you’ll see something like this:

image

All this is certainly workable, and, in many cases, isolating strings to a resource file (of some sort) can be a really good thing, but in other cases, it’s just a pain.

In VB10, Microsoft is finally doing what VB should have done all those years ago. They’re adding “intelligent line continuation” which is to say, you can continue a line in certain syntactic situations simply by continuing the code on a new line. No need for the line continuation underscore char, etc.  It won’t necessarily work everywhere (esp in places where it the code would end up ambiguous), but hey, that only makes sense.

In the meantime, you can actually have multi-line string literals in VB.Net without too much pain.

The trick is to use XML Literals. Generally, I’m no fan of XML literals. Once they get peppered with variable references, embedded For loops and other code, they become just so much spaghetti code, albeit with a modern twist.

But, they can be handy for multi-line string literals.

Dim q as String = <String>Select *
From MyTable
Where MyColumn = 1
</String>.value

So what’s going on here? First, I dim the result variable Q as a string (technically, this isn’t necessary as VB can infer the type, but you get the picture).

Then, I declare the XML Literal with the <String></String> tags. Everything between those tags (even across lines), gets treated as part of the string, no quotes or line continuation needed.

Finally, since the result of the <String></String> tags is going to be an XElement object, and what I want is a string, I need to invoke the value property of the XElement object to retrieve its string value.

And there you go.

But, as with anything good, there are a few caveats.

  1. line breaks AND whitespace are preserved in the string, which means if you indent those lines, you’ll get space characters where you might not have wanted them.
  2. if you want to include certain characters that are illegal in XML (for instance, the “<” or “>” characters), you’ll need to escape them.

Handling Escape Characters

One option you have for handling escape characters is to use the XML escape sequences, as in:

Dim q as String = <String>Select * as My&gt;Test&lt;var
From MyTable
Where MyColumn = 1
</String>.value

The &gt; and &lt; will be replaced with > and < respectively. You can escape any arbitrary character with &#nnn; where nnn is the ascii code of the character.

But this is code, dammit, not XML, and, at least for me, I’d much rather see normal C-style escape sequences (\r\n anyone <g> ) here than have to muck with XML “&” sequences. If we’re talking about web pages and HTML, that might be a different story, though.

At any rate, you can have your cake and eat it too, as it turns out.

Just feed that string through System.Text.RegularExpressions.Regex.Unescape to convert those sequences, hence:

Dim q as String = System.Text.RegularExpressions.Regex.Unescape(<String>Select * as MyTestvar \r\n
From MyTable
Where MyColumn = 1
</String>.value)

Notice how the XElement.value is simply passed through to the Unescape method to process that \r\n into a normal carriage return/linefeed pair.

Using that, you can now easily embed C-style escaped multi-line strings in your VB code.

But wait, that Unescape call is fairly nasty to look at and use. Wouldn’t it be nice if that was more or less automatic? Well, with a nice Extension Method, it can be.

<System.Runtime.CompilerServices.Extension()> _
Public Function UnEscaped(ByVal v As XElement) As String
    Return System.Text.RegularExpressions.Regex.Unescape(v.Value)
End Function

.....

Dim q as String = <String>Select * as MyTestvar \r\n
From MyTable
Where MyColumn = 1
</String>.Unescaped

First, define an Extension Method that would apply to the XElement object. This is a very simple method that just takes the associated XElement object, retrieves it’s value, and runs that string through the Regex Unescape method.

Since the extension method extends the XElement itself, you can now simply call the Unescaped method directly on the long string definition (the very last line in the code above).

You can’t get much cleaner than that. Or can you? Let me know!

Using TLBIMP.exe to create Strong Named Interop Assemblies

2
Filed under .NET, Utilities

Working on a VSTO 8 Word addin recently, I discovered to in order to deploy it, you must “strong name” the assembly.

I’d already obtained a Verisign certificate for digital code signing, and had a password protected PFX file containing both my public and private keys, so I figured I had what I needed.

So, I loaded the project, set the signing options to use my PFX file and compiled.

Full stop.

“Interop.Word.dll” is not strong named

Doh.

A little research later and it turns out that if you strong name one assembly, every single assembly that it references must also be strong named!

Yikes. In my case, I have almost a dozen, plus piles of interop assemblies for accessing legacy COM libraries.

First, the .NET assemblies. Setting them to be strong named using the same PFX file is easy, just use VS, under the project properties.

image

But, if your .net code refers to any COM components, it’s very likely you also have references to “interop” assemblies. These are DLLs that VS creates for you “behind the scenes” to act as convenient .net wrappers around the COM library objects.

The problem is that VS won’t automatically strong name those interop assemblies, and if you strong name your .net assembly, those interop assemblies also must be strong named.

First, I tried just creating an interop assembly.

I chose the Word 2000 object library to initially play with, but just about any COM dll or TLB or OLB file will do.

tlbimp "{path}\msword9.olb" /verbose /out:Interop.Word2000.dll /sysarray

(note, {path} is the path to your copy of this file, and the /sysarray option appears to be required if your working with just about any COM based interface). Worked like a champ.

So, I add in the keyfilename argument:

tlbimp "{path}\msword9.olb" /verbose /keyfilename:mycert.pfx /out:Interop.Word2000.dll /sysarray

and TLBIMP responds with an “Incorrect strong name arguments specified” error.

I then tried the PUBLICKEY option, like so:

tlbimp "{path}\msword9.olb" /verbose /keyfilename:mycert.pfx /out:Interop.Word2000.dll /sysarray

This appeared to work, until I actually tried to run my application. It failed, indicating that the “Strong Named Assembly was corrupt or was not signed with a private key”. Apparently, PUBLICKEY means just that, that the public key is used for one part of the signing process, and that you have to use a private key for the other.

I really didn’t want this to be a two step process so I kept looking.

Eventually, I discovered that signing an assembly is a completely separate process from “Strong Naming” an assembly.

Strong Naming an assembly doesn’t guarantee that you wrote it, but it does provide a way for one assembly to guarantee that it is loading the exact same assembly that it was compiled to work with, a subtle but significant difference.

Signing an assembly, on the other hand, requires a certificate from a signing authority, like Verisign. Signing does guarantee that an assembly was written by who the certificate says it was written by.

From what I can tell with VS2008, signing an assembly automatically strong names it, but the reverse is not true.

And certain assemblies need to be signed, (such as the primary assembly for an Office Addin), but others don’t.

And still others need to be strong named, but don’t necessarily need to be signed (such as any assembly or interop assembly referenced by an Office Addin.

But when you do this, you might notice that you end up with another unexplained dll in your output folder, called Office.dll.

After some digging, it turns out that TLBIMP will also create interop files for those COM libraries referenced by the COM library that you’re creating an interop file from. And you’ll need to include those additional interop files with your app for everything to load properly.

This is where the /reference option comes in. First, generate a strong named interop file for the referenced COM library, and then for the COM library you actually care about, like so:

tlbimp "{path}mso9.dll" /out:Interop.Office2000.dll /sysarray /verbose /keyfilename:mycert.snk 
tlbimp "{path}msword9.olb" /out:Interop.Word2000.dll /sysarray /verbose /keyfilename:mycert.snk /reference:Interop.Office2000.dll

The first line will create the Interop file for the Office.dll, which is referenced by Word.

The second line will create the interop file for Word itself, but note that it uses the /reference option to indicate that this interop file should reference the just created interop file for the Office library.

Finally, a note about namespaces. You’ll notice that I specified an OUT filename of Interop.Word2000.dll.

TLBIMP appears to automatically assume that when you do that, you want the classes defined within that interop file to exist within the “Interop.Word2000” namespace (ie the file name minus the “.dll”).

image

This may be appropriate, but it may not be, depending on your needs. Why might you want to change it? The only reason I can think of offhand is that you are referencing multiple versions of a single COM interface. A common example would be the need to reference both the Office 2007 Primary Interop files (available directly from Microsoft), and the Office 2000 interop files you created yourself (because MS doesn’t provide them).

It is possible that the two Interop files would have the same namespace and would collide.

Changing the namespace of one of them will allow your assembly to reference both interop files with no collisions. This is one element of .net that is, at the same time, unfortunately complicated, but incredibly useful.

.NET Interop and InstallShield

0
Filed under .NET, Installations

I’ve got a VSTO Com Addin for Word that I’m building an installation for. In the past, the addin had been handled via a DOT file, which is effective, but isn’t really the best way to build addins for Word, especially not now with VSTO.

At any rate, the product is working great, so I was putting an installation together.

I marked the particular assembly as .NET Interop, and when I built, IS complained that it couldn’t get the interop info from the assembly.

image

Huh? After some digging, turns out that InstallShield runs REGASM.EXE with the /REGFILE: switch to extract the COM registry information from the assembly, and then saves that in the MSI.

So, I tried running REGASM on my dev machine with the assembly.

REGASM MyName.MyAsm.dll /REGFILE:TestFile.reg

Worked perfectly.

So I tried it on my IS2009 box with the same assembly.

RegAsm : error RA0000 : Could not load file or assembly ‘Extensibility, Version=
7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ or one of its depe
ndencies. The system cannot find the file specified.

Hrm?

After scratching my head for a bit, I realized that my assemblies were compiled with .NET Framework 3.5, and that they definitely use some of the 3.5 specific features. It’d make sense that not having .NET 3.5 on the IS box could cause this.

So, a painstakingly lengthy .NET 3.5 install later, and I try the same regasm on my IS box.

No joy. Grrr.

So, I do what I should have done in the first place, open the project in Visual Studio and check the references. Sure enough, it points to an Extensibility.dll from the VSTO installation. Duh.

So, I go and install VSTO (to the InstallShield box, another grrr…)

No problems there, but still no joy.

Another look in the project and this Extensibility.dll lives with the VSTO V9.0 stuff. If I’m not mistaken, that means it’s an Office 2007 thing, not 2003 (which just happens to be the version of Office on the IS box).

Ok, maybe if I install the Office 2007 Primary Interop Assemblies?

image

More Grrr…

At this point, things are just irritating. I don’t want to install Office 2007 on what should ideally remain a more or less pristine dev box for doing install builds, so what next.

I know where this Extensibility.dll lives on my dev machine, I’m just not sure how REGASM looks for it, the GAC maybe, a search path (not likely), or some other arcana?

Screw it, I grab a copy of Extensibility.dll off my dev machine and copy it to the same folder as the DLL I’m trying to run regasm on.

RegAsm : error RA0000 : Could not load file or assembly ‘Microsoft.VisualStudio.
Tools.Applications.Runtime.v9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToke
n=b03f5f7f11d50a3a’ or one of its dependencies. The system cannot find the file
specified.

Aha! Similar message, different DLL! Now we’re getting somewhere.

Grab a copy of this “‘Microsoft.VisualStudio.Tools.Applications.Runtime.v9.0.dll” file off my dev machine, copy it to the same place and regasm again.

Repeat for another two dlls and REGASM will finally run through with no errors.

I’ve already had to pull TLBs and OLBs from Office installs for reference purposes in the application (for use on some dev boxes that don’t have either Office 2003 or 2007 installed), so grabbing a few more dlls for referencing purposes isn’t a big deal; they don’t have to be shipped with the application (they come with either VSTO or Office itself), and they can be checked into version control just like anything else.

In the end, I’m still not entirely convinced VSTO is a good way to build commercial addins for Office applications. It’s fine for quick and dirty internal projects, but it just seems to add a lot of additional complexity to releasing a commercial app. It’s harder to build a non-VSTO addin, but the end results may justify the extra effort.

Your mileage may vary.

Shared Class Members or Modules?

0
Filed under .NET, Rants, VB Feng Shui

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!

Simplified VB.Net Configuration

0
Filed under .NET, Code Garage

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<g>.

  • 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!

What’s next with Reflector

0
Filed under .NET, Utilities

One of my favorite .NET tools is about to transition in an unknown way.

“After more than eight years of working on .NET Reflector, I have decided it is time to move on and explore some new opportunities.”

So begins the email I just received from Lutz Roeder, the man behind Reflector, an almost scarily powerful tool for .NET development.

Eight years? Wow. Just doesn’t seem that long. But I can completely understand the desire to move on.

Unfotunately, the following passage gives a little clue as to what might be next.

“Red Gate will continue to provide the free community version and is looking for your feedback and ideas for future versions.”

Ah… So there will be a community version and then something else. The thing is, Reflector seems to be one of those tools that’s incredibly useful, to an incredibly small audience. And almost useless to everyone else. A bit like the SysInternals tools I’ve mentioned before. So successfully “productizing” it might be a pretty good challenge.

The thing is, even for me, and even though Reflector has been quite useful, I’m not sure I’d have paid for it, to be brutally honest.

But I have to applaud Mr. Roeder for what he’s accomplished and congratulate him in moving forward.

Tooltips for Disabled Controls

3
Filed under .NET, Code Garage, VB Feng Shui

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?<g>)

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

Another VS Programming Font

1
Filed under .NET, Fonts

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.

No More Simulated Multithreading

0
Filed under .NET, VB Feng Shui

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….