How can I take a screenshot of the desktop in WPF? Preferably with the mouse cursor showing.
Without trying to steal the answer, use the code give in the CodeProject article referenced by Johannes to create the GDI bitmap. You can then use the following code to convert it into a BitmapSource for use in WPF:
public static BitmapSource ToBitmapSource(this System.Drawing.Bitmap source)
{
var hBitmap = source.GetHbitmap();
try
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
catch (Win32Exception)
{
return null;
}
finally
{
NativeMethods.DeleteObject(hBitmap);
}
}
where the code for NativeMethods.DeleteObject() is:
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteObject(IntPtr hObject);
Related
I need to take a screenshot of a non-active external application, for example, TeamSpeak or Skype.
I have searched and i didn't find much, i know that it is not possible to screenshot a minimised application, but i think it should be possible to screenshot a non-active application.
PS : I want to screenshot just the application, so if another application is on top of the one i want, would it be a problem?
I have no code right now, i have found a user32 API that can do what i want but i forgot the name..
Thanks for the help.
The API you're after is PrintWindow:
void Example()
{
IntPtr hwnd = FindWindow(null, "Example.txt - Notepad2");
CaptureWindow(hwnd);
}
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr handle, ref Rectangle rect);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public void CaptureWindow(IntPtr handle)
{
// Get the size of the window to capture
Rectangle rect = new Rectangle();
GetWindowRect(handle, ref rect);
// GetWindowRect returns Top/Left and Bottom/Right, so fix it
rect.Width = rect.Width - rect.X;
rect.Height = rect.Height - rect.Y;
// Create a bitmap to draw the capture into
using (Bitmap bitmap = new Bitmap(rect.Width, rect.Height))
{
// Use PrintWindow to draw the window into our bitmap
using (Graphics g = Graphics.FromImage(bitmap))
{
IntPtr hdc = g.GetHdc();
if (!PrintWindow(handle, hdc, 0))
{
int error = Marshal.GetLastWin32Error();
var exception = new System.ComponentModel.Win32Exception(error);
Debug.WriteLine("ERROR: " + error + ": " + exception.Message);
// TODO: Throw the exception?
}
g.ReleaseHdc(hdc);
}
// Save it as a .png just to demo this
bitmap.Save("Example.png");
}
}
Using GetWindowRect coupled with PrintWindow from user32 API should be all you need to implement the feature. PrintWindow will properly capture the contents of a specific application even if it's obscured by another window on top of it.
It's worth noting that this might not work for capturing contents of DirectX windows.
I have a .cur file path ("%SystemRoot%\cursors\aero_arrow.cur") witch I want to display in an image control. So I need to convert Cursor to ImageSource. I tried both CursorConverter and ImageSourceConverter but had no luck. I also tried creating Graphics from the cursor and then converting it to Bitmap, But that didn't work either.
This thread says:
it is complicated to convert Cursor to Icon direcetly because Cursor
doesn't expose the imagesource it use.
and
If you really want to bind an image to cursor, there is an approach
you might want to try.
Since WindowForm is capable of drawing the cursor, we can use
WindowForm to draw cursor on a bitmap. After that we could find a way
to copy that bitmap to something WPF support.
Now the funny thing is that I can't create a new instance of System.Windows.Forms.Cursor with neither the file path nor the stream since It throws the following exception:
System.Runtime.InteropServices.COMException (0x800A01E1):
Exception from HRESULT: 0x800A01E1 (CTL_E_INVALIDPICTURE)
at System.Windows.Forms.UnsafeNativeMethods.IPersistStream.Load(IStream pstm)
at System.Windows.Forms.Cursor.LoadPicture(IStream stream)
So can anybody tell me the best way to convert System.Windows.Input.Cursor to ImageSource?
And what about .ani cursors? If I remember correctly System.Windows.Input.Cursor does not support animated cursors, So how can I show them to the user? Converting them to gif then using the 3d party gif libraries?
I found the solution in this thread: How to Render a Transparent Cursor to Bitmap preserving alpha channel?
So here is the code:
[StructLayout(LayoutKind.Sequential)]
private struct ICONINFO
{
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
[DllImport("user32")]
private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO pIconInfo);
[DllImport("user32.dll")]
private static extern IntPtr LoadCursorFromFile(string lpFileName);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool DeleteObject(IntPtr hObject);
private Bitmap BitmapFromCursor(Cursor cur)
{
ICONINFO ii;
GetIconInfo(cur.Handle, out ii);
Bitmap bmp = Bitmap.FromHbitmap(ii.hbmColor);
DeleteObject(ii.hbmColor);
DeleteObject(ii.hbmMask);
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
Bitmap dstBitmap = new Bitmap(bmData.Width, bmData.Height, bmData.Stride, PixelFormat.Format32bppArgb, bmData.Scan0);
bmp.UnlockBits(bmData);
return new Bitmap(dstBitmap);
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
//Using LoadCursorFromFile from user32.dll, get a handle to the icon
IntPtr hCursor = LoadCursorFromFile("C:\\Windows\\Cursors\\Windows Aero\\aero_busy.ani");
//Create a Cursor object from that handle
Cursor cursor = new Cursor(hCursor);
//Convert that cursor into a bitmap
using (Bitmap cursorBitmap = BitmapFromCursor(cursor))
{
//Draw that cursor bitmap directly to the form canvas
e.Graphics.DrawImage(cursorBitmap, 50, 50);
}
}
It's written for Win Forms and draws an image. But can be used in wpf as well with referencing to System.Windows.Forms. and then you can convert that bitmap to bitmap source and show it in an image control...
The reason I'm using System.Windows.Forms.Cursor instead of System.Windows.Input.Cursor is that I can't get to create a new instance of cursor using the IntPtr handle...
Edit: The above method does NOT work with cursors having low color bits.
An alternative is to use Icon.ExtractAssociatedIcon instead:
System.Drawing.Icon i = System.Drawing.Icon.ExtractAssociatedIcon(#"C:\Windows\Cursors\arrow_rl.cur");
System.Drawing.Bitmap b = i.ToBitmap();
Hope that helps someone ...
so ive been having a bit of a memory leak problem with my C# code, I have searched fairly thoroughly and do realize there is another question here WPF CreateBitmapSourceFromHBitmap() memory leak (and many others) that sort of cover the same topic but doesnt meet my criteria or solve my problem.
So here is my code, I got this from another project a while back
Im fairly positive that the problem has to do with // Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
Here is the full method code, this is being called like this and
BitmapSource captured = CaptureRegion(
hWnd,
rect.X,
rect.Y,
rect.Width,
rect.Height,
true);
And is also being called many times per second.(70-100ms per call)
// capture a region of a the screen, defined by the hWnd
public static BitmapSource CaptureRegion(
IntPtr hWnd, int x, int y, int width, int height, bool addToClipboard)
{
IntPtr sourceDC = IntPtr.Zero;
IntPtr targetDC = IntPtr.Zero;
IntPtr compatibleBitmapHandle = IntPtr.Zero;
BitmapSource bitmap = null;
//IntPtr hBitmap = bitmap.GetHbitmap();
// IntPtr hBitmap;
try
{
// gets the main desktop and all open windows
sourceDC = User32.GetDC(User32.GetDesktopWindow());
//sourceDC = User32.GetDC(hWnd);
targetDC = Gdi32.CreateCompatibleDC(sourceDC);
// create a bitmap compatible with our target DC
compatibleBitmapHandle = Gdi32.CreateCompatibleBitmap(sourceDC, width, height);
// gets the bitmap into the target device context
Gdi32.SelectObject(targetDC, compatibleBitmapHandle);
// copy from source to destination
Gdi32.BitBlt(targetDC, 0, 0, width, height, sourceDC, x, y, Gdi32.SRCCOPY);
// Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
catch (Exception ex)
{
throw new ScreenCaptureException(string.Format("Error capturing region {0},{1},{2},{3}", x, y, width, height), ex);
}
finally
{
Gdi32.DeleteObject(compatibleBitmapHandle);
// Gdi32.DeleteObject(hBitmap);
User32.ReleaseDC(IntPtr.Zero, sourceDC);
User32.ReleaseDC(IntPtr.Zero, targetDC);
}
return bitmap;
}
I really cant understand how I can apply the fix mentioned in the above thread to my problem, because my BitmapSource doesnt seem to be created from a bitmap, I cant see how deleting the bitmap object can help.
Thanks a lot for your help.
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;
}
This is the code I am using to take a screenshot:
[DllImport("gdi32.dll",EntryPoint="DeleteDC")]
public static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll",EntryPoint="DeleteObject")]
public static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll",EntryPoint="BitBlt")]
public static extern bool BitBlt(IntPtr hdcDest,int xDest,
int yDest,int wDest,int hDest,IntPtr hdcSource,
int xSrc,int ySrc,int RasterOp);
[DllImport ("gdi32.dll",EntryPoint="CreateCompatibleBitmap")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc,
int nWidth, int nHeight);
[DllImport ("gdi32.dll",EntryPoint="CreateCompatibleDC")]
public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport ("gdi32.dll",EntryPoint="SelectObject")]
public static extern IntPtr SelectObject(IntPtr hdc,IntPtr bmp);
[DllImport("user32.dll", EntryPoint="GetDesktopWindow")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll",EntryPoint="GetDC")]
public static extern IntPtr GetDC(IntPtr ptr);
[DllImport("user32.dll",EntryPoint="GetSystemMetrics")]
public static extern int GetSystemMetrics(int abc);
[DllImport("user32.dll",EntryPoint="GetWindowDC")]
public static extern IntPtr GetWindowDC(Int32 ptr);
[DllImport("user32.dll",EntryPoint="ReleaseDC")]
public static extern IntPtr ReleaseDC(IntPtr hWnd,IntPtr hDc);
public static Bitmap GetDesktopImage()
{
//In size variable we shall keep the size of the screen.
SIZE size;
//Variable to keep the handle to bitmap.
IntPtr hBitmap;
//Here we get the handle to the desktop device context.
IntPtr hDC = GetDC(GetDesktopWindow());
//Here we make a compatible device context in memory for screen
//device context.
IntPtr hMemDC = CreateCompatibleDC(hDC);
//We pass SM_CXSCREEN constant to GetSystemMetrics to get the
//X coordinates of the screen.
size.cx = GetSystemMetrics(SM_CXSCREEN);
//We pass SM_CYSCREEN constant to GetSystemMetrics to get the
//Y coordinates of the screen.
size.cy = GetSystemMetrics(SM_CYSCREEN);
//We create a compatible bitmap of the screen size and using
//the screen device context.
hBitmap = CreateCompatibleBitmap
(hDC, size.cx, size.cy);
//As hBitmap is IntPtr, we cannot check it against null.
//For this purpose, IntPtr.Zero is used.
if (hBitmap!=IntPtr.Zero)
{
//Here we select the compatible bitmap in the memeory device
//context and keep the refrence to the old bitmap.
IntPtr hOld = (IntPtr)SelectObject
(hMemDC, hBitmap);
//We copy the Bitmap to the memory device context.
BitBlt(hMemDC, 0, 0,size.cx,size.cy, hDC,0, 0,SRCCOPY);
//We select the old bitmap back to the memory device context.
SelectObject(hMemDC, hOld);
//We delete the memory device context.
DeleteDC(hMemDC);
//We release the screen device context.
ReleaseDC(GetDesktopWindow(), hDC);
//Image is created by Image bitmap handle and stored in
//local variable.
Bitmap bmp = System.Drawing.Image.FromHbitmap(hBitmap);
//Release the memory to avoid memory leaks.
DeleteObject(hBitmap);
//This statement runs the garbage collector manually.
GC.Collect();
//Return the bitmap
return bmp;
}
//If hBitmap is null, retun null.
return null;
}
It does capture a screenshot, but I want to take the screenshot with the tooltip visible. What do I have to add to take screenshot with the tooltip and cursor visible?
Vista has a snipping Tool to easily do so as given in http://blog.falafel.com/2008/08/12/CaptureScreenShotsWithToolTipsAndPopupsUsingVistaSnippingTool.aspx.
There is also an application via Silverlight: http://blogs.msdn.com/swick/archive/2007/12/18/snipping-pictures-with-silverlight.aspx
I don't know if source code is available for the snipping tool. You can try taking a video, then taking a snapshot of a part of the video as shown here.