使用自定义 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)
它不起作用的原因可能是通常所说的“空域”问题。基本上,您使用的代码是使用 GDI 渲染像素,而 WPF 是使用 DirectX 实现的,并且这两种渲染技术不知道如何共享像素。可能还发生了一些更微妙的事情,但乍一看我怀疑就是这样。
在 WPF 中执行此操作的方式类似于 Mranz 之前建议的方式。有几种简单的方法可以使用 WPF 获得模糊效果:
直接在彼此之上创建两个文本块(例如,在占据同一单元格的网格中),两者具有相同的字体和文本等,但使底部有您想要发光的颜色,然后对其应用模糊效果。您需要小心,如果没有效果,它们都会以相同的方式渲染,否则模糊将会关闭。
创建一个椭圆形,或一个边缘有椭圆形的矩形,并执行相同的技巧,将其放在带有标题的文本块后面,并在其上添加模糊效果。
创建一个
这也有点棘手,因为 Windows 在 Vista 和 7 之间改变了一些行为(我不知道 8 最终会是什么样子)。如果我没记错的话,在 Vista 中,当你最大化窗口时,模糊效果会消失,字体颜色会变成白色。到了7,这种情况就不再发生了。至少在 8(和 Office 2010)的开发者预览版中,标题文本现在居中。此外,当您自己执行此操作时,您需要小心不要遮挡标题按钮,因为它可能不会在您期望的位置开始剪辑文本。
如果您对这两种方法有任何疑问,请随时联系我。我也许可以整理一些示例代码,但我没有方便的样式。另外,如果您解决了该问题,请发布您的解决方案,以便其他人受益:)
希望有所帮助,
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
你不应该经历所有这些麻烦。查看下面我的代码示例。带有
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.