Saturday, July 28, 2012

Common Controls and Shell Functions


To elaborate further on my previous progress report, I'll go into detail about some of the problems I was facing and how I ultimately solved them. 

Now, perhaps you are wondering why I was so stressed about INITCOMMONCONTROLSEX not working. First off, I was a bit misleading. The all-caps version is a typedef. The function is InitCommonControlsEx - the capitalization here also makes it a lot harder to, uh, mis-read the last couple words...

Additionally, I was calling it twice - once with InitCommonControlsEx(&InitCtrlEx); and again with if (!InitCommonControlsEx(&InitCtrlEx)) { make an error message box} , the latter of which is actually unnecessary if you've already called the function.

It's the same as registering a window class. You don't need to call RegisterClassEx() if you have a conditional that simply tells the program what to do if a function doesn't work. With that in place, the program will say "Oh, well I can register the class, so why not? Saves me from throwing an error message. Moving right along..."

So, the step-by-step process to ensuring you can use common controls in your pure Win32 API VC application:
  • Locate ComCtl32.lib in C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib
  • Link to it. If you have made your own dependency directory, copy it to the lib folder and link to it in that directory.
  • Put #include <commctrl.h> in your main header or wherever your #includes are.
  • Right-click your Resource File and click Add Item... then New Item... and select XML file (it's towards the bottom)
  • Overwrite the contents of the new blank XML with this:  
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
      version="1.0.0.0"
      processorArchitecture="*"
      name="CompanyName.ProductName.YourApplication"
      type="win32"
/>
  <description>Your application description here.</description>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
          type="win32"
          name="Microsoft.Windows.Common-Controls"
          version="6.0.0.0"
          processorArchitecture="*"
          publicKeyToken="6595b64144ccf1df"
          language="*"
        />
    </dependentAssembly>
  </dependency>
</assembly> 
  • Now, save that XML as YourApplicationName.exe.manifest. For instance, mine is called MuPuPriNT_1.exe.manifest.
  • Almost done. Put this in your main header (e.g stdafx.h):
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' "\   
"version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 
  • Finally, we need to enable visual styles on a resource level. Close your resource file(s) if it is / they are open, then right-click on the .rc and click View Code. 
  • Put this at the beginning after #include resource.h:
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "the name of the manifest file you just created"


If this is all successful, you should find that your program now has correct Windows 7 visual styles. That means things like shiny buttons that glow when your cursor is over them, smooth lime-green progress bars, boxes etched more naturally, etc. Basically, the program will no longer look like it's running on Windows 2000.


I cannot take credit for this fix - I found it on MSDN, of all places - but I can at least try to explain it in a somewhat more easily understood fashion.


-



I also found something out about ShellExecute(). Besides that it is an incredibly useful and powerful function, I learned that calling it is a real pain in the brain unless (provided you are using Visual Studio 2010 like I am) you do exactly this:
  • Be sure that shell32.lib is included in your linker. There is no good reason why it shouldn't, as I'm fairly sure it is linked by default, but check anyway. 
  • If for whatever reason it's not, you'll find it in the same place we found ComCtl32.lib above.
Here's the tricky part I found out. Although ShellExecute() will only work with UNICODE programs, that wasn't an issue for me - the header it uses defines the function as ShellExecuteW(), whilst what the user implements has no W because the normal ShellExecute()function is defined as the wide-character version. Okay, cool. Why is that so tricky? It's not. All you need to gather from this is that if you are using ANSI encoding, you have to use ShellExecuteEx()

So, I mentioned a header, but didn't name it. The header you want is called shellapi.h, and it is worth noting that VC is not case-sensitive with the names of include files - for instance, in the Windows 7 SDK, it's called ShellAPI.h, but contains exactly the same stuff. 

The trick is that even if you #include it in your main header, even if you #include it after Windows.h, even if you #include it dead last...you will still get a C3061 compiler error - "ShellExecute is undefined."

WTF?! I linked the library, I included it in my header with all the other stuff, it's after Windows.h, it's spelled correctly...what do you want from me?!

Not that. Turns out, header files containing things like very specific function definitions are better off #included separately. At least, this is how I see it, since the program worked perfectly after I took ShellAPI.h out of stdafx.h and #included it at the beginning of my main cpp source file - so in this case, declarations in your big main cpp file ought to look like this:

#include "stdafx.h"
#include "resource.h"
#include "ShellAPI.h" 

I forgot the final bullet point, I guess.
  • #include the shellapi.h in the declarations at the beginning of your cpp source file, not in a precompiled header.

That was a frustrating start to the morning for sure...

No comments:

Post a Comment