使用自定义 chrome 和 DWM 时重新绘制窗口标题
我正在使用 WPF Shell 集成库 (http://archive.msdn.microsoft.com/WPFShell)但是,当我将自定义镀铬物与 Aero 一起使用时,标题栏被删除。
我知道我需要使用 DrawThemeTextEx 函数来重新绘制窗口标题,但是我找不到任何执行此操作的 C# 示例。我在 使用 DWM 的自定义窗口框架中找到了指南(Windows) 详细说明了绘画的标题标题。
我不太确定(我对 pinvoke 没什么经验)如何在 C# 中执行此操作,以便使用正确的系统字体等。是否有人能够提供一个 C# 示例,我可以将其集成到 WPF Shell 集成库中?
更新 #1: 我已在 Windows 窗体项目中尝试过此代码,并且运行良好。我注意到,如果我将窗体移出屏幕,Windows 窗体将丢失标题文本。所以我相信这个问题可能与此有关。我尝试在 OnRender 事件中绘制标题文本,但这并不能解决问题。
我已将以下代码添加到 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 title = "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);
}
}
}
}
然后,在 DWM 玻璃扩展后,我在以下函数中调用 DrawCustomTitle。知道为什么这不起作用。
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);
}
}
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 title = "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);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
你不应该经历所有这些麻烦。查看下面我的代码示例。带有
Text={TemplateBinding Title}
的标签可以满足您的需求。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.它不起作用的原因可能是通常所说的“空域”问题。基本上,您使用的代码是使用 GDI 渲染像素,而 WPF 是使用 DirectX 实现的,并且这两种渲染技术不知道如何共享像素。可能还发生了一些更微妙的事情,但乍一看我怀疑就是这样。
在 WPF 中执行此操作的方式类似于 Mranz 之前建议的方式。有几种简单的方法可以使用 WPF 获得模糊效果:
直接在彼此之上创建两个文本块(例如,在占据同一单元格的网格中),两者具有相同的字体和文本等,但使底部有您想要发光的颜色,然后对其应用模糊效果。您需要小心,如果没有效果,它们都会以相同的方式渲染,否则模糊将会关闭。
创建一个椭圆形,或一个边缘有椭圆形的矩形,并执行相同的技巧,将其放在带有标题的文本块后面,并在其上添加模糊效果。
创建一个
这也有点棘手,因为 Windows 在 Vista 和 7 之间改变了一些行为(我不知道 8 最终会是什么样子)。如果我没记错的话,在 Vista 中,当你最大化窗口时,模糊效果会消失,字体颜色会变成白色。到了7,这种情况就不再发生了。至少在 8(和 Office 2010)的开发者预览版中,标题文本现在居中。此外,当您自己执行此操作时,您需要小心不要遮挡标题按钮,因为它可能不会在您期望的位置开始剪辑文本。
如果您对这两种方法有任何疑问,请随时联系我。我也许可以整理一些示例代码,但我没有方便的样式。另外,如果您解决了该问题,请发布您的解决方案,以便其他人受益:)
希望有所帮助,
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,