Beyond Hello World - An Update On My First WPF Application (with source)
I've been working on my first useful WPF application and I've learned several things since my last post and answered a few questions. I've included a ZIP file with my updated source (you can find it at the bottom of the post, it includes full Visual Studio 2005 project) Here is what I've leaned:
- How to create wrapper functions for native Win32 interfaces.
- How to create a console window and direct the output to the Trace and Debug classes to the console window.
- How to create an animated button using a rectangle, styles, and a storyboard - all in XAML.
- How to close a WPF Window object.
(1) Of course it is possible to write a WPF or managed application without calling Win32 APIs. But that can sometimes be limiting as there is functionality provided by the Win32 APIs that are not available via .NET classes. The WIn32 console classes are a good example: .NET provides the Console class, but no methods for actually creating or manipulating the console window itself.
I knew from talking to others that calling native API's should be easy and I found a few examples of this using marshaling. However, the examples didn't work for some of the APIs I needed. So, I emailed the internal .NET development alias with a couple of questions. It turns out that calling native Win32 APIs is really, really easy - much easier than some of the examples on the web. You can see how to do this in the Win32API.CS file.
Here are a few things to note in this file:
- There are no explicit marshaling statements. When declared correctly, the C# compiler generate the interfaces correctly.
- Note that the output parameters that need a pointer to a structure are simply declared using the 'ref' statement. A .NET class would have worked here as well, but value objects (structs) seemed more appropriate.
- You should use IntPtr as the type for handle - these are compatible with the safe handle classes.
- Note the use of types like UInt32 and UInt16 which mirror the standard Win32 data types.
(2) I learned how to use the trace and debug classes with a console window from the application itself. CLCV now creates its own console window and uses the ConsoleTraceListener class to direct all the output to the Trace and Debug classes to the console window. This was a bit tricky as I needed to interface directly with some low level Win32 APIs. To do this, I created two wrapper classes called K32 and U32. These classes provide the data structures and interfaces necessary to call into the Kernel32 and User32 DLLs respectively.
With these interfaces, its straight forward to create a console, open up the CONOUT$ handle to activate STDOUT, then set the size of the console window to 50 lines and 132 columns (the default is a bit small). This is really handy - it allows me to see the trace and debug output without using any other applications.
I'll update CLCV to make this console window optional in the future using a configuration dialog and possibly a hidden control.
(3) The WPF Button object is nice, but it controls its own behavior when it gets the focus - there is no way to control this. So, I decided I wanted a spiffier close button on my main window. After about three hours of reading documentation and experimenting, I developed used a Rectangle object to build a nice close button with a mouse over animation. This was a little tricky because the Rectangle object doesn't support the IsMouse over property, it only supports MouseEnter and MouseLeve. This mean I needed to animate both events.
The outcome was pretty nice even though I'm a pretty poor UI designer (check it out!)
(4) Doh! Sometimes things are so simple. The code in the previous post simply used the red X button in the window frame to close the app because I couldn't find the event or message to close the window. I was trying too hard! Closing a WPF window object is trivially easy - just call its Close() method.
I continue to be surprised with how easy many things are with WPF and .NET. For example, the animated close button would require large amounts of relatively complex code in C++. With WPF, the animation itself took zero C# code and 33 lines of simple XAML code. With even a basic understanding of XAML (which is what I currently have), the code for the button is straight forward and easy to understand. I believe that anyone who is proficient in HTML could understand what is happening without know XMAL.
I'm really starting to see how XAML is a great bridge between designers and developers - while it can be complex, its straight forward enough to be understood by people who are not developers.
Of course, all this simplicity comes at a price - the XAML and .NET run times are not free - they can have significantly more computational overhead and memory usage than native code. That being said, it seems like a reasonable price to pay for many types of applications and (like many technologies) they will get more efficient over time.
Notes on the source code
- This update includeI some very nicely implemented class from Sven Groot (Ookii.org) that make it easy to use the Vista style common file dialogs for .NET 2.0 (version 1.2). You can get the full package here.
- I've included a "testdata" directory along with the source code that contains empty files. Thiles files all have the ".clc.csv" suffix. This is what the CLCV applicatoin looks for.