重新绘制窗口标题时,使用自定义铬和DWM

本文关键字:自定义 DWM 新绘制 绘制 窗口标题 | 更新日期: 2023-09-27 18:06:37

我正在使用WPFShell集成库(http://archive.msdn.microsoft.com/WPFShell),但是当我使用自定义chrome与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);
        }
    }

重新绘制窗口标题时,使用自定义铬和DWM

你不应该经历所有这些麻烦。请查看下面的代码示例。有Text={TemplateBinding Title}的标签可以满足您的要求。

<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>

可能它不工作的原因是通常被称为"空域"问题。基本上,你使用的代码是用GDI渲染像素,而WPF是用DirectX实现的,这两种渲染技术不知道如何共享像素。也许还有更微妙的事情正在发生,但乍一看,我怀疑就是这样。

在WPF中,您想要这样做的方式与Mranz之前建议的类似。使用WPF有几个简单的方法来获得模糊效果:
  1. 直接在彼此的顶部创建两个文本块(例如在一个网格占用相同的单元格),具有相同的字体和文本等,但使底部的一个有你想要的发光颜色,然后应用一个模糊效果。你需要注意的是,如果没有效果,它们都以相同的方式渲染,否则模糊将被关闭。

  2. 创建一个椭圆,或者一个边缘有椭圆的矩形,用同样的技巧把它放在带有标题的文本块后面,并给它添加一个模糊效果。

这有点棘手,因为Windows在Vista和7之间改变了一些这些行为(我不知道8最终会是什么样子)。如果我没记错的话,在Vista中,当你最大化窗口时,模糊效果会消失,字体颜色会变成白色。到了7岁,这种情况就不再发生了。至少在开发者预览版的8(和Office 2010)中,标题文字现在居中了。另外,当你自己这样做的时候,你需要小心不要模糊标题按钮,因为它可能不会在你期望的地方开始剪切文本。

如果你对这两种方法中的任何一种有问题,请随时联系我。我可能会把一些示例代码放在一起,但我没有现成的样式。如果你解决了这个问题,请把你的解决方案贴出来,这样其他人也能从中受益:)

希望有帮助,