C++ 使用子窗口(控件)创建分层窗口的策略

C++ 使用子窗口(控件)创建分层窗口的策略,c++,winapi,alpha,layered,C++,Winapi,Alpha,Layered,我想创建一个形状不规则/蒙皮不规则的窗口(目前仅使用圆角、alpha混合角)。创建顶级窗口后,我将处理WM_CREATE消息,并执行以下操作: 创建一个兼容的内存DC 创建与窗口DC兼容的DIB节 在内存DC中选择DIB 画我的背景 应用alpha通道和预乘RGB值 调用UpdateLayeredWindow() 稍后,我计划通过设置alpha通道并对相关位进行预乘来实现边缘的圆角 现在,如果我在我的窗口中创建一个按钮,它将不会自己渲染。我知道为什么,我正试图想出一个优雅的解决方案来制作我的自定

我想创建一个形状不规则/蒙皮不规则的窗口(目前仅使用圆角、alpha混合角)。创建顶级窗口后,我将处理WM_CREATE消息,并执行以下操作:

  • 创建一个兼容的内存DC
  • 创建与窗口DC兼容的DIB节
  • 在内存DC中选择DIB
  • 画我的背景
  • 应用alpha通道和预乘RGB值
  • 调用UpdateLayeredWindow()
  • 稍后,我计划通过设置alpha通道并对相关位进行预乘来实现边缘的圆角

    现在,如果我在我的窗口中创建一个按钮,它将不会自己渲染。我知道为什么,我正试图想出一个优雅的解决方案来制作我的自定义控件(子窗口)

    我最初的方法是首先放弃使用子窗口,只让顶层窗口完成所有的绘图以及输入处理、命中测试等。我发现这是一种乏味的方式,相反,我想让Windows为我处理这一切

    现在,我知道如果我创建一个子窗口,它当然会正常运行(例如,对用户输入作出反应),我想利用它。我计划通常使用CreateWindowEx()创建子窗口(自定义控件),以便它们获得窗口句柄,并接收窗口消息,而不必担心手动传递它们

    不知何故,我需要把这些窗口画出来,在我看来,唯一可行的方法就是通过画整个顶层窗口的例行程序。我需要发明某种逻辑,让顶层窗口的绘制例程在必要时绘制我的子窗口。据我所知,UpdateLayeredWindow()函数需要重新绘制整个窗口

    例如,让子控件渲染发送到顶层窗口的绘制例程的自身图像是否明智?例如,子窗口向顶层窗口发送用户WM,并将指向其渲染位图的指针作为WPARAM传递,将指向定义其位置和大小的结构的指针作为LPRAM传递

    还有更好的主意吗?这有什么意义吗

    谢谢, Eirik

    引用:

    WM_PAINT消息由系统生成,不应由应用程序发送。要强制窗口绘制到特定的设备上下文中,请使用WM_PRINT或WM_PRINTCLIENT消息。请注意,这需要目标窗口支持WM_PRINTCLIENT消息。大多数常用控件支持WM_PRINTCLIENT消息

    因此,看起来您可以在步骤5和步骤6之间使用内存DC遍历所有子窗口并发送它们

    它看起来像这样:

    static BOOL CALLBACK MyPaintCallback(HWND hChild,LPARAM lParam) {
        SendMessage(hChild,WM_PRINT,(WPARAM)lParam,(LPARAM)(PRF_CHECKVISIBLE|PRF_CHILDREN|PRF_CLIENT));
        return TRUE;
    }
    
    void MyPaintMethod(HWND hWnd) {
        //steps 1-5
    
        EnumChildWindows(hWnd,MyPaintCallback,MyMemoryDC);
    
        //step 6
    }
    
    CPaintDC dc(this);
    OnDraw(&dc);
    
    引述:

    WM_PAINT消息由系统生成,不应由应用程序发送。要强制窗口绘制到特定的设备上下文中,请使用WM_PRINT或WM_PRINTCLIENT消息。请注意,这需要目标窗口支持WM_PRINTCLIENT消息。大多数常用控件支持WM_PRINTCLIENT消息

    因此,看起来您可以在步骤5和步骤6之间使用内存DC遍历所有子窗口并发送它们

    它看起来像这样:

    static BOOL CALLBACK MyPaintCallback(HWND hChild,LPARAM lParam) {
        SendMessage(hChild,WM_PRINT,(WPARAM)lParam,(LPARAM)(PRF_CHECKVISIBLE|PRF_CHILDREN|PRF_CLIENT));
        return TRUE;
    }
    
    void MyPaintMethod(HWND hWnd) {
        //steps 1-5
    
        EnumChildWindows(hWnd,MyPaintCallback,MyMemoryDC);
    
        //step 6
    }
    
    CPaintDC dc(this);
    OnDraw(&dc);
    

    我想我会选择这个解决方案:

    顶层窗口 顶层窗口维护两个位图。一个是显示的窗口,另一个没有呈现任何子控件。后者只需要在窗口大小改变时重新绘制。窗口将有一个消息处理程序,用于在显示的位图上呈现子控件。消息处理程序将期望一个指向包含子控件的DIB或实际位(目前不确定哪一位最好)的指针作为WPARAM,以及一个指向包含子控件应作为LPRAM绘制的矩形的结构的指针。在AlphaBlend()调用将子控件位图渲染到显示的位图表面之前,将调用BitBlt()清除底层表面(这是其他位图的位置)

    每当调整父窗口的大小或出于某种原因需要重新绘制其子窗口时,父窗口将调用EnumChildWindows。当然,这里可能会强制执行某种无效机制,以减少子控件的不必要渲染。不过,我不确定提速是否值得

    子窗口 创建子控件实例时,将创建与顶级窗口的位图兼容的内部位图。子对象将自己呈现到该内部位图中,每当需要重画时,它都会通过SendMessage()函数通知其父窗口,并将指向其位图的指针作为WPARAM传递,将RECT作为定义其位置和尺寸的LPRAM传递。如果父窗口需要重画,它会向其所有子窗口发出一条消息,请求它们的位图。当孩子们决定需要重新画自己的时候,他们会用他们通常会发出的同样的信息来回应


    Eirik

    我想我会选择这个解决方案:

    顶层窗口 顶层窗口维护两个位图。一个是显示的窗口,另一个没有呈现任何子控件。后者只需要在窗口大小改变时重新绘制。窗口将有一个消息处理程序,用于在显示的位图上呈现子控件。消息处理程序将期望一个指向包含子控件的DIB或实际位(目前不确定哪一位最好)的指针作为WPARAM,以及一个指向包含子控件应作为LPRAM绘制的矩形的结构的指针。在调用AlphaBlend()渲染子控件位图之前,将调用BitBlt()清除底层表面(这是其他位图的位置)
    BEGIN_MESSAGE_MAP(MyButton, CButton)
        ...other messages
        ON_MESSAGE(WM_PRINT, OnPrint)
    END_MESSAGE_MAP()
    
    LRESULT MyButton::OnPrint(WPARAM wParam, LPARAM lParam) {
        CDC* dc = CDC::FromHandle((HDC)wParam);
        OnDraw(dc);
        return 0;
    }
    
    LRESULT MyCWnd::OnPrint(WPARAM wParam, LPARAM lParam) {
        CDC* dc = CDC::FromHandle((HDC)wParam);
        // Do my own drawing using custom drawing
        OnDraw(dc);
    
        // And get the default handler to draw children
        return Default();
    }
    
    // This method is not very efficient but the CBitmap class doens't
    // give me a choice I have to copy all the pixel data out, process it and set it back again.
    // For performance I recommend using another bitmap class
    //
    // This method makes every pixel have an opacity of 255 (fully opaque).
    void Vg2pImageHeaderRibbon::ClearBackgroundAndPrepForPerPixelTransparency( 
        CDC& dc, const BITMAP& raw_bitmap, int bytes, char* bits, CBitmap& dc_buffer 
    ) {
        CRect rect;
        GetClientRect(&rect);
        dc.FillSolidRect(0, 0, rect.Width(), rect.Height(), RGB(0,0,0));
        dc_buffer.GetBitmapBits(bytes, bits);
        UINT* pixels = reinterpret_cast<UINT*>(bits);
        for (int c = 0; c < raw_bitmap.bmWidth * raw_bitmap.bmHeight; c++ ){
            pixels[c] |= 0xff000000;
        }
        dc_buffer.SetBitmapBits(bytes, bits);
    }
    
    // This method is not very efficient but the CBitmap class doens't
    // give me a choice I have to copy all the pixel data out, process it and set it back again.
    // For performance I recommend using another bitmap class
    //
    // This method modifies the opacity value because we know GDI drawing always sets
    // the opacity to 0 we find all pixels that have been drawn on since we called 
    // For performance I recommend using another bitmap class such as the IcfMfcRasterImage
    // ClearBackgroundAndPrepForPerPixelTransparency. Anything that has been drawn on will get an 
    // opacity of 255 and all untouched pixels will get an opacity of 100.
    void Vg2pImageHeaderRibbon::CorrectPerPixelAlpha( 
        CDC& dc, const BITMAP& raw_bitmap, int bytes, char* bits, CBitmap& dc_buffer 
    ) {
        const unsigned char AlphaForBackground = 100; // (0 - 255)
        const int AlphaForBackgroundWord = AlphaForBackground << 24;
        dc_buffer.GetBitmapBits(bytes, bits);
        UINT* pixels = reinterpret_cast<UINT*>(bits);
        for (int c = 0; c < raw_bitmap.bmWidth * raw_bitmap.bmHeight; c++ ){
            if ((pixels[c] & 0xff000000) == 0) {
                pixels[c] |= 0xff000000;
            } else {
                pixels[c] = (pixels[c] & 0x00ffffff) | AlphaForBackgroundWord;
            }
        }
        dc_buffer.SetBitmapBits(bytes, bits);
    }