我想创建一个不规则形状/皮肤的窗口(现在只有圆形的alpha混合角)。创建顶级窗口后,我正在处理WM_CREATE消息并执行以下操作:
稍后我计划通过设置alpha通道并预先计算相关位来对边缘进行舍入,以实现这一点。
现在,如果我在窗口中创建了一个按钮,它就不会呈现自己。我知道为什么,我想在这里提出一个优雅的解决方案来制作我的自定义控件(子窗口)。
我最初的做法是首先使用子窗口,然后让顶层窗口完成所有绘图,输入处理,命中测试等等。我发现这是单调乏味的方式,而我想让Windows为我处理所有这些。
现在,我知道如果我创建一个子窗口,它当然表现正常(例如对用户输入作出反应),我想利用它。我打算通常使用CreateWindowEx()来创建子窗口(自定义控件),这样他们就可以得到一个窗口句柄,并且不必担心手动传递窗口消息。
不知何故,我需要绘制这些窗口,而且正如我所看到的,唯一可行的方法是绘制整个顶层窗口的例程。我需要发明一些逻辑来获得顶级窗口的绘图例程,以便在必要时绘制我的子窗口。据我所知,UpdateLayeredWindow()函数需要重绘整个窗口。
例如让子控件呈现自己发送到顶级窗口的绘图例程的图像是否明智?例如,子窗口将用户WM发送到顶层窗口,将指向其渲染位图的指针作为WPARAM传递,并将指针定义为将其位置和大小定义为LPARAM的结构。
有更好的想法吗?这有什么意义吗?
谢谢, 的Eirik
答案 0 :(得分:2)
我想我会选择这个解决方案:
顶级窗口保留两个位图。一个是显示的窗口,一个没有任何子控件呈现。后者只需要在窗口改变大小时重绘。该窗口将有一个消息处理程序,用于在显示的位图上呈现子控件。消息处理程序将指向一个指向包含子控件的DIB或指向实际位(不确定哪个是最好的)作为WPARAM的指针,以及一个指向包含该子项应该为矩形的结构的指针。作为LPARAM绘制。在AlphaBlend()调用之前,将调用BitBlt()以清除底层表面(这是其他位图的位置),以便将子控件位图渲染到显示的位图表面上。
只要调整大小或由于某种原因需要重绘其子项,父窗口将调用EnumChildWindows。当然可以在这里实施某种失效制度,以减少对儿童控制的不必要的渲染。但不确定速度的增加是否值得付出努力。
创建子控件实例后,将创建与顶级窗口兼容的内部位图。子将其自身渲染到此内部位图中,每当需要重绘时,它通过SendMessage()函数通知其父窗口,将指针作为WPARAM传递给其位图,并将RECT作为LPARAM定义其位置和尺寸。如果父级需要重绘,它会向其所有子窗口发出一条消息,请求它们的位图。然后,当孩子们决定需要重绘时,孩子们会回复他们通常会发送的相同信息。
的Eirik
答案 1 :(得分: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()调用绘制。您可以在视图的右边缘和按钮之间看到半透明背景。
答案 2 :(得分:0)
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
}