Friday, October 28, 2011

Write a Glass Window Behavior



The following effect can be achieved using WPF Shell Integration Library and WPF Customizable Window, the purpose of this article is to use the behavior and dwmapi to manager forms.

There are glass behavior in the Expression Gallery. See Link and Link.

By read his code, the author uses the ShaderEffectLibrary.

By reading this artile, we knows how to create a glass window. And by reading the discussion here, we know how to hide the close/max/min button in a form.

In this case my this article will talking about writing a behavior which can make a glass window, Like the following picture.





For .Net 4.0, we can use the code:

var window=new Window();

var handle=new WindowInteropHelper(window).EnsureHandle();

to make sure that the handle will be created. See the discussion here. So if it is .Net 4.0, to create the behavior is pretty simple.

You can download my code here.

or below:

namespace GlassWindowBehaviorTest

{ using System;

using System.Runtime.InteropServices;

using System.Windows;

using System.Windows.Interactivity;

using System.Windows.Interop;

using System.Windows.Media;

///

/// TODO: Update summary.

///

public class GlassWindowBehavior : Behavior<Window>

{

#region Constants and Fields

///

/// The gw l_ style.

///

private const int GWL_STYLE = -16;

///

/// The w s_ sysmenu.

///

private const int WS_SYSMENU = 0x80000;

#endregion

#region Public Methods

///

/// The create glass window.

///

///

/// The window.

///

public void CreateGlassWindow(Window window)

{

window.Background = new SolidColorBrush(Colors.Transparent);

IntPtr windowInteropHelper = new WindowInteropHelper(window).EnsureHandle();

IntPtr myHwnd = windowInteropHelper;

SetWindowLong(myHwnd, GWL_STYLE, GetWindowLong(myHwnd, GWL_STYLE) & ~WS_SYSMENU);

HwndSource mainWindowSrc = HwndSource.FromHwnd(myHwnd);

mainWindowSrc.CompositionTarget.BackgroundColor = Color.FromArgb(0, 0, 0, 0);

var margins = new Margins { cxLeftWidth = -1, cxRightWidth = -1, cyTopHeight = -1, cyBottomHeight = -1 };

DwmExtendFrameIntoClientArea(myHwnd, ref margins);

}

#endregion

#region Methods

///

/// The on attached.

///

protected override void OnAttached()

{

Window window = this.AssociatedObject;

this.AssociatedObject.Loaded += (s, e) => this.CreateGlassWindow(window);

base.OnAttached();

}

///

/// The on detaching.

///

protected override void OnDetaching()

{

base.OnDetaching();

}

///

/// The dwm extend frame into client area.

///

///

/// The hwnd.

///

///

/// The p mar inset.

///

///

/// The dwm extend frame into client area.

///

[DllImport("DwmApi.dll")]

private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref Margins pMarInset);

///

/// The get window long.

///

///

/// The hwnd.

///

///

/// The n index.

///

///

/// The get window long.

///

[DllImport("user32.dll", SetLastError = true)]

private static extern int GetWindowLong(IntPtr hwnd, int nIndex);

///

/// The set window long.

///

///

/// The hwnd.

///

///

/// The n index.

///

///

/// The dw new long.

///

///

/// The set window long.

///

[DllImport("user32.dll")]

private static extern int SetWindowLong(IntPtr hwnd, int nIndex, int dwNewLong);

#endregion

///

/// The margins.

///

[StructLayout(LayoutKind.Sequential)]

private struct Margins

{

///

/// The cx left width.

///

public int cxLeftWidth;

///

/// The cx right width.

///

public int cxRightWidth;

///

/// The cy top height.

///

public int cyTopHeight;

///

/// The cy bottom height.

///

public int cyBottomHeight;

}

}

}

The problem is for the .net 3.5 or earlier version, there is no EnsureHandle method, WPF guru Pete Brown has a blog on this. I am going to follow his artile to re write my behavior class so that the behavior can be used in .net 3.5 version.


And on msdn,Alexander Yudakov wrote the extension method for EnsureHandle using reflection, this is very cool. So if we have .net 3.5 application, another way to make sure the handle is created, we can use his extension method.

using System.Reflection;
namespace System.Windows.Interop
{
///
/// Provides NetFX 4.0 EnsureHandle method for
/// NetFX 3.5 WindowInteropHelper class.
///

public static class WindowInteropHelperExtensions
{
///
/// Creates the HWND of the window if the HWND has not been created yet.
///

/// An instance of WindowInteropHelper class.
/// An IntPtr that represents the HWND.
/// Use the EnsureHandle method when you want to separate
/// window handle (HWND) creation from the
/// actual showing of the managed Window.

public static IntPtr EnsureHandle(this WindowInteropHelper helper)
{
if (helper == null)
throw new ArgumentNullException("helper");
if (helper.Handle == IntPtr.Zero)
{
var window = (Window)typeof(WindowInteropHelper).InvokeMember("_window",
BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic,
null, helper, null);
typeof(Window).InvokeMember("SafeCreateWindow",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
null, window, null);
}
return helper.Handle;
}
}
}
Finally what I built a photo frame like this:




Thursday, October 20, 2011

Apply Themes to WPF applications


NuGet command:
PM> Install-Package wpfthemes
As the WPF.Themes is built in Environment .Net 4.0, if we are building our application for .Net 4.0 above version, we can apply themes simply like below:
using WPF.Themes;     

namespace UseNuGetApplyThemes 
{          /// Interaction logic for MainWindow.xaml           public partial class MainWindow : Window     
      {         
          public MainWindow()         
         {             
             InitializeComponent();             
             this.ApplyTheme("WhistlerBlue");         
         }     
     } 
} 
The Themes names can be:
  • ExpressionDark
  • ExpressionLight
  • Rainier
  • RainierPurple
  • RainierOrange
  • RainierRadialBlue
  • Shiny
  • ShinyDarkGreen
  • ShinyDarkTeal
  • ShinyDarkPurple
  • ShinyBlue
  • ShinyRed
  • Bureau
  • BureauBlack
  • BureauBlue
  • Whistler
  • WhistlerBlue
  • DavesGlossyControls
  • DavesGlossyControls
  • UXMusing
  • UXMusingRed
  • UXMusingGreen
  • UXMusingRough
  • UXMusingRoughGreen
  • UXMusingRoughRed
  • UXMusingBubbly
  • UXMusingBubblyBlue
Let's use ILSpy to analyze the WPF.Themes.dll assembly, we w
ill see it use ThemeManager class extension method to apply themes, it is merge the theme to app resources dictionary, so if not .net 4.0 application, like .net 3.5, we can use its ThemeManager class, sometimes we don't want all the themes files included in the assemblies or use other themes not in the above list, we can use its method to apply themes too. Let's post it below:
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
namespace WPF.Themes
{
 public static class ThemeManager
 {
  public static readonly DependencyProperty ThemeProperty = DependencyProperty.RegisterAttached("Theme", typeof(string), typeof(ThemeManager), new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(ThemeManager.OnThemeChanged)));
  public static ResourceDictionary GetThemeResourceDictionary(string theme)
  {
   ResourceDictionary result;
   if (theme != null)
   {
    Assembly assembly = Assembly.LoadFrom("WPF.Themes.dll");
    string uriString = string.Format("/WPF.Themes;component/{0}/Theme.xaml", theme);
    result = (Application.LoadComponent(new Uri(uriString, UriKind.Relative)) as ResourceDictionary);
   }
   else
   {
    result = null;
   }
   return result;
  }
  public static string[] GetThemes()
  {
   return new string[]
   {
    "ExpressionDark", 
    "ExpressionLight", 
    "ShinyBlue", 
    "ShinyRed", 
    "DavesGlossyControls", 
    "WhistlerBlue", 
    "BureauBlack", 
    "BureauBlue", 
    "BubbleCreme", 
    "TwilightBlue", 
    "UXMusingsRed", 
    "UXMusingsGreen", 
    "UXMusingsBubblyBlue"
   };
  }
  public static void ApplyTheme(this Application app, string theme)
  {
   ResourceDictionary themeResourceDictionary = ThemeManager.GetThemeResourceDictionary(theme);
   if (themeResourceDictionary != null)
   {
    app.Resources.MergedDictionaries.Clear();
    app.Resources.MergedDictionaries.Add(themeResourceDictionary);
   }
  }
  public static void ApplyTheme(this ContentControl control, string theme)
  {
   ResourceDictionary themeResourceDictionary = ThemeManager.GetThemeResourceDictionary(theme);
   if (themeResourceDictionary != null)
   {
    control.Resources.MergedDictionaries.Clear();
    control.Resources.MergedDictionaries.Add(themeResourceDictionary);
   }
  }
  public static string GetTheme(DependencyObject d)
  {
   return (string)d.GetValue(ThemeManager.ThemeProperty);
  }
  public static void SetTheme(DependencyObject d, string value)
  {
   d.SetValue(ThemeManager.ThemeProperty, value);
  }
  private static void OnThemeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
   string text = e.NewValue as string;
   if (!(text == string.Empty))
   {
    ContentControl contentControl = d as ContentControl;
    if (contentControl != null)
    {
     contentControl.ApplyTheme(text);
    }
   }
  }
 }
}