One of the things that I talked a little about in my talk about “Modern Windows Applications on Windows 7” was around the idea of using a TaskDialog when you’re trying to get a response from the user rather than using your own custom dialog or a MessageBox or similar.
As an aside, this isn’t just about TaskDialogs and MessageBoxes and so on – there’s other reasons why you would want to get the latest version of common controls loaded into a WPF application but this particular area was one that I’d left “hanging” at that previous talk as I hadn’t resolved it at the time.
TaskDialog isn’t represented in the .NET Framework at all as far as I know so you need to look to the Windows API Code Pack which provides a managed TaskDialog class that you can make use of to make getting to the native APIs pretty easy.
To illustrate this in its entirety, here’s the world’s simplest example.
and then I can put a nice button my app;
and if I add a reference to the right assembly from the code pack;
and write a little code;
TaskDialog dialog = new TaskDialog() { Caption = "I'm a task dialog", InstructionText = "Not much to see here right now", StandardButtons = TaskDialogStandardButtons.Ok }; dialog.Show();
then it’s “all good”. Except it isn’t in that running the application won’t work. It hits an exception because of loading the wrong version of the common controls library so when you run it and try and raise the TaskDialog the code in the code pack is smart enough to realise that the particular entry point that it’s looking for in common controls isn’t there in the DLL and raise a nice .NET exception saying so. It looks a bit like this;
But “not to worry” because this is easily fixed by adding a new application manifest to the project (from the Add New Item dialog) and the default manifest file that Visual Studio inserts for you comes with a bit of XML that you can just uncomment;
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) --> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency>
and then the application “works”;
until it comes time to try and publish it via ClickOnce. At that point, you descend into some error messages…
Now, I must admit that I spent quite a lot of time blocked by this error message. I tried lots of ways to work around it but ended up being pointed to this post which talks you through how to work around this manually.
However, even after I’d read that post I still wasn’t 100% sure about the exact steps to follow and so I thought I’d write up my own findings here. In doing that, I also ended up reading;
- “Understanding Relationship between ClickOnce and Win32 SxS”
- “.NET Framework 4 RTM Application Compatibility Walkthrough”
Remove the Manifest Just Added
The first thing to do is to undo the last step and remove the app.manifest that we added in Visual Studio from your project in order to return to the place where the project will build and publish via ClickOnce but will crash when you first trying and make use of TaskDialog because the wrong version of common controls is loaded.
Adding the Manifest to the Executable
Now, publish via the ClickOnce bits in Visual Studio. Then go to the folder that you’ve published into which will probably look something like this (for a vanilla, “hello world” style of application);
and within the “Application Files” folder there’s a version-specific folder that contains (in this simple case);
The first thing to do is to edit the executable in order to insert a resource containing a manifest that specifies V6 of common controls. I used this file;
<?xml version="1.0" encoding="utf-8"?> <asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency> </asmv1:assembly>
which I saved to my desktop in notepad with a UTF-8 encoding. I then opened up the EXE file which in my case is MyTestApp.exe.deploy in Visual Studio’s binary resource editor. That is;
and then in the resource editor, add a new resource as in;
and then import a resource;
and import the file that you saved the manifest into giving it an identifier of RT_MANIFEST as in;
and then using the properties window to change its ID to 1 rather than 101;
and then save the file from Visual Studio.
Updating Signatures
The problem with what we’ve just done is that we’ve changed the executable and there’s a chain of signatures that we’ve just invalidated in the application manifest and then the deployment manifest.
The Application Manifest
Firstly, the application manifest file which in my case is MyTestApp.exe.manifest;
This needs fixing because it has a picture of the files that make up our application and that needs refreshing now that we’ve updated the main binary.
There’s more than one way of doing this but the route I took is to use the MageUI.exe tool.
The first thing to remember with this tool is it’s easier to use if you use the Preferences option to tell the tool which certificate you want to use for signing. If you just did a quick publish from Visual Studio then the certificate would have been generated by the environment for you and would be called _TemporaryKey.pfx so you could select that one as I’m doing here;
and then ( as long as you have “Sign on save” set ) then that certificate will be used when you save manifests from here.
Next, open up the equivalent of MyTestApp.exe.manifest in the tool;
and then we want it to rebuild its picture of the bits/bytes of the files that make up our deployment so go to the Files tab;
and remove the MyTestApp.exe.deploy by selecting the row and hitting DEL on it and then use the Populate button to re-populate the file list from the folder;
and in this case I have ended up with an extra MyTestApp.application.deploy sfile o I get rid of that file by hitting DEL again;
Now, save the file and it should have refreshed its picture of the MyTestApp.exe.deploy file and have signed the resulting manifest.
The Deployment Manifest
Next, open the deployment manifest which in my case would be called MyTestApp.application and resides 2 folders up from the application manifest.
We need to get this file to refresh it view of the application manifest that we just altered so go to the “Application Reference” tab and re-select the application manifest that is already selected;
and with that re-selected, save the file.
As an aside related to .NET 4.0, I find that when I’ve saved this file and I open it up in notepad then a required element called compatibleFrameworks seems to disappear. There are various references to this on the web and what I recommend is simply opening up the file again in notepad, adding a compatibleFrameworks element such as;
<compatibleFrameworks xmlns="urn:schemas-microsoft-com:clickonce.v2"> <framework targetVersion="4.0" profile="Client" supportedRuntime="4.0.30319" /> <framework targetVersion="4.0" profile="Full" supportedRuntime="4.0.30319" /> </compatibleFrameworks>
and then re-opening in mageui.exe and saving again to refresh the signature which (strangely) seems to leave the compatibleFrameworks element alone this second time.
With that done, you should be able to perform your ClickOnce deployment and have the TaskDialog display correctly on the screen Here’s my “app” run from the start menu and doing the right thing;
That seems to work for me although it’s worth saying that I’ve only tested to deployment on Windows7 and you might have to tweak for Vista/XP.
The last thing I’ll say here is that you may well want to do this from a build rather than doing it manually with visual studio and I think that mage.exe as a command line tool can help you there and you can perhaps look to mt.exe in order to embed the manifest into the executable after it’s been built.