Merging a WPF application into a single EXE

I always dislike handing off little applications to people. Not because I can’t, but because of the steps involved to make sure it all just works. Small apps are the most problematic because I never want to take the time to create a whole installer project for just a few assemblies, and packaging up a zip file must be accompanied by “Unzip this into a folder in your programs directory and create a shortcut…” which brings us back to the whole installer business we started with.

There are a few tools already out there such as ILMerge by Microsoft Research (Which works great for most .NET-y things, but chokes on WPF applications) and a few paid tools by third party vendors that you could fork over a few hundred for to get. But, I’m a developer, which means I want to do it the Hard Way™. I did a little research and found the following blog posts on setting up and merging in DLL’s as resources into the main assembly and then extracting and loading them into memory when you run your application.

Links:

There were a few things I didn’t like about each solution. The first one (richarddingwall.name) ends up having you directly adding the .dll’s as resources directly. I hate maintaining things manually, especially when it will run fine on my machine but break when when I move it somewhere else because I forgot to update the resources when I added a new project. The one from blog.mahop.net builds on the previous one and changes the resolve location to a custom class with its own startup method. Better, because it resolves the resources earlier. Finally, the one from Daniel Chambers (digitallycreated.net) added in the final piece that automatically including the assemblies as resources. Unfortunately, the way he looks for culture specific assemblies didn’t work and I had to remove / change it to be closer to the one on mahop.net.

Final solution I’m currently using is as follows:

To the main executable project, unload and edit the .csproj file, and below the following line:

  1. <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Add this XML to the project file, save, and load it back up.

  1. <Target Name="AfterResolveReferences">
  2. <ItemGroup>
  3. <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
  4. <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
  5. </EmbeddedResource>
  6. </ItemGroup>
  7. </Target>

It should look something like this when your done:

You’ll then add a new code file to the main project and add the following code to it (modified to fit how your application is named / structured, in a WPF application, a good place to put it would be App.xaml.cs):

  1. [STAThread]
  2. public static void Main()
  3. {
  4. AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
  5.  
  6. App.Main(); // Run WPF startup code.
  7. }
  8.  
  9. private static Assembly OnResolveAssembly(object sender, ResolveEventArgs e)
  10. {
  11. var thisAssembly = Assembly.GetExecutingAssembly();
  12.  
  13. // Get the Name of the AssemblyFile
  14. var assemblyName = new AssemblyName(e.Name);
  15. var dllName = assemblyName.Name + ".dll";
  16.  
  17. // Load from Embedded Resources - This function is not called if the Assembly is already
  18. // in the same folder as the app.
  19. var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(dllName));
  20. if (resources.Any())
  21. {
  22.  
  23. // 99% of cases will only have one matching item, but if you don't,
  24. // you will have to change the logic to handle those cases.
  25. var resourceName = resources.First();
  26. using (var stream = thisAssembly.GetManifestResourceStream(resourceName))
  27. {
  28. if (stream == null) return null;
  29. var block = new byte[stream.Length];
  30.  
  31. // Safely try to load the assembly.
  32. try
  33. {
  34. stream.Read(block, 0, block.Length);
  35. return Assembly.Load(block);
  36. }
  37. catch (IOException)
  38. {
  39. return null;
  40. }
  41. catch(BadImageFormatException)
  42. {
  43. return null;
  44. }
  45. }
  46. }
  47.  
  48. // in the case the resource doesn't exist, return null.
  49. return null;
  50. }

Finally, make sure you update the target method for your main application to be the main method for the project you just added:

And, that’s it!

When you build your application you’ll still see all the assemblies in the output directory, but you should be able to take just the executable, move it somewhere else, and run it just as it is.

Native Glass Windows with WPF in Windows 7 / Vista

One of the awesome benefits of working at InterKnowlogy is that we get time to do what we call RECESS: Research and Experimental Coding to Enhance Software Skills. It’s a 4 hour time span where we can work on interesting technologies to enhance, grow, and keep up on the latest technologies and methodologies. It keeps us sharp, interested, and many of our demos have come from these short code jams.

Over the past few weeks I’ve wanted to figure out how to add or extend glass into my application like you see in Word (2010 preview):

Glass

As you can see, the whole title bar area is seamlessly integrated into the look and feel of windows, it feels native, its got that cool semi-transparent blur-the-background effect and everything. So come RECESS I did some research and pieced together what you’d need to get an effect like this.

DesktopWindowManagerAPI.cs
  1.  
  2. using System.Runtime.InteropServices;
  3. using System.Windows;
  4. using System.Windows.Interop;
  5. using System.Windows.Media;
  6. using Codelogic.Windows.Native.APIManagedExceptions;
  7.  
  8. namespace Codelogic.Windows.Native
  9. {
  10. public static class DesktopWindowManagerAPI
  11. {
  12. public static void AllGlassWindow(this Window window)
  13. {
  14. ExtendFrameIntoClientArea(window, new Thickness(-1), false);
  15. }
  16.  
  17. public static void ExtendFrameIntoClientArea(Window window, Thickness thickness)
  18. {
  19. ExtendFrameIntoClientArea(window, thickness, false);
  20. }
  21.  
  22. public static void ExtendFrameIntoClientArea(Window window, Thickness thickness, bool exceptionOnFail)
  23. {
  24. var compEnabled = IsCompositionEnabled();
  25. if (exceptionOnFail && !compEnabled)
  26. throw new DWMNotEnabledException();
  27.  
  28. if (exceptionOnFail && !window.IsInitialized)
  29. throw new WindowNotLoadedException();
  30.  
  31. if (!compEnabled) return;
  32.  
  33. var margins = thickness.ToDWMMargins();
  34. var windowPointer = new WindowInteropHelper(window).Handle;
  35.  
  36. //convert the background to nondrawing
  37. var mainWindowHwnd = HwndSource.FromHwnd(windowPointer);
  38. if (mainWindowHwnd != null)
  39. mainWindowHwnd.CompositionTarget.BackgroundColor = Color.FromArgb(0, 0, 0, 0);
  40.  
  41. try
  42. {
  43. DwmExtendFrameIntoClientArea(windowPointer, ref margins);
  44. }
  45. catch (DllNotFoundException)
  46. {
  47. window.Background = Brushes.White;
  48. }
  49. }
  50.  
  51. public static bool IsCompositionEnabled()
  52. {
  53. try
  54. {
  55. return DwmIsCompositionEnabled();
  56. }
  57. catch (DllNotFoundException)
  58. {
  59. return false;
  60. }
  61. }
  62.  
  63. #region WPF to Native
  64.  
  65. private static DWMMargins ToDWMMargins(this Thickness t)
  66. {
  67. var rtrn = new DWMMargins();
  68.  
  69. rtrn.Top = (int)t.Top;
  70. rtrn.Bottom = (int)t.Bottom;
  71. rtrn.Left = (int)t.Left;
  72. rtrn.Right = (int)t.Right;
  73.  
  74. return rtrn;
  75. }
  76.  
  77. #endregion
  78.  
  79. #region Native Interop
  80.  
  81. [StructLayout(LayoutKind.Sequential)]
  82. private struct DWMMargins
  83. {
  84. public int Left;
  85. public int Right;
  86. public int Top;
  87. public int Bottom;
  88. }
  89.  
  90. /// <summary>
  91. /// Extends an hwind's frame into the client area by the specified margins.
  92. /// </summary>
  93. /// <param name="hwnd">Integer pointer to the window to change the glass area on.</param>
  94. /// <param name="margins">Margins, what to set each side to</param>
  95. [DllImport("dwmapi.dll", PreserveSig = false)]
  96. private static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref DWMMargins margins);
  97.  
  98. /// <summary>
  99. /// Checks to see if the Desktop window manager is enabled.
  100. /// </summary>
  101. [DllImport("dwmapi.dll", PreserveSig = false)]
  102. private static extern bool DwmIsCompositionEnabled();
  103.  
  104. #endregion
  105. }
  106. }
  107.  

Alright, lets begin breaking this down.

DesktopWindowManagerAPI.cs
  1.  
  2. [StructLayout(LayoutKind.Sequential)]
  3. private struct DWMMargins
  4. {
  5. public int Left;
  6. public int Right;
  7. public int Top;
  8. public int Bottom;
  9. }
  10.  
  11. [DllImport("dwmapi.dll", PreserveSig = false)]
  12. private static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref DWMMargins margins);
  13.  
  14. [DllImport("dwmapi.dll", PreserveSig = false)]
  15. private static extern bool DwmIsCompositionEnabled();
  16.  

First, Windows 7 and Vista provide us with the Desktop Window Manager which was first included in Vista and continues on into Windows 7, it manages all the cool graphical windowing and effects you see in those operating systems. It also gives us an API dll to access these all the features, including many that are unavailable in WPF.

The first declaration you see sets up the data type that the DLL uses internally to represent Left, Right, Top, and Bottom glass margins. The others are pointers to unmanaged (non .NET) methods in the DLL. DwmExtendFrameIntoClientArea is the method that allows me to adjust how far in the glass extends in to the client drawable area, and DwmIsCompositionEnabled tells me if Aero Glass is enabled.

DesktopWindowManagerAPI.cs
  1.  
  2. private static DWMMargins ToDWMMargins(this Thickness t)
  3. {
  4. var rtrn = new DWMMargins();
  5.  
  6. rtrn.Top = (int)t.Top;
  7. rtrn.Bottom = (int)t.Bottom;
  8. rtrn.Left = (int)t.Left;
  9. rtrn.Right = (int)t.Right;
  10.  
  11. return rtrn;
  12. }
  13.  

A simple extension method (denoted by the ‘this’ in front of the Thickness t, it allows me to write the declaration as though the method were part of the Thickness class, so if I have a thickness variable thick I could convert it to a DWMMargins type by writing var margins = thick.ToDWMMargins();) it converts a WPF Thickness object to the internal DWMMargins struct.

DesktopWindowManagerAPI.cs
  1.  
  2. public static void ExtendFrameIntoClientArea(this Window window, Thickness thickness)
  3. {
  4. ExtendFrameIntoClientArea(window, thickness, false);
  5. }
  6.  
  7. public static void ExtendFrameIntoClientArea(this Window window, Thickness thickness, bool exceptionOnFail)
  8. {
  9. var compEnabled = IsCompositionEnabled();
  10. if (exceptionOnFail && !compEnabled)
  11. throw new DWMNotEnabledException();
  12.  
  13. if (exceptionOnFail && !window.IsInitialized)
  14. throw new WindowNotLoadedException();
  15.  
  16. if (!compEnabled) return;
  17.  
  18. var margins = thickness.ToDWMMargins();
  19. var windowPointer = new WindowInteropHelper(window).Handle;
  20.  
  21. //convert the background to nondrawing
  22. var mainWindowHwnd = HwndSource.FromHwnd(windowPointer);
  23. if (mainWindowHwnd != null)
  24. mainWindowHwnd.CompositionTarget.BackgroundColor = Color.FromArgb(0, 0, 0, 0);
  25.  
  26. try
  27. {
  28. DwmExtendFrameIntoClientArea(windowPointer, ref margins);
  29. }
  30. catch (DllNotFoundException)
  31. {
  32. window.Background = Brushes.White;
  33. }
  34. }
  35.  
  36. public static bool IsCompositionEnabled()
  37. {
  38. try
  39. {
  40. return DwmIsCompositionEnabled();
  41. }
  42. catch (DllNotFoundException)
  43. {
  44. return false;
  45. }
  46. }
  47.  

Finally I wrapped native methods with .NET versions that take a more useful WPF Window class and WPF Thickness class for the ExtendFrameIntoClientArea method. Internally it checks to make sure the window is initialized and that Desktop Composition is enabled, gets the integer pointer to the window, resets the background, and then calls the native method to extend the glass into the drawable (client) area of the window. Two custom classes not shown are the DWMNotEnabledException class and the WindowNotLoadedException class, which are thrown if something goes wrong.

That’s all well and good, but wouldn’t it be nice if we didn’t have to worry about all these calls to this custom DesktopWindowManagerAPI class and could just set how much we wanted the glass to extend into the client area? Or bind it to something so that the glass area expands or contracts when a value changes?

I thought so:

GlassWindow.cs
  1.  
  2. using System.Windows;
  3. using Codelogic.Windows.Native;
  4.  
  5. namespace Codelogic.Controls.WPF
  6. {
  7. public class GlassWindow : Window
  8. {
  9. #region Glass Thickness Dependency Property
  10.  
  11. public static readonly DependencyProperty GlassThicknessProperty = DependencyProperty.Register(
  12. "GlassThickness", typeof(Thickness), typeof(GlassWindow), new PropertyMetadata(new Thickness(0, 0, 0, 0), GlassThicknessChanged));
  13.  
  14. //when the thickness changes, apply the change to the window.
  15. private static void GlassThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  16. {
  17. ((GlassWindow)d).UpdateGlassState();
  18.  
  19. }
  20.  
  21. /// <summary>
  22. /// Local property for Glass thickness.
  23. /// </summary>
  24. public Thickness GlassThickness
  25. {
  26. get { return (Thickness)GetValue(GlassThicknessProperty); }
  27. set { SetValue(GlassThicknessProperty, value); }
  28. }
  29.  
  30. public static readonly DependencyProperty IsAllGlassProperty = DependencyProperty.Register("IsAllGlass",
  31. typeof(bool),
  32. typeof(GlassWindow),
  33. new PropertyMetadata(
  34. OnIsAllGlassChanged));
  35.  
  36. private static void OnIsAllGlassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  37. {
  38. ((GlassWindow)d).UpdateGlassState();
  39. }
  40.  
  41. public bool IsAllGlass
  42. {
  43. get { return (bool)GetValue(IsAllGlassProperty); }
  44. set { SetValue(IsAllGlassProperty, value); }
  45. }
  46.  
  47. private void UpdateGlassState()
  48. {
  49. if (!IsInitialized) return;
  50.  
  51. if (IsAllGlass)
  52. this.AllGlassWindow();
  53. else
  54. this.ExtendFrameIntoClientArea(GlassThickness);
  55. }
  56.  
  57. static GlassWindow()
  58. {
  59. DefaultStyleKeyProperty.OverrideMetadata(typeof(GlassWindow), new FrameworkPropertyMetadata(typeof(GlassWindow)));
  60. }
  61.  
  62. protected override void OnSourceInitialized(EventArgs e)
  63. {
  64. base.OnSourceInitialized(e);
  65. UpdateGlassState();
  66. }
  67. }
  68. }
  69.  

Alright, this class creates a WPF Window subclass, adds a dependency property for GlassThickness and a change handler that internally calls the DesktopWindowManagerAPI.ExtendFrameIntoClientArea if the window is loaded or attaches an event handler if the window is not loaded. Now all you have to do is change your window class over to a GlassWindow class, set the thickness and you rock and roll!

GlassDemoWindow.cs
  1.  
  2. <WPF:GlassWindow x:Class="Codelogic.Controls.WPF.Demo.GlassDemoWindow"
  3. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  5. xmlns:WPF="clr-namespace:Codelogic.Controls.WPF;assembly=Codelogic.Controls.WPF"
  6. Title="Glass Window Demo"
  7. Height="300"
  8. Width="300"
  9. GlassThickness="10000">
  10. <Grid>
  11. <InkCanvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent" />
  12. </Grid>
  13. </WPF:GlassWindow>
  14.  

Glass Ink

Something I have not yet done that I would like to do is figure out how to add functional buttons into the title-bar of an application. If you remember the Microsoft Word snippet from above there’s save / undo / redo buttons in the title bar. But that will have to be a later post.

Hope someone enjoyed my Glassy exploration,

Updates:

  • 2 Dec 2009
    • Changed ExtendFrameIntoClientArea to check to see if window.IsInitialized instead of window.IsLoaded
    • Updated GlassWindow to override OnSourceInitialized instead of adding an event handler for Loaded
    • Added in a method that turns the entire client area into glass by setting the margins to -1.
    • Changed Glass Window to have a Boolean Dependency property to turn it all glass.

– Paul Rohde