If you are reading this article, you obviously have an interest in learning .NET or learning more about it. Well, this article isn't really about learning .NET although that will be the side effect of (at least I hope it will be). The main thing I want to show is how to get around limitation of the current CLS. You see, .NET is a wonderful environment and is nothing short of amazing in many regards. However, it's only been out for a little over a year and nothing is fully mature after only a year (if you ask my mom, some things don't mature even after 31 years, but that's a different story). If you look at all of the things .NET does, it's hard to believe it was ever made in the first place. For one thing, being able to write code in C# or VB.NET and have it compile to almost identical IL is pretty amazing. The new features of Visual Studio are pretty cool too. So as you can see, Microsoft had a lot of work to do when making .NET. And they still do. As such, many things aren't included yet. In 10 years, there will probably be a CLS class for just about everything, but that's not the case now. And for the next few years, there are still going to be some limitations.
If you need to use functionality that isn't yet provided, get ready to jump into the Windows API. Now, I don't profess to be an expert on the Win32 API, but I'm no novice either. I've done more than my share of writing COM and COM+ components, and that's probably the main reason I love .NET so much. But let's face it, many many things haven't been incorporated into .NET yet, so much so that entire books (like .NET Framework Solutions, In Search of the Lost Win32 API by John Paul Mueller - which is a MUST have in any programming library) have been written on the subject. So let's walk through a little example.
Let's say that your boss calls you into the office and wants some modifications done to your new .NET app. Users are having some trouble with the UI and are complaining it's hard to use. Since every programmer out there is an expert on Usability changing the UI to accomdate the users is out of the question. After all, the UI is so simple to use that the dumb users must simply learn how to use it better. You can all the other programmers agree on this so you need to look to another solution. The next thing you look to is building a better Help system. However, the technical writer says that every feature is documented and that when a MessageBox pops up, the users often click through it, forget what the message said and can't remember anything to put in the Help search menu. This is also creating a problem because no one knows what the problem is since the MessageBox is no longer showing itself. What the boss wants is something that will let the users pull up help related to the problem each time a dialog box pops up telling the users they did something wrong. At this point, it becomes obvious that your UI is probably not as intuitive as you once thought and that your annoying MessageBoxes aren't going to take the place of an easy to navigate the system. But these problems exist all over the system and re-writing it is out of the question right now. You think back to that stupid Don't Make Me Think book you saw at the store and wish you would have read it. However it's too late for that so you have to come up with something quick. Aha, I can embed a help button in my MessageBoxes and then I can fire help from there. This is perfect because you don't have to admit that DialogBoxes are totally annoying and that you were wrong about them, and you can still keep the MessageBox visible while the user looks for help. Only one problem, there is no Help button on MessageBoxes. Huh, I'm a real programmer and I'll simply subclass the MessageBox class. But then you realize that it's a Sealed Class and you can't Inheritfrom it. Some guy on the newsgroups points out that you can go under the code into IL and wipe out the Sealed directive, but you don't know IL so that's not an approach. Desperately, you think hard about what you can do. "I remember back in C++ I could do this, I think I could in VB6 too. This Object Oriented Stuff and Shared Methods is for the birds you say. Then you try to good old MsgBox function thinking that will fix it. But no, it sure won't. What to do now?
Well, thanks to .NET's System.Runtime.Interop Libraries, you can dig into the Windows API and use the MessageBoxEx function, and if you do everything correctly, you'll be able to add a MessageBox with a Help Button, use Windows to identify that Help button as a sender of the HelpRequested Event, then use the System.Process class to start Internet Explorer and pass it the URL of the Help file your technical writer so proudly created. If you make a class out of this MessageBox, you can call it something like NewMessageBox and make the appropriate changes in your code and have enough time to practice up on your new XBOX game Street Vol. 2.
Ok, off we go (please note that I'm using C# solely for this example. While I hate VB.NET vs. C# debates, I think C# and its support of unsafe code makes it the preferable language when using COM and interop code. VB.NET can handle most of it most of the time, but anytime you need unsafe blocks you have to go to C# anyway, so C# is probably the language of choice for this type of stuff):
First, add the namespace constants so you can use an abbreviated syntax...(these are in addition to the ones that C# adds by default)
| using System.Runtime.InteropServices; using System.Diagnostics; using System.Reflection; using System.Runtime; using System.Resources; |
| //I origninally learned this in John Paul Mueller's //book, .NET Framework Solutions, In Search of the Lost // Win32 API - I've modified it some but he's the guy //who is owed the credit! [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern int MessageBoxEx( IntPtr hWnd, [MarshalAs(UnmanagedType.LPTStr)]String Message, [MarshalAs(UnmanagedType.LPTStr)]String Header, UInt32 Type, UInt16 LanguageID); |
| /// <summary> /// These are all of the buttons that you can add to your new MessageBox /// class. Notice the last one - Help. This is a prime example of a /// time when you need to hit the API if you want to add some pretty /// common and simple functionality /// </summary> public class MBButton { public const UInt32 MB_OK = 0x00000000; public const UInt32 MB_OKCANCEL= 0x00000001; public const UInt32 MB_ABORTRETRYCANCEL = 0x00000002; public const UInt32 MB_YESNOCANCEL = 0x00000003; public const UInt32 MB_YESNO = 0x00000004; public const UInt32 MB_RETRYCANCEL = 0x00000005; public const UInt32 MB_CANCELTRYCONTINUE = 0x00000006; public const UInt32 MB_HELP = 0x00004000; } |
| /// <summary> /// This is a list of the possible Icon types, also /// natively in Windows, however, a few of them aren't in the /// framework YET /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public class MBIcon { public const UInt32 MB_ICONHAND = 0x00000010; public const UInt32 MB_ICONQUESTION = 0x00000020; public const UInt32 MB_ICONEXCLAMATION= 0x00000030; public const UInt32 MB_ICONASTERISK = 0x00000040; public const UInt32 MB_USERICON = 0x00000080; public const UInt32 MB_ICONWARNING = MB_ICONEXCLAMATION; public const UInt32 MB_ICONERROR = MB_ICONHAND; public const UInt32 MB_ICONINFORMATION = MB_ICONASTERISK; public const UInt32 MB_ICONSTOP = MB_ICONHAND; } //Notice that since we defined a few of these already we can immediately use them //as we are doing for MB_ICONWARNING and the rest. These ones use the same //values as the first, so you can use the named constant instead of //trying to remember their values |
| /// <summary> /// Here are the default buttons, once again pretty standard stuff /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public class MBDefButton { public const UInt32 MB_DEFBUTTON1 = 0x00000000; public const UInt32 MB_DEFBUTTON2 = 0x00000100; public const UInt32 MB_DEFBUTTON3 = 0x00000200; public const UInt32 MB_DEFBUTTON4 = 0x00000300; } |
| public class MBModal { public const UInt32 MB_APPLMODAL = 0x00000000; public const UInt32 MB_SYSTEMMODAL = 0x00001000; public const UInt32 MB_TASKMODAL = 0x00002000; } public class MBSpecial { public const UInt32 MB_SETFOREGROUND = 0x00010000; public const UInt32 MB_DEFAULT_DESKTOP_ONLY = 0x00020000; public const UInt32 MB_SERVICE_NOTIFICATION_NT3x = 0x00040000; public const UInt32 MB_TOPMOST = 0x00040000; public const UInt32 MB_RIGHT = 0x00080000; public const UInt32 MB_RTLEADING = 0x00100000; public const UInt32 MB_SERVICE_NOTIFICATION = 0x00200000; } public enum MBReturn { IDOK = 1, IDCANCEL = 2, IDABORT = 3, IDRETRY = 4, IDIGNORE = 5, IDYES = 6, IDNO = 7, IDCLOSE = 8, IDHELP = 9, IDTRYAGAIN =10, IDCONTINUE =11, IDTIMEOUT = 32000 } |
| private void button1_Click(object sender, System.EventArgs e) { MBReturn SomeResult; //Since the SomeResult is a result which waits for the return of the Function //I can just call it within the switch and make it not only more clear and //concise, but save a few lines in the process. More importantly, notice the //use of the Bit Flags which this API allows us to pass in with the Pipe | seperator switch(SomeResult = (MBReturn)MessageBoxEx(this.Handle, "Sample Message.", "SampleBox", MBButton.MB_CANCELTRYCONTINUE | MBButton.MB_HELP | MBIcon.MB_ICONEXCLAMATION | MBModal.MB_SYSTEMMODAL | MBDefButton.MB_DEFBUTTON4 | MBSpecial.MB_TOPMOST, 0)) { case MBReturn.IDCANCEL: MessageBox.Show("They Hit 'Return'"); break; case MBReturn.IDTRYAGAIN : MessageBox.Show("They Hit 'Return'"); break; case MBReturn.IDCONTINUE: MessageBox.Show("They Hit 'Continue'"); break; case MBReturn.IDABORT: MessageBox.Show("Abort, but we didn't give this option so won't be chosen"); break; case MBReturn.IDHELP: MessageBox.Show("This is where this could be cool"); break; default: MessageBox.Show("Not sure what they chose"); break; } } |
| this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.Form1_HelpRequested); private void Form1_HelpRequested(object sender, System.Windows.Forms.HelpEventArgs hlpevent) { Process.Start("iexplore.exe" , "hepltopic.html"); //Or MessageBox.Show("The user requested Help: \r\n" + "\r\n Sender: " + sender.ToString() + "\r\n Mouse Position: " + hlpevent.MousePos, "Help Requested", MessageBoxButtons.OK, MessageBoxIcon.Information); hlpevent.Handled = true; } |
| try { //Something failed like a file wasn't found //and our annoying box is going to be shown. } catch(System.IO.IOException ex) { MessageBox.Show("You are stupid! Read the Manual and do it right", "My UI's Are the Best", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Error); System.Windows.Forms.HelpEventArgs hlpevent = new System.Windows.Forms.HelpEventArgs(Cursor.Position); Form1_HelpRequested(this, hlpevent); //Lazy way of rasing the event } |