一尘不染

如何在窗口的标题栏中添加一个额外的按钮?

c#

我已经看到某些应用程序(可能不是.NET应用程序)在表单标题栏上的“最小化”按钮左侧有一个额外的按钮吗?如何在C#中实现呢?


阅读 400

收藏
2020-05-19

共1个答案

一尘不染

更新 :添加了一个解决方案,该解决方案将与启用了Windows Vista和Windows 7的Aero一起使用


非航空解决方案

窗口交互的非客户区域由一系列非客户特定消息管理。例如,将WM_NCPAINT消息发送到窗口过程以绘制非客户区。

我从未从.NET做到这一点,但我怀疑您可以覆盖WndProc并处理WM_NC *消息以实现所需的功能。

更新:因为我从没有尝试过.NET,所以花了几分钟时间,我想我可以快速尝试一下。

在Windows
7上尝试此操作,我发现如果我想让OS进行非工作区的基础渲染,则需要禁用窗口主题。因此,这是一个简短的测试。我使用GetWindowDC而不是GetDCEx来获取整个窗口的DC,这仅仅是因为我可以从内存中互操作,并且没有查找GetDcEx的所有标志常量。当然,该代码可以执行更多错误检查。

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
  public partial class CustomBorderForm : Form
  {
    const int WM_NCPAINT = 0x85;

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr GetWindowDC(IntPtr hwnd);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern void DisableProcessWindowsGhosting();

    [DllImport("UxTheme.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern IntPtr SetWindowTheme(IntPtr hwnd, string pszSubAppName, string pszSubIdList);

    public CustomBorderForm()
    {
      // This could be called from main.
      DisableProcessWindowsGhosting();

      InitializeComponent();
    }

    protected override void OnHandleCreated(EventArgs e)
    {
      SetWindowTheme(this.Handle, "", "");
      base.OnHandleCreated(e);
    }

    protected override void WndProc(ref Message m)
    {
      base.WndProc(ref m);

      switch (m.Msg)
      {
        case WM_NCPAINT:
          {
            IntPtr hdc = GetWindowDC(m.HWnd);
            using (Graphics g = Graphics.FromHdc(hdc))
            {
              g.FillEllipse(Brushes.Red, new Rectangle((Width-20)/2, 8, 20, 20));
            }
            int r = ReleaseDC(m.HWnd, hdc);
          }
          break;
      }
    }
  }
}

顺便说一句。我叫DisableProcessWindowsGhosting,如果应用程序响应Windows消息花费的时间太长,这将阻止OS绘制非客户区。如果您不这样做,则在某些情况下将渲染边框,但不会显示您的装饰。因此,这取决于您的要求,它是否适合您。


航空支持的解决方案

在@TheCodeKing的评论提示下,我想我会再来看一下。事实证明,可以在支持Aero的同时以完整记录的方式完成此操作。但这不是为了胆小的人。在这里,我将不提供完整的解决方案,但仍有一些锻炼方法,但是它具有基本知识。

此代码/解决方案基于Win32示例,该示例可在以下位置找到: http://msdn.microsoft.com/zh-
cn/library/bb688195(VS.85).aspx

原则上,您需要执行以下操作。

  • 扩展窗口的工作区以覆盖框架。这是通过处理WM_NCCALCSIZE消息并返回0来完成的。这使非客户端区域的大小为0,因此客户端区域现在覆盖了整个窗口。
  • 使用DwmExtendFrameIntoClientArea将Frame扩展到工作区。这使操作系统可以在客户端区域上渲染框架。

以上步骤将为您提供带有标准玻璃框的窗口,但不包括系统菜单(“窗口”图标)和标题。最小化,最大化和关闭按钮仍将绘制并起作用。您将无法执行的操作是拖动或调整窗口大小,这是因为框架并不真正存在,请记住客户区域覆盖了整个窗口,我们只是要求操作系统将框架绘制到客户区域上。

现在,您可以像平常一样在窗口上绘制,甚至可以在框架顶部绘制。您甚至可以将控件放在字幕区域。

最后,通过从您的DwmDefWindowProc调用DWMWndProc(在处理它之前),为您处理命中测试。它返回一个布尔值,指示DWM是否为您处理了该消息。

2020-05-19