July 2003 - Posts

I'm experiencing a weird bug -- weird because I can repro it on all my .NET v1.1 machines, even on various flavors of XP (Home, Pro, Tablet) but at least some other folks on the advanced-dotnet mailing list claim they can't.

And I tend to believe them, because I only found one reference to this in the archives, from way way back in v1.0 days -- and nobody claimed to be able to repro then, either!

Here's the code, if you'd like to play along...  just compile w/ csc.exe, and give it a run.
http://www.arithex.com/temp/SFDbug.cs.html

The story goes like this.

When SaveFileDialog.OverwritePrompt is set to 'true' (the default), the DialogResult returned to me by SaveFileDialog.ShowDialog() is "Cancel", even when I choose "yes" to overwrite an existing file.

Turning off the OverwritePrompt feature (uncomment the line of code, in the above sample) returns "OK" when an existing file is chosen.

The workaround is pretty easy -- just look at sfd.FileNames.Length, which is 0 when user answers "No" to the overwrite prompt, or 1 otherwise.

But I'm still interested in getting to the bottom of this bug.  Is there some obscure OS setting that I've tweaked, which may be causing this?  (I honestly haven't taken the time to repro on a truly clean XP box.)

I'm surprised by how many people don't know this trick...  but equally surprised by how obscure the UI is.

The Problem

Let's say you have a clever little bit of C# code you'd like to reuse, but it's something too trivial to warrant its building, supporting, and maintaining its own assembly.  Or perhaps the code is something proptrietary, and you'd like to obfuscate it as fully as possible -- which usually means not exposing it in any public types.  Or perhaps it's just a small set of compile-time constants that you'd like to bring into multiple assemblies.

As a trivially simple example, perhaps you get tired of specifying the same AssemblyCompany and AssemblyCopyright strings, over and over again, in all your projects?  Wouldn't it be nice to have a little two-line C# file tucked away somewhere, and include it by reference in all your projects?

[assembly: System.Reflection.AssemblyCompany("Arithex.com")]
[assembly: System.Reflection.AssemblyCopyright("© 2003 Shawn A. Van Ness. All rights reserved.")]

With C++ this is trivial -- you create a header file, and #include it from wherever you like.  But C# doesn't have a #include directive.  Or does it?

The Solution

No, it doesn't (sorry to tease ;-).  But Visual Studio .NET does offer a way to bring in C# code from a source file that's nowhere near the rest of the source files in your project directory -- it's not intra-file lexicographic inclusion, as in C++, but every C# source file can see every other C# source file in the build, so in practice there's not much difference!

Unfortunately, the UI to accomplish this task is pretty obscure -- which is probably why I run into so few developers who know about it.  It's so obscure, in fact, that I often find it easier to close VS and hack the .csproj file manually.  Here's what a <File> element might look like, for a C# project file (a .csproj file) that references a shared definition for my hypothetical AssemblyCompany and AssemblyCopyright strings, above:

   <File
      RelPath = "AssemblyCompanyAndCopyright.cs"
      Link = "..\..\AssemblyCompanyAndCopyright.cs"
      SubType = "Code"
      BuildAction = "Compile"
   />

Note that "RelPath" attribute is not, as you might think, a relative path to the file.  That would make sense!  Rather, it's the relative path of where the node will appear in the Solution Explorer treeview.  (This is the same "RelPath" attribute that you see on every other file in your C# project.)  The "Link" attribute is the interesting one -- that's the relative path to the actual file (relative to the .csproj file).  In Solution Explorer, the "linked' file will appear with an Explorer-style shortcut arrow emblazoned over its icon.

If you have some trepidation about hacking up your .csproj files, or if you absolutely, positively insist on using a mouse...  right-click your project in Solutions Explorer, and select Add Existing Item.  Browse to your shared C# source file, then click the unnoticeable little dropdown arrow next to the Open button.  See the ctxmenu item entitled "Link File"?  That's your man.

The Catch

(There's always a catch.)  For anything more complex than assembly-level attributes, one must think carefully about duplicating C# code across multiple assemblies.  Consider the following hypothetical SharedConstants.cs file...

using System;
namespace MyCompany {
internal class SharedConstants {

    public const string Hello = "Hello";
    public const string World = "World";
    }
}

This will produce a new, different type named MyCompany.SharedConstants in each and every assembly you "include" it in.  If the constants are large, or if the sanctity of SharedConstants' type-identity is important to you (imagine, perhaps, defining an enum this way, then later developing methods and properties which attempt to pass parameters of that type across assembly boundaries) then this approach may be undesirable -- or it may flat out break.

In that case, your only real option is to build a shared assembly which defines the constants (or enumeration values, etc), then ref that assembly in your other projects.  And that is, of course, what the founding fathers of .NET intended.

I love the productivity of WinForms as a UI development platform, but I hate all the little inconsistencies between the various controls', and their standard Windows counterparts.  A couple of examples:

  • The RichTextBox control's scrollbar, which seems to be the new XP style regardless of how all the other controls in my application feel about the matter.
  • The ContextMenu control, which does not respond to right-clicks for the purpose of selecting a menu item (with some trackballs, and tablet pens, etc, it's not so easy to follow a right-click with a left click).

Plus, there are a great many more trivial inconsistencies that are probably best attributed to differences in the WinForms Designer's default settings (think: the curious default font point size of 8.25, new/different grid alignment, the use of pixels instead of DLUs, etc).

But one of my biggest pet peeves is the TextBox control, in password-mode.  XP uses some kind of small, black, very cool looking circle character -- while .NET's TextBox control continues to use the old-style "*" asterisk character.

I Google'd around a bit, and the small bit of info I could find on this matter suggested I use TextBox.Font = "Wingdings" and TextBox.PasswordChar = 'l' (that's a lowercase Latin L, or U+006C).  Sure enough, that looks right!

But is it?  I mean, oughtn't there be a way to let the OS handle this detail, for me?  What if, for example, Longhorn uses a different password character, by default?  (Maybe Wingdings' U+005F? ;-)  I'd like to write code today that looks right, in both places -- without hardcoding aspects of the UI like this.

The Platform SDK docs for ES_PASSWORD seem to indicate that Windows XP, given an app with a manifest to load v6.0 of the common controls dll, will handle this.  But it doesn't seem to, for the .NET TextBox control.  Just like RichTextBox and ContextMenu, the TextBox control window is completely different window class than the "standard" Windows common controls.

Why did the architects of WinForms reinvent so many wheels?  I'm sure they had their reasons...  but I'd love to know what they are.

The code for my O'Reilly Network article on UdpClient.JoinMulticastGroup is broken under v1.1!

To workaround the unhandled SocketException crash on the client-side, when running under the v1.1 runtime, simply uncomment the two lines in UdpRendezvous.cs which handle the SocketException (lines 331-332).

Explanation:  Although this exception behavior has been documented since v1.0 days, in practice I found that v1.0's UdpClient.Receive() would throw an ObjectDisposedException, instead.  I commented out the SocketException handler, so we would all be alerted to this change/fix, if it ever happened.  Guess what?  It happened!

Also note:  the IPAddress.Address property is officially deprecated in v1.1, and you'll see a handful of compiler warnings alerting you to this fact.  (The Address property attempts to represent the IP address as an Int64 value, but IPv6 uses an address space of 128 bits.)  My code used the Address property merely as an optimization for comparing/hashing the HostResponse structure.  It was not necessary -- changing the code to avoid the warnings is trivial.

Updated .zip file for Visual Studio 2003 and .NET v1.1 can be found here:
http://arithex.com/UdpRendezvous.zip

Cheers,
-S

It's funny how quickly Visual Studio's Intellisense feature was transformed from the 6th generation's "omigod, quick, disable it before it crashes your IDE" to the 7th generation's "must-have, can't-possibly-live-without" IDE feature.

Sure, it's not too hard to imagine what clean, new languages like C# and VB.NET, with full-fidelity type information and whatnot, have lent to its stability and overall usefulness... but I'm positively flabbergasted by how well (and quickly!?) it processes a mass of C++ header files, representing decades of global-scope namespace pollution, to present me a popup list of tokens when I type ::.  It makes me a bit sad that I don't do much C++ coding, anymore.

The best news is, it keeps getting better!  Let's take a moment to extoll the virtues of VS 7.1's cool new Intellisense features, shall we?

Starting small, my favorite improvement is that VS seems to remember which member of a type I selected last -- and preselects that item in the list, rather than the (somewhat arbitrary) member which happens to come first, alphabetically.  Put simply, this means never having to hit the down-arrow key, to get at Console.WriteLine instead of Console.Write.  So subtle, but so beautiful.  I can just feel the mileage on my internal Carpal Tunnel odometer rolling backward...!

Not impressed?  In C#, try typing the override keyword, in the scope of a class implementing a WinForms UserControl.  You'll be presented with a popup list of public/protected virtual functions in the base class(es), which can be overriden!  Even better, it demonstrates that the VS team is finally starting to get into the business of statement autocompletion, by emitting (a) the right parameter list for the virtual method, (b) open- and closing-curly braces for the method body, and (c) a call up to the base class's implementation.  Sweet.  More, please!

The enhancements to C#'s event-subscription autocompletion are arguably even more impressive.   (Just don't fall into the lazy habit of subscribing to your own events -- remember, they made overriding easier, too!)  After typing the += operator, you're presented with a popup statement-autocompletion-tooltip-prompt-thing (not sure what to call it).  Press [Tab] and you'll get a completed event-subscription statement -- instantiating a new delegate of the appropriate type, around a member function named according to the bog-standard button1_Click convention.  Press [Tab] again, and VS will actually stub out a new, private member function by the same name, automagically!  Wow.

And this brings me to my rant...  I hate that naming convention.  I like all my methods, private event handlers or otherwise, to start with an uppercase letter -- which in turn should be the first letter of an english language verb, indicating the method's purpose.  For .NET event handlers, I've been using names like HandleButton1Click.  This gives all my event handlers alphabetic locality, followed by a modicum of locality based on the name of the member they're subscribing to...

But I am rapidly facing the reality that IDE productivity is more important than my own anal retentive hangups.  I was willing to manually change the event handlers generated by the WinForms designer... not sure I have it in me to manually change the ones I'm keying in, manually, to begin with!

Heck, I gave up preferring spaces over tabs, once I saw what an excellent job VS 7.0's editor did with tabs (the default).  Chris Sells may still disagree with me on that one...  but even though I'm willing to incur his disapproval on that matter, I'm still left feeling that if I let the bugs, features, and quirks of Microsoft's latest IDE dictate my coding style, I'm somehow less of a person...  less of an artist, anyway.

Alan Cooper says we're craftsman, not artists.  I suppose he's right.

More Posts