全屏手绘
我正在尝试制作一个简单的小工具,允许用户从正常操作切换到禁用所有应用程序消息的模式,并且他们可以使用鼠标进行一些手绘绘图,然后再次切换模式以保持绘图状态当他们做任何他们想做的其他正常事情时,他们会盯着屏幕。如果我决定的话,这可能会发展成为一个好东西,您可以通过保存您所做的装饰并稍后加载它们来使用装饰屏幕。
当我开始这个时(半年前,发现 Windows API 后不久),我只是进行了全局鼠标跟踪,并在 GetDC(NULL) hdc 的任何地方画了一个圆圈。当然,问题在于,当它下面的任何内容更新时,它会消失,并且仍然会传递鼠标消息,因此,例如,如果我按住桌面上的按钮,它会在整个绘画中调整矩形内容的大小。
今天,自从 6 个月前的最后一次主要工作以来,终于有了一些空闲时间,我决定重新制作它,看看是否能实现我想要的。我制作了一个透明的、最上面的、WS_CHILD、分层的、最大化的窗口(基本上屏幕不会改变,但在所有内容的顶部有一个窗口可以让消息通过)。接下来,我做到了,当它处于绘画模式时,它将 alpha 值设置为 1 并让用户绘画。直到我这样做之后我才意识到,由于窗口的 alpha 值为 1,因此所有绘画都将不可见。
接下来,我尝试使用 GetDC(NULL),但记得当某些内容更新时它会被删除。
现在我只是想到使用位图和dcs将屏幕重复存储到一个dc中,在另一个dc上绘画,然后将其复制回存储的屏幕,并且未绘制的部分具有透明度,然后复制回到屏幕,但我有点失落。这是我的源代码(掩码函数取自本教程)。请告诉我其中一些是否是不必要的。我确实使用了位图进行双缓冲,但我不太确定在哪里需要它们。
//Global mask since it takes longer to make
HBITMAP mask;
//Window Procedure Start
HDC screenDC; //hdc for entire screen
screenDC = GetDC (NULL); //get DC for screen
HDC memDC = CreateCompatibleDC (screenDC); //create DC for holding the screen+paint
HBITMAP bm = CreateCompatibleBitmap (screenDC, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN)); //create bitmap for memDC
HDC paintDC = CreateCompatibleDC (screenDC); //create DC to paint on
HBITMAP paintBM = CreateCompatibleBitmap (screenDC, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN)); //create bitmap for paintDC
SelectObject (memDC, bm); //select bitmap into memDC
SelectObject (paintDC, paintBM); //select painting bitmap into paintDC
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), screenDC, 0, 0, SRCCOPY); //copy screen to memDC
SetBkColor (paintDC, RGB(0,0,0)); //set background of paintDC to black so it's all transparent to start
//WM_CREATE
mask = CreateBitmapMask (bm, RGB(0,0,0)); //create black mask (paint colours are limited 1-255 now)
//painting is done into paintDC
//at end of Window Procedure
SelectObject (paintDC, mask); //select mask into paintDC
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCAND); //this in combination with the next should make it bitblt with all of the black taken out I thought
SelectObject (paintDC, paintBM); //select bitmaps into DCs
SelectObject (memDC, bm);
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCPAINT); //second part of transparent bitblt
BitBlt (screenDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCCOPY); //copy memDC back to screen
DeleteObject (paintBM); //delete stuff
DeleteObject (mask);
DeleteDC (memDC);
DeleteDC (paintDC);
ReleaseDC (hwnd, screenDC);
//CreateBitmapMask() (taken directly from http://www.winprog.org/tutorial/transparency.html
HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
HDC hdcMem, hdcMem2;
HBITMAP hbmMask;
BITMAP bm;
// Create monochrome (1 bit) mask bitmap.
GetObject(hbmColour, sizeof(BITMAP), &bm);
hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
// Get some HDCs that are compatible with the display driver
hdcMem = CreateCompatibleDC(0);
hdcMem2 = CreateCompatibleDC(0);
SelectObject(hdcMem, hbmColour);
SelectObject(hdcMem2, hbmMask);
// Set the background colour of the colour image to the colour
// you want to be transparent.
SetBkColor(hdcMem, crTransparent);
// Copy the bits from the colour image to the B+W mask... everything
// with the background colour ends up white while everythig else ends up
// black...Just what we wanted.
BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
// Take our new mask and use it to turn the transparent colour in our
// original colour image to black so the transparency effect will
// work right.
BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);
// Clean up.
DeleteDC(hdcMem);
DeleteDC(hdcMem2);
return hbmMask;
}
我知道代码很可能很糟糕。我正在考虑所有建议,只是我对这个主题不太清楚,并且不了解正在发生的所有事情,这使得修复变得困难。这段代码对我来说是通过经常放置一个全屏黑色矩形来运行的。
我的主要问题是:有什么方法可以在屏幕上绘画,而当下面的窗口更新时,它不会被删除?我现在能想到的唯一真实的事情就是存储用户绘制的所有细线段的位置,并不断在屏幕顶部重新绘制它们。乍一看,这似乎非常低效并且浪费内存。
另外,在编写本文时,我非常确定在提供的代码段之前我不需要任何理论内容的代码示例。现在大部分都消失了,但这实际上更多的是一个理论问题。
编辑: 我刚刚发现 TransparentBlt 函数似乎非常适合这种情况,所以我尝试使用它来代替 SRCPAINT 和 SRCAND BitBlts,它产生了相同的结果:一个黑色矩形覆盖屏幕,有时当我的鼠标移动时部分会消失事物。
I'm trying to make a simple little tool that would allow a user to switch from normal operation to a mode where all application messages are disabled and they can use the mouse to do some freehand drawing, then switch modes again to keep their drawing on the screen while they do whatever other normal stuff they want. This could, if I decide, evolve into a nice thing you could use to use a decorated screen by saving the decorations you do and loading them later.
When I started this (which was over half a year ago, soon after discovering the Windows API) I just did global mouse tracking and painted a circle wherever it was to a GetDC(NULL) hdc. The problems, of course, were that it would disappear when anything under it updated and would still have the mouse messages put through, so if I held down the button on the desktop, for example, it would put resizing rectangle things throughout the paintings.
Today, after finally having some spare time since the last major work on this most of that 6 months ago, I decided to remake it and see if I could achieve what I wanted. I made a transparent, topmost, WS_CHILD, layered, maximized window (basically the screen doesn't change, but there's a window on top of everything letting messages through). Next, I made it so that when it was in painting mode, it would set the alpha value to 1 and let the user paint. The thing I didn't realize until I did it was that since the alpha value of the window was 1, none of the painting would be visible.
Next, I tried using GetDC(NULL), but remembered that gets erased when something updates.
Now I just thought of using bitmaps and dcs to repeatedly store the screen into a dc, paint on another dc, and then copy it back to the one with the stored screen with transparency for the parts that aren't drawn on, and copy that back to the screen, but I'm losing a bit of heart. Here's my source code for that (the mask function is taken from this tutorial). Please tell me if some of this is unnecessary. I've used bitmaps for double buffering sure, but I'm not all that sure on where I need them.
//Global mask since it takes longer to make
HBITMAP mask;
//Window Procedure Start
HDC screenDC; //hdc for entire screen
screenDC = GetDC (NULL); //get DC for screen
HDC memDC = CreateCompatibleDC (screenDC); //create DC for holding the screen+paint
HBITMAP bm = CreateCompatibleBitmap (screenDC, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN)); //create bitmap for memDC
HDC paintDC = CreateCompatibleDC (screenDC); //create DC to paint on
HBITMAP paintBM = CreateCompatibleBitmap (screenDC, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN)); //create bitmap for paintDC
SelectObject (memDC, bm); //select bitmap into memDC
SelectObject (paintDC, paintBM); //select painting bitmap into paintDC
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), screenDC, 0, 0, SRCCOPY); //copy screen to memDC
SetBkColor (paintDC, RGB(0,0,0)); //set background of paintDC to black so it's all transparent to start
//WM_CREATE
mask = CreateBitmapMask (bm, RGB(0,0,0)); //create black mask (paint colours are limited 1-255 now)
//painting is done into paintDC
//at end of Window Procedure
SelectObject (paintDC, mask); //select mask into paintDC
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCAND); //this in combination with the next should make it bitblt with all of the black taken out I thought
SelectObject (paintDC, paintBM); //select bitmaps into DCs
SelectObject (memDC, bm);
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCPAINT); //second part of transparent bitblt
BitBlt (screenDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCCOPY); //copy memDC back to screen
DeleteObject (paintBM); //delete stuff
DeleteObject (mask);
DeleteDC (memDC);
DeleteDC (paintDC);
ReleaseDC (hwnd, screenDC);
//CreateBitmapMask() (taken directly from http://www.winprog.org/tutorial/transparency.html
HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
HDC hdcMem, hdcMem2;
HBITMAP hbmMask;
BITMAP bm;
// Create monochrome (1 bit) mask bitmap.
GetObject(hbmColour, sizeof(BITMAP), &bm);
hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
// Get some HDCs that are compatible with the display driver
hdcMem = CreateCompatibleDC(0);
hdcMem2 = CreateCompatibleDC(0);
SelectObject(hdcMem, hbmColour);
SelectObject(hdcMem2, hbmMask);
// Set the background colour of the colour image to the colour
// you want to be transparent.
SetBkColor(hdcMem, crTransparent);
// Copy the bits from the colour image to the B+W mask... everything
// with the background colour ends up white while everythig else ends up
// black...Just what we wanted.
BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
// Take our new mask and use it to turn the transparent colour in our
// original colour image to black so the transparency effect will
// work right.
BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);
// Clean up.
DeleteDC(hdcMem);
DeleteDC(hdcMem2);
return hbmMask;
}
I know that code may very well be horrible. I'm taking all suggestions into account, it's just that I'm not too clear with this subject, and don't get everything that's happening, which makes it hard to fix. This code runs for me by pretty much putting a fullscreen black rectangle every so often.
My main question is: Is there any way I can paint onto the screen without it getting erased when windows underneath update? The only real thing I can think of now would be to store all of the locations of the tiny line segments the user draws and keep redrawing them on top of the screen. At first glance it seems very inefficient and wasteful of memory.
Also, I was pretty sure while writing this that I didn't need any code examples for the theoretical stuff before the supplied code segments. Most of it is gone now, but this really is more of a theory issue.
EDIT:
I just found out about the TransparentBlt function which seemed perfect for the situation, so I tried using that instead of the SRCPAINT and SRCAND BitBlts and it produced the same result: a black rectangle covering the screen, sometimes having parts disappear when my mouse moves over things.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
也许最简单的方法:
在非绘图模式下,使用 SetLayeredWindowAttributes 为透明窗口设置“透明键”颜色。使窗口的 alpha 完全不透明,但用该关键颜色填充窗口(FillRect 或类似颜色),它将全部显示为透明。然后,您以非关键颜色绘制的任何内容都将显示为实心,位于透明分层窗口下方的所有窗口的顶部。
要进入绘图模式,一种方法是创建一个新窗口,其中在透明层的正下方捕获桌面的位图。或者避免使用位图,并使其稍微不透明并且全部为纯色 - 例如,这样桌面看起来就像“变灰”。关键是这个窗口不是完全透明的,因此它将能够接收鼠标输入,然后您可以使用该输入在实际的透明层上进行绘制。
Simplest way, perhaps:
When in non-drawing mode, use SetLayeredWindowAttributes to set a 'transparency key' color for the transparent window. Make the window's alpha fully opaque, but fill the window (FillRect or similar) with that key color, and it will all appear transparent. Then anything you draw in the non-key color will appear as solid, on top of all the windows beneath the transparent layered window.
To go into drawing mode, one approach is to create a new window with a captured bitmap of the desktop immediately under your transparent layer. Or avoid the bitmap, and make it slightly non-transparent and all a solid color - eg so it looks like the desktop is "greyed out". The key thing is that this window is not completely transparent, so it will be able to receive mouse input that you can then use to draw on the actual transparent layer.
我认为在显示全屏显示内存 DC 内容的窗口之前,最好创建屏幕快照并将其保存在位图中(以内存 DC 的形式)。这样,您实际上可以获取由您自己的窗口上的点击等引起的消息,并像往常一样处理它们。
好主意吗?
I think you would be best of by creating a snapshot of the screen and save that in a bitmap (in the form of a memory DC) BEFORE you show a window which displays the contents of the memory DC in fullscreen. That way you actually fetch the messages caused by clicks etc on your own window and process them as usual.
Good idea?