Tuesday, September 16, 2008

Tomorrow night I have the opportunity to speak at the monthly Utah County .NET User Group meeting.  Here's a synopsis of the talk:

Delving Into the WinForms Control Designer Experience

 

The .NET framework makes writing WinForms controls accessible to any and all programmers.  In this session we’re going to focus on the control’s design-time experience; that is, what users of your controls will experience when writing software using your controls.  There is a rich set of classes and tools that can help you create slick, professional components that will make your users’ lives easier, but without knowing what they are or where to find them it can be a challenge.  Even sometimes when armed with the knowledge, it’s not always intuitive how to use the objects.  We’re going to discuss Editors, Extensions, Verbs, Action Lists, Designers, and more.  We’ll see what it takes to have your control actually ‘run’ at design-time to offer a rich, interactive experience for the end user.

 

If you write WinForms controls for the .NET platform, you won’t want to miss it!

The meeting will be held at the NuSkin building at 1175 S 350 E in Provo.  I look forward to seeing you there!

Tuesday, September 16, 2008 11:36:12 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Sunday, September 07, 2008

When granting database permissions to built-in security accounts you need to be aware of potentially localized account names.

Just this past week I ran into an issue when attempting to execute a SQL Server database script on a German server OS.  The database script in question had hard-coded the usage of 'NT AUTHORITY\NETWORK SERVICE' as the account name for granting database permissions.  As it turns out, however, this account doesn't exist in the German version of Windows.  Instead, the account is 'NT-AUTORITÄT\NETZWERKDIENST'.  I found it interesting that the account name is not localized on other non-English versions of Windows.

Unless I misunderstanding something, the documentation indicates that "the name of the account in all locales is NT AUTHORITY\NETWORK SERVICE" so I was initially confused by this.

In this particular case my script file is, conveniently, not executed 100% verbatim.  Via a little script engine that I wrote, I have the ability to update variables with runtime values.  In this case, rather than use the exact string 'NT AUTHORITY\NETWORK SERVICE', I replaced it with a variable and determined the value to use at runtime.

Resolving to the properly localized account name is pretty easy:

private static string getNetworkServiceAcctName() {
   SecurityIdentifier id = new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null);
   NTAccount acct = ( NTAccount )id.Translate(typeof( NTAccount ));
   return acct.Value;
}

What's quite interesting is after researching the issue and updating the application to properly resolve the account name, we stumbled upon a blog post made a couple of years ago that resolved the exact same issue.

Sunday, September 07, 2008 6:08:42 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Monday, September 01, 2008

I ran across some interesting (read "unexpected") behavior the other day when writing a .NET WinForms control.  This particular control has some logic that executes if and only if the host OS has visual themes enabled.

In order to check for the existence of enabled themes, I call a method called 'IsVisualStylesEnabled()' which is defined as below:

public static bool IsVisualStylesEnabled() {
   bool isEnabled = false;
   try {
      OperatingSystem os = Environment.OSVersion;
      // decide whether the OS even supports visual styles
      bool isSupported = ( os.Platform == PlatformID.Win32NT )
                         && ( ( ( os.Version.Major == 5 ) && ( os.Version.Minor >= 1 ) )
                              || ( os.Version.Major > 5 ) );
      if ( isSupported ) {
         int majorDllVersion = getComCtlMajorVersion();
         isEnabled = ( majorDllVersion > 5 ) && IsThemeActive() && IsAppThemed();
      }
   }
   catch ( Exception ) {
      // do nothing explicitly
   }
   return isEnabled;
}


private static int getComCtlMajorVersion() {
   DLLVERSIONINFO dllVersion = new DLLVERSIONINFO { cbSize = Marshal.SizeOf(typeof( DLLVERSIONINFO )) };
   DllGetVersion(ref dllVersion);
   return dllVersion.dwMajorVersion;
}

 

[StructLayout(LayoutKind.Sequential)]
private struct DLLVERSIONINFO {
   public int cbSize;
   public int dwMajorVersion;
   public int dwMinorVersion;
   public int dwBuildNumber;
   public int dwPlatformID;
}

 

[DllImport("comctl32.dll", CharSet = CharSet.Auto)]
private static extern int DllGetVersion(ref DLLVERSIONINFO version);


[DllImport("uxtheme.dll", CharSet = CharSet.Auto)]
private static extern bool IsThemeActive();


[DllImport("uxtheme.dll", CharSet = CharSet.Auto)]
private static extern bool IsAppThemed();

This code is pretty standard.

At design-time (i.e., within Visual Studio) my control would render properly.  The problem, however, was evident when I executed the code - it would think that visual styles were disabled and degrade gracefully to the non-themed output.

After a little bit of debugging I discovered that the comctl32.dll version being loaded at design-time was version 6 (the desired version) whereas at runtime I was getting version 5.  The first thing I tried was embedding a manifest into my executable to explicitly load version 6.  No dice - I was still getting version 5.

Ultimately, however, I did discover the source of the issue: I was calling the IsVisualStylesEnabled() too early in the load process of my application.  This was made apparent by placing my control on a form other than the start-up form - everything worked!

When initialized, my control began setting up UI components such as brushes, pens, etc and some of this logic was based on whether the themes were enabled.  Most, if not all, of the setup logic occurs in the InitializeComponent() method which is called from the .ctor() of the control.

In order to guarantee that the proper version of comctl32.dll is loaded, you cannot call the GetDllVersion() method until at least the OnHandleCreated() method of your control.  If you call it earlier, you effectively lock your application (and consequently the host application if your application is a visual component) into using version 5 whether or not the manifest dictates otherwise.

For my purposes, I had to wait until the OnPaintBackground() method to initialize the brushes and other auxiliary objects, but that at least happens after the OnHandleCreated().

Monday, September 01, 2008 10:50:27 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback