PrintScreen only active window - c#

I'm writing a simple Windows Forms utility to screenshot either the entire screen or an active window and save the file. The winform has a button called 'Capture' that, when clicked, takes the screenshot.
I have the code entirely working for the entire screen but can't figure it out for the active window.
By my current code, if I select 'Active Window', it ends up Print Screening the application itself which isn't what I want.
Here's my code (under the Capture_Click method):
try
{
this.Hide();
bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);
//bmpScreenshot = new Bitmap(this.Bounds.Width, this.Bounds.Height, PixelFormat.Format32bppArgb);
gfxScreenshot = Graphics.FromImage(bmpScreenshot);
if (AltPrint.Checked)
{
gfxScreenshot.CopyFromScreen(this.Bounds.X, this.Bounds.Y, 0, 0, this.Bounds.Size, CopyPixelOperation.SourceCopy);
}
else
{
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
}
bmpScreenshot.Save(SaveLocation, ImageFormat.Png);
number++;
}

In your button_Click method, to capture a window other than the one which is doing the capturing, the only possible way I can see is to make use of the user32 Windows API.
You will first need to platform invoke some methods like this:
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, GetWindowLongNIndex nIndex);
I recommend www.pinvoke.net/ for more info about this.
Here is my own Window class (work in progress, but useable) which you are welcome to use as an example: http://pastebin.com/fj2tEaPY
Some useful properties in there for you are:
get AllWindows
get TaskbarWindows
set ForegroundWindow
You'll need to get the handle of your current window (use get ForegroundWindow, then the Handle property), then find the window which you want to capture the image of, perhaps using code similar to that in TaskbarWindows. Once you have the window, you will need to send the keypresses Alt + PrtScn (again maybe using user32 API, but I won't go into it.) Then use the static ForegroundWindow property to set the orignal window as the forground using the handle which you saved at the start.
Good luck!

As far as I can see your code uses the bounds of its own form to capture the "active" window. That is probably not what you intend.
If you want to use the mouse to "print" a window (and not hotkey which probably is easier to implement) you need to be able to from your own form let the user point to another window to capture. Here is an idea describing how to do that:
When the user presses the "Capture" button capture the mouse and initiate a drag operation.
Let the user drag the mouse (perhaps now having a "bullseye" or a "camera" cursor) to the window to capture.
When the user releases the mouse determine the window it was on top and use the location and size of this window to capture the correct pixels.
You will probably need to use P/Invoke to get information about all top-level windows on the desktop.
Note that "mouse capture" is very different from "screen capture". When an application "captures the mouse" it takes ownership of the mouse cursor and doesn't the ability to track the mouse even when it is moved outside the bounds of the window of the application.

Your comments seem to understand that once your app gets the focus (when you click on it) the other app has lost it and is no longer 'active'.
A possible alternative: your application gets a WM_SETFOCUS message when it gets focus. The wParam of that message is a handle to whichever window has just given up the focus -- and that window belongs to the app you want to capture.
Of course, that window will only be a subwindow or control, but you can figure out which app owns that button.

I ended up taking a different route.
I allow the user to resize my form window to cover exactly the area they need to screenshot, and then when they click the button, the form hides itself, takes a screenshot of the area it was covering and appears again. Contrary to my initial plan, I believe this will be more beneficial, as my targeted users (friends at an overclocking forum I'm part of) seldom take screenshots of just one window. They mostly need a couple of windows covered (temp monitoring, clockspeed monitoring, stability test etc.).
Thanks for all the answers guys. They definitely got me thinking, and made me realize what my options were.
Should anyone else need it, this is the code I use for the button that captures only part of the screen:
try
{
this.Hide();
Thread.Sleep(250);
bmpScreenshot = new Bitmap(this.Bounds.Width, this.Bounds.Height, PixelFormat.Format32bppArgb);
gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(this.Bounds.X, this.Bounds.Y, 0, 0, this.Bounds.Size, CopyPixelOperation.SourceCopy);
bmpScreenshot.Save(SaveLocation, ImageFormat.Png);
tbxStatus.AppendText(Environment.NewLine);
tbxStatus.AppendText(Environment.NewLine);
tbxStatus.AppendText("Screenshot saved at " + SaveLocation);
numSuffix++;
}
catch (Exception ex)
{
tbxStatus.AppendText(Environment.NewLine);
tbxStatus.AppendText(Environment.NewLine);
tbxStatus.AppendText("Unable to take screenshot. Exception: " + ex.ToString());
}
finally
{
this.Show();
}

You need to borrow the design of commercially available screen capturing apps, such as Psp. I think you need to use a 'hot' key to trigger the capturing of the active window. Instead of doing it on Capture_Click event. This way, you can hide your application's window so that it does not become the active window. Then when the user pushes the 'hot' key, you can capture it.

What about SendKeys?
Windows.Forms.SendKeys.Send("%{PRTSC}");
It will copy currently selected window in to Clipboard object.
Than calling
BitmapSource image = Clipboard.GetImage();
you can get your image back

Related

Omit handle from GDI window capture (DirectShow Filter)

I currently have implemented a DirectShow live source screen capture filter in C#. The DShow filter is being consumed by a separate process (ffmpeg) that is live encoding the stream.
I would like to render some UI/Controls on the screen for starting/stopping the capture of ffmpeg, (UI provided by a separate process) but I don't want the UI to be captured by the DShow filter and end up in the screen capture video.
Here is an excerpt, which contains the DirectShow FillBuffer method where I capture the screen frames.
public int FillBuffer(ref IMediaSampleImpl _sample)
{
IntPtr _ptr;
_sample.GetPointer(out _ptr);
IntPtr srcContext = User32.GetWindowDC(IntPtr.Zero);
IntPtr destContext = GDI32.CreateCompatibleDC(srcContext);
IntPtr destBitmap = GDI32.CreateCompatibleBitmap(srcContext, _captureWidth, _captureHeight);
IntPtr hOld = GDI32.SelectObject(destContext, destBitmap);
GDI32.BitBlt(destContext, 0, 0, _captureWidth, _captureHeight, srcContext, _captureX, _captureY, GDI32.SRCCOPY);
//Draw cursor, ommitted for brevity
GDI32.SelectObject(destContext, hOld);
GDI32.GetDIBits(destContext, destBitmap, 0, (uint)Math.Abs(_captureHeight), _ptr, ref m_bmi, 0);
GDI32.DeleteDC(destContext);
User32.ReleaseDC(IntPtr.Zero, srcContext);
GDI32.DeleteObject(destBitmap);
_sample.SetActualDataLength(_sample.GetSize());
_sample.SetSyncPoint(true);
return NOERROR;
}
I'm not exactly 100% sure how this would be possible, but here are the only things that remotely make sense to me (not saying they are possible, just brainstorming):
Render the UI controls via a passed window pointer from another process to srcContext each frame but only after its copied to destContext
how would you even render the passed pointer?
how do you refresh the screen so that it wont just get captured next frame (now that its rendered to the screen)?
could this really be viable performance wise (refreshing the screen each frame)?
Is it really going to look nice? - i can't imagine that rendering to the WindowDC is reccomended
is there a way to trigger a re-draw of just the UI control region before the next frame and not refresh the entire screen?
Some how capture the window with GDI but omit the UI window handle via some native API interface i'm unaware of
Capture the entire screen with current method, then re-draw the area that contains the UI controls with the contents of whatever window(s) were underneath (is this even possible?)
Capture individual windows, and then render each visible window separately in z-order but skip the UI window (i have a sneaking suspicion that this may be the only way to actually do this, but i doubt that it would meet reasonable performance requirements)
EDIT, i just thought of using PrintWindow of all of the windows behind the UI control, but PrintWindow is slow and unreliable, and this is performance critical code.. So i would really like a different solution.
I understand that Fraps and similar, use DirectX hooks to intercept paint messages further up the pipeline, and are able to draw overlays on the screen that way - but this is not an option, and neither is a display mirror driver.

Set specific size to application's window programmatically

My program (C#) is a taskbar and in its menu I can launch applications, compatible XP to 8.1. I would like to start any applications with a specific window's size, because few applications (OpenOffice, LibreOffice...), start flattened when i launch them for the first time.
I've tested to maximize this window with ShellExecute with ShowCommands.SW_MAXIMIZE parameter:
ShellExecute(IntPtr.Zero, "open", executablePath, executableParam, "", ShowCommands.SW_MAXIMIZE);
But when I clicked on the "restore" button of the window, there is the same problem, the window is flattened.
While the "restored" size isn't configured by manual resizing, the used value is specific for each applications.
Instead of ShellExecute, I use CreateProcess to specify a size :
const uint NORMAL_PRIORITY_CLASS = 0x0020;
STARTUPINFO si = new STARTUPINFO();
si.dwY = 50;
si.dwX = 50;
si.dwXSize = 200;
si.dwYSize = 800;
si.dwFlags = 0x00000006; //STARTF_USESIZE + STARTF_USEPOSITION
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
CreateProcess(programPath, programParams, IntPtr.Zero, IntPtr.Zero, true, NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref si, out pi);
But there is one problem, we can launch an other OpenOffice/LibreOffice process with File->New->... In this case the application isn't launch by my program so my default size isn't applied.
I've checked the Windows Registry before and after changing this value, two keys are changed :
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU\reg]
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}\Count]
These keys contains hexa values (first key) :
before :
"MRUListEx"=hex:00,00,00,00,04,00,00,00,01,00,00,00,05,00,00,00,03,00,00,00,02,\00,00,00,ff,ff,ff,ff
after :
"MRUListEx"=hex:01,00,00,00,00,00,00,00,04,00,00,00,05,00,00,00,03,00,00,00,02,\00,00,00,ff,ff,ff,ff
It isn't understandable to me, so I'm still searching if exist a programmatic way to specific window size of an application/all applications or an other specification to set into the Windows Registry. I'm not searching a solution to resize or move a window already launched like SetWindowPos.
But many websites explain there's just one solution to define the window's size: "drag the window to the size, then close the window and restart it"
http://help.wfu.edu/public/computers/standard-load-software/windows-7---set-default-window-size
http://www.tomshardware.co.uk/forum/28659-45-change-default-window-size
This answer is the final solution or there is a way to do it programmatically?
Thanks in advance.
EDIT :
Actually, when a window is restored, I resize only the first time until the user kill it.
My program keeps every window's handle in an object with a boolean initialized to false to specify if my window has been resized. I use GetForegroundWindow then with GetWindowPlacement I check if WINDOWPLACEMENT.flags == WindowPlacementFlag.WPF_NONE and if my boolean is always egals to false in that case I use GetWindowRect and SetWindowPos to set a specific size with the same position and assign my boolean to true.
I think is a good solution, but I loose the saved position by user (I can do it when window's size is under a specific value but the problem still there) and we seen the resizing...
This problem usually occurs because the coder used GetWindowPos to get the position to save, but the window is closed while minimised, so the position is a tiny window hidden by the taskbar. This is then the position which is saved. The correct method is to use GetWindowPlacement to save the position.
You could enumerate all top-level windows, check GetWindowPlacement to see which are visible and restored but with an inappropriate size or position (e.g. off-screen, too small). Then you could call SetWindowPlacement to move those windows back to a reasonable minimum size on-screen.
In all likelihood when they save their position again they will save the new position.
Don't poll though. Instead make it a utility that the user can run when they need to. (You will probably need an exceptions list too - some utilities might create an off-screen window on purpose.)

How to detect if a window is visible on modern multi monitor systems

How do I detect if window is not visible to the user and then move it to a position that is visible to allow the user to size it to their liking.
The problem:-
I have a program that save the position and size of the form this works perfectly.
Unfortunately, many users now have multiple screen graphics cards that can be switched between duplicate and extended.
Therefore, if the user closes the form (the program saves where it was) and the user switches his system to say extended from duplicate then opens the program.
The program form is now not visible because it has moved off the limits of the current screen system :(
The user can see it running on the task bar. However clicking the icon only gives a brief flash of minimising or maximising the program and right clicking only has the option to close.
I need to trap for not visible to the user and then auto resize to any active window. The user can then resize/move as they see fit.
I would welcome any ideas/pointers
This answer was provided for the same question on the MSDN forums - essentially, it's a quick check of the bounds of the screen, and checks to see if the program is within those bounds. If not, it moves it to 0, 0:
using System.Windows.Forms;
class Form1 : Form {
protected void EnsureVisible() {
foreach (Screen scrn in Screen.AllScreens) {
// You may prefer Intersects(), rather than Contains()
if (scrn.Bounds.Contains(this.Bounds)) {
return;
}
}
this.Location = new Point( 0, 0 );
}
}
You could make use of the AllScreens property to get a list of all the screens connected to the computer. Using that, you can check the bounding boxes of each screen and test if your window lies outside all of them. If so, then reset the position.
And Jared Harley beat me to it, while I was typing this.

Find info about a window

I found out the Yahoo Messenger window that notifies you when someone signs in or out is the only window that actually appears on top of a full screen movie or game and won't force you to exit full screen.
So my question is how can I find out what makes this window behave like this? I tried Spy++ but nothing interesting came up.
There are different ways to do this. Some video card drivers on older versions of windows will behave differently.
1) Grab the desktop hwnd and paint to it.
HWND hwnd = GetDesktopWindow();
HDC hdc = GetDC(hwnd);
RECT rect = {};
GetClientRect(hwnd, &rect); // dimensions of the primary monitor are rect.right,rect.bottom
// Use hdc to paint whatever you want to the screen
2) Just create a top-most window without a titlebar and use the WS_EX_TOPMOST style. Then draw whatever you want on it
CreateWindowEx(WS_EX_TOPMOST, ...);
Could be using the windows notification API (I don't have Yahoo messenger, so I'm not sure). Here's some more information about the notification area:
http://msdn.microsoft.com/en-us/library/aa511448.aspx

How to effectively draw on desktop in C#?

I want to draw directly on the desktop in C#. From searching a bit, I ended up using a Graphics object from the Desktop HDC (null). Then, I painted normally using this Graphics object.
The problem is that my shapes get lost when any part of the screen is redrawn. I tried a While loop, but it actually ends up drawing as fast as the application can, which is not the update rate of the desktop.
Normally, I would need to put my drawing code in a "OnPaint" event, but such thing does not exist for the desktop.
How would I do it?
Example code: https://stackoverflow.com/questions/1536141/how-to-draw-directly-on-the-windows-desktop-c
I posted two solutions for a similar requirement here
Basically you have two options.
1- Get a graphics object for the desktop and start drawing to it. The problem is if you need to start clearing what you have previously drawn etc.
Point pt = Cursor.Position; // Get the mouse cursor in screen coordinates
using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
{
g.DrawEllipse(Pens.Black, pt.X - 10, pt.Y - 10, 20, 20);
}
2- The second option that I provide in the link above is to create a transparent top-most window and do all your drawing in that window. This basically provides a transparent overlay for the desktop which you can draw on. One possible downside to this, as I mention in the original answer, is that other windows which are also top-most and are created after your app starts will obscure your top most window. This can be solved if it is a problem though.
For option 2, making the form transparent is as simple as using a transparency key, this allows mouse clicks etc. to fall through to the underlying desktop.
BackColor = Color.LightGreen;
TransparencyKey = Color.LightGreen;
When you draw to HDC(NULL) you draw to the screen, in an unmanaged way. As you've discovered, as soon as windows refreshes that part of the screen, your changes are overwritten.
There are a couple of options, depending upon what you want to achieve:
create a borderless, possibly
non-rectangular window. (Use
SetWindowRgn to make a window
non-rectangular.) You can make this a child of the desktop window.
subclass the desktop window. This is not straightforward, and involves
injecting a DLL into the
Explorer.exe process.
To get an OnPaint for the desktop you would need to subclass the desktop window and use your drawing logic when it receives a WM_PAINT/WM_ERASEBKGND message.
As the thread you linked to says, you can only intercept messages sent to a window of an external process using a hook (SetWindowsHookEx from a DLL).
As mentioned a transparent window is another way to do it, or (depending on the update frequency) copying, drawing and setting a temporary wallpaper (as bginfo does).
This is difficult to do correctly.
It will be far easier, and more reliable, to make your own borderless form instead.

Categories

Resources