Several months ago I wrote an article on enabling XP Visual Styles in your .NET 1.1 applications via a method called EnableVisualStyles(). This method, when coupled with a subsequent call to DoEvents() to work around a bug in the implementation of EnableVisualStyles() provided the nice, attractive Windows XP look and feel to which we've all grown accustomed. If you fail to provide the DoEvents(), you will be greeted with an SEHException() that is not quite informative and can leave you scratching your head.
The alternative to this approach is to fall back to what was required in the good old days of .NET 1.0: that is, we must create a .manifest file. Including the .manifest file along with your application is not very desirable. A convenient, but painstaking remedy, is to embed the .manifest file xml directly into your executable post-build. Simply open your executable in Visual Studio, add a new resource of the type RT_MANIFEST and paste your content. Assign a resource id of 1 and change the Language to Neutral. At this point you'd be good to go. This process can be automated via code if you like as well.
These approaches are documented ad nauseum across the internet, so I won't consume any more time eating bandwidth on how they work (a simple Google search will provide all the answered you could want - and more).
One might ask, however, why on earth would I want to go through all the trouble of using a .manifest file (embedded or otherwise) when I have the option of using EnableVisualStyles()? Well, the short answer is that EnableVisualStyles() doesn't fix the whole problem. As it turns out, the issue resurfaced for me again this evening.
There is still an issue with respect to ComboBoxes and XP styles. If you call a DoEvents() or show a dialog from a ComboBox event (such as KeyPress or SelectedIndexChanged) the SEHException will be thrown. For example, consider the following code:
private void cboSearch_KeyPress(object sender, KeyPressEventArgs e) {
if ( e.KeyChar == (char)Keys.Enter ) {
e.Handled = true;
doSearch();
}
}
private void doSearch() {
pnlStatus.Visible = true;
Application.DoEvents(); // make sure the panel is fully visible before proceeding
try {
// ...search logic here...
}
finally {
pnlStatus.Visible = false;
}
}
The previous code will throw because of the call to DoEvents(). However, when a .manifest file is present this is a non-issue.
In fact, I think I'd be fine with the solution of using the .manifest exception for one BIG issue. If your assembly is signed with a strong name (as mine is), you can't go changing the .exe file after it has been compiled. Doing so will cause the runtime to throw a nasty “Strong name validation failed“ error message due to the fact that the checksums don't match up any more. Upon seeing this error, I tried several solutions:
- First, I created my own Win32 .res file in C++ with my .manifest file embedded as an RT_MANIFEST resource. Then I wanted to add the .res file to my .csproj file but because of how .NET embeds resources that wouldn't work.
- Second, I went to the command line to compile via csc. The csc compiler accepts a switch /win32res that allows you to designate the .res file to embed within your assembly...this looked promising. In fact it worked fine. However, I have a solution of 5+ projects with 175+ files, several of which are forms that have .resx files associated with them...this complicates the build process as I do most of my compiling within the IDE.
- I wish I could designate a .sln (or even a .csproj) file in the csc command line
- ...looking forward to the MSBuild tool in Visual Studio 2005.
If you designate the /win32res switch on the csc commandline you will also experience an unfortunate pair of side effects:
- You trump VS's ability to create a VERSION resource based off of your Assembly attributes, so your .res file will need to include this if you want Explorer to show a 'Version' tab in the properties dialog for your executable and you'll have to maintain it manually versus allowing VS to update it. Not to mention that VS adds some properties to the VERSION resource that aren't a part of the initial version block (such as Assembly Version).
- You cannot specify the /win32icon switch together with /win32res so you need to embed your app.ico file into your resource file properly, thereby eliminating VS's automatic generation of the Icon resource.
All in all it's a pain, and it's definitely not worth it...I'd rather distribute a .manifest file that have to deal with those headaches.
This exception was frustrating the first time I ran into the EnableVisualStyles() bug because I didn't know at first what was causing the problem. It's even more frustrating & enfuriating now because I know that it's causing the problems and I can seemingly not do anything about it in code....or can I?
As it turns out, I can perform some magic...being the prestidigitator that I'd like to believe I am. The remedy that I found, and yes, it is a sort of a hack, is to defer control to a Timer temporarily and have the timer invoke the call that has the DoEvents(). For example:
private void cboSearch_KeyPress(object sender, KeyPressEventArgs e) {
if ( e.KeyChar == (char)Keys.Enter ) {
e.Handled = true;
tmrSearch.Interval = 10; // make it quick so the user doesn't see any delay
tmrSearch.Enabled = true;
}
}
private void tmrSearch_Tick(object sender, EventArgs e) {
// work around a bug in the ComboBox/EnableVisualStyles() with respect
// to calling DoEvents from within a ComboBox event.
tmrSearch.Enabled = false; // only allow the event to run once
doSearch();
}
private void doSearch() {
pnlStatus.Visible = true;
Application.DoEvents(); // make sure the panel is fully visible before proceeding
try {
// ...search logic here...
}
finally {
pnlStatus.Visible = false;
}
}
So there seems to be a remedy in code, but I can't say that I really recommend it as a best practice. The best way (i.e. the least error prone) is to go the route of the .manifest file, but it's not without its complications and gotchas. I would much prefer to use the EnableVisualStyles(), and now that I know of a way to work around the issues until fixed I can live with it.