1

我想创建一个不规则形状/蒙皮的窗口(现在只有圆形的、alpha 混合的角)。创建顶级窗口后,我正在处理 WM_CREATE 消息并执行以下操作:

  1. 创建兼容的内存 DC
  2. 创建一个兼容window DC的DIB部分
  3. 选择DIB到内存DC
  4. 画我的背景
  5. 应用 Alpha 通道并预乘 RGB 值
  6. 调用 UpdateLayeredWindow()

稍后,我计划通过设置 alpha 通道并预乘相关位来实现边缘的舍入。

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

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

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

不知何故,我需要绘制这些窗口,正如我所见,唯一可能的方法是使用绘制整个顶层窗口的例程。我需要发明某种逻辑来让顶层窗口的绘制例程在必要时绘制我的子窗口。据我了解 UpdateLayeredWindow() 函数需要重绘整个窗口。

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

有更好的想法吗?这有任何意义吗?

谢谢,埃里克

4

3 回答 3

2

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

顶层窗口

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

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

子窗口

在创建子控件实例时,会创建一个与顶层窗口兼容的内部位图。子进程将自己渲染到这个内部位图中,并且每当需要重绘时,它都会通过 SendMessage() 函数通知其父窗口,将指向其位图的指针作为 WPARAM 传递,并将 RECT 作为 LPARAM 定义其位置和尺寸。如果父级需要重绘,它会向其所有子窗口发出一条消息,请求它们的位图。然后,孩子们将用他们通常在决定需要重绘自己时发送的相同消息进行响应。

埃里克

于 2011-11-03T18:00:08.100 回答
2

我试图做一件非常相似的事情。从阅读这个和其他搜索网络。它将推荐的​​用于绘制 CWnd(或 HWND)的机制和它的孩子连接到您自己的 CDC(或 HDC)上是使用打印 API。

CWnd 有方法 Print 和 PrintClient 并正确发送 WM_PRINT。还有 Win32 方法:PrintWindow。

起初我很难让它工作,但我最终得到了正确的方法和参数。对我有用的代码是:

void Vg2pImageHeaderRibbon::Update() {
    // Get dimensions
    CRect window_rect;
    GetWindowRect(&window_rect);

    // Make mem DC + mem  bitmap
    CDC* screen_dc = GetDC(); // Get DC for the hwnd
    CDC dc;
    dc.CreateCompatibleDC(screen_dc);
    CBitmap dc_buffer;
    dc_buffer.CreateCompatibleBitmap(screen_dc, window_rect.Width(), window_rect.Height());
    auto hBmpOld = dc.SelectObject(dc_buffer);

    // Create a buffer for manipulating the raw bitmap pixels (per-pixel alpha).
    // Used by ClearBackgroundAndPrepForPerPixelTransparency and CorrectPerPixelAlpha.
    BITMAP raw_bitmap;
    dc_buffer.GetBitmap(&raw_bitmap);
    int bytes =  raw_bitmap.bmWidthBytes * raw_bitmap.bmHeight;
    std::unique_ptr<char> bits(new char[bytes]);

    // Clears the background black (I want semi-transparent black background).
    ClearBackgroundAndPrepForPerPixelTransparency(dc, raw_bitmap, bytes, bits.get(), dc_buffer);

    // To get the window and it's children to draw using print command
    Print(&dc, PRF_CLIENT | PRF_CHILDREN | PRF_OWNED);

    CorrectPerPixelAlpha(dc, raw_bitmap, bytes, bits.get(), dc_buffer);

    // Call UpdateLayeredWindow
    BLENDFUNCTION blend = {0};
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;
    CPoint ptSrc;
    UpdateLayeredWindow(
        screen_dc, 
        &window_rect.TopLeft(), 
        &window_rect.Size(), 
        &dc, 
        &ptSrc, 
        0, 
        &blend, 
        ULW_ALPHA
    );

    SelectObject(dc, hBmpOld);
    DeleteObject(dc_buffer);
    ReleaseDC(screen_dc);
}

这对我有用。但是如果你的窗口或孩子不支持 WM_PRINT 我查看了它是如何为 CView 类实现的,我发现这个类提供了一个名为 OnDraw(CDC* dc) 的虚拟方法,它提供了一个 DC 来绘制。WM_PAINT 是这样实现的:

CPaintDC dc(this);
OnDraw(&dc);

并实现了 WM_PAINT:

CDC* dc = CDC::FromHandle((HDC)wParam);
OnDraw(dc);

所以 WM_PAINT 和 WM_PRINT 产生一个 OnDraw(),并且绘图代码实现了一次。

您基本上可以在您自己的 CWnd 派生类中添加相同的逻辑。使用 Visual Studio 的类向导可能无法做到这一点。我必须将以下内容添加到消息映射块:

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

注意:如果您在已经自动支持此功能的类上添加自定义 WM_PRINT 处理程序,那么您将失去默认实现。OnPrint 没有 CWnd 方法,因此您必须使用 Default() 方法来调用默认处理程序。

我没有尝试以下,但我希望它有效:

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();
}

上面我定义了一些奇怪的方法:ClearBackgroundAndPrepForPerPixelTransparency 和 CorrectPerPixelAlpha。当子控件完全不透明(这是我的每像素透明度)时,这些允许我将对话框的背景设置为半透明。

// 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);
}

这是我的测试应用程序的屏幕截图。当用户将鼠标悬停在“更多按钮”按钮上时,将创建具有半透明背景的对话框。按钮“B1”到“B16”是从 CButton 派生的子控件,正在使用上面显示的 Print() 调用进行绘制。您可以在视图的右侧边缘和按钮之间看到半透明背景。

在此处输入图像描述

于 2013-09-04T11:48:01.707 回答
0

引用MSDN 页面的 WM_PAINT

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

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

它看起来像这样:

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
}
于 2011-11-02T15:20:55.103 回答