Screenshot method generates black images - c#

After failing to use the control.drawtobitmap in c#, my second option was to take screenshots of the desktop and crop out the desired sections.
My hiccup shows up once i switch user accounts, although the program does not crash, once the user is switched the program generates pure black images only.
I used this code as a reference:
WebBrowser.DrawToBitmap() or other methods?
I guess logically this makes sense as this would help windows save resources.
What options/ solutions do i have in my situation?
Edit 1
made a modification to the code for testing:
int c = 0;
while (true)
{
try
{
c++;
Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
bmp.Save("picture" + c.ToString() + ".jpg");
Thread.Sleep(5000);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
this works perfectly while on the user account, but as soon as i switch users, it returns the exception: The handle is invalid.
Any ideas?
Edit 2:
The bug in DrawToBitmap is not exactly random...
if i used the code you supplied:
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
bmp.Save(".\\picture.jpg");
it works perfectly, example: http://oi61.tinypic.com/1z23ynp.jpg
However, the moment i right-click on the web-browser control, DrawToBitmap will return a blank image.
example: http://oi60.tinypic.com/9ay0yc.jpg
So i can easily overcome this bug by adding
((Control)webbrowser1).Enabled = false;
this makes any clicking impossible on the web-browser, but unfortunately to deactivate it would render my project useless as its main function is to emulate mouse clicks on a web-browser control.
although this might also be a problem if the window is hidden.
currently im looking at this post, where code is supplied to give you a window handle.
Simulate click into a hidden window
it seems it might be of some value... do have a look.

What were the problems you had with DrawToBitmap?
It works fine here, (W8.1, VS2013) even with a Webcontrol and also after switching users. (But see the edit at the end for the conditions!)
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
// Clipboard.SetImage(bmp); for testing only
bmp.Dispose();
Here is code to take a screenshot of your window:
Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height );
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
//Clipboard.SetImage(bmp); for testing only
bmp.Dispose();
I can switch users like I want, the program keeps working.
BTW, the link you posted is really old, many things may have improved.
Edit:
With the updated question things are a lot clearer.
So you want to continuously get a screenshot of your program even when the user has changed, right?
and you want to display a WebControl, right?
A user can have three types of desktop: the logon/logoff screen, the screensaver screen and one or more normal desktop screen(s). But while the user is logged off he has no desktop screen at all.
Therefore the screenshot method will not work if the user has no active desktop, neither as g.CopyFromScreen, which will cause a GDI-error nor using a window handle like in the various solutions on the web, including the ones your link leads to. All these will, at best, show a blank or black screen.
So the DrawToBitmap method is the only one that works.
You wrote that it has random errors. That's not what I see.
The problems come in predictably when the user interacts with the WebBrowser in any way. This includes scrolling or clicking with or without navigation. After these interactions the WebBrowser will draw itself as an empty box until its URL is reloaded - not only refreshed - but really reloaded by webBrowser1.Uri = new Uri(uriPath). This can be done, see my other post
The WebBrowser also has another issue when doing a DrawToBitmap: It will fail (with the said empty box) for any pages that include an <input type="text" element. I'm not sure what's the best way to workaround this, let alone why it happends in the first place.. A screenshot method doesn't have that specific problem.
Edit 2:
The code the OP has dug up code which, using a call to PrintWindow, seems to solve all problems we had: It works while being logged off, works with Refeshing even after clicking in the WebBrowser and scrapes all pages, including those with textual input fields. Hoorah!
After cutting down the slack here is a version that can create a copy of the Form or just the WebBroser (or any other Control) with or without borders:
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
public Bitmap CaptureWindow(Control ctl)
{
//Bitmap bmp = new Bitmap(ctl.Width, ctl.Height); // includes borders
Bitmap bmp = new Bitmap(ctl.ClientRectangle.Width, ctl.ClientRectangle.Height); // content only
using (Graphics graphics = Graphics.FromImage(bmp))
{
IntPtr hDC = graphics.GetHdc();
try { PrintWindow(ctl.Handle, hDC, (uint)0); }
finally { graphics.ReleaseHdc(hDC); }
}
return bmp;
}

Finally this code seems to work even when i have switched users.
Code to take screen shot of any unsaved Notepad process ("Untitled - Notepad")
private void Form1_Load(object sender, EventArgs e)
{
//loop for debugging
int c = 0;
while(true)
{
c++;
System.Drawing.Bitmap image = CaptureWindow(FindWindow(null, "Untitled - Notepad"));
image.Save(".\\picture"+c.ToString()+".jpg");
Thread.Sleep(5000);
}
}
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public System.Drawing.Bitmap CaptureWindow(IntPtr hWnd)
{
System.Drawing.Rectangle rctForm = System.Drawing.Rectangle.Empty;
using (System.Drawing.Graphics grfx = System.Drawing.Graphics.FromHdc(GetWindowDC(hWnd)))
{
rctForm = System.Drawing.Rectangle.Round(grfx.VisibleClipBounds);
}
System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(rctForm.Width, rctForm.Height);
System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(pImage);
IntPtr hDC = graphics.GetHdc();
try
{
PrintWindow(hWnd, hDC, (uint)0);
}
finally
{
graphics.ReleaseHdc(hDC);
}
return pImage;
}
Note that the window may be hidden but it must still be maximized on the user account to get a complete screen shot.

Digital Rights Management may be stopping this, because Windows adds protection for digital media.
If, for example, you are attempting to create a screen capture of something in Media Player or Media Center using Microsoft's rendering of Graphics - yes, Microsoft is going to "protect you from any potential lawsuit."
Try this: Click "Print Screen" on your keyboard and then go into Microsoft Paint and try pasting your screen capture into it. Is anything there?

I faced with the same problem and I couldn't use CopyFromScreen method because my form with WebBrowser could be hidded. PrintWindow method also didn't work well generating images with black areas, especially when my MDI form partially covered by MDI parent form.
Finally I used OleDraw method as in this topic on SO, but integrated it in a class derived from WebBrowser. Basically, it just overrides standard control reaction to WM_PRINT message. This allows to do normal Control.DrawToBitmap not only for the WebBrowser, but for a form with WebBrowser in it as well. This also works if the form is hidden (covered by another form, including MDI parent form) and should work when user has locked session with Win+L (I haven't tested it).
public class WebBrowserEx : WebBrowser
{
private const uint DVASPECT_CONTENT = 1;
[DllImport("ole32.dll", PreserveSig = false)]
private static extern void OleDraw([MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwAspect,
IntPtr hdcDraw,
[In] ref System.Drawing.Rectangle lprcBounds
);
protected override void WndProc(ref Message m)
{
const int WM_PRINT = 0x0317;
switch (m.Msg)
{
case WM_PRINT:
Rectangle browserRect = new Rectangle(0, 0, this.Width, this.Height);
// Don't know why, but drawing with OleDraw directly on HDC from m.WParam.
// results in badly scaled (stretched) image of the browser.
// So, drawing to an intermediate bitmap first.
using (Bitmap browserBitmap = new Bitmap(browserRect.Width, browserRect.Height))
{
using (var graphics = Graphics.FromImage(browserBitmap))
{
var hdc = graphics.GetHdc();
OleDraw(this.ActiveXInstance, DVASPECT_CONTENT, hdc, ref browserRect);
graphics.ReleaseHdc(hdc);
}
using (var graphics = Graphics.FromHdc(m.WParam))
{
graphics.DrawImage(browserBitmap, Point.Empty);
}
}
// ignore default WndProc
return;
}
base.WndProc(ref m);
}
}

Related

Confused about why two Panels work differently

I have a problem that is really confusing me. Let me lay some background. I am trying to develop my own Editor Control. I wish to have a blinking caret, I know I can do this using CreateCaret, ShowCaret ect but this is not how I wish to do it, I wish to implement this myself. My caret does not blink and I cant understand why.
The way I'm trying to implement this is by caching the area below the caret and then display the caret. Then half a second later I repaint the cached data back to the Editor Control therefore effecting a flashing caret. I have tried using just a Graphics Object and a Bitmap for the cache neither worked but I think I know why. So I decided to experiment. I set up one Panel on the Form itself through the Designer and one is handed coded into the Caret class itself. The Panel on the Form works but the Panel encapsulated within the class doesn't and I don't know why. My question is why?
Below is the Code. The BlinkTimer_Tick method just causes the Blink. Paint just Paints the caret, none of this should be hard to understand.
BackupBackground copies the area below the Caret to the cache, while RestoreBackground copies the cache to the Control. Now the problem is if you comment out the lines commented with "Works if this line is commented out" in both methods it all works but when these are not commented it doesn't work the caret does not blink. Both these Panels are set up the same.
private void BlinkTimer_Tick(object sender, EventArgs e)
{
Paint();
_BlinkTimer.Start();
}
private void BackupBackground(Graphics SrcGraph)
{
Form TF = _Parent.FindForm() as Form;
Panel P = TF.Controls["_TestPanel"] as Panel;
P = _Buffer; // Works if this line is Commentted out
Graphics DestGraph = P.CreateGraphics();
IntPtr SrcHDC = SrcGraph.GetHdc();
IntPtr DestHDC = DestGraph.GetHdc();
BitBlt(DestHDC, 0, 0, _Size.Width, _Size.Height,
SrcHDC, _Location.X, _Location.Y, TernaryRasterOperations.SRCCOPY);
DestGraph.ReleaseHdc(DestHDC);
SrcGraph.ReleaseHdc(SrcHDC);
}
private void RestoreBackground(Graphics DestGraph)
{
Form TF = _Parent.FindForm() as Form;
Panel P = TF.Controls["_TestPanel"] as Panel;
P = _Buffer; // Works if this line is Commentted out
Graphics SrcGraph = P.CreateGraphics();
IntPtr SrcHDC = SrcGraph.GetHdc();
IntPtr DestHDC = DestGraph.GetHdc();
BitBlt(DestHDC, _Location.X, _Location.Y, _Size.Width, _Size.Height,
SrcHDC, 0, 0, TernaryRasterOperations.SRCCOPY);
DestGraph.ReleaseHdc(DestHDC);
SrcGraph.ReleaseHdc(SrcHDC);
}
internal void Paint()
{
Graphics Graph = _Parent.CreateGraphics();
if (!_BlinkOn)
{
// Restore Graphics from Backup
RestoreBackground(Graph);
_BlinkOn = true;
}
else
{
// Backup Graphics
Graph.Flush();
BackupBackground(Graph);
// Draw Caret
using (SolidBrush P = new SolidBrush(Color.Black))
{
Graph.FillRectangle(P, new Rectangle(_Location, _Size));
}
_BlinkOn = false;
}
}
The Caret should flash in both circumstances as I change nothing really but it only flashes when I use the Panel on the Form.
Note: I do not intend to use a Panel for the cache I was just experimenting and found this behavior and its weird, so I need to know.
Thanks Danny.

How to capture window contents of a Windows Store App in C#

I have a bit of code to capture windows desktop app contents and save to a Bitmap object in .NET. It uses User32.dll and Gdi32.dll (BitBlt) and works just fine. However, the code produces all-black bitmaps when I give the code a handle to a window that holds a Windows Store app. I'm not sure if this is a security feature or what. I cannot use the ScreenCapture api as the contents of the window, after being resized, are almost always taller/larger than the screen. Has anyone had any luck capturing window contents even when they're larger than the screen, for a Windows Store app?
EDIT: Just as a note I am trying to capture a different program's window, not my own program. My program can be assumed to be a Windows Console application in .NET 4.6.1 / C#
Also, I know that this must be possible somehow in Windows APIs, because the Aero Peek feature, where if you hover over the taskbar on the running program's icon shows the full height of the window, including offscreen components. (see tall window on right, set to 6000px much higher than my display)
As of Windows 8.1, you can use Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap to render elements to a bitmap. There are a couple of caveats to this:
You can capture elements that are offscreen, as long as they are in the XAML visual tree and have Visibility set to Visible and not Collapsed.
Some elements, like video, won't be captured.
See the API for more details:
https://msdn.microsoft.com/library/windows/apps/xaml/windows.ui.xaml.media.imaging.rendertargetbitmap.aspx
This might do the trick. Basically get the window handle to the app, call the native functions on it to figure out the app window position, provide those do the graphics class and copy from the screen.
class Program
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string strClassName, string strWindowName);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
public struct Rect
{
public int Left { get; set; }
public int Top { get; set; }
public int Right { get; set; }
public int Bottom { get; set; }
}
static void Main(string[] args)
{
/// Give this your app's process name.
Process[] processes = Process.GetProcessesByName("yourapp");
Process lol = processes[0];
IntPtr ptr = lol.MainWindowHandle;
Rect AppRect = new Rect();
GetWindowRect(ptr, ref AppRect);
Rectangle rect = new Rectangle(AppRect.Left, AppRect.Top, (AppRect.Right - AppRect.Left), (AppRect.Bottom - AppRect.Top));
Bitmap bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
// make sure temp directory is there or it will throw.
bmp.Save(#"c:\temp\test.jpg", ImageFormat.Jpeg);
}
}

C# Transparent Form using UpdateLayeredWindow draw controls

I'm developing an exe where I need to have a transparent background. I have made the image in Photoshop and it has all the neat stuff (shadows/opacity, reflection etc).
I have been struggling to get it working using TransparentColor+BackColor+Background Image, but I always end up with some pixel not being transparent. So I switched to UpdateLayeredWindow which works fine, but no control is being drawn now.
Here is some of my code
private void Form1_Load(object sender, EventArgs e)
{
UpdateFormDisplay(this.BackgroundImage);
}
protected override void OnPaint(PaintEventArgs e)
{
UpdateFormDisplay(this.BackgroundImage);
}
public void UpdateFormDisplay(Image backgroundImage)
{
IntPtr screenDc = API.GetDC(IntPtr.Zero);
IntPtr memDc = API.CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr oldBitmap = IntPtr.Zero;
try
{
//Display-image
Bitmap bmp = new Bitmap(backgroundImage);
hBitmap = bmp.GetHbitmap(Color.FromArgb(0)); //Set the fact that background is transparent
oldBitmap = API.SelectObject(memDc, hBitmap);
//Display-rectangle
Size size = bmp.Size;
Point pointSource = new Point(0, 0);
Point topPos = new Point(this.Left, this.Top);
//Set up blending options
API.BLENDFUNCTION blend = new API.BLENDFUNCTION();
blend.BlendOp = API.AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = API.AC_SRC_ALPHA;
API.UpdateLayeredWindow(this.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, API.ULW_ALPHA);
//Clean-up
bmp.Dispose();
API.ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
API.SelectObject(memDc, oldBitmap);
API.DeleteObject(hBitmap);
}
API.DeleteDC(memDc);
}
catch (Exception)
{
}
}
Here are some images to explain better
If you want to use regular control inside layered window that uses UpdateLayeredWindow API, you need to override control's OnPaint method to redirect drawing to off-screen bitmap which you later use with UpdateLayeredWindow method to update window look.
If you don't want to dig into controls code, or don't have much custom made controls, WM_PRINT message could be used to force controls to paint themselves into provided device context. Classic window subclassing (SetWindowLong/GetWindowLong) for catching moments when controls invalidate themselves is useful, but may be slightly dangerous - you have to watch for callback chain.
Finally, you can use lightweight (windowless) controls. They use form message queue to receive events and draw themselves, so only form drawing code modifications are necessary. Some of standard winforms controls support this mode.

PrintScreen Source Code/Simulation

I would like to take a picture of a fullscreen direct3D game. I know I need to create an "overlay" which is harder in c# and I found out that using printscreen (and than pasting it in mspaint) does capture the game window.
I ended up with this unstable code :
try
{
SendKeys.SendWait("{PRTSC}");
Thread.Sleep(100);
if (Clipboard.ContainsImage())
return Clipboard.GetImage();
}
catch (Exception)
{
throw;
}
Bitmap bitmap = new Bitmap(1, 1);
return bitmap;
This code sometimes work, sometimes it throws an ExternalException and sometimes and Clipboard.ContainsImage() returns false and it simply returns a 1,1 sized image.
I would like to try and improve that code so I won't have to rely on the time it takes to copy something to the clipboard (with thread.sleep(20000), it'll work but this code is just a part of a larger code that executes every 800ms).
So I need ideas on how to send keys more reliably or get the that method printscreen uses.
public static class Win32
{
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
}
public Bitmap CaptureWindow()
{
Bitmap bmp = new Bitmap(this.Width,this.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
Win32.PrintWindow(this.Handle, g.GetHdc(), 0);
g.ReleaseHdc();
}
return bmp;
}

How do I find the position / location of a window given a hWnd without NativeMethods?

I'm currently working with WatiN, and finding it to be a great web browsing automation tool. However, as of the last release, it's screen capturing functionality seems to be lacking. I've come up with a workable solution for capturing screenshots from the screen (independently generating code similar to this StackOverflow question) in addition to some code by Charles Petzold. Unfortunately, there is a missing component: Where is the actual window?
WatiN conveniently provides the browser's hWnd to you, so we can (with this simplified example) get set to copy an image from the screen, like so:
// browser is either an WatiN.Core.IE or a WatiN.Core.FireFox...
IntPtr hWnd = browser.hWnd;
string filename = "my_file.bmp";
using (Graphics browser = Graphics.FromHwnd(browser.hWnd) )
using (Bitmap screenshot = new Bitmap((int)browser.VisibleClipBounds.Width,
(int)browser.VisibleClipBounds.Height,
browser))
using (Graphics screenGraphics = Graphics.FromImage(screenshot))
{
int hWndX = 0; // Upper left of graphics? Nope,
int hWndY = 0; // this is upper left of the entire desktop!
screenGraphics.CopyFromScreen(hWndX, hWndY, 0, 0,
new Size((int)browser.VisibileClipBounds.Width,
(int)browser.VisibileClipBounds.Height));
screenshot.Save(filename, ImageFormat.Bmp);
}
Success! We get screenshots, but there's that problem: hWndX and hWndY always point to the upper left most corner of the screen, not the location of the window we want to copy from.
I then looked into Control.FromHandle, however this seems to only work with forms you created; this method returns a null pointer if you pass the hWnd into it.
Then, further reading lead me to switch my search criteria...I had been searching for 'location of window' when most people really want the 'position' of the window. This lead to another SO question that talked about this, but their answer was to use native methods.
So, Is there a native C# way of finding the position of a window, only given the hWnd (preferably with only .NET 2.0 era libraries)?
I just went through this on a project and was unable to find any managed C# way.
To add to Reed's answer the P/Invoke code is:
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
Call it as:
RECT rct = new RECT();
GetWindowRect(hWnd, ref rct);
No - if you didn't create the form, you have to P/Invoke GetWindowRect. I don't believe there is a managed equivalent.
The answer is as others have stated, probably "No, you cannot take a screenshot of a random window from an hwnd without native methods.". Couple of caveats before I show it:
Forewarning:
For anyone who wants to use this code, note that the size given from the VisibleClipBounds is only inside the window, and does not include the border or title bar. It's the drawable area. If you had that, you might be able to do this without p/invoke.
(If you could calculate the border of the browser window, you could use the VisibleClipBounds. If you wanted, you could use the SystemInformation object to get important info like Border3DSize, or you could try to calculate it by creating a dummy form and deriving the border and title bar height from that, but that all sounds like the black magic that bugs are made of.)
This is equivalent to Ctrl+Printscreen of the window. This also does not do the niceties that the WatiN screenshot capability does, such as scroll the browser and take an image of the whole page. This is suitable for my project, but may not be for yours.
Enhancements:
This could be changed to be an extension method if you're in .NET 3 and up-land, and an option for the image type could be added pretty easily (I default to ImageFormat.Bmp for this example).
Code:
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public class Screenshot
{
class NativeMethods
{
// http://msdn.microsoft.com/en-us/library/ms633519(VS.85).aspx
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
// http://msdn.microsoft.com/en-us/library/a5ch4fda(VS.80).aspx
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
}
/// <summary>
/// Takes a screenshot of the browser.
/// </summary>
/// <param name="b">The browser object.</param>
/// <param name="filename">The path to store the file.</param>
/// <returns></returns>
public static bool SaveScreenshot(Browser b, string filename)
{
bool success = false;
IntPtr hWnd = b.hWnd;
NativeMethods.RECT rect = new NativeMethods.RECT();
if (NativeMethods.GetWindowRect(hWnd, ref rect))
{
Size size = new Size(rect.Right - rect.Left,
rect.Bottom - rect.Top);
// Get information about the screen
using (Graphics browserGraphics = Graphics.FromHwnd(hWnd))
// apply that info to a bitmap...
using (Bitmap screenshot = new Bitmap(size.Width, size.Height,
browserGraphics))
// and create an Graphics to manipulate that bitmap.
using (Graphics imageGraphics = Graphics.FromImage(screenshot))
{
int hWndX = rect.Left;
int hWndY = rect.Top;
imageGraphics.CopyFromScreen(hWndX, hWndY, 0, 0, size);
screenshot.Save(filename, ImageFormat.Bmp);
success = true;
}
}
// otherwise, fails.
return success;
}
}

Categories

Resources