I need to port some old code which implements docking windows in plain C to Vista and up. When dragging docking windows around, the old code draws the docking window's outline directly to the desktop DC by doing something like this:
hdc = GetDCEx(NULL, NULL, DCX_WINDOW|DCX_CACHE|DCX_LOCKWINDOWUPDATE);
...
PatBlt(hdc, left, top, width, height, PATINVERT);
...
ReleaseDC(NULL, hdc);
This still works on Vista and up but it is very, very slow - probably, because the DWM has to copy the desktop's frame buffer into a temporary mutable pixel buffer, do the drawing, and copy it back to the now immutable frame buffer. This is very slow.
Thus, I was wondering how such code should be ported to Vista and up. AFAIK, drawing directly to the desktop DC is no longer recommended with the introduction of the DWM in Vista so I was wondering how I should draw my docking windows' outline when the user is dragging them? What's the best way to do this?
I was thinking of abusing cursor images (or drag images or whatever Win32 has on offer here) to do what I want but I'm not sure whether this is really the best way to go. Any ideas?
Thanks!
Related
While there are lots of variations of the question, there doesn't seem to be a specific answer to a simple case of wanting to use built-in common controls on a transparent window using Win32. I don't want the controls to be transparent, I just want the border around it to be transparent. I can't believe MS didn't update the .dll's to handle transparency when they added it, but I guess they forgot? Is there a specific method that works. A button can get close with WS_EX_TRANSPARENT, but flaky where it works most of the time but at times part of the border shows up. Edit controls, change depending on when get focus or not.
So the question is simply:
Is there a way to make common controls on transparent window so there is no white border around them?
If not, is there a good replacement library that does it via owner draw?
If some, which ones and what is the method?
Seems silly to reinvent the wheel just because of the area around the control.
TIA!!
If I am not mistaken, you can take the following steps to achieve this effect.
1.Create a GDI+ Bitmap object with the PixelFormat32bppPARGB pixel format.
2.Create a Graphics object to draw in this Bitmap object.
3.Do all your drawing into this object using GDI+.
4.Destroy the Graphics object created in step 2.
5.Call the GetHBITMAP method on the Bitmap object to get a Windows HBITMAP.
6.Destroy the Bitmap object.
7.Create a memory DC using CreateCompatibleDC and select the HBITMAP from step 5 into it.
8.Call UpdateLayeredWindow using the memory DC as a source.
9.Select previous bitmap and delete the memory DC.
10.Destroy the HBITMAP created in step 5.
This method should allow you to control the alpha channel of everything that is drawn: transparent for the background, opaque for the button.
A similar discussion: Transparent window containing opaque text and buttons
Say you have a form that you can expand to the left to show additional controls:
Collapsed:
Expanded:
The simplest way to achieve this in Delphi is to use alRight as the primary anchor for all controls (instead of alLeft) and then simply adjust the width and X coordinate of the form. Either you can set the Width and Left properties individually, or you can use a function that sets them simultaneously, like
if FCollapsed then
SetWindowPos(Handle, 0, Left - Width, Top, 2 * Width, Height, 0)
else
SetWindowPos(Handle, 0, Left + Width div 2, Top, Width div 2, Height, 0)
The problem is that there is quite noticeable flickering in the always-visible part of the form (in this example, the buttons) while expanding or collapsing. Try it yourself!
It is possible for the operating system to resize the form to the left without any flickering at all -- just grab the left edge of the form using the mouse and drag the mouse to the left or right -- but I am unable to find any function in the Windows API that exposes this kind of resizing.
I have tried to use several different Windows API functions to resize and reposition the form, tried their various parameters (for instance, the SWP_* flags), tried LockWindowUpdate, WM_SETREDRAW, TForm.DoubleBuffered etc. to no avail. I also examined the possibility to use the WM_SYSCOMMAND SC_SIZE approach.
I am not yet sure if the problem lies at the OS level or the VCL level.
Any suggestions?
Edit: I am very surprised to see that this Q received close votes. Let me try to clarify:
Create a new VCL forms application.
Add a few buttons to the right side of the main form and a memo to the left. Set Anchors to [alTop, alRight] on all controls. On the OnClick handler of the buttons, add the following code:
if FCollapsed then
SetWindowPos(Handle, 0, Left - Width, Top, 2 * Width, Height, 0)
else
SetWindowPos(Handle, 0, Left + Width div 2, Top, Width div 2, Height, 0);
FCollapsed := not FCollapsed;
where FCollapsed is private boolean field of the form (initialized to false).
Now, click the buttons repeatedly. (Or give one of them keyboard focus and hold the Enter key for a few seconds.) You will probably notice that the region with the buttons on your monitor will not display a perfect still image, but will flicker. In addition, you might actually see 'ghosts' of the buttons to the left of the actual column of buttons.
I am unable to capture this millisecond flickering using screen capture, so instead I used a digital camera to record my screen:
https://privat.rejbrand.se/VCLFormExpandFlicker.mp4
In this video clip, it is apparent that the column of buttons isn't a static image on the screen; instead, for a few milliseconds each time the form is resized, this region is something else than it should be. It is equally apparent that there is a 'ghost' column of buttons to the left.
My question is if there is any reasonably simple way to get rid of these visual artefacts (that at least to me are very visible even if you expand/collapse the form a single time).
On my Windows 10/Delphi 10.1 computer at work, the form is resized in a perfect manner when I drag its left-most edge using the mouse: the unaffected client area of the form is perfectly static on the monitor. However, on my Windows 7/Delphi 2009 PC at home, I do see that there is a lot of repositioning going on when I do this.
I can provide some insight about why you see ghost images of the other half of your UI and possibly a way to stop it. The ghost image indicates that someone is copying your client area pixels (and copying them to the wrong place, always flush-left in your window) before you have a chance to redraw them with the correct pixels.
There are likely two different, overlapping sources of these ghost pixels.
The first layer applies to all Windows OSes and comes from a BitBlt inside SetWindowPos. You can get rid of that BitBlt in several ways. You can create your own custom implementation of WM_NCCALCSIZE to tell Windows to blit nothing (or to blit one pixel on top of itself), or alternately you can intercept WM_WINDOWPOSCHANGING (first passing it onto DefWindowProc) and set WINDOWPOS.flags |= SWP_NOCOPYBITS, which disables the BitBlt inside the internal call to SetWindowPos() that Windows makes during window resizing. This has the same eventual effect of skipping the BitBlt.
However, Windows 8/10 aero adds another, more troublesome layer. Apps now draw into an offscreen buffer which is then composited by the new, evil DWM.exe window manager. And it turns out DWM.exe will sometimes do its own BitBlt type operation on top of the one already done by the legacy XP/Vista/7 code. And stopping DWM from doing its blit is much harder; so far I have not seen any complete solutions.
For sample code that will break through the XP/Vista/7 layer and at least improve the performance of the 8/10 layer, please see:
How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?
Since you have multiple child windows, the situation is even a little more complicated. The BitBlt type operations I mentioned above happen on your whole top-level window as a whole (they treat the window as one set of pixels regardless of how many windows are underneath, and regardless of CLIPCHILDREN). But you need to have windows move atomically so that on the next redraw they are all positioned correctly. You may find BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos useful for that (but only go there if the above tricks do not work).
I'm hoping I can get away with communicating the issue I'm having clearly without posting a ton of source code. The source is all ASM and while most would understand it, it's still not their primary "thing."
I have a layered window which is working perfectly - up to the point of resizing it. The window is all per-pixel alpha. There are no transparent pixels. The borders and caption are opaque (255 alpha); the client area is something like 128 alpha or whatever I put it at. So no transparent pixels.
I have a function that handles redraws - it's actually geared toward rebuilding the frame for a new window size. GetWindowDC is called on the layered window. CreateCompatibleDC creates off of that DC. The bitmap is a DIB section. The bitmap is properly sent to UpdateLayeredWindow. Again, everything works perfectly as long as the window is not in active resize (border drag).
I have tried using DefWindowProc to handle border drags, as well as completely handling all aspects of that functionality myself. The results are the same: massive flicker but ONLY beyond the window boundaries that existed before border drag began, and then the window size does not ultimately change - sort of. The original window area remains displayed without issue. No flicker there. However if (for example) I'm dragging the right border to the right, everything right of the original window right edge flickers rapidly then vanishes. When I release the left mouse button, the display remains at the original size (the size the window was before dragging the border). I've verified the bitmap is correctly rebuilt to the new size. It seems like the DC for the layered window doesn't want to resize because the entire area of original window size is completely stable, but any new area appearing post-resize flickers then hides.
I've even tried removing the ws_ex_layered style before resizing the window in my manual handling (I've tried both MoveWindow and SetWindowPos) then resetting the ex style after SetWindowPos / MoveWindow but before dragging.
When I let DefWindowProc handle the updates, I got the exact same result. So indications are that something is wrong with my repainting. But I even whittled that process down (commenting out oodles of code for test) and just filled the bitmap with 0xFF0000FF (arbitrary test value, blue, full opacity) then jumped straight to UpdateLayeredWindow. Same result.
The bitmap used for UpdateLayeredWindow is deleted before calling the "rebuild" function and within the function that bitmap is recreated to the new size. I have verified the new size extensively - I can see the new bitmap as it high-speed flickers and it's all good. The DC is GetWindowDC from the layered window. The compatible DC creates off that DC. The bitmap is selected into the compatible DC, and that compatible DC is passed to UpdateLayeredWindow. If any of these things were off, I would never see the new bitmap displayed. It's there - it just flickers at high speed ONLY beyond the window bounds that existed before resize began. DefWindowProc yields the same results. Within that original area, my caption buttons slide off to the right as I drag the right border to the right, further verifying everything is good.
I'm at a loss. Any input would be welcome.
Is it possible to use the trick with overriding wm_nccalcsize to draw over entire window area with opengl?
I need to keep all aero features for windows (win 7 in this case), so i use (WS_POPUP | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME) styles
It works fine for maximized mode, I can leave the border outside the screen by adjusting the Lparam.
But a regular window still has 8px borders around it, even though they are supposedly part of client rect (which I checked with GetClientRect()
Image: dark-grey border is visible
And this is all done before I init opengl context. So I dont know what's happening. Or if this even possible. Am I supposed to just create borderless and re-implement all aero features? (no way I'm doing that)
upd 2:
If I draw a rect with GDI right before I init Opengl context this is what i get:
a nice (0,0,200,200) rect, starting in the nonclient area
So it is opengl context issue. I saw in msdn docs, that opengl draws only in client area. And it still does that, ignoring that I extended the client rect to whole window. siiiigh.
The usual way to combine Aero with OpenGL is to set the window client area using the DWM API to zero. This allows you to draw to the whole window (including titlebar) using OpenGL. I have this test program to tinker with it as part of my wglarb wrapper:
https://github.com/datenwolf/wglarb/blob/master/test/layered.c
You may also be interested in my dwm_load wrapper, which allows to you call DWM functions without rigidly linking your program against DWM (which makes it incompatible with older Windows versions; you wouldn't believe how often I still get "must run on WinXP" as a feature requirement) https://github.com/datenwolf/dwm_load
We need to overlay a target window with a custom shape and tracks the position of the target window such that the overlay drawing always appears above it. Also, the overlay drawing should appear in screenshots taken using BitBlt or PrintClient by screen-capturing tools like Camtasia, Debut, etc. Also, moving the target window around should not leave traces of the drawing at earlier location. The target window is not made using our code.
So far we've tried several ways but each method has its problems:
1) Layered Window:
Using a layered window as the child/owned window of the target window is the easiest thing and it works. But the problem is on Windows 7 and XP, layered windows do not appear in a BitBlt done without the CAPTUREBLT flag and the screen-capturing tools may call BitBlt without the flag, thereby skipping our window from the capture.
2) Region Window:
The crude approach to support all Windows versions then is to use a region window using SetWindowRgn and make the target window its owner. However, region windows are generally very slow in rendering complex shapes and also impact the performance of other windows, sometimes to the point of freezing the application. Region window also leaves traces on dragging the application window.
3) Subclassing and Drawing on HDC:
Another approach is to sub-class the target window and draw the shape on its HDC on the OnPaint() event inside the window procedure hook. The shape can be drawn on the desktop window instead too. The problem is that applications may draw without a paint event, like when some text is selected using the cursor, and such drawing may erase a part of the custom drawing. Tracking all possible paint events is not a good way to do this.
4) Drawing continuously in a timer:
The last resort is to draw the custom shape on the target window in a timer continuously so the drawing is always above the target, even on text selection. But this may show a bit of flicker when the text is selected. Not to mention, it is very performance heavy to draw constantly.
Question
What is the best way to achieve an overlay drawing which works on all Windows versions (since XP) at the same time appearing in screen-captures. We've exhausted all approaches. Is there some method I'm missing? Can the above mentioned ways be used with some improvement? Is DirectX an option? I'm looking for methods with fast rendering capacity to achieve this. Appreciate any help!
I think the best solution to draw an overlay window, is to use a double-buffered technique GDI, just like in this example:
Overlay Drawing in CScrollView
I have myself the same task, and the only solution was that one. Hope it help you.