DWM 实现半透明窗口

#include <atlbase.h>
#include <atlwin.h>

#include <d2d1.h>
#include <d2d1effects.h>
#include <dwmapi.h>
#include <shellscalingapi.h>

#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "Shcore.lib")
#pragma comment(lib, "D2d1.lib")

#define GET_X_LPARAM(lParam) ((int)(short)LOWORD(lParam))
#define GET_Y_LPARAM(lParam) ((int)(short)HIWORD(lParam))

const int kTitlebarHeight = 40;

class DWMTranslucentWindow : public CWindowImpl<DWMTranslucentWindow> {
 public:
  BEGIN_MSG_MAP(DWMTranslucentWindow)
  MESSAGE_HANDLER(WM_CREATE, OnCreate)
  MESSAGE_HANDLER(WM_NCCALCSIZE, OnNCCalcSize)
  MESSAGE_HANDLER(WM_NCACTIVATE, OnNCActivate)
  MESSAGE_HANDLER(WM_NCHITTEST, OnNCHitTest)
  MESSAGE_HANDLER(WM_NCPAINT, OnNCPaint)
  MESSAGE_HANDLER(WM_PAINT, OnPaint)
  MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
  MESSAGE_HANDLER(WM_WINDOWPOSCHANGED, OnWindowPosChanged)
  END_MSG_MAP()

  LRESULT OnCreate(UINT uMsg, WPARAM wparam, LPARAM lparam, BOOL&) {
    DWMNCRENDERINGPOLICY policy = DWMNCRP_DISABLED;
    DwmSetWindowAttribute(m_hWnd, DWMWA_NCRENDERING_POLICY, &policy,
                          sizeof(DWMNCRENDERINGPOLICY));
    return 0;
  }

  LRESULT OnNCCalcSize(UINT uMsg, WPARAM wparam, LPARAM l_param, BOOL&) {
    BOOL mode = wparam;

    RECT* client_rect =
        mode ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param)->rgrc[0])
             : reinterpret_cast<RECT*>(l_param);
    HMONITOR monitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONULL);
    if (!monitor) {
      monitor = MonitorFromRect(client_rect, MONITOR_DEFAULTTONULL);
      if (!monitor) {
        return 0;
      }
    }

    RECT insets;
    if (GetClientAreaInsets(monitor, &insets)) {
      client_rect->left += insets.left;
      client_rect->top += insets.top;
      client_rect->bottom -= insets.bottom;
      client_rect->right -= insets.right;
    }
    return 0;
  }
  LRESULT OnNCActivate(UINT uMsg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
    RedrawWindow();
    return 0;
  }

  LRESULT OnNCHitTest(UINT uMsg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
    POINT pt{GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)};
    ::MapWindowPoints(HWND_DESKTOP, m_hWnd, &pt, 1);
    if (pt.y <= kTitlebarHeight) {
      return HTCAPTION;
    }
    return ::DefWindowProc(m_hWnd, WM_NCHITTEST, 0, lparam);
  }

  LRESULT OnNCPaint(UINT uMsg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
    HDC hdc = ::GetWindowDC(m_hWnd);
    if (hdc) {
      RECT rcclient;
      ::GetClientRect(m_hWnd, &rcclient);
      RECT rcwin;
      ::GetWindowRect(m_hWnd, &rcwin);
      POINT ptupleft;
      ptupleft.x = rcwin.left;
      ptupleft.y = rcwin.top;
      ::MapWindowPoints(0, m_hWnd, (LPPOINT)&rcwin,
                        (sizeof(RECT) / sizeof(POINT)));
      ::OffsetRect(&rcclient, -rcwin.left, -rcwin.top);
      ::OffsetRect(&rcwin, -rcwin.left, -rcwin.top);

      HRGN rgntemp = NULL;
      if (wparam == NULLREGION || wparam == ERROR) {
        ::ExcludeClipRect(hdc, rcclient.left, rcclient.top, rcclient.right,
                          rcclient.bottom);
      } else {
        rgntemp = ::CreateRectRgn(
            rcclient.left + ptupleft.x, rcclient.top + ptupleft.y,
            rcclient.right + ptupleft.x, rcclient.bottom + ptupleft.y);
        if (::CombineRgn(rgntemp, (HRGN)wparam, rgntemp, RGN_DIFF) ==
            NULLREGION) {
          // nothing to paint
        }
        ::OffsetRgn(rgntemp, -ptupleft.x, -ptupleft.y);
        ::ExtSelectClipRgn(hdc, rgntemp, RGN_AND);
      }

      ::FillRect(hdc, &rcwin,
                 reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));

      ::ReleaseDC(m_hWnd, hdc);
      if (rgntemp != 0) {
        ::DeleteObject(rgntemp);
      }
    }
    handled = TRUE;
    return 0;
  }

  LRESULT OnPaint(UINT uMsg, WPARAM wparam, LPARAM lparam, BOOL&) {
    PAINTSTRUCT ps;
    HDC display_dc = ::BeginPaint(m_hWnd, &ps);

    if (!IsRectEmpty(&ps.rcPaint)) {
      ::FillRect(display_dc, &ps.rcPaint,
                 reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH)));
    }

    ::EndPaint(m_hWnd, &ps);

    DrawD2D();
    return 0;
  }

  LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wparam, LPARAM lparam, BOOL&) {
    return 1;
  }

  LRESULT OnWindowPosChanged(UINT uMsg,
                             WPARAM wparam,
                             LPARAM lparam,
                             BOOL& handled) {
    UpdateDwmFrame();
    handled = false;
    return 0;
  }

  bool GetClientAreaInsets(HMONITOR monitor, RECT* insets) const {
    UINT monitor_dpi_x, monitor_dpi_y;
    ::GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &monitor_dpi_x,
                       &monitor_dpi_y);
    const int frame_thickness =
        ::GetSystemMetricsForDpi(SM_CXSIZEFRAME, monitor_dpi_x) +
        ::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, monitor_dpi_x);

    insets->left = 0;
    insets->top = ::IsZoomed(m_hWnd) ? frame_thickness : 0;
    insets->right = frame_thickness;
    insets->bottom = frame_thickness;
    return false;
  }

  void UpdateDwmFrame() {
    MARGINS margins = {
        .cxLeftWidth = -1,
        .cxRightWidth = -1,
        .cyTopHeight = -1,
        .cyBottomHeight = -1,
    };

    ::DwmExtendFrameIntoClientArea(m_hWnd, &margins);
  }

  void InitD2D(HWND hwnd) {
    if (!d2d_factory_) {
      D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2d_factory_);
    }

    if (!render_target_) {
      RECT rc;
      ::GetClientRect(hwnd, &rc);

      D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
          D2D1_RENDER_TARGET_TYPE_DEFAULT,
          D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),
          0, 0);

      D2D1_HWND_RENDER_TARGET_PROPERTIES hwnd_props =
          D2D1::HwndRenderTargetProperties(hwnd,
                                           D2D1::SizeU(rc.right, rc.bottom));

      d2d_factory_->CreateHwndRenderTarget(props, hwnd_props, &render_target_);
    }
  }

  void DrawD2D() {
    InitD2D(m_hWnd);

    RECT rc;
    ::GetClientRect(m_hWnd, &rc);

    render_target_->BeginDraw();

    render_target_->Clear(D2D1::ColorF(0, 0, 0, 0));
    {
      CComPtr<ID2D1SolidColorBrush> brush;
      render_target_->CreateSolidColorBrush(
          D2D1::ColorF(1.0f, 1.0f, 0.0f, 0.5f), &brush);
      render_target_->FillRectangle(
          D2D1::RectF(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top),
          brush);
    }

    {
      CComPtr<ID2D1SolidColorBrush> brush;
      render_target_->CreateSolidColorBrush(
          D2D1::ColorF(1.0f, 0.1f, 0.1f, 1.0f), &brush);
      render_target_->FillRectangle(
          D2D1::RectF(rc.left, rc.top, rc.right - rc.left,
                      rc.top + kTitlebarHeight),
          brush);
    }
    render_target_->EndDraw();
  }

 private:
  CComPtr<ID2D1Factory> d2d_factory_;
  CComPtr<ID2D1HwndRenderTarget> render_target_;
};

int main(int argc, char** argv) {
  DWMTranslucentWindow main_window;

  RECT rc{
      .left = 0,
      .top = 0,
      .right = 800,
      .bottom = 600,
  };
  main_window.Create(nullptr, rc, nullptr, WS_POPUP);
  main_window.CenterWindow(::GetDesktopWindow());
  main_window.ShowWindow(SW_NORMAL);

  MSG msg;
  while (::GetMessage(&msg, nullptr, 0, 0)) {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
  }

  return 0;
}

posted @ 2025-08-06 14:06  吱吱的笔记  阅读(38)  评论(0)    收藏  举报