Re-drawing the Window title when using custom chrome and DWM
I am using the WPF Shell Integration Library (http://archive.msdn.microsoft.com/WPFShell) however when I use the custom chrome with Aero the title bar is removed.
I understand that I need to use DrawThemeTextEx function to re-draw the window title, however I can not find any C# samples that do this. I have located a guide at Custom Window Frame using DWM (Windows) that details the painting the caption title.
I am not really sure (I have little experience with pinvoke) how to do this in C# so that the correct system fonts are used etc. Is someone able to provide a C# sample that I could integrate into the WPF Shell Integration Library?
Update #1: I have tried this code within a Windows Form project and it works fine. I notice that the Windows Form will loose the title text if I move the form off the screen. So I believe the issue maybe related to this. I have tried drawing the title text within the OnRender event however that doesn't fix the issue.
I have added the following code to the WindowChromeWorker.cs:
private void _DrawCustomTitle(IntPtr hwnd)
{
if (NativeMethods.DwmIsCompositionEnabled())
{
Standard.RECT rcClient = new Standard.RECT();
NativeMethods.GetClientRect(hwnd, ref rcClient);
Standard.RECT rcPaint = rcClient;
rcPaint.Top += 8;
rcPaint.Right -= 125;
rcPaint.Left += 8;
rcPaint.Bottom = 50;
IntPtr destdc = NativeMethods.GetDC(hwnd);
IntPtr Memdc = NativeMethods.CreateCompatibleDC(destdc); // Set up a memory DC where we'll draw the text.
IntPtr bitmap;
IntPtr bitmapOld = IntPtr.Zero;
IntPtr logFont;
uint uFormat = NativeMethods.DT_SINGLELINE | NativeMethods.DT_TOP | NativeMethods.DT_LEFT | NativeMethods.DT_WORD_ELLIPSIS;
BITMAPINFO dib = new BITMAPINFO();
dib.bmiHeader.biHeight = -(rcClient.Bottom - rcClient.Top); // negative because DrawThemeTextEx() uses a top-down DIB
dib.bmiHeader.biWidth = rcClient.Right - rcClient.Left;
dib.bmiHeader.biPlanes = 1;
dib.bmiHeader.biSize = Marshal.SizeOf(typeof(BITMAPINFOHEADER));
dib.bmiHeader.biBitCount = 32;
dib.bmiHeader.biCompression = NativeMethods.BI_RGB;
if (!(NativeMethods.SaveDC(Memdc) == 0))
{
bitmap = NativeMethods.CreateDIBSection(Memdc, ref dib, NativeMethods.DIB_RGB_COLORS, 0, IntPtr.Zero, 0); // Create a 32-bit bmp for use in offscreen drawing when glass is on
if (!(bitmap == IntPtr.Zero))
{
bitmapOld = NativeMethods.SelectObject(Memdc, bitmap);
System.Drawing.Font font = new System.Drawing.Font("Segoe UI", 9f);
IntPtr hFont = font.ToHfont();
logFont = NativeMethods.SelectObject(Memdc, hFont);
try
{
System.Windows.Forms.VisualStyles.VisualStyleRenderer renderer = new System.Windows.Forms.VisualStyles.VisualStyleRenderer(System.Windows.Forms.VisualStyles.VisualStyleElement.Window.Caption.Active);
NativeMethods.DTTOPTS dttOpts = new NativeMethods.DTTOPTS();
dttOpts.dwSize = (int)Marshal.SizeOf(typeof(NativeMethods.DTTOPTS));
dttOpts.dwFlags = NativeMethods.DTT_COMPOSITED | NativeMethods.DTT_GLOWSIZE;
dttOpts.iGlowSize = 15;
string titl开发者_开发知识库e = "Windows Title";
NativeMethods.DrawThemeTextEx(renderer.Handle, Memdc, 0, 0, title, -1, uFormat, ref rcPaint, ref dttOpts);
NativeMethods.BitBlt(destdc, rcClient.Left, rcClient.Top, rcClient.Right - rcClient.Left, rcClient.Bottom - rcClient.Top, Memdc, 0, 0, NativeMethods.SRCCOPY);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
// Clean Up
NativeMethods.SelectObject(Memdc, bitmapOld);
NativeMethods.SelectObject(Memdc, logFont);
NativeMethods.DeleteObject(bitmap);
NativeMethods.DeleteObject(hFont);
NativeMethods.ReleaseDC(Memdc, -1);
NativeMethods.DeleteDC(Memdc);
}
}
}
}
I am then calling the DrawCustomTitle within the following function after the DWM glass is extended. Any idea why this would not be working.
private void _ExtendGlassFrame()
{
Assert.IsNotNull(_window);
// Expect that this might be called on OSes other than Vista.
if (!Utility.IsOSVistaOrNewer)
{
// Not an error. Just not on Vista so we're not going to get glass.
return;
}
if (IntPtr.Zero == _hwnd)
{
// Can't do anything with this call until the Window has been shown.
return;
}
// Ensure standard HWND background painting when DWM isn't enabled.
if (!NativeMethods.DwmIsCompositionEnabled())
{
_hwndSource.CompositionTarget.BackgroundColor = SystemColors.WindowColor;
}
else
{
// This makes the glass visible at a Win32 level so long as nothing else is covering it.
// The Window's Background needs to be changed independent of this.
// Apply the transparent background to the HWND
_hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent;
// Thickness is going to be DIPs, need to convert to system coordinates.
Point deviceTopLeft = DpiHelper.LogicalPixelsToDevice(new Point(_chromeInfo.GlassFrameThickness.Left, _chromeInfo.GlassFrameThickness.Top));
Point deviceBottomRight = DpiHelper.LogicalPixelsToDevice(new Point(_chromeInfo.GlassFrameThickness.Right, _chromeInfo.GlassFrameThickness.Bottom));
var dwmMargin = new MARGINS
{
// err on the side of pushing in glass an extra pixel.
cxLeftWidth = (int)Math.Ceiling(deviceTopLeft.X),
cxRightWidth = (int)Math.Ceiling(deviceBottomRight.X),
cyTopHeight = (int)Math.Ceiling(deviceTopLeft.Y),
cyBottomHeight = (int)Math.Ceiling(deviceBottomRight.Y),
};
NativeMethods.DwmExtendFrameIntoClientArea(_hwnd, ref dwmMargin);
this._DrawCustomTitle(_hwnd);
}
}
You shouldn't have to go through all of that trouble. Check out my code example below. The label with Text={TemplateBinding Title}
does what you want.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:Microsoft.Windows.Shell;assembly=Microsoft.Windows.Shell"
Title="MainWindow" Height="350" Width="525">
<Window.Style>
<Style TargetType="Window">
<Setter Property="shell:WindowChrome.WindowChrome">
<Setter.Value>
<shell:WindowChrome GlassFrameThickness="4,40,4,4" ResizeBorderThickness="5" CaptionHeight="30"/>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Window">
<Grid x:Name="PART_ComponentRoot">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition/>
<RowDefinition Height="4"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4"/>
<ColumnDefinition/>
<ColumnDefinition Width="4"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{TemplateBinding Title}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<ContentPresenter Grid.Row="1" Grid.Column="1" Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Style>
<Grid Background="White">
</Grid>
Probably the reason it isn't working is what is commonly referred to as "Airspace" issues. Basically the code you're using is rendering pixels with GDI and WPF is implemented with DirectX, and the two rendering technologies don't know how to share pixels. There may be something more subtle also happening, but at first glance I suspect that's it.
The way you'd want to do this in WPF is similar to what Mranz suggested before. There are a couple simple ways to get the blur effect with WPF:
Create two text blocks directly on top of each other (e.g. in a grid occupying the same cell) both with the same font and text, etc, but make the bottom one have the color you want for glow and then apply a blur effect on it. You need to be careful that without the effect they are both rendering the same way or the blur will be off.
Create an ellipse, or a rectangle with ellipses at the edges, and do the same trick of putting it behind the textblock with the title and put a blur effect on it.
It's a bit tricky also because Windows changed some of these behaviors between Vista and 7 (and I have no idea what 8 is ultimately going to look like). If I remember correctly, in Vista when you maximized the window the blur effect would go away and the font color would turn white. In 7 that doesn't happen anymore. In at least the developer preview of 8 (and Office 2010) the title text is now centered. Also when you're doing this yourself you need to be careful about not obscuring the caption buttons because it probably won't start clipping the text at the place you'd expect.
If you have problems with either of those approaches feel free to ping me. I can probably put together some sample code but I don't have the styles handy. Also if you solve it, please post your solution so others can benefit :)
Hope that helps,
精彩评论