I am using the code below to capture a screenshot of the currently active window. This code comes from Capture screenshot Including Semitransparent windows in .NET, with a few small additions, i.e. it uses GetForegroundWindow and also a timer so that I can select the desired window.
On Windows 10 (x64) this works fine for Firefox browser, but it does not work with Chrome or Edge.
I find it strange that Screenshot captured using BitBlt in C# results a black image on Windows 10 [duplicate] is marked as a duplicate, because the answer from above (first link) does not solve this problem.
Any ideas why it does not work for Chrome or Edge?
Code:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace App1
{
/// <summary>
/// Description of MainForm.
/// </summary>
public partial class MainForm : Form
{
public MainForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
//
// TODO: Add constructor code after the InitializeComponent() call.
//
}
void DoCapture()
{
label1.Text = "Capturing...";
try
{
IntPtr hDesk = GetForegroundWindow();
//IntPtr hDesk = GetDesktopWindow();
var windowRect = new RECT();
GetWindowRect(hDesk, out windowRect);
int width = (int)(windowRect.Right - windowRect.Left);
int height = (int)(windowRect.Bottom - windowRect.Top);
Size sz = new Size(width, height);
sz.Width = (int)(sz.Width * 1.25); // this is just an adjustment for the Windows zoom factor of 125%
sz.Height = (int)(sz.Height * 1.25);
IntPtr hSrce = GetWindowDC(hDesk);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, sz.Width, sz.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, 0,0, sz.Width, sz.Height, hSrce,
0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
Bitmap bmp = Bitmap.FromHbitmap(hBmp);
SelectObject(hDest, hOldBmp);
DeleteObject(hBmp);
DeleteDC(hDest);
ReleaseDC(hDesk, hSrce);
bmp.Save(#"c:\temp\test.png");
bmp.Dispose();
label1.Text = "Done";
}
catch (Exception e){
label1.Text = "Exception Occurred";
textBox1.Text = e.ToString();
}
}
void Button1Click(object sender, EventArgs e)
{
timer1.Enabled = true;
}
// P/Invoke declarations
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
void Timer1Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
DoCapture();
}
}
}
Running this in SharpDevelop 5.1
Likely it doesn't work for these 2 browsers because they use a hardware-accelerated graphics context (OpenGL- or DirectX-based) to render their content, and this is incompatible with GDI-based calls.
I can suggest a simple workaround:
First use GetForegroundWindow to get the active window rect. Then call GetDesktopWindow and use that handle with the call to BitBlt:
Just add a single line to above code:
IntPtr hDesk = GetForegroundWindow();
... // get dimensions of active window
hDesk = GetDesktopWindow(); // add this line
IntPtr hSrce = GetWindowDC(hDesk);
IntPtr hDest = CreateCompatibleDC(hSrce);
Works fine.
Related
I wanted to automate the creation of screenshots taken with my balloon tip from a notification icon, so that could easily validate the look of the different languages my application supports. Problem is that the balloon tip is absent from the screenshot although it is shown on the screen on Windows 7.
I have tried with the solutions from Capture screenshot of active window?, e.g.
// From http://www.developerfusion.com/code/4630/capture-a-screen-shot/
var sc = new ScreenCapture();
trayIcon.ShowBalloonTip(10000, "My Title", "My message", ToolTipIcon.Info);
Thread.Sleep(2000); // Just to make sure that the balloon tip is shown
sc.CaptureScreenToFile("MyScreenshot.png", ImageFormat.Png);
and
Rectangle bounds = Screen.GetBounds(Point.Empty);
using(Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using(Graphics g = Graphics.FromImage(bitmap))
{
trayIcon.ShowBalloonTip(10000, "My Title", "My message", ToolTipIcon.Info);
Thread.Sleep(2000); // Just to make sure that the balloon tip is shown
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
}
bitmap.Save("MyScreenshot.png", ImageFormat.Png);
}
But both take screenshots without showing the balloon tip. So, is there a way to programmatically take a screenshot that includes balloon tips?
Bonus information: On Windows 10 the balloon tip is forced into the normal notification system and taking screenshots of this works as expected.
As Hans Passant mentions in a commet to the question, using CopyPixelOperation.CaptureBlt is the key to the solution.
As it did not work with the solutions I already tried I found a similar question Capture screenshot Including Semitransparent windows in .NET, that deals with semi-transparent windows.
All-in-all the solutions that enables me to take a screenshot that includes the balloon tip from a notification icon looks like this:
class ScreenCapture
{
public void CaptureScreenToFile(string fileName)
{
Size sz = Screen.PrimaryScreen.Bounds.Size;
IntPtr hDesk = GetDesktopWindow();
IntPtr hSrce = GetWindowDC(hDesk);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, sz.Width, sz.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, 0, 0, sz.Width, sz.Height, hSrce, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
Bitmap bmp = Bitmap.FromHbitmap(hBmp);
SelectObject(hDest, hOldBmp);
DeleteObject(hBmp);
DeleteDC(hDest);
ReleaseDC(hDesk, hSrce);
bmp.Save(fileName);
bmp.Dispose();
}
// P/Invoke declarations
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
}
I trying to run tessnet on a bitmap returned from a screenshot created with getwindow function but the result is bad. I tried to run on a bmp file saved in paint. This image is same as image created with getwindow and for this the tessnet work. This is the image Any idea?
public const int SRCCOPY = 13369376;
public const int WM_CLICK = 0x00F5;
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "GetDC")]
internal extern static IntPtr GetDC(IntPtr hWnd);
[DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")]
internal extern static IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
internal extern static IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
internal extern static IntPtr DeleteDC(IntPtr hDc);
[DllImport("user32.dll", EntryPoint = "ReleaseDC")]
internal extern static IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll", EntryPoint = "BitBlt")]
internal extern static bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, int RasterOp);
[DllImport("gdi32.dll", EntryPoint = "SelectObject")]
internal extern static IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
internal extern static IntPtr DeleteObject(IntPtr hDc);
[DllImport("user32.dll")]
public static extern int SendMessage(
int hWnd, // handle to destination window
uint Msg, // message
long wParam, // first message parameter
long lParam // second message parameter
);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
public static Bitmap createBitmapFromWindow(string windowClass,string windowTitle,Point sarok1,Point sarok2)
{
IntPtr hWnd = FindWindow(windowClass, windowTitle);
Bitmap bmp = null;
IntPtr hdcFrom = GetDC(hWnd);
IntPtr hdcTo = CreateCompatibleDC(hdcFrom);
RECT windowSize;
GetWindowRect(hWnd, out windowSize);
int height = windowSize.Bottom;
int width = windowSize.Right;
IntPtr hBitmap = CreateCompatibleBitmap(hdcFrom, width, height);
if (hBitmap != IntPtr.Zero)
{
// adjust and copy
IntPtr hLocalBitmap = SelectObject(hdcTo, hBitmap);
int posx, posy;
if (sarok1.X > sarok2.X)
{
posx = sarok2.X;
}
else
{
posx = sarok1.X;
}
if (sarok1.Y > sarok2.Y)
{
posy = sarok2.Y;
}
else
{
posy = sarok1.Y;
}
BitBlt(hdcTo, 0, 0, Math.Abs(sarok1.X-sarok2.X), Math.Abs(sarok1.Y-sarok2.Y),
hdcFrom, posx, posy, SRCCOPY);
SelectObject(hdcTo, hLocalBitmap);
//We delete the memory device context.
DeleteDC(hdcTo);
//We release the screen device context.
ReleaseDC(hWnd, hdcFrom);
//Image is created by Image bitmap handle and assigned to Bitmap variable.
bmp = System.Drawing.Image.FromHbitmap(hBitmap);
DeleteObject(hBitmap);
}
return bmp;
}
public static void main()
{
Bitmap b1 = new Bitmap(createBitmapFromWindow(null, "window title", new Point(557, 460), new Point(670, 500)));
Bitmap b = b1.Clone(new Rectangle(new Point(0, 0), new Size(110, 29)),PixelFormat.Format24bppRgb);
var ocr = new Tesseract();
ocr.Init(#"path", "eng", false);
b.SetResolution(300, 300);
List<Word> l = ocr.DoOCR(b, Rectangle.Empty);
}
Tesseract expects black font on white background. An invert, a histogram equalization and a conversion to grayscale will help.
Looks like your images will consists of numbers only. You can hint tesseract to look for a numbers (and the point only). But I don't know how to do that with Tessnet.
Tesseract doesn't like different sized fonts. If the result is still not good it might be necessary to split the image in two different images and feed them separately.
I am trying to take the screenshot of a particular area (application) in the desktop. Please, check this code...
try
{
//Process p = Process.Start("notepad");
//Process p = Process.GetProcessById(11152);
if (p == null)
Console.WriteLine("Got Null");
else
{
IntPtr h = p.Handle;
SetForegroundWindow(h);
ShowWindow(h, 9);
Rect rect = new Rect();
IntPtr error = GetWindowRect(p.MainWindowHandle, ref rect);
while (error == (IntPtr)0)
{
error = GetWindowRect(p.MainWindowHandle, ref rect);
}
Thread.Sleep(2000);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
System.IO.FileStream fs = System.IO.File.Create(#"D:\snapshot.jpg");
Bitmap bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics.FromImage(bitmap).CopyFromScreen(rect.left, rect.top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
bitmap.Save(fs, System.Drawing.Imaging.ImageFormat.Jpeg);
fs.Close();
bitmap.Dispose();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
and I have this outside this method..
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
When I try to open a new application and try to take screenshot (like notepad in this eg) it works like a charm. But when I try to take the screenshot of a currently running application (like task manager etc..) It does not work (I still get an image of small black rectangle).. Please help me..
Change
IntPtr h = p.Handle;
to
IntPtr h = p.MainWindowHandle;
and check.
I am trying to move winamps main window, with this code:
[DllImport("user32.dll")]
static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
static void resize()
{
Process w = new Process();
w.StartInfo = new ProcessStartInfo("winamp.exe");
w.Start();
Thread.Sleep(5000);
IntPtr hWnd = GetForegroundWindow();
RECT rect;
GetWindowRect(hWnd, out rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
MoveWindow(hWnd, 0, 0, width, height, true);
}
This code snippet works with all processes that I tested, except Winamp. When I use the mainWindowHandle of the process it moves another window.
Anyone an idea how to get it working with Winamp?
With the following code I can confirm changing WinAmp's main window size does not work via the Win32 API's.
Changing other winamp window sizes via the Win32 API's did work, but is not a solution:
public partial class Form1 : Form
{
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
[DllImport("user32.dll")]
static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
private System.IntPtr hWnd;
private void button1_Click(object sender, EventArgs e)
{
Process p = Process.Start(#"C:\Program Files\Winamp\winamp.exe");
try
{
do
{
p.Refresh();
}
while (p.MainWindowHandle.ToInt32() == 0);
hWnd = new IntPtr(p.MainWindowHandle.ToInt32());
}
catch (Exception ex)
{
//Do some stuff...
throw;
}
}
private void button2_Click(object sender, EventArgs e)
{
//Make sure we have a handle to the shelled exe
if (hWnd == new IntPtr(0)) return;
ResizeExternalExeChildWindows(hWnd);
}
private void ResizeExternalExeChildWindows(IntPtr parent)
{
List<IntPtr> childWindows = GetChildWindows(hWnd);
foreach (IntPtr childWindow in childWindows)
{
RECT rect;
GetWindowRect(childWindow, out rect);
int width = rect.Right - rect.Left;
int height = rect.Bottom - rect.Top;
MoveWindow(hWnd, 0, 0, width, height, true);
}
}
// <summary>
/// Returns a list of child windows
/// </summary>
/// <param name="parent">Parent of the windows to return</param>
/// <returns>List of child windows</returns>
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
return result;
}
/// <summary>
/// Callback method to be used when enumerating windows.
/// </summary>
/// <param name="handle">Handle of the next window</param>
/// <param name="pointer">Pointer to a GCHandle that holds a reference to the list to fill</param>
/// <returns>True to continue the enumeration, false to bail</returns>
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
// You can modify this to check to see if you want to cancel the operation, then return a null here
return true;
}
/// <summary>
/// Delegate for the EnumChildWindows method
/// </summary>
/// <param name="hWnd">Window handle</param>
/// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
/// <returns>True to continue enumerating, false to bail.</returns>
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
}
}
Cause
Because WinAmp is skinnable it supports resizing via config files (not via external apps using win32 API's).
Solution
Open the file C:\Users[username]\AppData\Roaming\Winamp\studio.xnf and edit the following values:
<entry name="Bento_nomax_h" value="492" />
<entry name="Bento_nomax_w" value="633" />
<entry name="Bento_nomax_x" value="27" />
<entry name="Bento_nomax_y" value="16" />
I am using the Bento Skin. If you open the WinAmp.ini file you can detect the skin being used:
skin=Bento
The solution to set the initial size for WinAmp's main window is to edit the config file before shelling WinAmp with Process.Start.
I have tried your code, it is not working always, and it is not re-sizing windows, it was moving it only,
Actually I found a problem in the 2000 ms sleep, which you can change to a while loop checking if the handle is zero then proceed with the code,
while (p.MainWindowHandle == 0)
{
}
IntPtr hWnd = p.MainWindowHandle;
this may help if the winamp takes too much time to build its own window
This worked for me using the latest version, please give it a try:
internal struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, ExactSpelling = true, SetLastError = true)]
internal static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
public static class HWND
{
public static IntPtr
NoTopMost = new IntPtr(-2),
TopMost = new IntPtr(-1),
Top = new IntPtr(0),
Bottom = new IntPtr(1);
}
[Flags]
public enum SetWindowPosFlags : uint
{
SWP_ASYNCWINDOWPOS = 0x4000,
SWP_DEFERERASE = 0x2000,
SWP_DRAWFRAME = 0x0020,
SWP_FRAMECHANGED = 0x0020,
SWP_HIDEWINDOW = 0x0080,
SWP_NOACTIVATE = 0x0010,
SWP_NOCOPYBITS = 0x0100,
SWP_NOMOVE = 0x0002,
SWP_NOOWNERZORDER = 0x0200,
SWP_NOREDRAW = 0x0008,
SWP_NOREPOSITION = 0x0200,
SWP_NOSENDCHANGING = 0x0400,
SWP_NOSIZE = 0x0001,
SWP_NOZORDER = 0x0004,
SWP_SHOWWINDOW = 0x0040,
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
static void Resize()
{
IntPtr winampMainWindow = IntPtr.Zero;
while (true)
{
winampMainWindow = FindWindow("BaseWindow_RootWnd", "Main Window");
if (winampMainWindow != IntPtr.Zero)
{
RECT rect = new RECT();
GetWindowRect(winampMainWindow, ref rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
SetWindowPos(winampMainWindow,
HWND.Top,
0,
0,
width,
height,
SetWindowPosFlags.SWP_SHOWWINDOW | SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOSENDCHANGING | SetWindowPosFlags.SWP_NOOWNERZORDER | SetWindowPosFlags.SWP_NOCOPYBITS);
break;
}
Thread.Sleep(1000);
}
}
[STAThread]
static void Main()
{
Process.Start("winamp.exe");
Resize();
}
Absolute nightmare!
However, right clicking in the "song window" DID open up the docking menu - I think the undock-(something) worked. One of these options allowed me to move the god-damned thing. It was a fluke, but something opened up there that worked.
I have code that takes a screenshot...
Size ssSize;
int ssX, ssY, ssWidth, ssHeight;
Bitmap thisScreenshot;
Graphics gfxScreenshot;
public Image Screenshot()
{
ssX = Screen.PrimaryScreen.Bounds.X;
ssY = Screen.PrimaryScreen.Bounds.Y;
ssWidth = Screen.PrimaryScreen.Bounds.Width;
ssHeight = Screen.PrimaryScreen.Bounds.Height;
ssSize = Screen.PrimaryScreen.Bounds.Size;
thisScreenshot = new Bitmap(ssWidth,ssHeight);
gfxScreenshot = Graphics.FromImage(thisScreenshot);
return((Image)gfxScreenshot.CopyFromScreen(ssX, ssY, 0, 0, ssSize));
}
On W7, the resulting image includes the pixels of the calling window;
but on XP it does not. I would like the image to always include the pixels of the
calling process/window. Any clue how I can force this?
UPDATE1:
I've done more experimentation with this, and as a result I'm more confused...
I took the above code and created a totally separate application so that there is no relationship between this and the application that I was originally launching it from.
Strangely enough, I am STILL not seeing the window of that application in the screenshot.
So now I have no relationship between the process doing the screenshot and the window that I want included in the screenshot; yet, that window is still not included.
I did try the PRNT-SCRN button and that does include the window.
Note that this is only a problem on XP.
Set your form's Opacity property to 100 and right-click the TransparencyKey property and select Reset. That ensures that your window is no longer a layered window and won't be missing from the screenshot.
If you want to keep these properties then you'll have to work around in a bug in Graphics.CopyFromScreen(). The overload that uses CopyPixelOperation with the CaptureBlt operation is required to capture layered windows. But won't work due to a bug in the argument validation code. The workaround isn't pretty but functional:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
Size sz = Screen.PrimaryScreen.Bounds.Size;
IntPtr hDesk = GetDesktopWindow();
IntPtr hSrce = GetWindowDC(hDesk);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, sz.Width, sz.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, 0, 0, sz.Width, sz.Height, hSrce, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
Bitmap bmp = Bitmap.FromHbitmap(hBmp);
SelectObject(hDest, hOldBmp);
DeleteObject(hBmp);
DeleteDC(hDest);
ReleaseDC(hDesk, hSrce);
bmp.Save(#"c:\temp\test.png");
bmp.Dispose();
}
// P/Invoke declarations
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
}
}