Thursday, August 28, 2008

Overriding ListBox's DefWndProc to Create a Gray Free Read-Only Control

Inside every Windows Forms control, there's a mysterious method called "DefWndProc". According to MSDN, this method "Sends the specified message to the default window procedure.". Here's how it works:

First, you click a button on your mouse, or press something on your keyboard. Windows processes your request, and sends your request, via the Message structure to a method called "WndProc". WndProc will process the message, and is responsible for raising the events you can attach to in your code, such as the OnMouseClick and OnKeyPress events. Before raising these events, it calls DefWndProc, for Windows to handle the default behavior on the control.

Handling the default behavior of the control includes doing things like painting a new character in a textbox, making a button look pressed when the user clicks it, drawing a check in a checkbox when the user clicks it, and highlighting and selecting items in a ListBox. The beauty of overriding DefWndProc is that, while the events of the control will still be raised and your custom actions will still be executed, you can still stop the default behavior from being executed.

One thing you should know before you start, however, is that the Message structure is not very user-friendly. The key to it, however, is the "Msg" property, which indicates the actual action that the user has performed. PInvoke.net has posted an enumeration including windows messages, and some of their descriptions so you can figure out which messages mean what. But the simplest (and in my opinion, most educational) way to learn these messages, however, is to insert a Debug.WriteLine in your overridden DefWndProc, and then watch the messages in Visual Studio's output window as you debug your application. You'll probably be surprised at the sheer number of messages that are sent from Windows to your control whenever you do anything relating to your control. Experimenting with blocking these messages in DefWndProc can give you great control over your control.

So, in the example below, I've extended the ListBox control to add a "ReadOnly" property, and also to block any messages from the UI that might affect my control from being selected. Essentially, this blocks the user out of the control selection process, while allowing the code to do the selecting and deselecting. It will allow the user to scroll through the ListBox (unlike the effect that setting Enabled to false has on the ListBox), and it will prevent the ForeColor from going gray.



   1:  using System.Windows.Forms;

   2:   

   3:  namespace CustomControls

   4:  {

   5:      public class ReadOnlyListBox : ListBox

   6:      {

   7:          private bool _readOnly = false;

   8:   

   9:          public bool ReadOnly

  10:          {

  11:              get { return _readOnly; }

  12:              set { _readOnly = value; }

  13:          }

  14:   

  15:          protected override void DefWndProc(ref Message m)

  16:          {

  17:              // If ReadOnly is set to true, then block any messages

  18:              // to the selection area from the mouse or keyboard.

  19:              // Let all other messages pass through to the

  20:              // Windows default implementation of DefWndProc.

  21:              if (!_readOnly

  22:                  ((m.Msg <= 0x0200 || m.Msg >= 0x020E)

  23:                  && (m.Msg <= 0x0100 || m.Msg >= 0x0109)

  24:                  && m.Msg != 0x2111

  25:                  && m.Msg != 0x87))

  26:              {

  27:                  base.DefWndProc(ref m);

  28:              }

  29:          }

  30:      }

  31:  }

Friday, August 15, 2008

Checking For Duplicate Classes

Here's a solution to a problem I've frequently seen online. Sometimes, your code just seems drunk. You get type mismatches all over the place, and you're racking your brain to figure out why. Here's a class you can use to see if you've inadvertantly put duplicate class names in your code or in assemblies referenced by your code.

It does three things:

1. Gathers the assembly names of all the related assemblies to the one you're running in, even down to mscorlib.dll.
2. Gathers information on every Type in all of those assemblies.
3. Returns the types that match a particular name.



   1:  public static class DuplicateClassFinder

   2:  {

   3:      private static List<string> CheckDuplicates(List<string> classNames, List<string> exploredAssemblies, Assembly assembly)

   4:      {

   5:          if (exploredAssemblies.Contains(assembly.FullName))

   6:              return new List<string>();

   7:          exploredAssemblies.Add(assembly.FullName);

   8:   

   9:          Console.WriteLine(assembly.FullName);

  10:          List<string> result = new List<string>();

  11:          foreach (Type t in assembly.GetTypes())

  12:          {

  13:              if (classNames.Contains(t.Name))

  14:              {

  15:                  result.Add(t.Name);

  16:              }

  17:              classNames.Add(t.Name);

  18:          }

  19:          AssemblyName[] referencedAssemblies = assembly.GetReferencedAssemblies();

  20:   

  21:          foreach (AssemblyName assemblyName in assembly.GetReferencedAssemblies())

  22:          {

  23:              result.AddRange(CheckDuplicates(classNames, exploredAssemblies, Assembly.Load(assemblyName)));

  24:          }

  25:          return result;

  26:      }

  27:   

  28:      private static void GetReferencedAssemblyTree(ref List<string> assemblyNames, AssemblyName assemblyName)

  29:      {

  30:          if (assemblyName == null)

  31:              assemblyName = Assembly.GetExecutingAssembly().GetName();

  32:          if (assemblyNames == null)

  33:              assemblyNames = new List<string>();

  34:   

  35:          if (assemblyNames.Contains(assemblyName.FullName))

  36:              return;

  37:   

  38:          assemblyNames.Add(assemblyName.FullName);

  39:   

  40:          foreach (AssemblyName name in Assembly.Load(assemblyName).GetReferencedAssemblies())

  41:              GetReferencedAssemblyTree(ref assemblyNames, name);

  42:   

  43:      }

  44:   

  45:      private static Dictionary<string, List<string>> GetTypesInAssemblies(List<string> assemblies)

  46:      {

  47:          Dictionary<string, List<string>> coll = new Dictionary<string, List<string>>();

  48:   

  49:          foreach (string assemblyName in assemblies)

  50:          {

  51:              foreach (Type t in Assembly.Load(assemblyName).GetTypes())

  52:              {

  53:                  if (!coll.ContainsKey(t.Name))

  54:                      coll.Add(t.Name, new List<string>());

  55:                  coll[t.Name].Add(string.Format("{0} (in {1})", t.FullName, t.Assembly.GetName().Name));

  56:              }

  57:          }

  58:   

  59:          return coll;

  60:      }

  61:   

  62:      public static string FindInstancesOfClassName(string className)

  63:      {

  64:          StringBuilder result = new StringBuilder();

  65:          List<string> assemblies = new List<string>();

  66:          GetReferencedAssemblyTree(ref assemblies, null);

  67:          Dictionary<string, List<string>> coll = GetTypesInAssemblies(assemblies);

  68:          if (coll.ContainsKey(className))

  69:          {

  70:              result.AppendLine(string.Format("{0} implementations of a class named {1} exist:", coll[className].Count, className));

  71:   

  72:              foreach (string value in coll[className])

  73:                  result.AppendLine(string.Format("   {0}", value));

  74:          }

  75:          return result.ToString();

  76:      }

  77:  }



Just call something like the following:


Console.WriteLine(DuplicateClassFinder.FindInstancesOfClassName("Timer"));


And pass the name of the suspect class.

Wednesday, August 13, 2008

Installing a Startup Registry Key in a Setup Projects

Here are the steps to install a startup registry key in a setup project in Visual Studio 2008.

1. Create a setup project.

2. Right click on the setup project, go to "View" and then "Registry":


3. Right click on the folders under HKEY_CURRENT_USER and select "New" and then "Key". You will need to add the folders in the following hierarchy:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run.

Some of the hierarchy may have already been created for you. If so, just start building your hierarchy off of that.



4. Next, you'll need to add the registry value. Right click on the "Run" folder (above), and choose "New" followed by "String Value". You should see a new value pop up in the pane on the right.

5. Rename the value on the pane on the right whatever you want to name it. It's customary to give it a name that fits with your application, and it's fine to just put your application name as the name of the value.

6. Set the Value property of your value. Right click on the value you just added, and select "Properties Window". In the window, you'll see a field called "Value". This is where we want to put in the path of the application to execute. Here's the catch: the default setup project allows the user to specify where they would like to install the application, so this path can be variable. We can capture the location where the application has been installed to by calling the "TARGETDIR" variable. This variable has to be put in square brackets for the installer to recognize that it's a dynamic value.

For example, if I want the application, named "MyApplication.exe" to run, I need to make the value of the key the following:

[TARGETDIR]MyApplication.exe

When the installer is running, it will replace "[TARGETDIR]" with the directory to which the file was installed. So if the user installs the application to "C:\Program Files\MyCompany\MyApplication\", the value that will be put in this registry key will be "C:\Program Files\MyCompany\MyApplication\MyApplication.exe".

That's it! Now run the installer, reboot, and your application should run!

Saturday, August 9, 2008

How the CLR Compiler Handles Try/Catch Statements in C#

Recently I received a question on the Microsoft Forums regarding the way that exception handling works in .NET. Specifically, the question was about how the C# compiler interprets different methods of catching and rethrowing errors.

There are three ways of using the "catch" keyword in C#:

1. No error type specified: "catch"
2. Error type specified but not stored to a variable: "catch (Exception)"
3. Error type specified and stored to a variable: "catch (Exception e)"

I've implemented all three of these styles in the code below, and have compiled all three of them to see how the compiler will implement this:

using System;

namespace ErrorHandling
{
class ErrorHandlingExamples
{
public void Run()
{
int i = 0;

try
{
int a = 25 / i;
}
catch
{
throw;
}

try
{
int b = 25 / i;
}
catch (Exception)
{
throw;
}

try
{
int c = 25 / i;
}
catch (Exception e)
{
throw e;
}
}
}
}
Here's the IL Generated code:
.method public hidebysig instance void  Run() cil managed
{
// Code size 33 (0x21)
.maxstack 2
.locals init ([0] int32 i,
[1] class [mscorlib]System.Exception e)
IL_0000: ldc.i4.0
IL_0001: stloc.0
.try
{
IL_0002: ldc.i4.s 25
IL_0004: ldloc.0
IL_0005: div
IL_0006: pop
IL_0007: leave.s IL_000c
} // end .try
catch [mscorlib]System.Object
{
IL_0009: pop
IL_000a: rethrow
} // end handler
.try
{
IL_000c: ldc.i4.s 25
IL_000e: ldloc.0
IL_000f: div
IL_0010: pop
IL_0011: leave.s IL_0016
} // end .try
catch [mscorlib]System.Exception
{
IL_0013: pop
IL_0014: rethrow
} // end handler
.try
{
IL_0016: ldc.i4.s 25
IL_0018: ldloc.0
IL_0019: div
IL_001a: pop
IL_001b: leave.s IL_0020
} // end .try
catch [mscorlib]System.Exception
{
IL_001d: stloc.1
IL_001e: ldloc.1
IL_001f: throw
} // end handler
IL_0020: ret
} // end of method ErrorHandlingExamples::Run

So here's our result:

In the first situation, since no exception type is specified, the compiler generates a catch to catch "System.Object". The value of System.Object is placed on the top of the method stack, but is popped off immediately (IL_0009), and the "rethrow" IL command is called, which simply re-throws the exception generated within the protected block of code (the "try" block).

In the second situation, since an exception type is specified, the compiler generates a catch block that catches only types that inherit from System.Exception (which theoretically is every exception generated in .NET). Again, it pops the exception off of the top of the method stack (IL_0013), and re-throws the exception generated in the protected block of code using the "rethrow"command.

In the third situation, an exception type is specified, and is directed to be stored in a local variable. Here, you'll notice no pop IL command is issued, but rather the value on the top of the method stack is stored in memory at location 1 (IL_001d). Then it is loaded again (IL_001e) and the value that is placed on the top of the method stack is thrown by calling the "throw" command, which does take a specific exception to throw.

While the IL command "rethrow" throws the exception generated in the try block, the "throw" command throws the exception placed on the top of the stack, thus allowing you to create your own exception if you desire.

Friday, August 1, 2008

Reinventing the wheel...

When it comes to programming, one of my favorite phrases is "KISS", or "Keep it simple, stupid". I am a regular contributor on Microsoft's Visual C# Forum, and I've noticed something lately: people reinventing the wheel. Here are two examples:

  • Rewriting generic BindingList because it doesn't inherit from a generic version of IBindingList, and that is what you want.
  • Rewriting the configuration system just simply because you want to. Nevermind the error you're getting from creating something that doesn't resemble an app.config file, and then insisting on calling it an app.config file. Yes. The .NET Framework actually does do something with the file called app.config when it's in the folder.

So, after a bit of this, I was prompted to write the following rant on another post. Take this as a suggestion to beginners.

#region Soapbox

Just because someone has posted code publicly on CodeProject, and not released it "professionally" doesn't mean that it's not still a really, really good way of doing things.

Beware of the sentiment that you'd rather start over than go with something that works. While this sentiment is very nice in an ideal world, the world is not ideal, and this means that sometimes you will have to do things like implement other people's code, even though you don't like it, in order to accomplish your task in a timely manner.

If I had all the time in the world, I would create the perfect application with the perfect architecture, perfect coding algorithms, and perfect readability. And I would also make it perfectly consise. I've seen too many people go the route of "I don't like that's provided for the exact purpose I need it for and seems to work properly, because it's not ."

Don't get me wrong, I'm not saying to go slap together unreadable, poorly designed code that has no semblance of an object oriented system, but I am saying five things:

1. Learning programming is a process. You will most likely underdesign your first application, and overdesign your second. Don't expect to write the perfect application the first time through. There is no such thing.

2. Your boss will probably not be happy with you if you spend so much time trying to do everything perfectly, that you never accomplish anything. I've seen people on these forums that have threads that go on for months where they are trying to accomplish a task "perfectly", when another simpler answer was already provided for them. There's a balance between quality, budget, and deadlines that have to be struck.

3. Some of the best programmers I've ever met don't earn their living that way. I once convinced an accountant to be a programmer by trade, simply because of his passion for the subject. Some people write incredible code and put it up on Code Project or Source Forge simply to get their name out there, because they do want to be professional coders, but they don't have the resume experience, etc. Read the code, and evaluate for yourself. It's a good way to learn good practices and not-so-good practices.

4. Learn the basics first. Otherwise, you'll find yourself in some crazy conversation with someone one day, and your vocabulary makes no sense to him, and he sounds like he's talking another language. This often happens when developers (I have been known to do this) make up terms for things that aren't the real terms.

5. Keep asking questions. Read everything you can get your hands on. And most importantly, code, code, code. Don't be afraid to screw up, because you will. Try everything, and if it doesn't work, try something else.

All that to say, I really suggest code project, because it's good code, and people actually walk you through portions of their code, explaining it along the way. I've been programming for two years, and I'd still rather learn something from code project than from Source Forge simply because I don't have the time to spend digging through line after line of code to find the one part I'm interested in.

Sorry for the rant, but I've seen alot of people lately trying to reinvent the wheel.

#endregion


I suppose that says about as much as I want to right now.