Hello all :)
(Sorry for my bad English)
Recently I've started a small application that can show live window previews using DWM. One of the features is to render only part of source window. That can be achieved using DWM API, and that is all working...
End-user can enter the
RECT
values (X, Y, Width and Height) but that is not practical and is very uncomfortable. My idea was to let user to select (with cursor or other pointing device) visually which part of the source window will be rendered. So, I need image of source window. Selecting and etc. is not a problem, I've finished that...
The problem I'm facing is
how to correctly capture a window?
I need help with writing some sort of function that accepts
hWnd/Handle
of window and returns its image.
Of course I've tried to solve problem by naive capturing (
BitBlt
direct to
Graphics.HDC
)
[1] or by
PrintWindow
function
[2]. After that, tried "advanced" capturing to CompatibleBitmap (created with
CreateCompatibleBitmap
function)
[3] and that is closest to what I want. But... It is closest, but too far to accept it as solution.
I also tried with
Form.DrawToBitmap()
[4] but unsuccessfully.
Problems:
[1] it doesn't capture very well (anyway, much more better is [3] method)
[2] some applications are ignoring the
WM_PRINT
and
WM_PRINTCLIENT
messages. Also,
PrintWindow
will print black image when window is minimized.
[3] Well, There are many problems... When window is minimized I can only get its titlebar (or for some windows, nothing, blank image).
Furthermore,
BitBlt
(IMHO) cannot capture some Aero portions, image 1 [
Link^]
Also, when window is maximized,
BitBlt
doesn't capture its titlebar, image 2 [
Link^]
In Image 1 you can see in the bottom right corner a little bit of taskbar, and black border around VS window (that black border is real Aero border)
Image 2: you can also see portions of taskbar, but VS titlebar is seen behind the window (where Aero titlebar should be)
That are problems I can't solve because I think it is a
BitBlt
problem. My program requires full window image, not the client area only. Also, problems posted are only when window is visible. In minimized state blank image is shown.
[4] I've tried this because DWM renders minimized windows very well. Create new form, render with DWM to it, and call
.DrawToBitmap
function. But this won't work because DWM is rendering on a higher layer. So, if I have a button on a form, DMW will render over it.
I will post VB.NET code of all 'solutions' I tried. I think it is easy to convert to C# or any other language...
But, before I do that, I want to mention that I want to capture window without using DicetX/OpenGL or and other 3rd party libraries. Also, solution that consists of Showing window, then capturing it, and minimizing it again is very unpractical, and my application is there to show content of window(s) when minimized.
Try [1]:
Dim B As New Bitmap(Rc.Width, Rc.Height)
Using G As Graphics = Graphics.FromImage(B)
Dim I As IntPtr = G.GetHdc
Dim U As IntPtr = GetWindowDC(H)
BitBlt(I, 0, 0, Rc.Width, Rc.Height, U, Rc.Left, Rc.Top, &HCC0020)
G.ReleaseHdc(I)
End Using
Try [2]:
Dim B As New Bitmap(Rc.Width, Rc.Height)
Using G As Graphics = Graphics.FromImage(B)
Dim I As IntPtr = G.GetHdc
PrintWindow(H, I, 0)
G.ReleaseHdc(I)
End Using
Try [3]
I'm not author of this particularly code, but is not hard to implement
Public Function CaptureWindow(handle As IntPtr) As Image
Dim hdcSrc As IntPtr = GetWindowDC(handle)
Dim windowRect As New RECT()
GetWindowRect(handle, windowRect)
Dim R As Rectangle = GetAeroRect(handle)
Dim width As Integer = windowRect.Right - windowRect.Left
Dim height As Integer = windowRect.Bottom - windowRect.Top
Dim hdcDest As IntPtr = CreateCompatibleDC(hdcSrc)
Dim hBitmap As IntPtr = CreateCompatibleBitmap(hdcSrc, width, height)
Dim hOld As IntPtr = SelectObject(hdcDest, hBitmap)
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, _
0, 0, CopyPixelOperation.SourceCopy)
SelectObject(hdcDest, hOld)
DeleteDC(hdcDest)
ReleaseDC(handle, hdcSrc)
Dim img As Image = Image.FromHbitmap(hBitmap)
DeleteObject(hBitmap)
Return img
End Function
Try 4 is simple, and I won't post it.
In Try [3], line commented with Note 1#
Dim R As Rectangle = GetAeroRect(handle)
That was another problem I was facing. The standard
GetWindowRect
function for some windows returned wrong results (ex. Skype window), because of Aero glass pixel padding
Link (new window/tab).
I created
GetAeroRect
function that uses
DwmGetWindowAttribute
API, and it is working for now (haven't noticed wrong results)
So, that is it. I'm really, really sorry for grammar mistakes and for the very
big question. But I haven't no choice, I want to post a crystal clear question, don't want to get questions like 'Have you tried PrintWindow?'; 'How you tried BitBlt?'; 'This is DirectX solution' etc... and to post again and again.
Solution in VB.NET, C#, or even C/C++ language is appreciated, but that is not important, important is a concept and set of API calls.
I will appreciate any help :)
Thanks in advance, Xson