Introduction
J'avais déjà écris des articles sur la programmation de l'api DWM mais ce n'était pas complet, car l'api DWM permet de faire beaucoup de choses. Commençons par un petit résumé.
La DLL DWAPI.DLL permet de dialoguer directement avec le Destop Window Manager de vista, il est ainsi possible de travailler avec les effets de vista. Pour cela on doit d'abord verifier qu'on est bien en mode composition. C'est cette fonction qui permet de faire le test:
[DllImport("dwmapi.dll", PreserveSig = false)]
static extern bool DwmIsCompositionEnabled();
Elle retourne un boolean en fonction.
Blur Effect
On a la possibilité de mettre l'effet Blur dans la zone cliente. Pour cela je créé un projet WPF et dans ma fenétre je mets un bouton:
Comme je vais manipuler cette fenêtre avec des fonctions de l'api Win32 il me faut sont Handle, c'est à dire son ID pour windows. En WPF on n'a pas de propriété Hwnd on doit la récupérer avec l'objet WindowInteropHelper de la maniére suivante:
IntPtr hwnd = new WindowInteropHelper(window).Handle;
Attention cette fonction ne retourne un Handle QUE DANS LE FORM LOAD pas dans le constructeur de la classe.
Pouvoir mettre le Blur dans la zone cliente, je vais utiliser cette fonction:
[DllImport("dwmapi.dll", PreserveSig = false)]
static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
Cette fonction prend en paramétre une structure MARGINS que voici:
[StructLayout(LayoutKind.Sequential)]
struct MARGINS
{
public MARGINS(Thickness t)
{
Left = (int)t.Left;
Right = (int)t.Right;
Top = (int)t.Top;
Bottom = (int)t.Bottom;
}
public int Left;
public int Right;
public int Top;
public int Bottom;
}
Cette structure definit les marges de la zone qui sera considérée comme cliente.Voici donc le code XAML:
<Window x:Class="WPFBlur.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPFBlur" Height="300" Width="300" Loaded="Form_Loaded"
>
<Grid>
<Button Height="23" Margin="102,104,115,0" Name="button1" VerticalAlignment="Top" >Button</Button>
</Grid>
</Window>
Et Voici le code C#:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using System.Windows.Interop;
namespace WPFBlur
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : System.Windows.Window
{
[DllImport("dwmapi.dll", PreserveSig = false)]
static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
[DllImport("dwmapi.dll", PreserveSig = false)]
static extern bool DwmIsCompositionEnabled();
public Window1()
{
InitializeComponent();
button1.Background = new LinearGradientBrush(Color.FromArgb(60, 0, 255, 0), Color.FromArgb(60, 255, 0, 0), 60);
}
private void Form_Loaded(object sender, RoutedEventArgs rea)
{
ExtendGlassFrame(this, new Thickness(-1));
}
public static bool ExtendGlassFrame(Window window, Thickness margin)
{
if (!DwmIsCompositionEnabled())
return false;
IntPtr hwnd = new WindowInteropHelper(window).Handle;
if (hwnd == IntPtr.Zero)
throw new InvalidOperationException("La fenêtre doit être visible avant d’étendre la frame sur la zone cliente");
window.Background = Brushes.Transparent;
HwndSource.FromHwnd(hwnd).CompositionTarget.BackgroundColor = Colors.Transparent;
MARGINS margins = new MARGINS(margin);
DwmExtendFrameIntoClientArea(hwnd, ref margins);
return true;
}
[StructLayout(LayoutKind.Sequential)]
struct MARGINS
{
public MARGINS(Thickness t)
{
Left = (int)t.Left;
Right = (int)t.Right;
Top = (int)t.Top;
Bottom = (int)t.Bottom;
}
public int Left;
public int Right;
public int Top;
public int Bottom;
}
}
}
Bien cela donne ceci:

Evidemment on peut prendre l'effet Blur mais pas mettre la transparence. Dans ce cas voici ce qu'il faut changer. D'abord on utilisera cette fonction:
[DllImport("dwmapi.dll", PreserveSig = false)]
static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);
Elle prend en paramétre le handle de la fenetre et une structure qui lui indique comment vas se mettre la transparence et le Blur de l'application:
[StructLayout(LayoutKind.Sequential)]
struct DWM_BLURBEHIND
{
public uint dwFlags;
public bool fEnable;
public IntPtr hRgnBlur;
public bool fTransitionOnMaximized;
}
La prémiére propriété est un flag qui indique comment mettre en place le blur voici les constantes qui entrent en ligne de compte pour ce flags:
public const uint DWM_BB_ENABLE = 0x00000001;
public const uint DWM_BB_BLURREGION = 0x00000002;
public const uint DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004;
Voici le code de la méthode qui permettra de supprimer la transparence de la fenêtre:
public bool EnableBlurBehindWindow(Window wnd)
{
if (!DwmIsCompositionEnabled())
return false;
IntPtr hwnd = new WindowInteropHelper(wnd).Handle;
if (hwnd == IntPtr.Zero)
throw new InvalidOperationException("The Window must be shown before extending glass.");
DWM_BLURBEHIND objBlur = new DWM_BLURBEHIND();
objBlur.fEnable = true;
objBlur.dwFlags = DWM_BB_ENABLE | DWM_BB_TRANSITIONONMAXIMIZED;
objBlur.fTransitionOnMaximized = true;
DwmEnableBlurBehindWindow(hwnd, ref objBlur);
return true;
}
Voici le résultat:
Allons plus loin.
Avec l'api DWM il y a la possibilité de contrôler la couleur des fenêtres et le théme qui est appliqué a windows. Prenons un exemple simple, la charge de la batterie. Ca me gave souvent d'aller voir a coté de l'heure ou en est la batterie. Ce qui pourrait être sympa c'est d'avoir la couleur des fenêtres qui changent de Vert vers Rouge par rapport au pourcentage de la battery. Pour pouvoir mettre à jours la couleur du thémes on utilisera la fonction suivante:
[DllImport("dwmapi.dll", EntryPoint = "#104")]
private static extern int DwmpSetColorization(uint ColorizationColor, bool ColorizationOpaqueBlend, uint Opacity);
Bien entendu on doit en premier temps sauvegarder ce que l'utilisateur avait choisi. Pour récupérer les informations de couleur du théme on utilisera cette fonction:
[DllImport("dwmapi.dll")]
private static extern void DwmGetColorizationColor(out uint ColorizationColor, out bool ColorizationOpaqueBlend);
Premiérement j'ai créé une application WPF avec visual studio. Dans cette application j'ai ajouté un timer qui regarde la charge de la batterie toutes les 10 secondes. Suivant l'état de la batterie il créé une couleur qui passe du vert au rouge. L'application mets cette couleur comme couleur de fenêtre.
Voici le code complet de l'application:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Timers;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace ThemeBatteryCharge
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : System.Windows.Window
{
private System.Timers.Timer timer=null;
uint myColor = 0;
bool myOpacity = false;
public Window1()
{
DwmGetColorizationColor(out myColor, out myOpacity);
timer = new System.Timers.Timer();
InitializeComponent();
timer.Interval = 10000;
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
timer.Start();
timer_Elapsed(null, null);
}
[DllImport("dwmapi.dll", EntryPoint = "#104")]
private static extern int DwmpSetColorization(uint ColorizationColor, bool ColorizationOpaqueBlend, uint Opacity);
[DllImport("dwmapi.dll")]
private static extern void DwmGetColorizationColor(out uint ColorizationColor, out bool ColorizationOpaqueBlend);
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
float battCharge = SystemInformation.PowerStatus.BatteryLifePercent;
System.Drawing.Color themeColor = System.Drawing.Color.FromArgb(200, (byte)(255 / (battCharge + 1)), (byte)(255 * battCharge), 0);
if (SystemInformation.PowerStatus.PowerLineStatus == System.Windows.Forms.PowerLineStatus.Offline)
{
DwmpSetColorization((uint)themeColor.ToArgb(), false, 0);
}
else
{
DwmpSetColorization(myColor, myOpacity, 0);
}
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
DwmpSetColorization(myColor, myOpacity, 0);
}
}
}
Voici ce que cela donne par rapport aux différentes charge de la battery, toutes les fenêtres de Windows sont mise à jours:
