Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

透明窗口:实现方式 #4

Open
suhao opened this issue Apr 23, 2019 · 15 comments
Open

透明窗口:实现方式 #4

suhao opened this issue Apr 23, 2019 · 15 comments

Comments

@suhao
Copy link
Member Author

suhao commented Apr 23, 2019

子窗口透明

https://blog.csdn.net/xcl119xcl/article/details/5574188

https://www.cnblogs.com/xnzzj/articles/4524085.html

子窗口的透明和主窗口完全不同, 主窗口是通过layeredwindow实现的, 而子窗口则必须自己实现, 尤其是半透明, 必须自己对获取的透明背景图进行操作。 要实现子窗口的透明需要做到下面几步:

  1. windows的ExStyle中需要包含WS_EX_TRANSPARENT属性

  2. windows的Style中要去掉WS_CLIPSIBLING属性

  3. 对继承自windows标准控件的子窗口( 比如edit, scrollbar等等 ), 需要截获WM_CTLCOLORxxx消息并返回一个style为BS_HOLLOW的brush的handle(即一个透明画刷的handle)

  4. 需要截获WM_ERASEBKGND消息并不要清除背景(如果做到了3, 则这一步可以省略)

  5. 最关键的一点(也是很多人没有想到或不知道的), 必须将parent窗口style中的WS_CLIPCHILDREN标志去掉, 否则PARENT窗口重画时不会重画被子窗口覆盖的部分.

  6. 做到以上各步后, 在子窗口收到WM_PAINT消息时就可以获得完整的背景图了, 接下去对这背景进行处理就可以实现半透明效果.

另外一点, 最好同时截获parent窗的WM_PAINT消息, 在parent窗重画前调用InvalidateRect让本子窗口显示实效, 这样子窗口才能同样也收到一个WM_PAINT消息( 这样做是为了保险, 因为我不是很确定主窗口重画系统是否会自动给具有WS_EX_TRANSPARENT属性的子窗口同样发WM_PAINT ).

窗口创建

bool Create(HWND parent, const std::wstring& title, bool child) {
    DWORD style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;     
    style |= child ? WS_CHILD : WS_POPUP;
    title_ = title;
    return NULL != CWindowWnd::Create(parent, title_.c_str(), style, 0);
}
  1. 方法一:win7以上,先创建popup,然后改为子窗口
Create(window, L"test", false);
ModifyStyle(WS_POPUP, WS_CHILD, SWP_FRAMECHANGED);
  1. 方法二:增加.manifest清单文件
Create(window, L"test", hight_than_xp);

以上两种方法亲测可用,使用了最通用的Create方式

第一种重新spy++抓取确认了下,是不成功的;此种方式修改为WS_CHILD失败了,使用的是弹出窗口的形式;所以只能使用第二种方式来实现了

附manifest文件:

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- Windows 10 -->
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
      <!-- Windows 8.1 -->
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
      <!-- Windows Vista -->
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <!-- Windows 7 -->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
      <!-- Windows 8 -->
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
    </application>
  </compatibility>
</assembly>

@suhao
Copy link
Member Author

suhao commented Apr 23, 2019

VC++实现窗口异形

https://www.cnblogs.com/lmqweeds/archive/2012/07/23/2605319.html

 由于工作的需要,最近一直在研究异形窗口的实现。网上也有一些相关的文章,能够满足各式各样的异形窗口要求。既然花了时间去研究,就想好好的将其总结记录下来,以免今后遇到类似问题,还要从新花时间去研究。

 我需要完成的效果很简单,但实现思路适合大部分的异形窗口

一、SetLayeredWindowAttributes函数
该函数能够实现整个窗口的透明效果,以及指定颜色透明效果。其原型如下:

BOOL WINAPI SetLayeredWindowAttributes( __in HWND hwnd, __in COLORREF crKey, __in BYTE bAlpha, __in DWORD dwFlags );

  hwnd 为需要变形的窗口句柄
  crKey 为指定透明颜色
  bAlpha 为指定透明度
  dwFlags 标示位,其值为LWA_ALPHA时,参数bAlpha 作为决定窗口透明的标准,crKey 无效;当其值为LWA_COLORKEY时,参数crKey 有效。

 代码实现如下:
  
 在Create时指定为WS_POPUP类型,透明的窗口不能为子窗口

BOOL CDragMoveDialog::Create(HWND hParentWnd)
{
    m_hWndParent = hParentWnd;

    return CreateEx(0, 
        L"CDrapDropTipWnd",
        L"DrapDropTipWnd", 
        WS_POPUP |WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
        m_rect.X,m_rect.Y, m_rect.Width, m_rect.Height, 
        NULL, NULL, g_hInstance);
}

在OnPaint()函数中添加如下代码:

1     BitBlt(hDC,0,0,m_rect.Width,m_rect.Height,hMemDC,0,0,SRCCOPY);
2     SetLayeredWindowAttributes(this->GetSafeHwnd(),0,155,1);

二、UpdateLayeredWindow函数

该函数是根据PNG图像的透明值,自动生成相应的不规则窗口。但该函数生成的异形窗口不能为子窗口,同时,由于设置窗口为WS_EX_LAYERED风格,因此窗口类只有在第一次启动时才会调用OnPaint()函数,除非通过主动调用InvalidateRect(NULL,TRUE)函数。代码实现如下:

1.定义透明结构体
BLENDFUNCTION m_Blend; // 透明属性
2. 在Create函数中对透明结构体赋值

1      m_Blend.BlendOp=AC_SRC_OVER; //theonlyBlendOpdefinedinWindows2000
2      m_Blend.BlendFlags=0; //nothingelseisspecial...
3      m_Blend.AlphaFormat=AC_SRC_ALPHA; //...
4      m_Blend.SourceConstantAlpha=255;//AC_SRC_ALPHA

3.在OnPaint()函数中添加绘制代码:

//----绘制窗口


    HDC hdcTemp = hDC;
    HDC hMemDC = CreateCompatibleDC(hdcTemp);
    HBITMAP hBitMap = CreateCompatibleBitmap(hdcTemp, m_rect.Width,m_rect.Height);
    SelectObject(hMemDC, hBitMap);

    HDC hdcScreen = hDC;
    RECT rct;
    GetWindowRect(&rct);
    POINT ptWinPos = {rct.left, rct.top};

    Graphics imageGraphics(hMemDC);
    // 设置层次窗口
    DWORD dwExStyle=GetWindowLong(GWL_EXSTYLE);

    if((dwExStyle&0x80000)!=0x80000)
    {
        SetWindowLong(GWL_EXSTYLE,dwExStyle^0x80000);
    }

    POINT    ptSrc        =    {0,0};
    SIZE    sizeWindow    =    {m_rect.Width,m_rect.Height};

    // 完成透明不规则窗口的绘制
    UpdateLayeredWindow(this->GetSafeHwnd(), hdcScreen, &ptWinPos, &sizeWindow, hMemDC, &ptSrc, 255, &m_Blend, ULW_ALPHA);

    // 释放空间
    imageGraphics.ReleaseHDC(hMemDC);
    DeleteObject(hBitMap);
    DeleteDC(hMemDC);
    hMemDC        = NULL;
    hdcScreen    = NULL; 
    hdcTemp        = NULL;

三、使用HRGN区域组合创建

效率不高,比较慢!!

//----绘制异形窗口

    HRGN wndRgn;

    // 创建总的窗体区域,初始region为0    
    wndRgn = CreateRectRgn(0,0,0,0);
    int y;
    for(y=0;y<=m_rect.Height ;y++)
    {
        HRGN rgnTemp; // 保存临时region

        int iX = 0;
        do
        {
            // 跳过透明色找到下一个非透明色的点.
            while (iX < m_rect.Width && GetPixel(hMemDC,iX, y) == 0)
                iX++;
            // 记住这个起始点
            int iLeftX = iX;

            // 寻找下个透明色的点
            while (iX < m_rect.Width && GetPixel(hMemDC,iX, y) != 0)
                ++iX;

            // 创建一个包含起点与重点间高为1像素的临时“region”
            rgnTemp = CreateRectRgn(iLeftX, y, iX, y+1);

            // 合并到主"region".
            CombineRgn(wndRgn,wndRgn, rgnTemp, RGN_OR);

            //删除临时"region",否则下次创建时和出错
            if ( rgnTemp != NULL )DeleteObject(rgnTemp);

        }while(iX<m_rect.Width);
    }

    SetWindowRgn(this->GetSafeHwnd(),wndRgn,TRUE);

    if ( wndRgn != NULL )DeleteObject(wndRgn);

参考网址:

1.UpdateLayeredWindow函数:

http://www.cnblogs.com/buffer/archive/2009/03/13/1410326.html

注意:

  1. Windows 8: TheWS_EX_LAYEREDstyle is supported for top-level windows and child windows. Previous Windows versions supportWS_EX_LAYEREDonly for top-level windows.

  2. 有些2003和XP的系统不支持WS_EX_LAYERED风格,因此无法显示透明窗口。解决办法目前还木有找到,尽提供以下参考 :

http://support.microsoft.com/kb/943326/zh-cn

@suhao suhao closed this as completed Jun 5, 2019
@suhao suhao reopened this Jun 5, 2019
@suhao
Copy link
Member Author

suhao commented Jun 6, 2019

关于如何实现鼠标穿透窗口和窗口半透明

https://www.cnblogs.com/xnzzj/articles/4524085.html

资料准备

  • WS_EX_TRANSPARENT | WS_EX_LAYERED
  • WM_NCHITTEST & return HTTRANSPARENT
  • SetLayeredWindowAttributes(…)
  • UpdateLayeredWindow(…)

窗口类型

  • Overlapped Windows
  • Pop-up Windows
  • Child Windows
  • Layered Windows \分层窗口(这是我们这节课研究的重点)
  • Message-Only Windows \消息窗口

Using a layered window can significantly improve performance and visual effects for a window that has a complex shape, animates its shape, or wishes to use alpha blending effects. The system automatically composes and repaints layered windows and the windows of underlying applications. As a result, layered windows are rendered smoothly, without the flickering typical of complex window regions. In addition, layered windows can be partially translucent, that is, alpha-blended.

这段大概就是说,layered窗口能够改善显示效果,给你的窗体加个alpha通道,可以平滑半透明地显示。嗯,还可以设置为一部分透明一部分不透明的样子。(据我研究,应该还能设置成渐变透明。)

To create a layered window, specify the WS_EX_LAYERED extended window style when calling the CreateWindowEx function, or call the SetWindowLong function to set WS_EX_LAYERED after the window has been created. After the CreateWindowEx call, the layered window will not become visible until the SetLayeredWindowAttributes or UpdateLayeredWindow function has been called for this window. Note that WS_EX_LAYERED cannot be used for child windows.

一旦你指定了这个属性,刚开始这个窗体是不能显示的,你得先设置整个窗体的Alpha通道。通过这两个函数SetLayeredWindowAttributes、UpdateLayeredWindow 。

值得一提的的是这个不能用于子窗体(也就是不能和WS_CHILD共存呗)。

To set the opacity level or the transparency color key for a given layered window, call SetLayeredWindowAttributes. After the call, the system may still ask the window to paint when the window is shown or resized. However, because the system stores the image of a layered window, the system will not ask the window to paint if parts of it are revealed as a result of relative window moves on the desktop. Legacy applications do not need to restructure their painting code if they want to add translucency or transparency effects for a window, because the system redirects the painting of windows that called SetLayeredWindowAttributes into off-screen memory and recomposes it to achieve the desired effect.

嗯,这段说实话以我高一的英语水平也没看太懂。大致意思就是,系统可能仍然会给你发送WM_PAINT,但是请注意,系统会自动帮你半透明。你不需要更改任何代码就能够实现半透明效果。

总的来说就是:除非程序中调用了InvalidateRect之类的函数,否则系统将接管窗口的绘制,用户收不到WM_PAINT消息!

image

For faster and more efficient animation or if per-pixel alpha is needed, call UpdateLayeredWindow. UpdateLayeredWindow should be used primarily when the application must directly supply the shape and content of a layered window, without using the redirection mechanism the system provides through SetLayeredWindowAttributes. In addition, using UpdateLayeredWindow directly uses memory more efficiently, because the system does not need the additional memory required for storing the image of the redirected window. For maximum efficiency in animating windows, call UpdateLayeredWindow to change the position and the size of a layered window. Please note that after SetLayeredWindowAttributes has been called, subsequent UpdateLayeredWindow calls will fail until the layering style bit is cleared and set again.

这段很重要!这俩函数是互相冲突的!想为窗口的每个像素设置Alpha通道你就直接调用UpdateLayeredWindow ,请不要调用SetLayeredWindowAttributes. 如果你想要重置,那就清除WS_EX_LAYERED然后重新设置!
Hit testing of a layered window is based on the shape and transparency of the window. This means that the areas of the window that are color-keyed or whose alpha value is zero will let the mouse messages through. However, if the layered window has the WS_EX_TRANSPARENT extended window style, the shape of the layered window will be ignored and the mouse events will be passed to other windows underneath the layered window.

碰撞测试(就是测试你的键鼠操作是不是落在这个窗体上)取决于你的透明度!也就是说:透明度为完全透明的窗体部分会被“穿透”。如果你想要整个窗体都被“穿透”,请设置WS_EX_TRANSPARENT属性!

——Come From MSDN

WM_NCHITTEST & return HTTRANSPARENT

了解这一部分的时候请注意了:这一部分不能实现穿透功能(准确的说是他的穿透功能很有限)。

首先我们先了解一下这个 WM_NCHITTEST 是什么玩意儿:

The WM_NCHITTEST message is sent to a window in order to determine what part of the window corresponds to a particular screen coordinate. This can happen, for example, when the cursor moves, when a mouse button is pressed or released, or in response to a call to a function such as WindowFromPoint. If the mouse is not captured, the message is sent to the window beneath the cursor. Otherwise, the message is sent to the window that has captured the mouse.

这个消息主要用于帮助系统判断窗体的哪一部分被点到了,哪一部分被操作了。

它的返回值有一条是这样的:

HTTRANSPARENT

In a window currently covered by another window in the same thread (the message will be sent to underlying windows in the same thread until one of them returns a code that is not HTTRANSPARENT).

这段我也不翻译了,相信你也注意到了“in the same thread ”已经连续出现了两次了!微软到底在强调什么呢…….呵呵

SetLayeredWindowAttributes

函数说明:
BOOL SetLayeredWindowAttributes(
__in HWND hwnd,
__in COLORREF crKey,
__in BYTE bAlpha,
__in DWORD dwFlags
);
\这函数用于设置窗口的alpha混合(不透明度)
\实现出来的效果是整个窗体都以某一透明度显示
函数参数:
hwnd [in]
HWND
Handle to the layered window. A layered window is created by specifying WS_EX_LAYERED when creating the window with the CreateWindowEx function or by setting WS_EX_LAYERED via SetWindowLong after the window has been created.

拥有WS_EX_LAYERED 属性的窗口句柄。

crKey [in]
COLORREF
COLORREF structure that specifies the transparency color key to be used when composing the layered window. All pixels painted by the window in this color will be transparent. To generate a COLORREF, use the RGB macro.

具体说起来又要长篇大论了,简单来说就是透明窗体的底色是什么??我们不需要底色,只需要能够看到该窗体下面的另一个窗体,我们就可以忽略这个参数。

bAlpha [in]

BYTE
Alpha value used to describe the opacity of the layered window. Similar to the SourceConstantAlpha member of the BLENDFUNCTION structure. When bAlpha is 0, the window is completely transparent. When bAlpha is 255, the window is opaque.

该值为0全透明,该值为255不透明。为什么是255呢??

该参数是BYTE类型 只有8位可存储来着 你把 11111111 转换为十进制就是255。

dwFlags [in]
DWORD
Specifies an action to take. This parameter can be one or more of the following values.

指定哪个参数我们使用了,因为我们忽略了crKey参数,所以我们仅仅指定LWA_ALPHA就可以了。

LWA_COLORKEY

Use crKey as the transparency color.

LWA_ALPHA

Use bAlpha to determine the opacity of the layered window.

函数返回:
BOOL

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

不解释,你懂得。

需要注意:
Note that once SetLayeredWindowAttributes has been called for a layered window, subsequent UpdateLayeredWindow calls will fail until the layering style bit is cleared and set again.

什么?你问我这一段意思是什么????那就请你重头认真看我的教程,不要走马观花!

UpdateLayeredWindow

函数说明:
BOOL UpdateLayeredWindow(
__in HWND hwnd,
__in HDC hdcDst,
__in POINT *pptDst,
__in SIZE *psize,
__in HDC hdcSrc,
__in POINT *pptSrc,
__in COLORREF crKey,
__in BLENDFUNCTION *pblend,
__in DWORD dwFlags
)\该函数更新分层窗口的位置、大小、形状、内容、透明度
函数参数:
hwnd [in]
HWND
Handle to a layered window. A layered window is created by specifying WS_EX_LAYERED when creating the window with the CreateWindowEx function.

hdcDst [in]
HDC
Handle to a DC for the screen. This handle is obtained by specifying NULL when calling the function. It is used for palette color matching when the window contents are updated. If hdcDst is NULL, the default palette will be used.

输出到的DC句柄(GetDC(NULL)),你也可以写NULL使用默认调色盘(不改变内容)。

If hdcSrc is NULL, hdcDst must be NULL.

pptDst [in]
POINT
Pointer to a POINT structure that specifies the new screen position of the layered window. If the current position is not changing, pptDst can be NULL.

更新位置信息(写NULL表示不移动)

psize [in]
SIZE
Pointer to a SIZE structure that specifies the new size of the layered window. If the size of the window is not changing, psize can be NULL. If hdcSrc is NULL, psize must be NULL.

更新大小信息(写NULL表示不缩放)

hdcSrc [in]
HDC
Handle to a DC for the surface that defines the layered window. This handle can be obtained by calling the CreateCompatibleDC function. If the shape and visual context of the window are not changing, hdcSrc can be NULL.

内容信息(用于更新窗口颜色)

pptSrc [in]
POINT
Pointer to a POINT structure that specifies the location of the layer in the device context. If hdcSrc is NULL, pptSrc should be NULL.

从hdcSrc的哪个位置开始更新?

crKey [in]
COLORREF
COLORREF structure that specifies the color key to be used when composing the layered window. To generate a COLORREF, use the RGB macro.

这个,同上个函数。

pblend [in]
BLENDFUNCTION
Pointer to a BLENDFUNCTION structure that specifies the transparency value to be used when composing the layered window.

用于更新alpha(我们将在下面给出详解)

dwFlags [in]
DWORD
This parameter can be one of the following values.

同上个函数

ULW_ALPHA

Use pblend as the blend function. If the display mode is 256 colors or less, the effect of this value is the same as the effect of ULW_OPAQUE.

ULW_COLORKEY

Use crKey as the transparency color.

ULW_OPAQUE

Draw an opaque layered window.

If hdcSrc is NULL, dwFlags should be zero.

函数返回:
If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero.

需要注意:

  • 更新一次就像永久更改了整个窗体的位图一样
  • 效率较低
  • 系统自动重新绘制
  • 没有WM_PAINT

最后是极其重要的内容:BLENDFUNCTION参数的讲解

基本概念:

BLENDFUNCTION结构体

AlphaBlend是Window自带的GDI函数,在作GUI的时候为了达到更漂亮的效果我们常常用它。 

这种结构的混合控制通过指定源和目标位图的混合功能。 

typedef struct _BLENDFUNCTION {

BYTE BlendOp;

BYTE BlendFlags;

BYTE SourceConstantAlpha;

BYTE AlphaFormat;

} BLENDFUNCTION, *PBLENDFUNCTION, *LPBLENDFUNCTION;

BlendOp 指定源混合操作。目前,唯一的源和目标的混合方式已定义为AC_SRC_OVER; 

BlendFlags 必须是0; 

SourceConstantAlpha 指定一个alpha透明度值,这个值将用于整个源位图;该SourceConstantAlpha值与源位图的每个像素的alpha值组合;如果设置为0,就会假定你的图片是透明的;如果需要使用每像素本身的alpha值,设置SourceConstantAlpha值255(不透明); 

AlphaFormat 这个参数控制源和目标的解析方式,AlphaFormat参数有以下值:

AC_SRC_ALPHA: 这个值在源或者目标本身有Alpha通道时(也就是操作的图本身带有透明通道信息时),提醒系统API调用函数前必须预先乘以alpha值,也就是说位图上某个像素位置的red、green、blue通道值必须先与alpha相乘。例如,如果alpha透明值是x,那么red、green、blue三个通道的值必须乘以x并且再除以255(因为alpha的值的范围是0~255),之后才能被调用。

应用备注:

1、当AlphaFormat参数的值是AC_SRC_ALPHA,那么源位图必须是32位深,否则的话,AlphaBland函数将调用失败

2、当BlendOp参数是AC_SRC_OVER时,源位图根据alpha透明度值直接覆盖在目标位图之上

3、如果源位图不带有透明度信息(那样的话,AC_SRC_ALPHA不设置),将由SourceConstanAlpha的值来决定如何混合源位图与目标位图,如下表中所示。表中SCA代表SourceConstantAlpha的值,同样,SCA除以了255,因为它的范围是从0到255.

Dst.Red = Src.Red * (SCA/255.0) + Dst.Red * (1.0 - (SCA/255.0))

Dst.Green = Src.Green * (SCA/255.0) + Dst.Green * (1.0 - (SCA/255.0))

Dst.Blue = Src.Blue * (SCA/255.0) + Dst.Blue * (1.0 - (SCA/255.0))

在这种情况下,如果目标位图有透明度信息,那么混合方式将按照下面的公式来:

Dst.Alpha = Src.Alpha * (SCA/255.0) + Dst.Alpha * (1.0 - (SCA/255.0))

4、如果源位图没有用SourceConstantAlpha参数值(那表示该参数等于255),每一个像素的透明度将决定源位图和目标位图的混合结果,如下所示:

Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red

Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green

Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue

在这种情况下,如果如果目标位图有透明度信息,那么混合方式将按照下面的公式来:

Dest.alpha = Src.Alpha + (1 - SrcAlpha) * Dst.Alpha

5、如果源位图既有SourceConstantAlpha值(也就是它的值不是255),每个像素又有透明度值,那么源位图的每一个像素将首先乘以SourceConstantAlpha的值,然后根据每个像素的透明度值混合,如下表中所示。同样,SourceConstantAlpha除以了255,因为它的范围是从0到255.

Src.Red = Src.Red * SourceConstantAlpha / 255.0;

Src.Green = Src.Green * SourceConstantAlpha / 255.0;

Src.Blue = Src.Blue * SourceConstantAlpha / 255.0;

Src.Alpha = Src.Alpha * SourceConstantAlpha / 255.0;

Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red

Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green

Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue

Dst.Alpha = Src.Alpha + (1 - Src.Alpha) * Dst.Alpha

@suhao
Copy link
Member Author

suhao commented Jun 6, 2019

鼠标穿透窗口 & 窗口渐变透明

https://www.cnblogs.com/xnzzj/p/4524484.html

第一步:创建一个窗口(实现鼠标穿透)

WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style            = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra        = 0;
    wcex.cbWndExtra        = 0;
    wcex.hInstance        = hInstance;
    wcex.hIcon            = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINTRANSPARENT));
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground    = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName    = 0;
    wcex.lpszClassName    = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

  RegisterClassEx(&wcex);

HWND hWnd = CreateWindowEx(WS_EX_LAYERED|WS_EX_TRANSPARENT,szWindowClass, szTitle,WS_POPUP,\*通过WS_EX_TRANSPARENT 让鼠标得以穿透窗口*\
      CW_USEDEFAULT, 0,200, 200, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }
   
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

第二步:创建包含Alpha信息的图片(实现渐变透明)

  1. 首先我们新建一张图片:

image

  1. 其次我们再创建Alpha通道
    image

  2. 然后填充Alpha通道
    image

image

  1. 最后保存一下
    image
    image

提醒一下:只有Alpha通道没有图像渲染出来还是什么都不是 这样编辑图像内容:

image

第三步:使用图片实现渐变透明(实现渐变透明)

HDC hdc= GetDC(hWnd);

\\获得我们需要填充的DC
   POINT pt;
   pt.x=0;pt.y=0;
   SIZE size;
   size.cx=200;
   size.cy=200;
   HDC rc=CreateCompatibleDC(hdc);
   HBITMAP bp =(HBITMAP) LoadImage(0,_T("pic.bmp"),IMAGE_BITMAP,200,200,LR_LOADFROMFILE);\\载入图片
   SelectObject(rc,bp);
   BLENDFUNCTION blend;
   blend.AlphaFormat = AC_SRC_ALPHA;
   blend.BlendOp = AC_SRC_OVER;
   blend.BlendFlags = 0;
   blend.SourceConstantAlpha = 255; \\由于我们使用的是图片内的Alpha通道,这项数值设为最大值255,意思是Alpha完全取决于你图片内的数值

    UpdateLayeredWindow(hWnd,hdc,&pt,&size,rc,&pt,0,&blend,ULW_ALPHA);
    
    ReleaseDC(hWnd,hdc);
    SetWindowPos(hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);

但是….请注意,第三步没有结束,这段代码是有问题的!!!

我们会发现输出的结果不对,为什么呢??让我们来看一个印度小伙的解释:

you must ensure your pixel colors have been premultiplied against their respective alpha values. This is a requirement of the Win32 API in order to achieve the proper and intended results. In my case, I was not able to export from Photoshop a proper 32-bit BMP file with premultiplied alpha, so I perform this at load time. If you run an off-line tool or otherwise have premultiplied pixels, then it is not necessary to perform this step. To see an example of pre-multiplying, see the downloadable source code attached to this post.

你必须确定你的每个像素已经预先和你的Alpha数值乘过了,这是Win32API输出正确结果的特殊癖好.就我来说,用PS我做不到这一点,所以我在加载图像时预乘一下,如果你有方法解决这个问题,就不需要这一步.

重要概念——预乘

  1. 什么是预乘?

预乘就是预先将图片的RGB通道乘以它的Alpha通道数值

  1. 怎么预乘?

公式:(单个颜色值*Alpha/255)

  1. 为什么要预乘?

答案是为了节约系统在混合时需要进行的运算:

我们知道半透明混合的公式大概是这样的:

结果值 =( 数值1 * ( 1 - Alpha/255) + 数值2 * Alpha /255 )

(MSDN:Dst.Red= Src.Red+ (1 - Src.Alpha) * Dst.Red)(这里的Alpha是1以内的小数)(这里的SRC已经经过预乘了)

如果进行了预乘,显然 数值2 * Alpha /255 这一步就可以快一些了。

PS:其实还是因为微软要求这样搞,要不我才不愿意- - 哼…..

  1. 已经写好的预乘算法:
for(int j = 0; j < g_iSplashImageHeight; ++j)
{
    for(int i = 0; i < g_iSplashImageWidth; ++i)
    {
        int index = ( g_iSplashImageHeight - j - 1 ) * g_iSplashImageWidth + i;

        DWORD d = pImageData[index];

        BYTE a = d >> 24;
        BYTE pmR = static_cast<BYTE>( ((d & 0x00FF0000) >> 16) * a / 255 );
        BYTE pmG = static_cast<BYTE>( ((d & 0x0000FF00) >> 8) * a / 255 );
        BYTE pmB = static_cast<BYTE>( ((d & 0x000000FF)) * a / 255 );
        d = pmB | (pmG << 8) | (pmR << 16) | (a << 24);
        
        pImageData[index] = d;
    }
}
  1. 被我玩坏的预处理器:

每次程序启动都要处理好麻烦,我就写了个预处理器,代码如下:

int _tmain(int argc, _TCHAR* argv[])
{
    printf("请将需要处理的图片拖入:\n");
    char* path = new char[261]  ;ZeroMemory(path,sizeof(path));gets_s(path,261);
    FILE* f;
    FILE* f2;
    if(*path==*"\"")
        {path = path+ 1 ;path[strlen(path)-1] = 0;}
    f = fopen(path,"rb+");
    f2 = fopen("pic.bmp","wb+");
    fseek(f,0,SEEK_END);
    int s = ftell(f);
    printf("filesize:%d",s);
    fseek(f,0L,SEEK_SET);
    fseek(f2,0L,SEEK_SET);
    void* date = new unsigned char [s];
    fread(date,s,1,f);
    // access the image bytes
    BITMAPFILEHEADER* pImageHeader =(BITMAPFILEHEADER*)date;
    
    
    DWORD* pImageData = (DWORD*)(pImageHeader->bfOffBits + (BYTE*)date);
    BITMAPINFOHEADER* bi=(BITMAPINFOHEADER *)((BYTE*)date+14);
    bi->biWidth;
    bi->biHeight;
    // store dimensions globally
   

    // modify image bytes so the pixels are premultiplied
    for(int j = 0; j < bi->biHeight; ++j)
    {
        for(int i = 0; i < bi->biWidth; ++i)
        {
            int index = ( bi->biHeight - j - 1 ) * bi->biWidth + i;

            DWORD d = pImageData[index];

            BYTE a = d >> 24;
            BYTE pmR = static_cast<BYTE>( ((d & 0x00FF0000) >> 16) * a / 255 );
            BYTE pmG = static_cast<BYTE>( ((d & 0x0000FF00) >> 8) * a / 255 );
            BYTE pmB = static_cast<BYTE>( ((d & 0x000000FF)) * a / 255 );
            d = pmB | (pmG << 8) | (pmR << 16) | (a << 24);

            pImageData[index] = d;
        }
    }
    fseek(f,0,SEEK_SET);
    fwrite(date,s,1,f2);
    printf(" writebytes:%d",ftell(f2));
    fclose(f);
    fclose(f2);
    //delete[] date;
    getchar();
    return 0;
}

image

image

image

@suhao
Copy link
Member Author

suhao commented Jun 6, 2019

Windows Vista for Developers – Part 3 – The Desktop Window Manager

https://weblogs.asp.net/kennykerr/Windows-Vista-for-Developers-_1320_-Part-3-_1320_-The-Desktop-Window-Manager

Although Windows 95 (and Window NT 3.51) introduced Windows developers to non-rectangular windows, through the ability to set the window region for a given window using the SetWindowRgn function, this did not provide transparency since the window was still completely opaque and merely afforded the developer some control over the window’s shape. Windows 2000 introduced layered windows by means of the WS_EX_LAYERED extended window style and developers were finally able to control not only the shape but also the level of transparency within the window region. Windows Vista introduces yet another twist on the road away from non-rectangular windows by allowing developers to apply translucency to portions of a window.

In this part 3 of the Windows Vista for Developers series, we are looking at the Desktop Window Manager (DWM) API. The DWM is responsible for the composition of windows on the desktop and the DWM API allows developers to control how composition affects a particular window. As you will see, the DWM is responsible for much more than just “glass”. We will also look at the existing functionality for transparency that Windows Vista inherited from Windows 2000 and see how it complements the new DWM functionality.

Terminology

When it comes to graphics, the terminology can be confusing. Here are some common terms you should be familiar with when working with transparency and translucency in Windows.

Transparency – Refers to the ability to see through something clearly and without obstruction. Think of it as clear glass. Some applications and APIs use the term transparency to refer to a scale that ranges from “completely” transparent to “completely” opaque.

Translucency – People often use translucency and transparency interchangeably but they actually mean very different things. Translucency refers to the ability to see through something where the background appears unclear whether it is out-of-focus or simply blurry in some way. Windows Vista refers to the glass effect as “transparent glass” when it technically should be called translucent glass.

Opacity – Opacity refers to the state of being opaque and opaque refers to something that is neither transparent nor translucent. Some applications and APIs use the term opacity to refer to a scale that ranges from completely opaque to completely transparent.

Alpha Channel – An alpha channel provides additional information for each pixel in an image that facilitates compositing images together.

Window Regions – A window’s region determines the area within the window where system permits painting. Although Windows 95 supported window regions, it was not until Windows XP that the default theme used regions to present windows with rounded corners. Although the default Windows Vista theme also presents windows with rounded corners, regions are no longer used unless you resort to the Windows Vista Basic theme.

Glass – Glass is the catchy marketing terms that Windows Vista uses to refer to translucency.

Blur – Some of the DWM APIs refer to blur and again this indicates translucency. Presumably, the Windows developers felt it was easier to spell and comprehend.

Desktop Composition – The DWM performs desktop composition, enabling visual effects on the desktop such as glass, 3D window transitions, etc.

RGB – RGB is short for Red, Green and Blue. RGB values are typically packed into a COLORREF (which is just a DWORD) as follows: 0x00BBGGRR. As you can see, the first byte is always zero and the remaining three bytes store the individual red, green and blue values in reverse order. Each color value ranges from zero through 255. If all three values are zero then the result is black. If all three values are 255 then the result is white. For example, to represent red specify 0x000000FF. You can also use the RGB macro as follows: RGB(255, 0, 0). As you can see, RGB does not provide an alpha channel.

ARGB – ARGB is short for Alpha, Red, Green and Blue. ARGB values are typically packed into an ARGB (which is just a DWORD) as follows: 0xAARRGGBB. The first byte stores the alpha value and the remaining three bytes store the red, green and blue values. Note that the color values are stored in the opposite order to RGB.

GDI – The Windows Graphics Device Interface (GDI) API is the original graphics interface used for 2D drawing in Windows. With the exception of a few newer functions, the GDI API does not honor the alpha channel in images. GDI uses RGB values to represent color.

GDI+ – GDI+ was introduced with Windows XP (and Windows Server 2003) to provide a more capable programming model for 2D drawing, imaging and typography and fully supports alpha blending. GDI+ uses ARGB values to represent color. Incidentally, GDI+ powers the the System.Drawing library in the .NET Framework.

Is Composition Enabled?

image

With the terminology out of the way, we can now dive into the guts of desktop composition. Of course, to take advantage of it we need to make sure that it is actually available at runtime. The user may have disabled desktop composition for performance reasons. This can be done as follows:

  1. Open the System Properties window using the following command:

    %windir%\system32\SystemPropertiesAdvanced.exe

  2. Click the Performance Settings button.

  3. Either check or clear the “Enabled desktop composition” check box.

Keep in mind that desktop composition is independent of “glass”. Although glass requires desktop composition, you can use desktop composition while disabling glass.

Windows Vista provides the DwmIsCompositionEnabled function to determine whether composition is currently enabled. Consider the following example:

BOOL enabled = FALSE;
HRESULT result = ::DwmIsCompositionEnabled(&enabled);

Of course, this is not especially useful if you are targeting older platforms as your application will fail to load since it will be linked to a library that is not available. One solution is to use a blend of delay loading and runtime dynamic linking. The DWM API is provided by the dwmapi.dll library and to ensure that your application will load on older platforms you can use Visual C++’s Delay Load facility to only load the DWM library if it is actually used. The first step is to instruct the linker to delay load the DWM library. This can be done as follows:

  1. Open the project’s property pages.

  2. Navigate to the Linker > Input section.

  3. Add dwmapi.dll to the list of Delay Load DLLs. You should already have dwmapi.lib in the list of Additional Dependencies to allow the linker to find the various DWM API functions that you make use of.

image

With this in place, the DWM library will only be loaded the first time you call any of its functions, but how do you know whether it is safe to do so? After all, calling DwmIsCompositionEnabled will attempt to load the DWM library and crash your application on older versions of Windows. The solution is to manually attempt to load the DWM library and for good measure attempt to retrieve the address of a DWM function. Consider the following wrapper function:

bool IsCompositionEnabled()
{
    HMODULE library = ::LoadLibrary(L"dwmapi.dll");
    bool result = false;

    if (0 != library)
    {
        if (0 != ::GetProcAddress(library, 
                                  "DwmIsCompositionEnabled"))
        {
            BOOL enabled = FALSE;
            result = SUCCEEDED(::DwmIsCompositionEnabled(&enabled)) && enabled;
        }

        VERIFY(::FreeLibrary(library));
    }

    return result;
}

The IsCompositionEnabled function attempts to load the DWM library and retrieve the address of the DwmIsCompositionEnabled function. If this is successful, you can assume it is running on Windows Vista or later. It then simply calls the DwmIsCompositionEnabled function, which will actually load the DWM library, to determine whether composition is enabled. Now you simply have to ensure that you do not call any other DWM functions if IsCompositionEnabled returns false.

The other thing to keep in mind is that since the user, and other applications for that matter (more on this in a moment), can enable and disable desktop composition at any time, your application needs to be able to cope with changes in the availability of desktop composition. The system will send your window the WM_DWMCOMPOSITIONCHANGED message to indicate that the availability of desktop composition has changed. The WPARAM and LPARAM values are not used so you must call the DwmIsCompositionEnabled function again to determine the current state of desktop composition.

As I hinted at, it is possible for applications to temporarily disable desktop composition for the lifetime of the application or for some subset thereof. The DwmEnableComposition function allows you to disable desktop composition for the entire desktop. Desktop composition will be disabled until you call DwmEnableComposition again to enable it. If you fail to re-enable desktop composition, the system will automatically enable it when the application exits.

The following code disables composition:

HRESULT result = ::DwmEnableComposition(DWM_EC_DISABLECOMPOSITION);

And the following code will enable it again:

HRESULT result = ::DwmEnableComposition(DWM_EC_ENABLECOMPOSITION);

Assuming you are using the default Windows Vista theme, these calls will toggle between the new “Window Vista” and “Windows Vista Basic” themes. Just remember, when you application exits, composition will be reset regardless of whether you re-enabled it.

Is Composition Translucent?

As I mentioned in the previous section, the fact that desktop composition is enabled does not necessarily mean that the “glass” is translucent. I took the following two window clippings of the exact same window. The one on the left was taken with translucent glass and the one of the right with opaque glass. As you can see, the translucent glass provides a hint of the desktop background color as well as the recycle bin hiding underneath the window whereas the opaque glass only offers the aurora effect provided by the DWM.
image
image

Users can set the translucency as well as the composition color, the color used for rendering glass, as follows:

  1. Right-click on the desktop and select the “Personalize” command.

  2. Click the “Window Color and Appearance” link.

Applications can determine whether composition is opaque or translucent as well as the composition color by calling the DwmGetColorizationColor function as follows:

Gdiplus::ARGB color = 0;
BOOL opaque = FALSE;

HRESULT result = ::DwmGetColorizationColor(&color,
&opaque);

Since the user can change these settings at any time, windows are notified of changes by means of the WM_DWMCOLORIZATIONCOLORCHANGED message. The WPARAM provides the new ARGB value and LPARAM is zero if composition is translucent and non-zero if composition is opaque.

Blurring the Client Area

Assuming desktop composition is enabled, the DWM will take care of rendering the non-client area of your window with glass. The client area however is opaque by default and applications must specifically request glass for all or part of the client area. The DwmEnableBlurBehindWindow function allows you to control the blur effect for a window. The function accepts a handle to a window as well as a pointer to a DWM_BLURBEHIND structure defined as follows:

struct DWM_BLURBEHIND
{
DWORD dwFlags;
BOOL fEnable;
HRGN hRgnBlur;
BOOL fTransitionOnMaximized;
};

The following flags are defined and they indicate which of the remaining fields the function should take note of:

DWM_BB_ENABLE
DWM_BB_BLURREGION
DWM_BB_TRANSITIONONMAXIMIZED

The DwmEnableBlurBehindWindow takes a bit of getting used to. Consider using the following C++ wrapper function that simplifies its use considerably:

HRESULT EnableBlurBehindWindow(HWND window,
bool enable = true,
HRGN region = 0,
bool transitionOnMaximized = false)
{
DWM_BLURBEHIND blurBehind = { 0 };

blurBehind.dwFlags = DWM_BB_ENABLE | DWM_BB_TRANSITIONONMAXIMIZED;
blurBehind.fEnable = enable;
blurBehind.fTransitionOnMaximized = transitionOnMaximized;

if (enable && 0 != region)
{
    blurBehind.dwFlags |= DWM_BB_BLURREGION;
    blurBehind.hRgnBlur = region;
}

return ::DwmEnableBlurBehindWindow(window,
                                   &blurBehind);

}

Now enabling or disabling blur behind is straightforward. Here are a few examples:

Enable blur behind client area:

HRESULT result = EnableBlurBehindWindow(window);

image
Disable blur behind client area:

HRESULT result = EnableBlurBehindWindow(window,
false);

Blur a region of the window:

CRgn rgn;
rgn.CreateEllipticRgn(30, 30, 170, 170);

HRESULT result = EnableBlurBehindWindow(window,
true,
rgn);

image

Render the blur behind as if the window were maximized:

HRESULT result = EnableBlurBehindWindow(window,
true,
0,
true);
image

As you can see, the DwmEnableBlurBehindWindow function provides a lot of functionality and with a little help from C++ it becomes quite simple to make use of. The ability to blur a region of the window is especially useful when combined with layered windows.

Extending the Window Frame

You may have noticed in the window clippings in the previous section that although the client area was blurred, the client edge was still visible. If you want to render glass seamlessly then you need to use a different approach. Of course, if your window does not have a frame then DwmEnableBlurBehindWindow is good for the full extent of the window.

To extend the frame into the window’s client area, you need to use the aptly named DwmExtendFrameIntoClientArea function. Unlike DwmEnableBlurBehindWindow, the DwmExtendFrameIntoClientArea function is very straightforward and you can simply use it directly.

The DwmExtendFrameIntoClientArea function accepts a window handle as well as a pointer to a MARGINS structure. The structure indicates how far into the client area the frame should be extended. The following example extends the bottom margin 20 pixels into the client area:

MARGINS margins = { 0 };
margins.cyBottomHeight = 20;

HRESULT result = ::DwmExtendFrameIntoClientArea(m_hWnd,
&margins);

image

To restore the frame margins simply set all the margins to zero:

MARGINS margins = { 0 };

HRESULT result = ::DwmExtendFrameIntoClientArea(m_hWnd,
&margins);

The DwmExtendFrameIntoClientArea function also has a little bonus feature that allows you to render the entire client and non-client area as a seamless sheet of glass. Just set any margin to -1:

MARGINS margins = { -1 };

HRESULT result = ::DwmExtendFrameIntoClientArea(m_hWnd,
&margins);
image

Painting

Up to this point, I have focused on the DWM functions for control blurring. What I have not yet mentioned is the secret sauce you need to get the blurring to kick in. Then of course, there is the issue of what to do with all that lovely glass. Presumably, you want to draw something on it!

The trick with understanding how glass works is having an understanding of the relationship between the DWM functions and the contents of your window. The DwmEnableBlurBehindWindow and DwmExtendFrameIntoClientArea functions promise to instruct the DWM to render as glass any portions of your window that are painted with an alpha channel brush that is not completely opaque. Consider the following window clipping displaying a PNG image I created using Photoshop:
image

The image painted on the window includes an alpha channel so the DWM faithfully blurs the background based on the transparency level of each individual pixel. The catch is that virtually all of the GDI functions that Windows developers have learnt to use do not actually have any knowledge of alpha values in colors and do not perform alpha blending. Therefore, if you are serious about transparency and translucency on Windows you need to use GDI+ (or some other graphics library). Before we look at GDI+ however, let us see what can be done with good old GDI.

It so happens that the bit pattern for RGB black (0x00000000) is the same as the bit pattern for 100% transparent ARGB so you can actually draw with “black” GDI brush and assuming you’ve instructed the DWM to blur the painted area, the result will be the desired glass effect. Let us look at a simple example of this using ATL:

class SampleWindow :
public CWindowImpl<SampleWindow, CWindow, CFrameWinTraits>
{
public:

BEGIN_MSG_MAP(SampleWindow)
    MSG_WM_ERASEBKGND(OnEraseBackground)
END_MSG_MAP()

SampleWindow()
{
    VERIFY(Create(0, 0, L"Sample"));

    const MARGINS margins = { -1 };

    COM_VERIFY(::DwmExtendFrameIntoClientArea(m_hWnd,
                                              &margins));
}

private:

virtual void OnFinalMessage(HWND)
{
    ::PostQuitMessage(0);
}

bool OnEraseBackground(CDCHandle dc)
{
    CRect rect;
    VERIFY(GetClientRect(&rect));

    dc.FillSolidRect(&rect, 
                     RGB(0, 0, 0));

    return true; // Yes, I erased the background.
}

};

Just after the window is created, the DwmExtendFrameIntoClientArea function is used to instruct the DWM to render the entire client area seamlessly with glass. The window then handles the WM_ERASEBKGND message and fills the client area with “black” as needed. The results are as you might expect:
image

The trouble with using this technique for rendering glass is that anything you might want to draw on your window better not use a black GDI brush otherwise it will also appear translucent. Consider a dialog box with an edit control:
image

If we apply the technique of using a black GDI brush to this window, the results will be less the desirable:
image

As you can see, since the control uses a black brush to draw the text, the DWM is fooled into thinking that this too should be rendered translucent. One solution is to owner-draw the control. I don’t know about you but I do not want to spend my time “owner drawing” all my controls. I really like the way the system does it and I would rather not have the responsibility of trying to duplicate it.

A more pragmatic solution is to make use of layered windows. Layered windows, first introduced with Windows 2000, are intimately aware of alpha blending, and thus ideally suited for rendering glass. Layered windows offer two distinct programming models. You can either use the UpdateLayeredWindow function and provide a device-independent bitmap to define the complete appearance of a window on screen (the hard way) or you can use the SetLayeredWindowAttributes function (the easy way). Let us start with the easy way.

The SetLayeredWindowAttributes function allows you to specify an RGB color so that any pixels painted with this color will be transparent. This frees the black brush from performing double-duty and you can once again draw controls using the regular black brush as needed. Assuming your window has the WS_EX_LAYERED extended window style, you can call SetLayeredWindowAttributes as follows to set the transparency color:

const COLORREF color = RGB(200, 201, 202);

VERIFY(::SetLayeredWindowAttributes(window,
color,
0,
LWA_COLORKEY));

Choosing an appropriate color is the hardest part. Ideally, you should use a color that will blend well but it is unlikely that you will know what color to blend with. Font smoothing will also not be perfect, as it will be blending with the transparency color instead of the actual background. Nevertheless, it is an adequate solution in many cases. It is also important that you specify a color that is unlikely to be used by any controls drawn on your window. The SetLayeredWindowAttributes function also tries to provide some of the more advanced capabilities of the UpdateLayeredWindow function for those stuck in GDI land. One such feature is the ability to create non-rectangular windows. This is somewhat off-topic but I mention it here only because SetLayeredWindowAttributes provides this functionality only if you choose a transparency color with the red, gren and blue values being equal. This does not mix well with the DWM as the glass will appear translucent but the user’s mouse will be directed to the window underneath.

With that, here is a complete example of using layered window translucency with a dialog box:

class SampleDialog :
    public CDialogImpl<SampleDialog>
{
public:

    enum { IDD = IDD_SAMPLE };

    BEGIN_MSG_MAP(MainWindow)
        MSG_WM_INITDIALOG(OnInitDialog)
        MSG_WM_ERASEBKGND(OnEraseBackground)
        COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
    END_MSG_MAP()

    SampleDialog() :
        m_transparencyKey(RGB(200, 201, 202))
    {
        // Do nothing
    }

private:

    LRESULT OnInitDialog(HWND /*control*/,
                         LPARAM /*lParam*/)
    {
        SetWindowLong(GWL_EXSTYLE,
                      GetExStyle() | WS_EX_LAYERED);

        VERIFY(::SetLayeredWindowAttributes(m_hWnd, 
                                            m_transparencyKey, 
                                            0, 
                                            LWA_COLORKEY));

        const MARGINS margins = { -1 };

        COM_VERIFY(::DwmExtendFrameIntoClientArea(m_hWnd,
                                                  &margins));

        return TRUE; // Yes, go ahead and set the keyboard focus.
    }

    bool OnEraseBackground(CDCHandle dc)
    {
        CRect rect;
        VERIFY(GetClientRect(&rect));

        dc.FillSolidRect(&rect, 
                         m_transparencyKey);

        return true; // Yes, I erased the background.
    }

    LRESULT OnCancel(WORD /*notifyCode*/, 
                     WORD identifier, 
                     HWND /*window*/, 
                     BOOL& /*handled*/)
    {
        VERIFY(EndDialog(identifier));
        return 0;
    }

    const COLORREF m_transparencyKey;

};

The results are considerably better than before:
image

The main difference between the SampleDialog class and the previous SampleWindow class using a black GDI brush is that in the SampleDialog example SetLayeredWindowAttributes is used to set the transparency color and the WM_ERASEBKGND message handler fills the client area with the transparency color instead of with black. Considering how little work is involved, this is certainly an attractive solution if you cannot afford to go all out owner-drawing controls.

Advanced Layered Windows

The previous section covered the use of layered windows by means of the SetLayeredWindowAttributes function to provide translucency without much work. For greater control, you need to take it on yourself to prepare the layered window’s off-screen bitmap yourself rather than relying on the system to compose it for you. Although using GDI+ might seem ideal, repeatedly drawing in a GDI+ bitmap and then converting it to a screen-compatible bitmap can be expensive. Fortunately, ATL provides the CImage class that provides just enough functionality so that you can avoid the overhead of GDI+ bitmaps while being able to take advantage of the drawing power of GDI+. The resulting image can then be efficiently copied to the screen sing the UpdateLayeredWindow function. The beauty of using UpdateLayeredWindow is that it honors the entire alpha channel of the image and not just a single transparency color. This allows you to use different levels of transparency in your window quite simply.

Let us walk through a simple example.

First, we will use the CImage class to create an image with an alpha channel and GDI+ to fill the image with a gradient brush starting with transparent in the top-left corner and ending with black in the bottom-right corner. Start by creating the bitmap as follows:

CImage image;

VERIFY(image.Create(300, // width
300, // height
32, // bits per pixel
CImage::createAlphaChannel));

This creates a device-independent bitmap (DIB) section 300 pixels square with an alpha channel. You can now use the GDI+ Graphics class to draw to the bitmap. CImage’s GetDC method is used to pass the Graphics class a device context in which to draw. Once the Graphics class is destroyed, CImage’s ReleaseDC method must be called. The Graphics object can then be used to do whatever drawing you require. The following example fills the bitmap with a linear gradient brush:

{
Gdiplus::Graphics graphics(image.GetDC());
GDIPLUS_VERIFY(graphics);

Gdiplus::Rect rect(0, 
                   0,
                   image.GetWidth(),
                   image.GetHeight());

Gdiplus::LinearGradientBrush brush(rect,
                                   Gdiplus::Color(0, 0, 0, 0),
                                   Gdiplus::Color(255, 0, 0, 0),
                                   Gdiplus::LinearGradientModeForwardDiagonal);

GDIPLUS_VERIFY(graphics.FillRectangle(&brush,
                                      rect));

}

image.ReleaseDC();

The braces are used simply to ensure that the Graphics object is destroyed before calling the ReleaseDC method on the CImage object. The rest is just GDI+ code that is beyond the scope of this article but should be self-explanatory.

With the image prepared, it is time to call UpdateLayeredWindow to update the layered window based on the bitmap. UpdateLayeredWindow requires a source DC to copy from so we can once again use the GetDC and ReleaseDC pair of methods:

CPoint windowPosition(0, 0);
CPoint layerPosition(0, 0);

CSize size(image.GetWidth(),
image.GetHeight());

BLENDFUNCTION blendFunction = { 0 };
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.BlendFlags = 0;
blendFunction.SourceConstantAlpha = 255;
blendFunction.AlphaFormat = AC_SRC_ALPHA;

VERIFY(::UpdateLayeredWindow(m_hWnd,
0, // obtain screen DC
0,
&size,
image.GetDC(),
&layerPosition,
0, // no color key
&blendFunction,
ULW_ALPHA));

image.ReleaseDC();

The results are what you might expect:

image

With the layered window in place, it is time to turn that transparency to translucency and achieve the glass effect. This is simple matter of calling the EnableBlurBehindWindow helper function described in the section entitled “Blurring the Client Area” and you are done:
image

The sample in the download for this article provides a more complete example and allows you to load an arbitrary PNG file to render in either a regular window or in a layered window, with or without glass.

DWM Window Attributes

There are a handful of additional window attributes that the DWM allows you to get or set using the DwmSetWindowAttribute function. Some are particularly interesting.

The DWMWA_TRANSITIONS_FORCEDISABLED attribute allows you to disable the animation that occurs when your window transitions between the restored, maximized and minimized states.

The DWMWA_ALLOW_NCPAINT attribute allows you to draw to the non-client area of the window, ensuring that what you draw is visible once desktop composition is done with it. Be very careful with this, as it is usually glaringly obvious when the painting conflicts with the glass resulting in an undesirable appearance. A good example of this is the beta 2 build of Word 2007 where the non-client area is poorly drawn (fortunately, this has been cleaned up in subsequent builds of Word 2007).

The DWMWA_FLIP3D_POLICY attribute allows you to control how the Flip3D feature (Windows key + Tab) handles your window. You can use the value DWMFLIP3D_EXCLUDEBELOW attribute value to instruct Flip3D to exclude your window from the animation and instead display your window behind the stack of rotating windows.

The sample in the download for this article allows you to experiment with a number of the DWM-provided window attributes.

Thumbnails

The final DWM feature I want to talk about is the ability to host your own thumbnails. You may have noticed the neat “thumbnail” feature provided by the Windows Vista task bar. Hovering your mouse over an application item in the taskbar results in a little thumbnail popping up allowing you to get a peak at the window without having to switch to it.

Did you know that the thumbnail is actually live? If you do not know what I mean then try the following experiment:

  1. Right-client on the date/time on the taskbar and select the “Adjust Date/Time” command. A window should appear that includes wonderfully rendered analog clock.

  2. Hover your mouse over the window’s task bar item and wait for the thumbnail to appear.

If you look closely, you should see the clock’s second hand moving in the thumbnail and as it turns out, the DWM exposes this feature so that you can add live thumbnails to your own applications!

The way thumbnails works is that you “register” a source and destination window with the DWM and as part of desktop composition, it will take care of updating the destination window to reflect the source window. It is amazingly powerful considering how little code you need to write to get it all working. Since registering a thumbnail results in a handle that must be used with subsequent calls to the DWM to update the thumbnail properties and ultimately unregister the thumbnail, it makes sense to use a C++ class to provide a light abstraction:

class Thumbnail
{
public:

explicit Thumbnail(HTHUMBNAIL handle = 0) :
    m_handle(handle)
{
    // Do nothing
}

~Thumbnail()
{
    if (0 != m_handle)
    {
        COM_VERIFY(Unregister());
    }
}

bool IsRegistered() const
{
    return 0 != m_handle;
}

HRESULT Register(HWND destination,
                 HWND source)
{
    ASSERT(0 == m_handle);
    CSize reserved(0, 0);

    return ::DwmRegisterThumbnail(destination,
                                  source,
                                  &reserved,
                                  &m_handle);
}

HRESULT Unregister()
{
    ASSERT(0 != m_handle);
    HRESULT result = ::DwmUnregisterThumbnail(m_handle);
    m_handle = 0;
    return result;
}

HRESULT QuerySourceSize(CSize& size)
{
    ASSERT(0 != m_handle);

    return ::DwmQueryThumbnailSourceSize(m_handle,
                                         &size);
}

HRESULT UpdateProperties(const DWM_THUMBNAIL_PROPERTIES& properties)
{
    ASSERT(0 != m_handle);

    return ::DwmUpdateThumbnailProperties(m_handle,
                                          &properties);
}

private:

HTHUMBNAIL m_handle;

};

Registering and unregistering the thumbnail is self-explanatory. The QuerySourceSize method allows you to determine the size of the source window so that you can factor that into your thumbnail properties. The UpdateProperties method must be called at least once to instruct the DWM to begin updating the live thumbnail. The DWM_THUMBNAIL_PROPERTIES structure offers some control over the behavior of the thumbnail.

struct DWM_THUMBNAIL_PROPERTIES
{
DWORD dwFlags;
RECT rcDestination;
RECT rcSource;
BYTE opacity;
BOOL fVisible;
BOOL fSourceClientAreaOnly;
};

The dwFlags field indicates which subsequent fields are populated.

The rcDestination field indicates the bounds in the client area of the destination window in which to render the thumbnail. Include the DWM_TNP_RECTDESTINATION flags to use this field.

The rcSource field indicates the bounds of the source window to include in the thumbnail. This is used to display only a portion of the source window in the thumbnail. Include the DWM_TNP_RECTSOURCE flag to use this field.

The opacity field indicates the level of opacity to use when blending the source with the destination window background. If the source window is semi-transparent or translucent it will automatically be alpha blended so this field just provides an additional degree of control. Include the DWM_TNP_OPACITY flag to use this field.

The fVisible field allows you to turn the thumbnail blending on or off temporarily without having to unregister the thumbnail completely. Include the DWM_TNP_VISIBLE flag to use this field.

Finally, the fSourceClientAreaOnly field allows you to limit the thumbnail to exclude the non-client area of the source window. This is incidentally the way the task bar displays thumbnails. Include the DWM_TNP_SOURCECLIENTAREAONLY flag to use this field.

The sample in the download for this article demonstrates the use of a live thumbnail by providing a resizable thumbnail window.

Sample

The sample project provided in the download for this article demonstrates much of the functionality described in the article. Most of the controls affect the image and thumbnail windows directly so you can play around with the options and the windows will automatically update.
image

That concludes my coverage of the transparency and translucency features of Windows Vista.

@suhao
Copy link
Member Author

suhao commented Jun 6, 2019

Controls and the Desktop Window Manager

https://weblogs.asp.net/kennykerr/controls-and-the-desktop-window-manager

image

Of all the Windows Vista for Developers series articles that I have written, the one about the Desktop Window Manager (DWM) is by far the most popular considering the blog traffic statistics and the amount of email I receive with questions about glass.

By far the most common question I hear is about how to get controls to render correctly on glass. If you recall, I wrote the DWM article before the release of Windows Vista to manufacturing. Those early builds included a hack that allowed you to easily draw controls on glass by tweaking the transparency key. I demonstrated this technique in the article. Unfortunately, when Microsoft finally released Windows Vista this “hack” no longer worked leaving developers to wonder how it could be done.

Having said my piece about DWM and what with being extremely busy with my day job and various other commitments I never quite found the time to present alternative solutions. For the sake of my email inbox, I am going to present a simple solution to address the most common request:

How can I display an edit control on glass?!

There are a number of ways of solving this sort of problem. Specifically, there are a number of ways to override the default drawing behavior of the standard and common controls.

You can handle WM_PAINT and draw it yourself. This tends to be more work than it’s worth so most developers avoid it, however it does allow you all the freedom necessary to paint with the necessary alpha channel information to render correctly on glass. My DWM sample demonstrates this technique, albeit not with a control.

Another solution is to owner draw controls. This is a lot of work but a lot less than the WM_PAINT approach as the system does a lot of the work for you. Owner draw is nice and works for most but not all controls. Notably it does not work with edit controls.

An even simpler approach is custom draw but this works with an even smaller set of controls.

Finally, for a small number of controls you can handle WM_CTLCOLORxxx to set the text and background color of the control.

Considering the options listed thus far, only the last one actually supports edit controls and does not involve a gargantuan amount of work. Unfortunately, it does not play nicely with glass since the technique involves GDI primitives that do not support alpha blending.

I repeat: How can I display an edit control on glass?!

It is at times like these that I wonder why I don’t work for Microsoft and am not compensated for the articles I post on my blog… :)

Having received yet another email yesterday asking about edit controls on glass I decided to poke around the Windows SDK to see if it has anything new to offer. By the way, if you do not regularly read the Windows SDK I highly encourage that you do so. On a hunch, I started my search in the Themes and Visual Styles section of the SDK. After all, this is the subsystem responsible for providing the look and feel for controls.

The first thing I noticed was a set of functions added to UxTheme.dll in Windows Vista to support buffered painting. At first this might not seem all that interesting since the DWM already provides a degree of double buffering, but having the ability to buffer painting operations means that you can capture, alter, and then paint the updated bitmap. Of course, this is nothing new and could be achieved manually but the new functions provided by Visual Styles just simplify the task and solves our immediate problem quite nicely.

The Buffered Paint API

The buffered paint API provides a set of functions to provide efficient off-screen painting to a device context (DC). Since painting is done to a DC, it means that your investment in GDI is not lost. That’s right folks, you don’t have to port your entire application to Windows Presentation Foundation just yet.

To start, you should call the BufferedPaintInit function at least once per thread to initialize the API for use. Each call to BufferedPaintInit must eventually be matched with a call to BufferedPaintUnInit on the same thread.

To begin a buffered paint operation, simply call the BeginBufferedPaint function. It accepts a target DC and target rectangle identifying the eventual location that the buffer will be copied to. A few additional paramteters allow you to control the buffer characteristics. One of these is the format of the buffer and thankfully, it supports device-independent bitmaps (DIBs) necessary for most alpha blending operations. BeginBufferedPaint then returns a handle that can be passed to various other buffered paint API functions as well as a DC that you can use to paint to the buffer.

One such function that you can pass the buffered paint handle to is BufferedPaintSetAlpha. It allows you to easily update the alpha channel for the entire buffer and set it to a single value to control the level of transparency/opacity. Note that it updates the alpha value for each pixel in the buffer to a single value.

Finally you can copy the buffer to the target DC and free the associated resources allocated by BeginBufferedPaint with a single call to the EndBufferedPaint function.

By now, you can probably imagine where I am going with this. You simply need to use the buffered paint API to create a buffer to paint the edit control into and then set the alpha channel on the buffer before updating the window DC. Let us bring it home with a sample.

The BufferedPaint class

The following class wraps the buffered paint API to simplify its use from C++.

class BufferedPaint
{
public:

BufferedPaint() :
    m_handle(0)
{
    COM_VERIFY(::BufferedPaintInit());
}

~BufferedPaint()
{
    COM_VERIFY(::BufferedPaintUnInit());
}

HRESULT Begin(HDC targetDC,
              const RECT& targetRect,
              BP_BUFFERFORMAT format,
              __in_opt BP_PAINTPARAMS* options,
              __out CDCHandle& bufferedDC)
{
    ASSERT(0 == m_handle);

    m_handle = ::BeginBufferedPaint(targetDC,
                                    &targetRect,
                                    format,
                                    options,
                                    &bufferedDC.m_hDC);

    HRESULT result = S_OK;

    if (0 == m_handle)
    {
        result = HRESULT_FROM_WIN32(::GetLastError());
    }

    return result;
}

HRESULT End(bool updateTargetDC)
{
    ASSERT(0 != m_handle);

    HRESULT result = ::EndBufferedPaint(m_handle,
                                        updateTargetDC);

    m_handle = 0;
    return result;
}

HRESULT SetAlpha(__in_opt const RECT* rect,
                 BYTE alpha)
{
    ASSERT(0 != m_handle);

    return ::BufferedPaintSetAlpha(m_handle,
                                   rect,
                                   alpha);
}

private:

HPAINTBUFFER m_handle;

};

The OpaqueEdit class

The following C++ class is used to subclass the system’s edit control so that we can more easily take over its painting duties.

class OpaqueEdit :
public CWindowImpl<OpaqueEdit, CEdit>
{
public:

BEGIN_MSG_MAP_EX(OpaqueEdit)
    MESSAGE_HANDLER(WM_PAINT, OnPaint)
    REFLECTED_COMMAND_CODE_HANDLER_EX(EN_CHANGE, OnChange)
    DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()

private:

LRESULT OnPaint(UINT /*message*/, 
                WPARAM /*wParam*/, 
                LPARAM /*lParam*/, 
                BOOL& /*handled*/)
{
    CPaintDC targetDC(m_hWnd);
    CDCHandle bufferedDC;

    if (SUCCEEDED(m_bufferedPaint.Begin(targetDC,
                                        targetDC.m_ps.rcPaint,
                                        BPBF_TOPDOWNDIB,
                                        0, // options
                                        bufferedDC)))
    {
        SendMessage(WM_PRINTCLIENT,
                    reinterpret_cast<WPARAM>(bufferedDC.m_hDC),
                    PRF_CLIENT);

        COM_VERIFY(m_bufferedPaint.SetAlpha(0, // entire buffer
                                            255)); // 255 = opaque

        // Copy buffered DC to target DC
        COM_VERIFY(m_bufferedPaint.End(true)); 
    }

    return 0;
}

void OnChange(UINT /*notifyCode*/,
              int /*control*/,
              HWND /*window*/)
{
    VERIFY(InvalidateRect(0, // entire window
                          FALSE)); // don't erase background
}

BufferedPaint m_bufferedPaint;

};

As you can see in my WM_PAINT handler, I send the edit control the WM_PRINTCLIENT message, instructing it to paint into the buffered DC. I then set the buffer’s alpha channel to 255 (completely opaque) and update the target DC. What might come as a surprise is the need for the EN_CHANGE handler. Since the edit control paints outside of the WM_PAINT message, I need to paint “over” the extra painting that occurs when the control’s text changes. In this example, I simply invalidate the control, which forces it to receive a new WM_PAINT message. I could probably optimize this further but it is sufficient for this example. Keep in mind that since the DWM provides double buffering automatically you should not notice any flicker due to the repeated painting.

The SampleDialog class

The following class simply ensures that the window is represented as a “sheet” of glass and uses the OpaqueEdit class to subclass the edit control.

class SampleDialog :
public CDialogImpl
{
public:

enum { IDD = IDD_SAMPLE };

BEGIN_MSG_MAP(MainWindow)
    MSG_WM_INITDIALOG(OnInitDialog)
    MSG_WM_ERASEBKGND(OnEraseBackground)
    COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
    REFLECT_NOTIFICATIONS()
END_MSG_MAP()

private:

bool OnInitDialog(HWND /*control*/,
                  LPARAM /*lParam*/)
{
    const MARGINS margins = { -1 };

    COM_VERIFY(::DwmExtendFrameIntoClientArea(m_hWnd,
                                              &margins));

    VERIFY(m_edit.SubclassWindow(GetDlgItem(IDC_CONTROL)));

    return true; // Yes, go ahead and set the keyboard focus.
}

bool OnEraseBackground(CDCHandle dc)
{
    CRect rect;
    VERIFY(GetClientRect(&rect));

    dc.FillSolidRect(&rect, 
                     RGB(0,0,0));

    return true; // Yes, I erased the background.
}

LRESULT OnCancel(WORD /*notifyCode*/, 
                 WORD identifier, 
                 HWND /*window*/, 
                 BOOL& /*handled*/)
{
    VERIFY(EndDialog(identifier));
    return 0;
}

OpaqueEdit m_edit;

};

That is all there is to it. I hope the techniques described in this article will help in your path to adopting Windows Vista as a developer.

@suhao
Copy link
Member Author

suhao commented Jun 6, 2019

使用 Windows 组合引擎实现高性能窗口分层

https://msdn.microsoft.com/zh-cn/magazine/dn745861.aspx?f=255&MSPPError=-2147217396

自从我第一次看到 Windows XP 中的分层窗口,我就对它情有独钟。我好像一直对消除传统桌面窗口的矩形或近矩形边框非常感兴趣。后来出现了 Windows Vista。这个备受非议的 Windows 版本从一开始就预示着更具吸引力和灵活性的功能的降临。我们才开始认同 Windows Vista 带来的概念启发。虽然现在 Windows 8 出现了,但它也标志着分层窗口在慢慢地走下坡路。

Windows Vista 引入了一项名为“桌面窗口管理器”的服务。这个名字一直以来都具有误导性。请把它看作是 Windows 组合引擎或合成程序。此组合引擎完全改变了应用程序窗口在桌面上的呈现方式。每个窗口不是直接呈现给显示器或显示适配卡,而是呈现给屏外表面或缓冲区。系统为每个顶层窗口都分配了一个这样的表面,所有 GDI、Direct3D(当然还有 Direct2D)图形都呈现给此类表面。此类屏外表面称为重定向表面,因为 GDI 绘图命令以及 Direct3D 交换链呈现请求都会重定向或(在 GPU 中)复制到重定向表面。

在某些时候,不受任何指定窗口的制约,组合引擎会鉴于最新一批更改决定是否该组合桌面。这就涉及将所有重定向表面组合在一起、添加非工作区(通常称为窗口镶边)、添加一些阴影和其他效果以及将最终结果呈现给显示适配卡。

此组合过程具有许多显著优势,我将在接下来的几个月中一边深入探索 Windows 组合,一边详细说明这些优势;不过它也存在一个潜在的严重限制,即这些重定向表面不透明。大多数应用程序都可以接受此限制,而且从性能角度来看此限制也是非常有意义的,因为 alpha 值混合处理的费用高昂。但这就导致分层窗口遭到排斥。

如果我要开发分层窗口,就需要掀起一场性能冲击。我在专栏“使用 Direct2D 绘制分层窗口”(msdn.microsoft.com/magazine/ee819134) 中介绍了具体的体系结构限制。总而言之,分层窗口由 CPU 处理,主要用于支持 alpha 值混合处理像素的命中测试。也就是说,CPU 需要复制分层窗口的表面区域的组成像素。不管我是在 CPU 上呈现(往往比 GPU 呈现慢很多)还是在 GPU 上呈现,我都必须支付总线带宽税,因为我呈现的所有内容都必须从视频存储器复制到系统内存。在之前提到的专栏中,我还介绍了如何充分利用 Direct2D 尽可能发挥系统性能,因为我只有通过 Direct2D 才能在 CPU 和 GUP 呈现之间做出选择。存在的隐患就是,即使分层窗口一定要位于系统内存中,组合引擎也可以立即将其复制到视频存储器中,这样分层窗口的实际组合就仍为硬件加速。

虽然我无法带来传统分层窗口即将重返主导地位的希望,但我确实带来了一些好消息。传统的分层窗口具有两项特定的相关功能。第一项功能是每像素 alpha 值混合处理。我呈现给分层窗口的任何内容都会与桌面以及任意给定时刻窗口后面的所有内容进行 alpha 值混合处理。第二项功能是 User32 能够根据像素 alpha 值对分层窗口执行命中测试,允许鼠标消息在特定点的像素为透明时贯透过去。从 Windows 8 和 8.1 开始,虽然 User32 一直没有发生显著变化,但细微变化也有,即完全支持 GPU 上的每像素 alpha 值混合处理,且将窗口表面传输到系统内存的费用也取消了。也就是说,如果我不需要执行每像素命中测试,现在就能够生成分层窗口效果,同时不会对性能造成影响。整个窗口将统一执行命中测试。撇开命中测试不谈,只是这样就已经令我兴奋不已,因为这是系统显然可以执行的任务,但应用程序就从来没能利用这种功能。如果您对此感兴趣,请继续阅读本文,我将向您介绍这是如何实现的。

实现此功能的关键点在于使用 Windows 组合引擎。组合引擎一开始作为桌面窗口管理器出现在 Windows Vista 中,它包含受限制的 API 并具有流行的半透明 Aero 毛玻璃效果。接下来 Windows 8 出现了,它引入了 DirectComposition API。此 API 适用于相同的组合引擎,只是受限制程度更低。随着 Windows 8 版本的发布,Microsoft 最终允许第三方开发人员利用这一面世已久的组合引擎。当然,您还需要使用由 Direct3D 强力驱动的图形 API(如 Direct2D)。但您首先需要处理不透明的重定向表面。

正如我之前提到的,系统为每个顶层窗口都分配了一个重定向表面。从 Windows 8 开始,您现在可以创建顶层窗口,并请求创建没有重定向表面的顶层窗口。严格来说,这与分层窗口并不相关。因此,请勿使用 WS_EX_LAYERED 扩展的窗口样式(实际上,分层窗口的相关支持在 Windows 8 中有了细微改进,不过我将在即将发表的专栏文章中深入介绍这些改进)。您需要改用 WS_EX_NOREDIRECTIONBITMAP 扩展的窗口样式,此样式可以指示组合引擎不为窗口分配重定向表面。我将从简单的传统桌面窗口入手。图 1 中的示例介绍了如何填充 WNDCLASS 结构、注册窗口类、创建窗口以及抽取窗口消息。其中并没有什么新内容,但这些基本原理仍然至关重要。窗口变量处于未使用状态,但您马上就需要使用这个变量。您可以将此变量复制到 Visual Studio 内的 Visual C++ 项目中,也可以只是从以下命令提示符编译此变量:

cl /W4 /nologo Sample.cpp

图 1:创建传统窗口


#ifndef UNICODE #define UNICODE #endif #include <windows.h> #pragma comment(lib, "user32.lib") int __stdcall wWinMain(HINSTANCE module, HINSTANCE, PWSTR, int) { WNDCLASS wc = {}; wc.hCursor       = LoadCursor(nullptr, IDC_ARROW); wc.hInstance     = module; wc.lpszClassName = L"window"; wc.style         = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = [] (HWND window, UINT message, WPARAM wparam, LPARAM lparam) -> LRESULT { if (WM_DESTROY == message) { PostQuitMessage(0); return 0; } return DefWindowProc(window, message, wparam, lparam); }; RegisterClass(&wc); HWND const window = CreateWindow(wc.lpszClassName, L"Sample", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, module, nullptr); MSG message; while (BOOL result = GetMessage(&message, 0, 0, 0)) { if (-1 != result) DispatchMessage(&message); } }


图 2 展示了窗口在我的桌面上的显示效果。请注意,此示例没有什么特别之处。虽然该示例未提供任何绘制和呈现命令,但窗口的工作区不透明,并且组合引擎添加了非工作区、边框和标题栏。要应用 WS_EX_NOREDIRECTIONBITMAP 扩展的窗口样式删除不透明的重定向表面(表示此工作区),只需使用接受扩展的窗口样式的主要参数将 CreateWindow 函数切换为 CreateWindowEx 函数即可:

HWND const window = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP, wc.lpszClassName, L"Sample", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, module, nullptr);

image

图 2:桌面上的传统窗口

变化只包括添加了主要参数、WS_EX_NOREDIRECTIONBITMAP 扩展的窗口样式,以及使用了 CreateWindowEx 函数(而不是更简单的 CreateWindow 函数)。不过,桌面上呈现的结果变化更为彻底。图 3 展示了窗口在我的桌面上的显示效果。请注意,窗口的工作区现在是完全透明。移动窗口可以说明这一点。我甚至可以在背景中播放视频,任何部分都不会被遮挡。另一方面,整个窗口统一执行命中测试,当您在工作区内单击时,不会失去窗口焦点。这是因为负责命中测试和鼠标输入的子系统没有意识到工作区是透明的。

image
图 3:不含重定向表面的窗口

当然,接下来您会问,如果没有可向组合引擎提供的重定向表面,如何能够将任意内容呈现给窗口?答案就是利用 DirectComposition API 及其与 DirectX 图形基础结构 (DXGI) 的深度集成。此技术同样可强力驱动 Windows 8.1 XAML 实施,从而在 XAML 应用程序内提供性能极高的内容组合。Internet Explorer Trident 呈现引擎也将 DirectComposition 广泛用于触控平移、缩放、CSS3 动画、切换和转换。

我就是要用它来组合通过每像素预乘 alpha 值支持透明度的交换链,并将其与桌面的其余部分混合。传统的 DirectX 应用程序通常使用 DXGI 工厂的 CreateSwapChainForHwnd 方法创建交换链。此交换链受到在呈现过程中有效交换的一对或一组缓冲区的支持,允许应用程序在前一帧得到复制的同时呈现下一帧。应用程序呈现到的交换链表面是不透明的屏外缓冲区。当应用程序呈现交换链时,DirectX 会将内容从交换链的后台缓冲区复制到窗口的重定向表面。正如我之前提到的,组合引擎最终会将所有的重定向表面都组合到一起,从而形成整个桌面。

在这种情况下,由于窗口不包含任何重定向表面,因此不能使用 DXGI 工厂的 CreateSwapChainForHwnd 方法。不过,我仍然需要使用交换链来支持 Direct3D 和 Direct2D 呈现。这正是 DXGI 工厂的 CreateSwapChainForComposition 方法的用途所在。我可以使用此方法创建一个无窗口的交换链及其缓冲区,但呈现此交换链不会将位数复制到不存在的重定向表面,而是直接向组合引擎提供。然后,组合引擎可以获取此表面,直接用它来取代窗口的重定向表面。由于此表面不是不透明的,而是像素格式完全支持每像素预乘 alpha 值,因此结果就是在桌面上进行完全适合像素的 alpha 值混合处理。速度也极快,这是因为不必在 GPU 内进行复制,进而也就不必通过总线复制到系统内存。

这些都是理论。现在是进行实践的时候了。由于 DirectX 是一种 COM 对象,因此我将使用 Windows 运行时 C++ 模板库中的 ComPtr 类模板来管理接口指针。我还需要添加并关联至 DXGI、Direct3D、Direct2D 和 DirectComposition API。以下代码展示了这是如何实现的:

#include <wrl.h> using namespace Microsoft::WRL; #include <dxgi1_3.h> #include <d3d11_2.h> #include <d2d1_2.h> #include <d2d1_2helper.h> #include <dcomp.h> #pragma comment(lib, "dxgi") #pragma comment(lib, "d3d11") #pragma comment(lib, "d2d1") #pragma comment(lib, "dcomp")

我通常会在预编译头中添加这些代码。在这种情况下,我会省略 using 指令,并且只在我的应用程序的源文件中添加此指令。

我不喜欢错误处理通篇存在并且偏离主题本身细节的示例代码,因此我将使用一个异常类和 HR 函数妥善处理错误检查。图 4 中展示了一个简单的实施示例,当然您也可以自行决定错误处理策略。

图 4:将 HRESULT 错误转变为异常

struct ComException { HRESULT result; ComException(HRESULT const value) :result(value) {} }; void HR(HRESULT const result) { if (S_OK != result) { throw ComException(result); } }

我现在可以开始组装呈现堆栈,自然就从 Direct3D 设备入手了。我将会快速介绍此主题,因为我已经在 2013 年 3 月的专栏“Direct2D 1.1 简介”(msdn.microsoft.com/magazine/dn198239) 中详细介绍了 DirectX 基础结构。以下是 Direct3D 11 接口指针:

ComPtr direct3dDevice;
这是设备的接口指针,您可以使用 D3D11Create­Device 函数创建设备:

HR(D3D11CreateDevice(nullptr, // 适配卡 D3D_DRIVER_TYPE_HARDWARE, nullptr, // 模块 D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, // 最高可用功能级别 D3D11_SDK_VERSION, &direct3dDevice, nullptr, // 实际功能级别 nullptr)); // 设备上下文
代码中没有什么太出人意料的内容。我将创建受 GPU 支持的 Direct3D 设备。D3D11_CREATE_DEVICE_BGRA_SUPPORT 标志实现了与 Direct2D 的互操作性。DirectX 系列通过 DXGI 紧密结合在一起,这样就针对各种 DirectX API 提供了常见 GPU 资源管理工具。因此,我必须查询 Direct3D 设备的 DXGI 接口:

ComPtr dxgiDevice; HR(direct3dDevice.As(&dxgiDevice));
ComPtr As 方法只是 QueryInterface 方法的包装器。在创建 Direct3D 设备后,我便可以创建用于组合的交换链。为此,我首先需要获取 DXGI 工厂:

ComPtr dxFactory; HR(CreateDXGIFactory2( DXGI_CREATE_FACTORY_DEBUG, __uuidof(dxFactory), reinterpret_cast<void **>(dxFactory.GetAddressOf())));
此时,我将选择获取额外的调试信息,这是开发过程中非常宝贵的辅助信息。创建交换链时的最棘手部分就是确定如何向 DXGI 工厂说明预期的交换链。此调试信息非常有助于对必要的 DXGI_SWAP_CHAIN_DESC1 结构进行微调:

DXGI_SWAP_CHAIN_DESC1 description = {};
这会将结构全部初始化为零。然后,我可以开始填充任何相关属性:

description.Format = DXGI_FORMAT_B8G8R8A8_UNORM; description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; description.BufferCount = 2; description.SampleDesc.Count = 1; description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
以下特定格式并不是您的唯一选项,但可为各种设备和 API 带来最佳的性能和兼容性:32 位像素格式,每个颜色通道为 8 位,再加上一个预乘的 8 位 alpha 分量。

必须将交换链的缓冲区使用设置为允许将呈现器目标输出定向到它。您必须进行此设置,这样 Direct2D 设备上下文才能创建位图来使用绘图命令定位 DXGI 表面。Direct2D 位图本身只是受到交换链支持的抽象概念。

组合交换链仅支持依序翻转的交换效果。这就是交换链取代重定向表面与组合引擎相关联的方式。在翻转模式下,所有缓冲区都直接与组合引擎共享。然后,组合引擎可以直接从交换链后台缓冲区组合桌面,无需进行其他任何复制操作。通常情况下,这是最有效的模式。组合也需要使用此模式,因此这就是我使用的模式。翻转模式也需要至少两个缓冲区,但不支持多重采样,因此将 BufferCount 设置为 2,并将 SampleDesc.Count 设置为 1。此计数是每像素的多重采样数量。将它设置为 1 可以有效禁用多重采样。

最后需要说明的是,alpha 模式至关重要。不透明的交换链通常会忽略 alpha 模式,但在本示例中,我确实想将透明行为包括在内。预乘的 alpha 值通常会带来最佳性能,而且它也是翻转模式支持的唯一选项。

在我可以创建交换链之前必须完成的最后一项操作是确定缓冲区的预期大小。调用 CreateSwapChainForHwnd 方法时,我通常会忽略大小,但 DXGI 工厂会向窗口查询工作区的大小。在这种情况下,DXGI 不知道我打算对交换链做什么,因此我需要告诉它所需的具体大小。在窗口创建后,您可以轻松查询窗口的工作区并相应地更新交换链说明:

RECT rect = {}; GetClientRect(window, &rect); description.Width = rect.right - rect.left; description.Height = rect.bottom - rect.top;
我现在可以创建包含此说明的组合交换链,并创建 Direct3D 设备指针。Direct3D 或 DXGI 接口指针均可使用:

ComPtr swapChain; HR(dxFactory->CreateSwapChainForComposition(dxgiDevice.Get(), &description, nullptr, // 不限制 swapChain.GetAddressOf()));
现在交换链已创建,我可以使用任何 Direct3D 或 Direct2D 图形呈现代码来绘制应用程序(使用创建预期透明度所需的 alpha 值)。这里没有什么新内容,因此,我将再次引用我的 2013 年 3 月专栏,介绍使用 Direct2D 呈现给交换链的细节。图 5 介绍了一个您可以遵循的简单示例。请务必注意支持按监视器 DPI 感知,我在 2014 年 2 月的专栏“为 Windows 8.1 编写高 DPI 应用程序”(msdn.microsoft.com/magazine/dn574798) 中对此进行了介绍。

图 5:使用 Direct2D 绘制到交换链

// 使用调试信息创建单线程 Direct2D 工厂 ComPtr d2Factory; D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION }; HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, d2Factory.GetAddressOf())); // 创建与 Direct3D 设备恢复关联的 Direct2D 设备 ComPtr d2Device; HR(d2Factory->CreateDevice(dxgiDevice.Get(), d2Device.GetAddressOf())); // 创建作为实际呈现器目标 // 并揭示绘图命令的 Direct2D 设备上下文 ComPtr dc; HR(d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, dc.GetAddressOf())); // 检索交换链的后台缓冲区 ComPtr surface; HR(swapChain->GetBuffer( 0, // 索引 __uuidof(surface), reinterpret_cast<void **>(surface.GetAddressOf()))); // 创建指向交换链表面的 Direct2D 位图 D2D1_BITMAP_PROPERTIES1 properties = {}; properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW; ComPtr bitmap; HR(dc->CreateBitmapFromDxgiSurface(surface.Get(), properties, bitmap.GetAddressOf())); // 将设备上下文指向位图以进行呈现 dc->SetTarget(bitmap.Get()); // 绘制内容 dc->BeginDraw(); dc->Clear(); ComPtr brush; D2D1_COLOR_F const brushColor = D2D1::ColorF(0.18f, // 红色 0.55f, // 绿色 0.34f, // 蓝色 0.75f); // alpha HR(dc->CreateSolidColorBrush(brushColor, brush.GetAddressOf())); D2D1_POINT_2F const ellipseCenter = D2D1::Point2F(150.0f, // x 150.0f); // y D2D1_ELLIPSE const ellipse = D2D1::Ellipse(ellipseCenter, 100.0f, // x 半径 100.0f); // y 半径 dc->FillEllipse(ellipse, brush.Get()); HR(dc->EndDraw()); // 为组合引擎提供交换链 HR(swapChain->Present(1, // 同步 0)); // 标志
我现在终于可以开始使用 DirectComposition API 来呈现所有内容。虽然 Windows 组合引擎处理呈现和组合整个桌面,但您也可以通过 DirectComposition API 使用同一技术为应用程序组合视觉对象。应用程序将不同的元素(称为视觉对象)组合在一起,形成应用程序窗口本身的外观。这些视觉对象可以通过各种方式进行动画处理和转换,从而形成丰富流畅的 UI。组合过程本身也随整个桌面的组合一同执行,因此,更多的应用程序展示会从应用程序线程中独立出来,以改善响应能力。

DirectComposition 主要用于将不同的位图组合在一起。与 Direct2D 一样,这里的位图概念在更大程度上是一个抽象概念,可允许不同的呈现堆栈互相合作,共同带来顺畅且具有吸引力的应用程序用户体验。

类似于 Direct3D 和 Direct2D,DirectComposition 是由 GPU 支持且强力驱动的 DirectX API。DirectComposition 设备是通过重新指向 Direct3D 设备而创建,与 Direct2D 设备重新指向基础 Direct3D 设备的方式基本相同。我使用之前用来创建交换链的同一 Direct3D 设备和 Direct2D 呈现器目标来创建 DirectComposition 设备:

ComPtr dcompDevice; HR(DCompositionCreateDevice( dxgiDevice.Get(), __uuidof(dcompDevice), reinterpret_cast<void **>(dcompDevice.GetAddressOf())));
DCompositionCreateDevice 函数需要 Direct3D 设备的 DXGI 接口,并向新建的 DirectComposition 设备返回 IDCompositionDevice 接口指针。DirectComposition 设备可用作其他 DirectComposition 对象的工厂,并提供至关重要的 Commit 方法,此方法用于将一批呈现命令提交给组合引擎以进行最后的组合和呈现。

接下来,我需要创建与视觉对象相关联的组合目标,这些视觉对象将与目标(即应用程序窗口)一起组合:

ComPtr target; HR(dcompDevice->CreateTargetForHwnd(window, true, // 顶级 target.GetAddressOf()));
CreateTargetForHwnd 方法的第一个参数是 CreateWindowEx 函数返回的窗口句柄。第二个参数表示视觉对象与其他任何窗口元素的组合方式。结果是 IDCompositionTarget 接口指针,它只有一个方法,称为 SetRoot。通过此方法,我可以将可能形成的可视化树中的根 Visual 设置为一起组合。我不需要整个可视化树,但需要至少一个视觉对象,为此我可以再次利用 DirectComposition 设备:

ComPtr visual; HR(dcompDevice->CreateVisual(visual.GetAddressOf()));
此视觉对象包含对位图的引用,并提供一组属性,这些属性会影响该视觉对象相对于树中的其他视觉对象和目标本身的呈现和组合方式。我已获取自己希望由此视觉对象传递给组合引擎的内容。此为我之前创建的交换链:

HR(visual->SetContent(swapChain.Get()));
视觉对象已准备就绪,我只需将其设置为组合目标的根即可:

HR(target->SetRoot(visual.Get()));
最后,在可视化树成形后,我便只需通过调用 DirectComposition 设备上的 Commit 方法即可告知组合引擎我已完成操作:

HR(dcompDevice->Commit());
对于可视化树不变化的此特定应用程序,我只需在应用程序开始时调用 Commit 一次,之后再也不会调用此方法了。我最初假设需要在交换链呈现之后调用 Commit 方法,但情况并非如此,因为交换链呈现与可视化树变化并不同步。

图 6 展示了在 Direct2D 已呈现给交换链且 Direct­Composition 已将部分透明的交换链提供给组合引擎之后的应用程序窗口外观。

image

图 6:DirectComposition 表面上的 Direct2D 绘制

我终于找到了老问题的解决方案,我感到非常兴奋:可以生成与桌面的其余部分进行 alpha 值混合处理的高性能窗口。让我激动不已的是 DirectComposition API 的功能及其如何影响在本机代码中设计和开发应用程序用户体验的未来。

想要绘制您自己的窗口镶边吗?没问题,只需在创建窗口时将 WS_OVERLAPPEDWINDOW 窗口样式替换为 WS_POPUP 窗口样式即可。祝您工作愉快!

Kenny Kerr 是加拿大的一名计算机程序员,也是 Pluralsight 的作者以及 Microsoft MVP。他的博客网址是 kennykerr.ca,您可以通过 Twitter twitter.com/kennykerr 关注他。

衷心感谢以下技术专家对本文的审阅:Leonardo Blanco (Microsoft)

@suhao
Copy link
Member Author

suhao commented Jun 6, 2019

使用 Direct2D 绘制分层窗口

https://msdn.microsoft.com/magazine/ee819134?f=255&MSPPError=-2147217396

在 Direct2D 上我第三个分期付款,我将展示其不匹配的电源的一些谈到的互操作性。 而不是彻底详细描述各种互操作性的所有选项 Direct2D 提供,我将引导您完成实用的应用程序:分层的窗口。 分层的窗口是那些已周围很长时间但 haven’t 发展大部分并因此需要特别小心,要有效地使用与现代图形的技术的 Windows 功能之一。

本文中我将假定您有基本的熟悉 Direct2D 编程。 如果不是,从六月 ( msdn.microsoft.com/magazine/dd861344 ) 读取我先前的文章和九月 ( msdn.microsoft.com/magazine/ee413543 ) 发出引入的编程和绘图 Direct2D 与基础知识的建议。

最初,分层的窗口提供几个不同的用途。 在具体的方式而言它们可用于轻松、 高效地生成视觉效果和无闪烁的呈现。 在 GDI 时已产生图形的主要方法将内容天什么内,这是真实的奖金。 在今天已经硬件加速世界上,但是,它不再引人注目因为分层的窗口仍属于 User32/GDI 的世界,并且没有更新任何重大的方式支持 DirectX,Microsoft 平台的高性能和高质量的图形。

分层的窗口提供唯一的撰写一个窗口在桌面上使用每像素 alpha 混合,它不能以其他方式与 Windows SDK 才能实现的能力。

我应该更不用说有真正两种类型的分层窗口。 区别涉及到您是否需要每像素不透明度控件向下或只是需要控制整个窗口的不透明度。 本文是前者但如果实际上只是需要控制一个窗口的不透明度有关可以简单地创建设置 alpha 值窗口后调用 SetLayeredWindowAttributes 函数来执行此操作。

Verify(SetLayeredWindowAttributes(
windowHandle,
0, // no color key
180, // alpha value
LWA_ALPHA));
本示例假定使用扩展样式 WS_EX_LAYERED 中创建窗口或使用 SetWindowLong 函数事实后应用它。图 1 提供这样的窗口的一个示例。好处应该是显而易见的:don’t 需要更改您的应用程序进行绘制桌面窗口管理器 (DWM) 自动将适当地进行窗口混合窗口方式有关的任何内容。翻转侧需要自己绘制绝对的所有内容。课程如果使用全新的呈现技术 (如 Direct2D 的不 ’s 问题 !

image

图 1 窗口具有 Alpha 值

因此涉及什么?嗯,在基本级别它十分简单。首先,需要填充 UPDATELAYEREDWINDOWINFO 结构中。此结构提供位置和大小的分层的窗口,以及定义窗口图面一个 GDI 设备上下文 (DC) — 和其中位于该问题。dc 属于旧的世界的 GDI 和是远从世界的 DirectX 和硬件加速。在稍后的上的更多。

除了需要分配您自己的结构的指针的完整,UPDATELAYEREDWINDOWINFO 结构 isn’t 完全记录在 Windows SDK,使其小于明显使用中。在所有,需要分配五种结构。有 ’s 源位置标识将从 DC 复制位图的位置。有 ’s 窗口位置标识窗口中一次更新在桌面上的放置位置。有 ’s 它还定义窗口的大小将复制,位图的大小:

POINT sourcePosition = {};
POINT windowPosition = {};
SIZE size = { 600, 400 };
然后 ’s BLENDFUNCTION 结构定义了如何与桌面混合分层的窗口。 这是一种令人惊讶的是通用结构,它经常忽视但十分有用。 通常您可能会填充它,如下所示:

BLENDFUNCTION blend = {};
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
AC_SRC_ALPHA 常数只指示源位图已是最常见的方案的 alpha 通道。

在 SourceConstantAlpha 但是,是有趣的您可以使用它在很大程度相同的方式,您可能使用 SetLayeredWindowAttributes 函数来控制整个窗口的不透明度。 当设置为 255 分层的窗口将仅使用每个像素的 alpha 值,但您可以调整它向零,或完全透明生成如淡入淡出的重绘成本的情况下窗口中或签出的效果。 它现在应该很明显 BLENDFUNCTION 结构将被命名为它的原因:得到的 alpha 混合窗口是此结构已经值的函数。

上次,’s 一起将 UPDATELAYEREDWINDOWINFO 结构:

UPDATELAYEREDWINDOWINFO info = {};
info.cbSize = sizeof(UPDATELAYEREDWINDOWINFO);
info.pptSrc = &sourcePosition;
info.pptDst = &windowPosition;
info.psize = &size;
info.pblend = &blend;
info.dwFlags = ULW_ALPHA;
这应该是相当容易理解在这时带有唯一未记录的成员被 dwFlags 变量。 外观应熟悉如果使用较旧的 UpdateLayeredWindow 函数之前的 ULW_ALPHA 的值只是表示应使用混合函数。

最后,您需要提供到源 DC 句柄和调用 UpdateLayeredWindowIndirect 函数以更新窗口:

info.hdcSrc = sourceDC;

Verify(UpdateLayeredWindowIndirect(
windowHandle, &info));
就这样, 在窗口 won’t 接收 WM_PAINT 消息。 您需要显示或更新窗口的任何时间只是调用 UpdateLayeredWindowIndirect 函数。 若要保留此样板代码的所有开,我将使用 本文的其余部分中的 图 2 所示 LayeredWindowInfo 包装类。

图 2 的 LayeredWindowInfo 包装类

class LayeredWindowInfo {
const POINT m_sourcePosition;
POINT m_windowPosition;
CSize m_size;
BLENDFUNCTION m_blend;
UPDATELAYEREDWINDOWINFO m_info;

public:

LayeredWindowInfo(
__in UINT width,
__in UINT height) :
m_sourcePosition(),
m_windowPosition(),
m_size(width, height),
m_blend(),
m_info() {

  m_info.cbSize = sizeof(UPDATELAYEREDWINDOWINFO);
  m_info.pptSrc = &m_sourcePosition;
  m_info.pptDst = &m_windowPosition;
  m_info.psize = &m_size;
  m_info.pblend = &m_blend;
  m_info.dwFlags = ULW_ALPHA;

  m_blend.SourceConstantAlpha = 255;
  m_blend.AlphaFormat = AC_SRC_ALPHA;
}

void Update(
__in HWND window,
__in HDC source) {

m_info.hdcSrc = source;

Verify(UpdateLayeredWindowIndirect(window, &m_info));

}

UINT GetWidth() const { return m_size.cx; }

UINT GetHeight() const { return m_size.cy; }
};
图 3 为使用 ATL/WTL 和 LayeredWindowInfo 包装类 的 图 2 从一个分层窗口提供了基本的骨架。 注意到此第一件事是 ’s 不需要调用 UpdateWindow,因为此代码 doesn’t 使用 WM_PAINT。 相反,它立即调用反过来,则需要执行一些绘图,并提供到 LayeredWindowInfo 已经 Update 方法 DC 该呈现方法。 该绘图发生方式,以及在 DC 所来自的地方是有趣获取。

图 3 分层窗口框架

class LayeredWindow :
public CWindowImpl<LayeredWindow,
CWindow, CWinTraits<WS_POPUP, WS_EX_LAYERED>> {

LayeredWindowInfo m_info;

public:

BEGIN_MSG_MAP(LayeredWindow)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()

LayeredWindow() :
m_info(600, 400) {

Verify(0 != __super::Create(0)); // parent
ShowWindow(SW_SHOW);
Render();

}

void Render() {
// Do some drawing here

m_info.Update(m_hWnd,
  /* source DC goes here */);

}

void OnDestroy() {
PostQuitMessage(1);
}
};
GDI / GDI + 方式
我首先向您展示如何 GDI 中已完成 / GDI +。 首先,您需要创建 pre-multiplied 的 32-位-每像素 (bpp) 位图使用蓝绿-红 alpha (BGRA) 颜色通道字节顺序。 颜色通道值具有已通过 alpha 值乘以被 pre-multiplied 的只是表示。 这往往 alpha 混合图像为提供更好的性能,但它意味着您需要反向除以颜色值,通过获取其真彩色值 
alpha 值的过程。 在 GDI 术语这被称为 32 bpp 设备无关位图 (DIB) 和通过填写 BITMAPINFO 结构与传递它要在 CreateDIBSection 工作 (请参阅 的 图 4) 创建。

图 4 创建 DIB

BITMAPINFO bitmapInfo = {};
bitmapInfo.bmiHeader.biSize =
sizeof(bitmapInfo.bmiHeader);
bitmapInfo.bmiHeader.biWidth =
m_info.GetWidth();
bitmapInfo.bmiHeader.biHeight =
0 – m_info.GetHeight();
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression =
BI_RGB;

void* bits = 0;

CBitmap bitmap(CreateDIBSection(
0, // no DC palette
&bitmapInfo,
DIB_RGB_COLORS,
&bits,
0, // no file mapping object
0)); // no file offset
有很多此处,详细信息,但它们 aren’t 相关到讨论。 此 API 函数返回一个长的方式。 什么您应注意的是确保指定为位图负高度。 BITMAPINFOHEADER 结构定义底部向上或自上而下的位图。 如果高度为正数则结束与自下而上的位图和 ’s 负数则得到自上而下的位图。 自上而下的位图左上角有其原始位置,而底部鎷位图中左下角有其原始位置。

尽管不是必需的但在本例中我倾向于使用自上而下的位图,则由大多数的现代的图像处理组件在 Windows 中使用该格式,从而提高了互操作性。 这还产生一个正的步幅,计算如下:

UINT stride = (width * 32 + 31) / 32 * 4;
此时,您有足够的信息,可以开始通过位指针位图中的绘图。 课程除非 ’re 完全 insane 希望使用某些绘图的函数,但遗憾的是那些由 GDI 提供的大多数 don’t 支持 alpha 通道。 ’s 其中 GDI + 附带。

尽管您可能位图数据直接传递到 GDI +,let’s 相反 DC 为其创建由于需要它仍要传递给 UpdateLayeredWindowIndirect 函数。 若要创建该 DC,调用创建内存与桌面兼容的 DC 在恰当命名的 CreateCompatibleDC 函数。 然后可以调用,以选择到 DC 的位图 SelectObject 函数。 GdiBitmap 的包装类,在 的 图 5 中环绕所有这样的并提供了一些额外的维护管理。

图 5 的 DIB 包装类

class GdiBitmap {
const UINT m_width;
const UINT m_height;
const UINT m_stride;
void* m_bits;
HBITMAP m_oldBitmap;

CDC m_dc;
CBitmap m_bitmap;

public:

GdiBitmap(__in UINT width,
__in UINT height) :
m_width(width),
m_height(height),
m_stride((width * 32 + 31) / 32 * 4),
m_bits(0),
m_oldBitmap(0) {

BITMAPINFO bitmapInfo = { };
bitmapInfo.bmiHeader.biSize = 
  sizeof(bitmapInfo.bmiHeader);
bitmapInfo.bmiHeader.biWidth = 
  width;
bitmapInfo.bmiHeader.biHeight = 
  0 - height;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = 
  BI_RGB;

m_bitmap.Attach(CreateDIBSection(
  0, // device context
  &bitmapInfo,
  DIB_RGB_COLORS,
  &m_bits,
  0, // file mapping object
  0)); // file offset
if (0 == m_bits) {
  throw bad_alloc();
}

if (0 == m_dc.CreateCompatibleDC()) {
  throw bad_alloc();
}

m_oldBitmap = m_dc.SelectBitmap(m_bitmap);

}

~GdiBitmap() {
m_dc.SelectBitmap(m_oldBitmap);
}

UINT GetWidth() const {
return m_width;
}

UINT GetHeight() const {
return m_height;
}

UINT GetStride() const {
return m_stride;
}

void* GetBits() const {
return m_bits;
}

HDC GetDC() const {
return m_dc;
}
};
可以用位图已经 DC 构造提供到一些设备的绘图的方法,在 GDI + 图形类。 图 6 显示的 的 图 3 从 LayeredWindow 类为支持用 GDI + 呈现的更新方式。 一旦您拥有样板 GDI 代码的所有开,’s 非常简单。 窗口已经大小传递给 GdiBitmap 构造函数和位图已经 DC 传递给图形构造函数和 Update 方法。 虽然简单,既不 GDI GDI + (大部分),将硬件加速也执行它们提供特别功能强大的呈现功能。

图 6 GDI 分层窗口

class LayeredWindow :
public CWindowImpl< ... {

LayeredWindowInfo m_info;
GdiBitmap m_bitmap;
Graphics m_graphics;

public:
LayeredWindow() :
m_info(600, 400),
m_bitmap(m_info.GetWidth(), m_info.GetHeight()),
m_graphics(m_bitmap.GetDC()) {
...
}

void Render() {
// Do some drawing with m_graphics object

m_info.Update(m_hWnd,
  m_bitmap.GetDC());

}
...
体系结构问题
这是通过相反所有所需创建分层的窗口与 Windows 演示基础 (WPF):

class LayeredWindow : Window {
public LayeredWindow() {
WindowStyle = WindowStyle.None;
AllowsTransparency = true;

// Do some drawing here

}
}
尽管令人难以置信简单,但它 belies 涉及的复杂性和使用的体系结构限制分层窗口。 不管如何 sugarcoat 它,分层的窗口必须按照本文中到目前为止概述该体系结构原则。 尽管 WPF 也许能够为其呈现使用硬件加速,结果仍需要复制到 pre-multiplied BGRA 位图选入兼容的 DC,通过对 UpdateLayeredWindowIndirect 函数调用更新显示之前。 WPF 不公开任何多于 bool 变量内容,因为它具有您没有控件的上方代表您做出某些选择。 为什么重要? 谈到硬件。

图形处理单元 (GPU) 首选以获得最佳性能的专用的内存。 到往往比系统内存中的两个位置之间复制要慢得多的 GPU 内存这意味着如果希望处理现有的位图它需要复制从系统内存 (RAM)。 在相反,也是这样:如果创建呈现位图使用该 GPU 然后决定将其复制到 ’s 昂贵的复制操作的系统内存。

通常这应该不会由该 GPU 呈现位图通常发送到显示设备的直接。 分层窗口的情况下该位图必须旅行返回到系统内存由于 User32/GDI 资源涉及到内核模式和用户模式需要访问该位图的资源。 渚嬪考虑 User32 需要命中测试事实分层窗口。 命中测试的分层窗口基于 alpha 的值,该位图允许通过鼠标消息,如果在某个特定时刻像素是透明。 作为结果的位图副本系统内存中需要允许发生这种情况。 一旦已经通过 UpdateLayeredWindowIndirect 复制位图,它是以该 GPU 发送直后,因此该 DWM 可以撰写在桌面上。

除了复制来回内存的零用金,强制与 CPU 同步 GPU 昂贵也是。 与典型的 CPU 绑定操作不同 GPU 操作倾向于所有被异步,执行批处理的呈现命令流时,它提供了很好的性能。 每次我们需要交叉路径替换为 CPU,它会强制刷新成批的命令并等待直到完成该 GPU,不是最佳的性能从而导致 CPU。

所有意味着您需要小心这些往返次数和频率以及所涉及的成本。 足够复杂呈现的场景是否硬件加速的性能可以很容易地超过复制该位图的成本。 手动,如果呈现不是非常昂贵,并且可以由 CPU 执行,您可能会发现对于没有硬件加速选择将最终会提供更好的性能。 这些选择 aren’t 容易进行。 某些 gpu don’t 甚至有专用的内存,而是使用减少了副本的成本的系统内存的一部分。

该问题是 GDI 和 WPF 都不向您提供一个选择。 在 GDI 的情况下您附着与 CPU。 在 WPF 的情况下您强制进入使用任何呈现方法 WPF 用途,这通常是通过 Direct3D 硬件加速。

然后来源 Direct2D。

GDI/DC 的 Direct2D
Direct2D 旨在呈现为任何面向您选择。 如果它 ’s Direct3D 纹理或窗口,Direct2D 执行此直接在该 GPU 上而不涉及任何复制。 如果它 ’s Windows 图像处理组件 (WIC) 位图,Direct2D 同样呈现直接转而使用 CPU。 而 WPF 力争其呈现的大部分内容置于该 GPU,并使用一个软件光栅化程序作为一个回退,Direct2D 提供最好的两个世界上的硬件加速 GPU unparalleled 即时模式呈现和高度优化的呈现与 CPU 上一个 GPU 时不可用或不需。

可以想象有相当几种方法可以呈现与 Direct2D 分层的窗口。 let’s 看一看一些和我指出推荐的方法,具体取决于您要使用硬件加速。

首先,您可能只需翻录出 GDI + 图形类,从 的 图 3 和替换它 Direct2D DC 与呈现目标。 这可能会使意义上,如果您有 GDI 中, 投入到很多旧版应用程序,但它 ’s 明确不最有效的解决方案。 而不是直接到 DC 呈现,Direct2D 呈现第一个对内部的 WIC 位图,然后将结果复制到 DC。 尽管比 GDI + 快,这不过涉及额外复制如果 didn’t 需要使用一个 DC 的呈现,则可以避免的。

若要用于这种方法启动通过初始化 D2D1_RENDER_TARGET_PROPERTIES 结构。 这会告诉 Direct2D 的位图以使用为其呈现目标格式。 回忆一下,它必须是 pre-multiplied 的 BGRA 像素格式。 这表示具有 D2D1_PIXEL_FORMAT 结构,定义如下:

const D2D1_PIXEL_FORMAT format =
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED);

const D2D1_RENDER_TARGET_PROPERTIES properties =
D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
format);
现在,您可以创建使用 Direct2D 工厂对象 DC 呈现目标:

CComPtr target;

Verify(factory->CreateDCRenderTarget(
&properties,
&target));
最后,您需要告诉到要发送其绘图命令哪些 DC 呈现目标:

const RECT rect = {0, 0, bitmap.GetWidth(), bitmap.GetHeight()};

Verify(target->BindDC(bitmap.GetDC(), &rect));
此时可以使用 Direct2D 照常 BeginDraw 和 EndDraw 方法调用之间绘制,然后调用 Update 方法作为前的与位图已经 DC。 EndDraw 方法可确保所有绘图已被都刷新到绑定的 DC。

WIC 的 Direct2D
现在如果您可以完全避免 GDI DC,只需直接使用 WIC 位图,您可以实现硬件加速不可能性能最佳。 若要使用此方法开始通过直接与 WIC 创建 pre-multiplied 的 BGRA 位图:

CComPtr factory;
Verify(factory.CoCreateInstance(
CLSID_WICImagingFactory));

CComPtr bitmap;

Verify(factory->CreateBitmap(
m_info.GetWidth(),
m_info.GetHeight(),
GUID_WICPixelFormat32bppPBGRA,
WICBitmapCacheOnLoad,
&bitmap));
接下来您需要再一次初始化 D2D1_RENDER_TARGET_PROPERTIES 结构中那样作为之前,只不过您必须还告诉 Direct2D 呈现目标需要是 GDI 兼容:

const D2D1_PIXEL_FORMAT format =
D2D1::PixelFormat(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED);

const D2D1_RENDER_TARGET_PROPERTIES properties =
D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
format,
0.0f, // default dpi
0.0f, // default dpi
D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE);
现在,您可以创建使用 Direct2D 工厂对象 WIC 呈现目标:

CComPtr target;

Verify(factory->CreateWicBitmapRenderTarget(
bitmap,
properties,
&target));
但是,完全不会 D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE 做什么? 它 ’s 提示来 Direct2D 您将查询 ID2D1GdiInteropRenderTarget 接口为呈现目标:

CComPtr interopTarget;
Verify(target.QueryInterface(&interopTarget));
为简单起见,并实现的效率,此接口的查询将总是成功。 当只尝试使用它,但是,它将失败如果 didn’t 指定预先您 desires。

ID2D1GdiInteropRenderTarget 接口具有只是两种方法:GetDC 和 ReleaseDC。 若要优化使用硬件加速的位置的情况下,这些方法被限制为呈现目标已经 BeginDraw 和 EndDraw 方法调用之间使用。 GetDC 将返回该 DC 之前刷新呈现目标。 由于 interop 接口已经方法需要成对,它意义 的 图 7 所示在 c + + 类环绕。

图 7 呈现目标 DC 包装类

class RenderTargetDC {
ID2D1GdiInteropRenderTarget* m_renderTarget;
HDC m_dc;

public:
RenderTargetDC(ID2D1GdiInteropRenderTarget* renderTarget) :
m_renderTarget(renderTarget),
m_dc(0) {

Verify(m_renderTarget->GetDC(
  D2D1_DC_INITIALIZE_MODE_COPY,
  &m_dc));

}

~RenderTargetDC() {
RECT rect = {};
m_renderTarget->ReleaseDC(&rect);
}

operator HDC() const {
return m_dc;
}
};
的 图 8 所示,现在可以使用该 RenderTargetDC 更新窗口已经呈现方法。 有关这种方法很好的事情是代码的所有特定于创建 WIC 呈现目标代码的 tucked 走 CreateDeviceResources 方法中。 接下来我向您展示如何创建 Direct3D 呈现目标,以获得对硬件加速,但在任一情况中 的 图 8 所示,呈现方法保持不变。 这使得您的应用程序相当方便地切换而不更改所有绘图代码的呈现目标的实现。

图 8 GDI 兼容呈现方法

void Render() {
CreateDeviceResources();
m_target->BeginDraw();
// Do some drawing here
{
RenderTargetDC dc(m_interopTarget);
m_info.Update(m_hWnd, dc);
}

const HRESULT hr = m_target->EndDraw();

if (D2DERR_RECREATE_TARGET == hr) {
DiscardDeviceResources();
}
else {
Verify(hr);
}
}
direct3d/DXGI 的 Direct2D
若要获得硬件加速的呈现,您需要使用 Direct3D。 因为不呈现直接与通过将自动,获得硬件加速的 ID2D1HwndRenderTarget HWND 需要自己创建 Direct3D 设备和连接点在基础 DirectX 图形基础结构 (DXGI),以便您可以获得兼容 GDI 的结果。

DXGI 是一个相对较新的子系统,居住从底层硬件抽象 Direct3D 和互操作方案提供了高性能网关的 Direct3D 下面图层上。 Direct2D 还利用简化的 Direct3D 的将来版本在移动此新的 API。 若要用于此方法首先创建 Direct3D 硬件设备。 这是表示 GPU 将执行呈现的设备。 此处为目前这必需的 Direct2D 使用 Direct3D 10.1 API:

CComPtr device;

Verify(D3D10CreateDevice1(
0, // adapter
D3D10_DRIVER_TYPE_HARDWARE,
0, // reserved
D3D10_CREATE_DEVICE_BGRA_SUPPORT,
D3D10_FEATURE_LEVEL_10_0,
D3D10_1_SDK_VERSION,
&device));
D3D10_CREATE_DEVICE_BGRA_SUPPORT 标志是至关重要的 Direct2D 互用性和 BGRA 像素格式现在外观应熟悉。 传统的 Direct3D 应用程序中可能会创建交换链,并作为纹理来呈现到提供呈现的窗口之前检索其后台缓冲区。 因为对于仅呈现,不应用于演示文稿在您使用 Direct3D,您只是可以直接创建纹理资源。 纹理是用于存储是像素的 Direct3D 等效的像素 Direct3D 资源。 尽管 Direct3D 提供 1-,2 和 3 维纹理您所需要的全部工作就是一个二维纹理,最紧密映射到一个二维表面 (请参阅 的 图 9)。

图 9 的 一个二维纹理

D3D10_TEXTURE2D_DESC description = {};
description.ArraySize = 1;
description.BindFlags =
D3D10_BIND_RENDER_TARGET;
description.Format =
DXGI_FORMAT_B8G8R8A8_UNORM;
description.Width = GetWidth();
description.Height = GetHeight();
description.MipLevels = 1;
description.SampleDesc.Count = 1;
description.MiscFlags =
D3D10_RESOURCE_MISC_GDI_COMPATIBLE;

CComPtr texture;

Verify(device->CreateTexture2D(
&description,
0, // no initial data
&texture));
D3D10_TEXTURE2D_DESC 结构描述来创建纹理。 D3D10_BIND_RENDER_TARGET 常数指示该纹理作为输出缓冲区或呈现目标 Direct3D 管道的绑定。 DXGI_FORMAT_B8G8R8A8_UNORM 常数可确保 Direct3D 将为 GDI 产生正确的像素格式。 最后,D3D10_RESOURCE_MISC_GDI_COMPATIBLE 常数,指示基础的 DXGI 面,以提供可通过其获得的呈现结果 GDI DC。 通过在上一节中介绍 ID2D1GdiInteropRenderTarget 界面公开此 Direct2D。

如我提到 Direct2D 都能够呈现到 DXGI API 来避免将关联到任何特定版本的 Direct3D API 通过一个 Direct3D 图面。 这意味着您需要获取 Direct3D 纹理已经基础 DXGI 表面接口传递给 Direct2D:

CComPtr surface;
Verify(texture.QueryInterface(&surface));
此时可以使用 Direct2D 工厂对象创建 DXGI 表面呈现目标:

CComPtr target;

Verify(factory->CreateDxgiSurfaceRenderTarget(
surface,
&properties,
&target));
呈现目标属性是我在上一节中描述的相同。请记住使用正确的像素格式,并请求 GDI 兼容性。您可以然后 ID2D1GdiInteropRenderTarget 接口查询和使用 的 图 8 从相同的呈现方法。

所有 ’s 并没有给它。如果要呈现与硬件加速您分层的窗口使用 Direct3D 纹理。否则使用 WIC 位图。这两种方法将提供与复制的最小可能性能最佳。

请务必检查出 DirectX 博客,并在特定的、 componentization 和互操作性在 blogs.msdn.com/directx 上的 Ben Constable 已经八月 2009年文章。

@suhao
Copy link
Member Author

suhao commented Jun 6, 2019

Windows透明窗体实现总结

https://blog.csdn.net/l198738655/article/details/78143996

此片文章是以前写的, 刚刚新开了博客, 就发出来跟大家分享下。

这篇文章主要讲得是vc中各种分层、透明、不规则窗口的实现, 基本囊括GDI、GDI+能使用的所有方法。

本文讲述了三种方法,其中第一种方法有两种不同效果,第三种方法有两种不同的实现方式。文中有方法使用了GDi+,关于GDI+的使用请自行查询资料,本文不进行细述。

方法一:窗体整体透明,支持子控件透明,支持OnPaint重绘。

这个方法比较简单,使用win32 Api 中SetLayeredWindowAttributes函数即可,关于该函数可查询MSDN,用这种方法有两种效果:

效果1:窗体整体透明,子控件也透明,可以实现半透明效果

//第一步要修改窗体属性,WS_EX_LAYERED支持透明

LONG lWindowStyle = ::GetWindowLong(hwnd, GWL_EXSTYLE) |

WS_EX_LAYERED;

//设置Alpha不透明度

BYTE byteAlpha = 150;

//注意最后一个参数为LWA_ALPHA,第二个参数颜色掩码(透明

//色无用)

SetLayeredWindowAttributes(m_hwnd, 0/any/, byteAlpha,LWA_ALPHA )

效果2:窗体整体透明,子控件不透明,实现不规则窗体,区域透明。

首先需要一张背景位图,需要透明的地方用单一颜色填充,然后将其贴在背景上,代码如下:

第一步跟效果一中一样需修改窗体属性

::SetWindowLong(hwnd, GWL_EXSTYLE, lWindowStyle);

//将红色设为透明色, 注意透明区域鼠标并不能穿透RGB(255, 0, 0)

//为透明色

//注意最后一个参数为LWA_COLORKEY,第三个参数透明度无用

::SetLayeredWindowAttributes(hwnd, RGB(255, 0, 0), 111/any/,

LWA_COLORKEY);

需要注意的是效果1和效果2可以结合起来使用,最后一个参数改成LWA_COLORKEY | LWA_ALPHA即可。使用SetLayeredWindowAttributes函数实现不规则形状简单易行,但是通常会有锯齿很难处理。

方法二:根据位图进行区域裁剪 ,关键函数CombineRgn和SetWindowRgn。

该方法跟方法一一样,需要将背景位图需要透明的地方填充为单一颜色,该方法的原理是遍历位图中的每个像素,将需要透明的像素过滤,将其他不需要透明的像素所在区域用CombineRgn函数连接起来形成一个区域,然后用SetWindowRgn将贴好背景图的窗体放进这个区域。此方法好处是可以实现镂空,即鼠标穿透透明区域。缺点是遍历每个像素对于大的位图算法时间复杂度高,效率很低。代码如下:

void CMeterHeadDlg::SetupRegion(CDC & pDC, HBITMAP cBitmap,
 
COLORREF TransColor)
 
{
 
CDC memDC;
 
HBITMAP pOldMemBmp=NULL;
 
BITMAP bit;
 
CRect rect;
 
GetWindowRect(rect);
 
CRgn wndRgn;
 
//创建于传入dc兼容的临时dc
 
memDC.CreateCompatibleDC(pDC);
 
//取得位图参数,要用其长和宽¨a
 
::GetObjectA(m_hBkBitmap, sizeof(bit), &bit);
//将位图选入临时dc
 
pOldMemBmp= memDC.SelectBitmap(m_hBkBitmap);
 
//创建总的窗体区域
 
wndRgn.CreateRectRgn(0,0,rect.Width(),rect.Height());
 
for(int y=0;y<rect.Height()+1;y++)
 
{
 
CRgn rgnTemp; //保存临时区域
 
int iX = 0;
 
do
 
{
 
//等于透明色跳过找到下一个非透明色
 
if (memDC.GetPixel(iX, y) == TransColor)
 
{
 
rgnTemp.CreateRectRgn(iX,y,iX+1,y+1);
 
//合并region,注意ComebineRgn最后一个参数为“异或”
 
wndRgn.CombineRgn(wndRgn, rgnTemp, RGN_XOR);
 
//删除临时region
 
rgnTemp.DeleteObject();
 
}
 
iX++;
 
}while(iX <rect.Width()+1);
 
iX = 0;
 
}
 
if(pOldMemBmp)
 
memDC.SelectBitmap(pOldMemBmp);
 
SetWindowRgn(wndRgn,TRUE);
 
SetForegroundWindow(m_hWnd);
 
DeleteDC(memDC);
 
}

方法三:使用透明png贴图,并实现透明区域的透明。

此方法的优点是可以实现不规则形状贴图,鼠标能穿透透明区,并且边缘无锯齿。

该方法根据实现方式可分为两种方法

1、使用CImage(ATL和MFC中都有该类,直接用win32 api没有CImage,会麻烦点可能要用CreateFIle函数加载)绘制。

为什么我们正常的的使用CImage加载png透明区总是有白色背景呢?查了很多资料才发现这其实是微软GDI+的设计问题,PNG 图片是ARGB,使用GDI+载入图片的时候,GDI+会默认已经进行了预剩运算(PARGB),即每象素的实际值是已经和ALPHA值按比例相乘的结果,实际上它根本就没有做预乘, 在使用透明图片的象素ALPHA通道的时候,CImage 内部正是调用的AlphaBlend,没有预乘的图当作预乘的图片处理的结果就是这相当于一张和纯白背景进行了预剩, 所以图象总是出现白色背景。所以我们只需要对症下药,载入图片前与处理下即可:


if (Image.GetBPP() == 32) //确认32位包含alpha通道
 
for (i = 0; i < Image.GetWidth(); i++)
 
{
 
for (j = 0; j < Image.GetHeight(); j++)
 
{
 
byte *pByte =(byte*)Image.GetPixelAddress(i, j);
 
 
pByte[0] = pByte[0] * pByte[3] / 255;
 
 
pByte[1] = pByte[1] * pByte[3] / 255;
 
 
pByte[2] = pByte[2] * pByte[3] / 255;
 
}
 
}
 
}

最后调用CImage中Draw方法就可。

2、使用GDI+贴图,利用UpdateLayeredWindow函数实现png透明区域透明。该函数请查询MSDN。这种方法不支持子控件透明, 不支持OnPaint重绘代码如下:首先在OnInitDialog中修改窗体属性

PS:UpdateLayeredWindow函数能够让窗口透明,并且鼠标能穿过窗口。

ModifyStyleEx(0, WS_EX_LAYERED | WS_OVERLAPPED);

下面为贴图函数,注意由于不支持OnPaint,所以需重绘是手动调用贴图函数,贴图中使用到GDI+

//在OnInitDialog中初始化m_pBkImage ,m_pBkImage为

//Gdiplus::Image指针,Image::FromFile是从外面文件夹中导入png文
//件,若果是从本地资源文件中导入,需使用其他方法。

m_pBkImage = Image::FromFile(g_strResPath+T("main.png"));

下面为贴图函数:
// 初始化时该函数放在OnInitDialog中调用,后面需要刷新时,手动

//调用,该方法贴的背景图不能响应WM_PAINT消息,也不能够在

//OnPaint函数中调用该绘图方法。

void CMainPanel::DrawAlphaPng()
 
{
 
CRect rcClient;
 
GetClientRect(&rcClient);
 
CClientDC dc(m_hWnd);
 
CDC memDc;
 
memDc.CreateCompatibleDC(dc.m_hDC);
 
CBitmap bmp;
 
bmp.CreateCompatibleBitmap(dc.m_hDC, rcClient.Width(),
 
rcClient.Height());
 
memDc.SelectBitmap(bmp);
 
//用GDI+显示图片
 
Graphics graph(memDc.m_hDC);
 
graph.DrawImage(m_pBkImage, 0,0 ,rcClient.Width(),
 
rcClient.Height());
 
BLENDFUNCTION _Blend;
 
_Blend.BlendOp = 0;
 
_Blend.BlendFlags = 0;
 
_Blend.AlphaFormat = 1;
 
_Blend.SourceConstantAlpha = 255;
 
SIZE sz = {rcClient.Width(), rcClient.Height()};
 
//::UpdateLayeredWindow(m_hWnd, hDC, &ptWinPos,&sizeWindow,
 
//hdcMemory, &ptSrc, 0, &stBlend, ULW_ALPHA);
 
UpdateLayeredWindow(m_hWnd, dc, &CPoint(0, 0), &sz, memDc,
 
&CPoint(0, 0), 0, &_Blend, ULW_ALPHA);
 
bmp.DeleteObject();
 
graph.ReleaseHDC(memDc.m_hDC);
 
ReleaseDC(dc.m_hDC);
 
}

需要注意的是使用方法三中第二种方法虽然不会出现锯齿,但是会导致界面上的子空间全部透明,这样我们在界面上添加的控件都没用。我试过在界面上用Create方法创建及对控件重绘,但是没用。解决这个问题的方法是结合方法一中的效果二。需要两个窗口A (背景窗口),B(用于放置控件)。用方法三第二种方式实现窗口A,使用方法一种第二种效果创建B,MFC中可以在OnCtlColor函数中将背景颜色设为单一颜色,ATL中则没有OnCtlColor,最简单的做法是在OnEraseBKGND消息函数中返回TRUE(背景会变成白色),然后将背景颜色设为透明色(这时B窗体会全透明,但是其上控件不会透明),将A上所需要的控件放在B上相应位置,B窗口覆盖在A上面与其重合(由于B透明所以B上控件看着像放在A上)。在移动B窗口时同时移动A窗口。这样就能达到我们想要的效果。除了这三种方法之外,使用GDI中TransparentBlt函数也可以实现透明,该函数可以将一张有背景的贴图消除背景贴在窗体上。关于该函数使用就不再介绍了,可以查询MSDN。

@suhao
Copy link
Member Author

suhao commented Jun 17, 2022

Direct3D 12无边框半透明窗口

https://zhuanlan.zhihu.com/p/127427494

语言:c++

环境:Visual Studio 2019

组件:Direct3D 12,DirectComposition,C++/WinRT

特点:解决抖动闪烁,支持拖动、调整大小、最大化、自动排列、无边框、半透明、阴影

一、实现无边框窗口

  1. 使用WS_OVERLAPPEDWINDOW样式创建标准窗口
  2. 处理WM_NCCALCSIZE消息去除非客户区,同时修正客户区外边距
    3.. 处理WM_NCHITTEST消息即可重新支持拖动、调整大小、最大化、自动排列

另外无边框窗口有个令人头疼的问题:抖动闪烁(拖动左上角时,右下角不停抖动,最大化时窗口透明闪烁)许多菜鸟比如我(╥﹏╥)都会往消息处理和窗口样式方面去想,同时也会发现系统处理的窗口没有丝毫抖动。这其实是因为WM_NCPAINT和WM_NCCALCSIZE消息返回后DWM进行了默认处理,消除了抖动闪烁,同时还实现了阴影和自动排列动画。

微软Windows图形接口
DWM 桌面窗口管理器(英语:Desktop Window Manager,简称DWM)是微软操作系统Windows Vista的桌面管理工具。在Windows Vista正式推出之前,DWM原来叫作桌面合成引擎(Desktop Compositing Engine)。 现在计算机的图形芯片飞速发展,具有了很强的处理能力,现在它们主要用于游戏等方面。DWM的主要目标就是利用图形芯片的处理能力也给非游戏用户带来尽可能好的体验。因此DWM是基于DirectX,特别是Direct3D。更准确的说,DWM是直接建立在一个称为Milcore(Milcore也是WPF的内核)的层次之上。Milcore又建立在DirectX之上。最终是用Direct3D纹理来表示窗口内容和窗口框架。DWM/Milcore调用适当的Direct3D函数把所有的Direct3D纹理合成为最终的桌面。Vista或Win7桌面就可以理解为一个全屏幕的Direct3D应用程序。

所以处理窗口消息和样式是毫无作用的,这时就要引入Direct3D 12和DirectComposition

//#include"stdafx.h"
#include <Windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_CREATE:
	{
		//OnInit(hWnd);//加载Direct3D 12
	}
	break;
	case WM_PAINT:
	{
		//OnRender();//Direct3D 12渲染
	}
	break;
	case WM_NCCALCSIZE:
	{
		if (wParam)
		{
			NCCALCSIZE_PARAMS* lp = (LPNCCALCSIZE_PARAMS)lParam;
			if (IsZoomed(hWnd))//最大化时修正客户区外边距
			{
				lp->rgrc[0].left += 8;
				lp->rgrc[0].top += 8;
				lp->rgrc[0].right -= 8;
				lp->rgrc[0].bottom -= 8;
			}
		}
	}
	return 0;//去除非客户区
	case WM_NCHITTEST:
	{
		RECT rc; GetClientRect(hWnd, &rc);//客户区矩形
		POINT pt; GetCursorPos(&pt); ScreenToClient(hWnd, &pt);//鼠标位置
		if (!IsZoomed(hWnd))//最大化时不用调整大小
		{
			int x = 8;//边框宽度
			if (pt.x < rc.left + x)
			{
				if (pt.y < rc.top + x)return HTTOPLEFT;//左上角
				if (pt.y >= rc.bottom - x)return HTBOTTOMLEFT;//左下角
				return HTLEFT;//左边
			}
			if (pt.x >= rc.right - x)//坐标从0开始,所以使用>=
			{
				if (pt.y < rc.top + x)return HTTOPRIGHT;//右上角
				if (pt.y >= rc.bottom - x)return HTBOTTOMRIGHT;//右下角
				return HTRIGHT;//右边
			}
			if (pt.y < rc.top + x)return HTTOP;//上边
			if (pt.y >= rc.bottom - x)return HTBOTTOM;//下边
		}
		return HTCAPTION;//标题栏
	}
	break;
	case WM_DESTROY:
	{
		//OnDestroy();//卸载Direct3D 12
		PostQuitMessage(0);
	}
	return 0;
	}
	return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
	WNDCLASS wc = { 0 };
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wc.hInstance = hInstance;
	wc.lpfnWndProc = WndProc;
	wc.lpszClassName = L"yuehua";
	RegisterClass(&wc);

	HWND hWnd = CreateWindow(/*Ex(WS_EX_NOREDIRECTIONBITMAP, */wc.lpszClassName, nullptr, WS_OVERLAPPEDWINDOW, 183, 84, 1000, 600, nullptr, nullptr, hInstance, nullptr);
	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

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

WinMain.cpp实现了一个无边框窗口。注释中WS_EX_NOREDIRECTIONBITMAP样式阻止绘制重定向表面(客户区),改为使用Direct3D 12接管绘制。

二、实现半透明窗口

stdafx.h引用Windows.h头文件并定义了四个关于Direct3D 12的函数

#pragma once

#include <Windows.h>

void OnInit(HWND hWnd);
void OnUpdate();
void OnRender();
void OnDestroy();

@suhao
Copy link
Member Author

suhao commented Jun 17, 2022

借助DirectX的窗口半透明

https://zhuanlan.zhihu.com/p/402835148

很久以前,我刚开始接触编程的时候,面对的实际上是VB这类东东——因此,我很喜欢图形界面,它们就是我最早入门时的Hello World。后来入了C++坑,用C++也做了类似的事情,借助Win API,去创建它们。在此后很长的一段时间里我告别了它们,现在因为某些原因,需要再涉及一下,因此记录一下下。

窗口里面有个老生常谈的话题——半透明,或者是,部分的半透明。

可以很轻松的创建一个全部半透明的窗口:

  1. 指定WS_EX_LAYERED拓展样式
  2. 使用SetLayerWindow改变透明度。

就可以得到这样一个窗口:

半透明窗口

如果稍加了解,会发现实现上述效果有一些其余的方案——例如使用Direct2D或者其余的东东操作32位位图,再使用UpdateLayeredWindow 去重绘窗口,可以实现各种有意思的半透明窗口。

它们基于CPU完成大部分工作,性能堪比某聊天软件,猫猫叹气。

严格来说,在Windows Vista后,一些方面发生了转变——这个时代的Windows引入了一个新东东:Desktop Window Manager ,即一般意义上所说的桌面窗口管理器。这个名字好奇怪的,实际上,DWM改变了Windows下窗口的渲染方式,窗口上渲染的东东不再是直接渲染到上面,而是渲染到了一个特殊的surface(一些地方把它叫做redirection surface),操作系统会帮忙把它定向到GPU上对应的位置。 但是很遗憾的是,虽然现在我们知道了这个渲染最终是扔到了GPU上,但是这个特殊的surface是不透明的。因此,其实下面的第一步也是会去折腾这个surface。

如果,不是很想关心Win7以及xp用户,而整个窗口的GUI是自己画的,可以来做个很有意思的事情——将这个过程丢到GPU上。

Step0. Qt? WPF? Window.UI ...?

在这里希望描述的是它们的某些部分实现的一种方式,而不是使用他们。

因此不会涉及任何GUI框架(笑

Step1. 基本思想

实现这个东东的第一步是处理一些小问题,我们需要把渲染到窗口的这些东东交给我们自己的程序,而不是经过一个中介。

很简单,只需要指定WS_EX_NOREDIRECTIONBITMAP 拓展样式即可。这样一来窗口上的内容就得依靠我们自己来搞了。

窗口现在完全透明了——

客户区透明的窗口

与分层窗口不同的是,如果单击这些透明的地方,会发现窗口依然能有反应——也就是说,其实负责鼠标等等的东东是不知道窗口透明了的(趁它们不注意偷偷匿掉了默认绘制~),这减少了不少麻烦。

接下来我们需要一个神奇的东东: https://docs.microsoft.com/en-us/windows/win32/directcomp/directcomposition-portal

它提供了这个最终混合到桌面,呈现给用户的过程的交互部分,实际上,WPF这类框架也借助了这个东东。

我们会用它来完成最终的混合。但是它需要和D3D配合使用,因此:

  1. 创建一个交换链,因为窗口变成公公了,因此需要使用CreateSwapChainForComposition 来解决,这是D3D的。
  2. 类似 ,创建D3D的Surface 。
  3. 将Direct2D和它关联起来。
  4. 将D3D那边的东东交给DirectComposition 完成混合。

因为这个新的surface完全是在GPU上完成了Alpha Blend,且,不需要关心redirection surface(因为消失啦!),因此性能会提升不少(可以在拖动窗口等等的时候看到喵~,占用比UpdateLayeredWindow要高(尤其CPU占用明显小下来了))。

干!

Step2. 创建窗口

按照上面描述的,创建窗口是很简单的:

void CreateMainWindow()
{
    // Step0. 加载一些资源, 如图标和窗口的Class Name
    // Step1. RegisterClass
    // Step2. 创建窗口: 
    hWnd = CreateWindowEx(
        // 多指定了这个拓展样式
        WS_EX_NOREDIRECTIONBITMAP,
        winClassName,
        winTitle, 
        // HINT 改成WS_POPUP就可以绘制整个窗口(包括非客户区)!!
        WS_OVERLAPPEDWINDOW,
        x, y, w, h,
        nullptr, 
        nullptr, 
        hInst, 
        nullptr
    );
    // Step4. ShowWindow......
}

只需要多指定一个拓展样式WS_EX_NOREDIRECTIONBITMAP即可。这一步可以得到前面那样的背景全透明,但是又能接受鼠标键盘事件的空窗口。

Step3. 图形API有关部分

接着就可以上D2D和D3D了,在此之前,来为COM组件做个小东东:

// 检查hr, 失败抛出TE异常
template<
    typename TE>
requires (
    std::is_base_of_v<std::runtime_error, TE>
)
static inline void TryThrow(const unichar *active, HRESULT hr)
{
    if (FAILED(hr))
    {
        throw TE(
            std::format(
                "Failed to {}, HRESULT = {:#x}",
                active,
                static_cast<uint32_t>(hr)
            )
        );
    }
}

这样就能方便处理下HRESULT的问题啦。

首先,我们要包括一些Header和库:

// 注意下这个宏
// COM会定义它, 假如有想写#define interface __interface这类东东要处理掉
#undef interface
#include <Windows.h>
#include <wrl.h>
#include <dcomp.h>
#include <dwrite.h>
#include <d2d1_2.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_2helper.h>
// 然后, 包括下面这些library:
// d2d1.lib
// dwrite.lib
// dxgi.lib
// d3d11.lib
// dcomp.lib
// 可以用#pragma comment(lib, "d2d1.lib")来在MSVC下包含
// 亦或者你用CMake, 可以使用target_link_libraries
// 等等...

dwrite和本文的内容无关,但是你一定希望显示文本,因此加上它(笑。

然后,为了方便偷懒,我们用ComPtr来管理COM,例如对于接口IFuckWindows:

Microsoft::WRL::ComPtr<IFuckWindows> fuckWindows;

一切都是很轻松完成的呢。

那么就到正题了。

第零步,声明变量——(害

// D2D
Microsoft::WRL::ComPtr<ID2D1Device1> d2dDevice;
Microsoft::WRL::ComPtr<ID2D1Factory2> d2dFactory;
Microsoft::WRL::ComPtr<ID2D1DeviceContext> dc;
Microsoft::WRL::ComPtr<ID2D1Bitmap1> bitmap;
// Dwrite工厂接口
Microsoft::WRL::ComPtr<IDWriteFactory> dWriteFactory;
// D3D设备
Microsoft::WRL::ComPtr<ID3D11Device> d3dDevice;
// DXGI
Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice;
Microsoft::WRL::ComPtr<IDXGIFactory2> dxgiFactory;
Microsoft::WRL::ComPtr<IDXGISurface2> surface;
// 交换链
Microsoft::WRL::ComPtr<IDXGISwapChain1> swapChain;
// Direct Composition
Microsoft::WRL::ComPtr<IDCompositionDevice> dCompDevice;
Microsoft::WRL::ComPtr<IDCompositionTarget> composTarget;
Microsoft::WRL::ComPtr<IDCompositionVisual> dCompVisual;

第一步,准备好各个鬼东东的Factory、device等等:

// Direct2D
const D2D1_FACTORY_OPTIONS options =
{ 
#if _DEBUG
    D2D1_DEBUG_LEVEL_INFORMATION,
#else
    D2D1_DEBUG_LEVEL_NONE,
#endif // _DEBUG
};
TryThrow<InitException>("create Direct2D factory", D2D1CreateFactory(
    D2D1_FACTORY_TYPE_SINGLE_THREADED,
    options,
    d2dFactory.GetAddressOf()
));
// Direct3D 11
TryThrow<InitException>("create Direct3D 11 device", D3D11CreateDevice(
    nullptr,
    D3D_DRIVER_TYPE_HARDWARE,
    nullptr,
    D3D11_CREATE_DEVICE_BGRA_SUPPORT,
    nullptr, 0,
    D3D11_SDK_VERSION,
    d3dDevice.GetAddressOf(),
    nullptr,
    nullptr
));
// DXGI
TryThrow<InitException>("create DXGI device", d3dDevice.As(
    &dxgiDevice
));
TryThrow<InitException>("create DXGI factory", CreateDXGIFactory2(
    DXGI_CREATE_FACTORY_DEBUG,
    __uuidof(dxgiFactory),
    reinterpret_cast<void **>(dxgiFactory.GetAddressOf())
));
// DirectWrite
TryThrow<InitException>("create DirectWrite factory", DWriteCreateFactory(
    DWRITE_FACTORY_TYPE_SHARED,
    __uuidof(IDWriteFactory),
    reinterpret_cast<IUnknown **>(dWriteFactory.GetAddressOf())
));
// D2D设备
TryThrow<InitException>("create Direct2D device", d2dFactory->CreateDevice(
    dxgiDevice.Get(),
    d2dDevice.GetAddressOf()
));
// Direct Composition
TryThrow<InitException>("create DirectComposition device", DCompositionCreateDevice(
    dxgiDevice.Get(),
    __uuidof(dCompDevice),
    reinterpret_cast<void **>(dCompDevice.GetAddressOf())
));
// Direct Composition混合目标
TryThrow<InitException>("create DirectComposition target", dCompDevice->CreateTargetForHwnd(
    hWnd,
    // 因为我的窗口不是TOPMOST的, 因此是false
    // 如果加了TOPMOST样式要改成true哈
    false,
    composTarget.GetAddressOf()
));
// Visual
TryThrow<InitException>("create DirectComposition visual", dCompDevice->CreateVisual(
    dCompVisual.GetAddressOf()
));

看起来并没有什么好说的,但是一点点,可能你注意到了,我在DEBUG下尝试开启了DirectX的调试。

接着,我们要创建交换链。需要注意的是,因为窗口的redirect surface被丢掉了,因此不能从窗口创建,要用CreateSwapChainForComposition来解决。同时,也是由于这点,我们需要手动指定大小。用GetClientRect 就能知道窗口的客户区有多大啦,假设你知道了它是winw宽winh高,那么交换链创建就可以是这样子:

DXGI_SWAP_CHAIN_DESC1 description =
{
    // 窗口客户区宽度放到这里~
    // 即right - left
    .Width = static_cast<UINT>(winw),
    // 窗口客户区的高度放到这里~
    // 即bottom - top
    .Height = static_cast<UINT>(winh),
    // 自然的...透明的肯定是RGBA
    .Format = DXGI_FORMAT_B8G8R8A8_UNORM,
    .SampleDesc = {
        .Count = 1
    },
    .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
    .BufferCount = 2,
    .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
    // 混合模式
    // 这个模式带来的就是半透明的感觉
    .AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED
};
TryThrow<InitException>("create swapchain", dxgiFactory->CreateSwapChainForComposition(
    dxgiDevice.Get(),
    &description,
    nullptr,
    swapChain.GetAddressOf()
));

然后,创建surface:

TryThrow<InitException>("create DXGI surface", swapChain->GetBuffer(
    // Buffer index
    0,
    __uuidof(surface),
    reinterpret_cast<void **>(surface.GetAddressOf())
));

回到D2D,需要准备一个DC:

TryThrow<InitException>("create Direct2D device context", d2dDevice->CreateDeviceContext(
    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
    dc.GetAddressOf()
));

然后给它绑定一张位图,类似GDI那样:

D2D1_BITMAP_PROPERTIES1 properties = 
{
    .pixelFormat = {
        .format = DXGI_FORMAT_B8G8R8A8_UNORM,
        .alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED,
    },
    .bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET 
                    | D2D1_BITMAP_OPTIONS_CANNOT_DRAW
};
TryThrow<InitException>("create Direct2D bitmap for DXGI", dc->CreateBitmapFromDxgiSurface(
    surface.Get(),
    properties,
    bitmap.GetAddressOf()
));
// 绑定到dc
dc->SetTarget(bitmap.Get());

现在就差不多了,最后,告诉DirectComposition要显示什么内容 ,以及要混合什么:

dCompVisual->SetContent(swapChain.Get());
composTarget->SetRoot(dCompVisual.Get());

一切完成。来画点什么东东验证一下它吧!

Step4. 画一些色块

画色块有了这些就是很简单的啦,因为ID2D1DeviceContext继承自ID2D1RenderTarget,因此可以用dc->xxxx的方式来画画。

首先:

dc->BeginDraw();

可以清空一下绘图区:

dc->Clear();

然后就可以开始啦,例如:

Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush;
// 刷子最好复用, 这里作为例子没有处理, 要记得喵
TryThrow<GraphException>("create solid brush", dc->CreateSolidColorBrush(
    颜色,
    brush.GetAddressOf()
));
//
D2D_RECT_F rc =
{
    .left = 1,
    .top = 2,
    .right = 100,
    .bottom = 200
};
//
dc->FillRectangle(rc, brush.Get());

就可以绘制一个纯色的矩形。

绘制结束用EndDraw停止,D2D会把绘图命令全部提交上去。然后,通知交换链提交,让DirectComposition完成混合即可:

// TODO 记得查一下EndDraw的用法喵
// 这里其实是不完全正确的
// 因为...诶嘿有些图形API画到一半居然会丢设备,然后要重新创建资源...
TryThrow<GraphException>("draw GUI", dc->EndDraw());
swapChain->Present(1, 0);
// 提交, 让DirectComposition去完成混合
TryThrow<GraphException>("commit drawcall", dCompDevice->Commit());

我画了好些颜色的色块:

image

Step5. 改变窗口大小

最后一步,我们要处理窗口大小改变的问题。很简单,只需要调用交换链的ResizeBuffers即可。但是——

按照D3D文档的描述,这一步之前必须销毁和交换链挂钩的资源,在这里有:surface、dc、bitmap,因此,第一步,我们手动释放它们:

dc.Reset();
bitmap.Reset();
surface.Reset();

接着,调用ResizeBuffers:

TryThrow<GraphException>("resize swapchain buffer", swapChain->ResizeBuffers(
    0,
    // 改变后
    // 窗口的客户区大小为neww*newh
    static_cast<UINT>(neww),
    static_cast<UINT>(newh),
    DXGI_FORMAT_UNKNOWN,
    0
));

然后重新创建这些资源:

// D2D设备上下文环境
TryThrow<GraphException>("recreate Direct2D device context", d2dDevice->CreateDeviceContext(
    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
    dc.GetAddressOf()
));
// 表面
TryThrow<GraphException>("recreate DXGI surface", swapChain->GetBuffer(
    0, // index
    __uuidof(surface),
    reinterpret_cast<void **>(surface.GetAddressOf())
));
// D2D位图, 用于surface
D2D1_BITMAP_PROPERTIES1 properties =
{
    .pixelFormat = {
        .format = DXGI_FORMAT_B8G8R8A8_UNORM,
        .alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED,
    },
    .bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET
                    | D2D1_BITMAP_OPTIONS_CANNOT_DRAW
};
TryThrow<GraphException>("recreate Direct2D bitmap for DXGI", dc->CreateBitmapFromDxgiSurface(
    surface.Get(),
    properties,
    bitmap.GetAddressOf()
));

最后,因为更改了它,因此要重新指定一次它们:

dc->SetTarget(bitmap.Get());
dCompVisual->SetContent(swapChain.Get());
composTarget->SetRoot(dCompVisual.Get());

这样,一切都ok啦。

image

@suhao
Copy link
Member Author

suhao commented Jun 17, 2022

无边框窗口:支持调整大小和自动排列,去除非客户区,解决窗口闪烁抖动,Direct3D 12实现阴影以及半透明

https://blog.csdn.net/g3030/article/details/104617476

代码思路

1.首先是处理WM_NCHITTEST消息:支持调整大小和自动排列。

2.其次是WM_NCCALCSIZE消息:返回0,去除非客户区。
wParam为真时,窗口被调整,需要重新计算大小,返回WVR_REDRAW,放弃复制原客户区,绘制新客户区。(拖影)
若为最大化窗口,那窗口位置为{-8,-8,x+8,y+8},窗口外边距超出屏幕,所以修正NCCALCSIZE_PARAMS结构。(双屏)
问题:最大化和自动排列伴有透明闪烁,调整大小时窗口强烈抖动。

3.再然后经过大量尝试,解决问题:
处理WM_ERASEBKGND消息:返回true,防止背景色重绘,调整大小时窗口不再抖动。
处理WM_NCPAINT消息:返回0,防止透明闪烁。
处理WM_NCACTIVATE消息:返回0,防止处理WM_NCPAINT消息时引入的默认边框。
在WM_CREATE消息里:设置窗口区域,防止处理WM_NCPAINT消息时引入的圆角和黑色背景和小几率抖动。

#include <Windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_CREATE:
	{
		HRGN rgn = CreateRectRgn(0, 0, 1000000, 1000000);
		SetWindowRgn(hWnd, rgn, 0);//设置窗口区域,防止圆角和黑色背景和小几率抖动
		DeleteObject(rgn);
		break;
	}
	case WM_NCPAINT:return 0;//防止透明闪烁
	case WM_NCACTIVATE:return 0;//防止默认边框
	case WM_ERASEBKGND:return true;//防止背景重绘导致的抖动
	case WM_NCCALCSIZE:
	{
		if (wParam)
		{
			NCCALCSIZE_PARAMS* lp = (LPNCCALCSIZE_PARAMS)lParam;
			if (IsZoomed(hWnd))//最大化时修正窗口外边距
			{
				lp->rgrc[0].left += 8;
				lp->rgrc[0].top += 8;
				lp->rgrc[0].right -= 8;
				lp->rgrc[0].bottom -= 8;
			}
			return WVR_REDRAW;//防止BitBlt复制导致的拖影和抖动
		}
		return 0;
	}
	case WM_NCHITTEST:
	{
		RECT rc; GetClientRect(hWnd, &rc);//客户区矩形
		POINT pt; GetCursorPos(&pt); ScreenToClient(hWnd, &pt);//鼠标位置
		if (!IsZoomed(hWnd))//最大化时不用调整大小
		{
			int x = 8;//边框宽度
			if (pt.x < rc.left + x)
			{
				if (pt.y < rc.top + x)return HTTOPLEFT;//左上角
				if (pt.y >= rc.bottom - x)return HTBOTTOMLEFT;//左下角
				return HTLEFT;//左边
			}
			if (pt.x >= rc.right - x)//坐标从0开始,所以使用>=
			{
				if (pt.y < rc.top + x)return HTTOPRIGHT;//右上角
				if (pt.y >= rc.bottom - x)return HTBOTTOMRIGHT;//右下角
				return HTRIGHT;//右边
			}
			if (pt.y < rc.top + x)return HTTOP;//上边
			if (pt.y >= rc.bottom - x)return HTBOTTOM;//下边
		}
		return HTCAPTION;//标题栏
	}
	case WM_DESTROY:PostQuitMessage(0);
	}
	return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
	WNDCLASS wc = { 0 };
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wc.hInstance = hInstance;
	wc.lpfnWndProc = WndProc;
	wc.lpszClassName = L"music";
	RegisterClass(&wc);

	HWND hWnd = CreateWindow(wc.lpszClassName, nullptr, WS_OVERLAPPEDWINDOW, 183, 84, 1000, 600, nullptr, nullptr, hInstance, nullptr);
	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

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

实现阴影以及半透明

用Windows.UI.Composition实现阴影以及半透明并解决初始透明。
这时用FillRect处理设备上下文又会开始抖动,所以不要用gdi了
先空着,以后写
再更,dwm的问题,用direct3d 12的窗口不会闪烁。
再更,Windows.UI.Composition也会带来窗口闪烁,所以直接用direct3d 12贴个阴影图好了,窗口透明又不知道如何实现了
这篇文章是个大坑,等我慢慢填吧!
还有一件事,窗口抖动和闪烁都是绘制引起的,不是位置计算不正确引起的.(验证过)
本该绘制的区域估计是被dwm填上了透明色,或者干脆没来的及绘制.

下面先贴一段代码,还未实现半透明,等以后更。(用winrt代替了WRL)

用Direct3D 12的无边框窗口不会闪烁抖动

#include <windows.h>

#include <d3d12.h>
#include "d3dx12.h"
#include <dxgi1_6.h>
#include <winrt/base.h>
#include <D3Dcompiler.h>
#include <DirectXMath.h>

#pragma comment(lib,"dxgi.lib")
#pragma comment(lib,"d3d12.lib")
#pragma comment(lib,"windowsapp.lib")
#pragma comment(lib,"d3dcompiler.lib")

using namespace winrt;
using namespace DirectX;

static const UINT FrameCount = 2;

struct Vertex
{
	XMFLOAT3 position;
	XMFLOAT4 color;
};

// Pipeline objects.
CD3DX12_VIEWPORT m_viewport(0.0f, 0.0f, static_cast<float>(1000), static_cast<float>(600));
CD3DX12_RECT m_scissorRect(0, 0, static_cast<LONG>(1000), static_cast<LONG>(600));
com_ptr<IDXGISwapChain3> m_swapChain;
com_ptr<ID3D12Device> m_device;
com_ptr<ID3D12Resource> m_renderTargets[FrameCount];
com_ptr<ID3D12CommandAllocator> m_commandAllocator;
com_ptr<ID3D12CommandQueue> m_commandQueue;
com_ptr<ID3D12RootSignature> m_rootSignature;
com_ptr<ID3D12DescriptorHeap> m_rtvHeap;
com_ptr<ID3D12PipelineState> m_pipelineState;
com_ptr<ID3D12GraphicsCommandList> m_commandList;
UINT m_rtvDescriptorSize;

// App resources.
com_ptr<ID3D12Resource> m_vertexBuffer;
D3D12_VERTEX_BUFFER_VIEW m_vertexBufferView;

// Synchronization objects.
UINT m_frameIndex = (0);
HANDLE m_fenceEvent;
com_ptr<ID3D12Fence> m_fence;
UINT64 m_fenceValue;

float m_aspectRatio = static_cast<float>(1000) / static_cast<float>(600);

void OnRender();
void OnDestroy();
void LoadPipeline(HWND hWnd);
void LoadAssets();
void PopulateCommandList();
void WaitForPreviousFrame();


// Load the rendering pipeline dependencies.
void LoadPipeline(HWND hWnd)
{

	com_ptr<IDXGIFactory4> factory;
	check_hresult(CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, __uuidof(factory), factory.put_void()));

	check_hresult(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, __uuidof(m_device), m_device.put_void()));

	// Describe and create the command queue.
	D3D12_COMMAND_QUEUE_DESC queueDesc = {};
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

	check_hresult(m_device->CreateCommandQueue(&queueDesc, __uuidof(m_commandQueue), m_commandQueue.put_void()));

	// Describe and create the swap chain.
	DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
	swapChainDesc.BufferCount = FrameCount;
	swapChainDesc.Width = 1000;
	swapChainDesc.Height = 600;
	swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
	swapChainDesc.SampleDesc.Count = 1;

	com_ptr<IDXGISwapChain1> swapChain;
	check_hresult(factory->CreateSwapChainForHwnd(
		m_commandQueue.get(),        // Swap chain needs the queue so that it can force a flush on it.
		hWnd,
		&swapChainDesc,
		nullptr,
		nullptr,
		swapChain.put()
	));

	// This sample does not support fullscreen transitions.
	check_hresult(factory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER));

	m_swapChain = swapChain.as<IDXGISwapChain3>();
	m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();

	// Create descriptor heaps.
	{
		// Describe and create a render target view (RTV) descriptor heap.
		D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
		rtvHeapDesc.NumDescriptors = FrameCount;
		rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
		rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
		check_hresult(m_device->CreateDescriptorHeap(&rtvHeapDesc, __uuidof(m_rtvHeap), m_rtvHeap.put_void()));

		m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
	}

	// Create frame resources.
	{
		CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());

		// Create a RTV for each frame.
		for (UINT n = 0; n < FrameCount; n++)
		{
			check_hresult(m_swapChain->GetBuffer(n, __uuidof(m_renderTargets), m_renderTargets[n].put_void()));
			m_device->CreateRenderTargetView(m_renderTargets[n].get(), nullptr, rtvHandle);
			rtvHandle.Offset(1, m_rtvDescriptorSize);
		}
	}

	check_hresult(m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(m_commandAllocator), m_commandAllocator.put_void()));
}

// Load the sample assets.
void LoadAssets()
{
	// Create an empty root signature.
	{
		CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
		rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

		com_ptr<ID3DBlob> signature;
		com_ptr<ID3DBlob> error;
		check_hresult(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, signature.put(), error.put()));
		check_hresult(m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), __uuidof(m_rootSignature), m_rootSignature.put_void()));
	}

	// Create the pipeline state, which includes compiling and loading shaders.
	{
		com_ptr<ID3DBlob> vertexShader;
		com_ptr<ID3DBlob> pixelShader;

#if defined(_DEBUG)
		// Enable better shader debugging with the graphics debugging tools.
		UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
		UINT compileFlags = 0;
#endif

		check_hresult(D3DCompileFromFile((L"shaders.hlsl"), nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, vertexShader.put(), nullptr));
		check_hresult(D3DCompileFromFile((L"shaders.hlsl"), nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, pixelShader.put(), nullptr));

		// Define the vertex input layout.
		D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
		};

		// Describe and create the graphics pipeline state object (PSO).
		D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
		psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
		psoDesc.pRootSignature = m_rootSignature.get();
		psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.get());
		psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.get());
		psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
		psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
		psoDesc.DepthStencilState.DepthEnable = FALSE;
		psoDesc.DepthStencilState.StencilEnable = FALSE;
		psoDesc.SampleMask = UINT_MAX;
		psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
		psoDesc.NumRenderTargets = 1;
		psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
		psoDesc.SampleDesc.Count = 1;
		check_hresult(m_device->CreateGraphicsPipelineState(&psoDesc, __uuidof(m_pipelineState), m_pipelineState.put_void()));
	}

	// Create the command list.
	check_hresult(m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator.get(), m_pipelineState.get(), __uuidof(m_commandList), m_commandList.put_void()));

	// Command lists are created in the recording state, but there is nothing
	// to record yet. The main loop expects it to be closed, so close it now.
	check_hresult(m_commandList->Close());

	// Create the vertex buffer.
	{
		// Define the geometry for a triangle.
		Vertex triangleVertices[] =
		{
			{ { 0.0f, 0.25f * 1, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
			{ { 0.25f, -0.25f * 1, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
			{ { -0.25f, -0.25f * 1, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
		};

		const UINT vertexBufferSize = sizeof(triangleVertices);

		// Note: using upload heaps to transfer static data like vert buffers is not 
		// recommended. Every time the GPU needs it, the upload heap will be marshalled 
		// over. Please read up on Default Heap usage. An upload heap is used here for 
		// code simplicity and because there are very few verts to actually transfer.
		check_hresult(m_device->CreateCommittedResource(
			&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
			D3D12_HEAP_FLAG_NONE,
			&CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize),
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			__uuidof(m_vertexBuffer), m_vertexBuffer.put_void()));

		// Copy the triangle data to the vertex buffer.
		UINT8* pVertexDataBegin;
		CD3DX12_RANGE readRange(0, 0);        // We do not intend to read from this resource on the CPU.
		check_hresult(m_vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin)));
		memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
		m_vertexBuffer->Unmap(0, nullptr);

		// Initialize the vertex buffer view.
		m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
		m_vertexBufferView.StrideInBytes = sizeof(Vertex);
		m_vertexBufferView.SizeInBytes = vertexBufferSize;
	}

	// Create synchronization objects and wait until assets have been uploaded to the GPU.
	{
		check_hresult(m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(m_fence), m_fence.put_void()));
		m_fenceValue = 1;

		// Create an event handle to use for frame synchronization.
		m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
		if (m_fenceEvent == nullptr)
		{
			check_hresult(HRESULT_FROM_WIN32(GetLastError()));
		}

		// Wait for the command list to execute; we are reusing the same command 
		// list in our main loop but for now, we just want to wait for setup to 
		// complete before continuing.
		WaitForPreviousFrame();
	}
}

// Update frame-based values.
void OnUpdate()
{
}

// Render the scene.
void OnRender()
{
	// Record all the commands we need to render the scene into the command list.
	PopulateCommandList();

	// Execute the command list.
	ID3D12CommandList* ppCommandLists[] = { m_commandList.get() };
	m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

	// Present the frame.
	check_hresult(m_swapChain->Present(1, 0));

	WaitForPreviousFrame();
}

void OnDestroy()
{
	// Ensure that the GPU is no longer referencing resources that are about to be
	// cleaned up by the destructor.
	WaitForPreviousFrame();

	CloseHandle(m_fenceEvent);
}

void PopulateCommandList()
{
	// Command list allocators can only be reset when the associated 
	// command lists have finished execution on the GPU; apps should use 
	// fences to determine GPU execution progress.
	check_hresult(m_commandAllocator->Reset());

	// However, when ExecuteCommandList() is called on a particular command 
	// list, that command list can then be reset at any time and must be before 
	// re-recording.
	check_hresult(m_commandList->Reset(m_commandAllocator.get(), m_pipelineState.get()));

	// Set necessary state.
	m_commandList->SetGraphicsRootSignature(m_rootSignature.get());
	m_commandList->RSSetViewports(1, &m_viewport);
	m_commandList->RSSetScissorRects(1, &m_scissorRect);

	// Indicate that the back buffer will be used as a render target.
	m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

	CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
	m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

	// Record commands.
	const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
	m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
	m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
	m_commandList->DrawInstanced(3, 1, 0, 0);

	// Indicate that the back buffer will now be used to present.
	m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

	check_hresult(m_commandList->Close());
}

void WaitForPreviousFrame()
{
	// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
	// This is code implemented as such for simplicity. The D3D12HelloFrameBuffering
	// sample illustrates how to use fences for efficient resource usage and to
	// maximize GPU utilization.

	// Signal and increment the fence value.
	const UINT64 fence = m_fenceValue;
	check_hresult(m_commandQueue->Signal(m_fence.get(), fence));
	m_fenceValue++;

	// Wait until the previous frame is finished.
	if (m_fence->GetCompletedValue() < fence)
	{
		check_hresult(m_fence->SetEventOnCompletion(fence, m_fenceEvent));
		WaitForSingleObject(m_fenceEvent, INFINITE);
	}

	m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_CREATE:
	{
		HRGN rgn = CreateRectRgn(0, 0, 1000000, 1000000);
		SetWindowRgn(hWnd, rgn, 0);//设置窗口区域,防止圆角和黑色背景和小几率抖动
		DeleteObject(rgn);
		break;
	}
	case WM_PAINT:
	{
		OnRender();
		break;
	}
	case WM_NCPAINT:return 0;//防止透明闪烁
	case WM_NCACTIVATE:return 0;//防止默认边框
	case WM_ERASEBKGND:return true;//防止背景重绘导致的抖动
	case WM_NCCALCSIZE:
	{
		if (wParam)
		{
			NCCALCSIZE_PARAMS* lp = (LPNCCALCSIZE_PARAMS)lParam;
			if (IsZoomed(hWnd))//最大化时修正窗口外边距
			{
				lp->rgrc[0].left += 8;
				lp->rgrc[0].top += 8;
				lp->rgrc[0].right -= 8;
				lp->rgrc[0].bottom -= 8;
			}
			return WVR_REDRAW;//防止BitBlt复制导致的拖影和抖动,在return后系统会默认绘制背景,无法控制
		}
		return 0;
	}
	case WM_NCHITTEST:
	{
		RECT rc; GetClientRect(hWnd, &rc);//客户区矩形
		POINT pt; GetCursorPos(&pt); ScreenToClient(hWnd, &pt);//鼠标位置
		if (!IsZoomed(hWnd))//最大化时不用调整大小
		{
			int x = 8;//边框宽度
			if (pt.x < rc.left + x)
			{
				if (pt.y < rc.top + x)return HTTOPLEFT;//左上角
				if (pt.y >= rc.bottom - x)return HTBOTTOMLEFT;//左下角
				return HTLEFT;//左边
			}
			if (pt.x >= rc.right - x)//坐标从0开始,所以使用>=
			{
				if (pt.y < rc.top + x)return HTTOPRIGHT;//右上角
				if (pt.y >= rc.bottom - x)return HTBOTTOMRIGHT;//右下角
				return HTRIGHT;//右边
			}
			if (pt.y < rc.top + x)return HTTOP;//上边
			if (pt.y >= rc.bottom - x)return HTBOTTOM;//下边
		}
		return HTCAPTION;//标题栏
	}
	case WM_DESTROY:PostQuitMessage(0);
	}
	return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
	WNDCLASS wc = { 0 };
	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wc.hInstance = hInstance;
	wc.lpfnWndProc = WndProc;
	wc.lpszClassName = L"music";
	RegisterClass(&wc);

	HWND hWnd = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP, wc.lpszClassName, nullptr, WS_OVERLAPPEDWINDOW, 183, 84, 1000, 600, nullptr, nullptr, hInstance, nullptr);
	LoadPipeline(hWnd);
	LoadAssets();
	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

	MSG msg;
	while (GetMessage(&msg, nullptr, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	OnDestroy();
	return 0;
}```

不要被DirectComposition页面的
[Windows10请使用Windows.ui.Compsition]
骗了,UWP才用它。
Direct3D 12不支持Windows.ui.Compsition
Direct3D 12支持DirectComposition

@suhao
Copy link
Member Author

suhao commented Jun 17, 2022

Windows下使用Direct3D和OpenGl创建带Alpha透明的窗口

https://github.com/Hxmg/Direct3DandOpenGLTransparentWindow

Direct2D

OpenGL

@kihlh
Copy link

kihlh commented Oct 16, 2023

给力嘿

@a442509097
Copy link

感谢, 到时候再细看, 焦点变化时, 屏幕会闪烁或抖动, 特别是用Chrome全屏视频时, 切换到自己的应用时, 视频画面老是会闪一下, GDI上暂时没找到好的办法, 用DX好像是可以的...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants