I am trying to create a program that makes lots of screenshots in succession. Instead of re-creating the screenshots over and over, I only want to find the changes between screens.
To do this I used the GetUpdateRect() method on a screen-level. Unfortunately it does not give me correct data. As I'm relatively new to C#, I'm sure I did something wrong :P
This code should log all the screen changes, but instead it returns [0,0,0,0]:
[DllImport("User32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("User32.dll")]
public static extern bool GetUpdateRect(IntPtr hWnd, out Rectangle lpRect, bool bErase);
static void Main()
{
Rectangle updateRect;
GetUpdateRect(GetDesktopWindow(), out updateRect, false);
while (true)
{
Thread.Sleep(100);
Console.WriteLine(updateRect);
}
}
All help is greatly appreciated! :D
Try using the code listed on Pinvoke.Net to import the RECT type instead of using System.Drawing.Rectangle (as #Alvin Wong suggests) and changing the signature of the GetUpdateRect() method to match.
HTH
Related
First question on here, so if I can improve this posting feel free to tell me :)
I'm currently programming a rather simple .Net application in C# that uses "PrintWindow" from "user32.dll" in order to take screenshots of an other application even if it runs behind another window.
I aim to let my program run endlessly / for a long period of time, but I encounter I problem I cannot solve.
At around 10.000 Screenshots my application always crashes. Here is the Code I used in a console application to reproduce the bug and the error that comes with it:
class Program
{
/* Get Image even if Process is running behind another window ******************* */
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
/* ****************************************************************************** */
static void Main(string[] args)
{
Process process = ReturnProcess();
int counter = 0;
Console.WriteLine(RotMG.ToString());
while (true)
{
Bitmap bmpTest = CaptureWindow(RotMG.MainWindowHandle);
bmpTest.Dispose();
counter++;
Console.WriteLine(counter.ToString());
}
}
private static Process ReturnProcess()
{
Process[] processes = Process.GetProcessesByName("desiredProcess");
return processes[0];
}
public static Bitmap CaptureWindow(IntPtr hWnd)
{
Rectangle rctForm = System.Drawing.Rectangle.Empty;
using (Graphics grfx = Graphics.FromHdc(GetWindowDC(hWnd)))
{
rctForm = Rectangle.Round(grfx.VisibleClipBounds);
}
Bitmap pImage = new Bitmap(rctForm.Width, rctForm.Height);
Graphics graphics = Graphics.FromImage(pImage);
IntPtr hDC = graphics.GetHdc();
try
{
PrintWindow(hWnd, hDC, (uint)0);
}
finally
{
graphics.ReleaseHdc(hDC);
graphics.Dispose();
}
return pImage;
}
}
IntPtr hDC = graphics.GetHdc(); System.ArgumentException: Parameter not valid
In my real application it obviously is not supposed to capture images that fast, but the same error occurs after an a few hours.
I code the important code from here: https://codereview.stackexchange.com/questions/29364/capturing-and-taking-a-screenshot-of-a-window-in-a-loop
Do I have to ditch PrintWindow for my project? I would rather stick to it as it is the only way I found so far to capture a window which is in background.
All right!
I found the problem, hopefully this helps someone in the future. With the help of GDIView I found that my application leaked "DC" objects. GDI refuses to work if more than 10.000 Objects are created (which I should have looked up in the first place).
The DC that is not being deleted afterwards hides in the following line:
using (Graphics grfx = Graphics.FromHdc(GetWindowDC(hWnd)))
If you add the following reference:
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
and modify the code like this:
IntPtr WindowDC = GetWindowDC(hWnd);
using (Graphics grfx = Graphics.FromHdc(WindowDC))
{
rctForm = Rectangle.Round(grfx.VisibleClipBounds);
}
DeleteDC(WindowDC);
Then the DC object is deleted correctly, the program no longer exceeds 10.000 DC objects and thus does not crash anymore.
I am trying to change multiple cursors to Cross cursor. This is the code I am using for that matter:
[DllImport("user32.dll")]
static extern bool SetSystemCursor(IntPtr hcur, uint id);
[DllImport("user32.dll")]
static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern Int32 SystemParametersInfo(UInt32 uiAction,
UInt32 uiParam, String pvParam, UInt32 fWinIni);
//Normal cursor
private static uint OCR_NORMAL = 32512;
//The text selection (I-beam) cursor.
private static uint OCR_IBEAM = 32513;
//The cross-shaped cursor.
private static uint OCR_CROSS = 32515;
Then I use these two functions I made:
static public void ChangeCursors() {
SetSystemCursor(LoadCursor(IntPtr.Zero, (int)OCR_CROSS), OCR_NORMAL);
SetSystemCursor(LoadCursor(IntPtr.Zero, (int)OCR_CROSS), OCR_IBEAM);
}
static public void RevertCursors() {
SystemParametersInfo(0x0057, 0, null, 0);
}
If I just use SetSystemCursor(LoadCursor(IntPtr.Zero, (int)OCR_CROSS), OCR_NORMAL);, everything works fine. The Normal cursor gets replaced by Cross cursor.
My problem is when I try to change multiple cursors to Cross cursor. If I call ChangeCursors(), the expected result would be Normal cursor AND I-beam cursor gets replaced by Cross cursor. But the result is something really weird.
When the software starts depending on the current state of the cursor when the program started, the following strange things happen:
If cursor was Normal when the software started, it changes to Cross (that's good). Also, I-beam is replaced with Normal (that's bad, it should be Cross).
If cursor was I-beam when the software started, it stays I-beam(that's bad, because it should be Cross). Then, by hovering over to where previously the cursor should be Normal it is now Cross (that's good). Then, if I hover over to where the cursor was I-beam 1 second ago, it magically changes to Normal (that's weird) and stays that way.
So, my question is, how can I change 2 or more Cursors to Cross cursor, using SetSystemCursor() ?
Dont get confused about the weird behaviour. It's just the cursors getting swapped each time when you Assign.
At first
Normal == Normal
IBeam == IBeam
Cross == Cross
You Assign Normal = Cross
Normal == Cross
IBeam == IBeam
Cross == Normal
And now assign IBeam = Cross (Which is Normal now)
Normal == Cross
IBeam == Normal
Cross == IBeam
So for not letting it get swapped, you have to keep copies of all the cursors. I'll give you an example having Normal and IBeam changed to CROSS.
Program.cs
static class Program
{
[DllImport("user32.dll")]
static extern bool SetSystemCursor(IntPtr hcur, uint id);
[DllImport("user32.dll")]
static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern Int32 SystemParametersInfo(UInt32 uiAction, UInt32
uiParam, String pvParam, UInt32 fWinIni);
[DllImport("user32.dll")]
public static extern IntPtr CopyIcon(IntPtr pcur);
private static uint CROSS = 32515;
private static uint NORMAL = 32512;
private static uint IBEAM = 32513;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
uint[] Cursors = {NORMAL, IBEAM};
for (int i = 0; i < Cursors.Length; i++)
SetSystemCursor(CopyIcon(LoadCursor(IntPtr.Zero, (int)CROSS)), Cursors[i]);
Application.Run(new Form1());
SystemParametersInfo(0x0057, 0, null, 0);
}
}
SetSystemCursor replaces the system cursor given by the second argument (OCR_CROSS in this example) with the cursor in the first argument. So, you set the OCR_CROSS cursor first to Normal, then to IBeam, so in effect the cross-cursor is set to look like an IBeam.
The documentation also specifically says
The system destroys hcur [the first argument] by calling the DestroyCursor function. Therefore, hcur cannot be a cursor loaded using the LoadCursor function. To specify a cursor loaded from a resource, copy the cursor using the CopyCursor function, then pass the copy to SetSystemCursor.
Your code does this, so things could go wrong here, or at the least leak handles.
In-depth look
The SetSystemCursor is far more invasive then you might think. It actually swaps the global cursor-data of the specified cursor in the second argument by the cursor object in the first argument.
This has consequences. Say that you replaced IDC_WAIT by IDC_CROSS and then IDC_ARROW by IDC_WAIT (code in C):
HCURSOR hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_CROSS));
HCURSOR hCopyCursor = CopyCursor(hCursor);
SetSystemCursor(hCopyCursor, (DWORD)IDC_WAIT); // Replace Wait by Cross
HCURSOR hCursor2 = LoadCursor(0, MAKEINTRESOURCE(IDC_WAIT)); // Load whatever is in Wait and put it in Arrow
HCURSOR hCopyCursor2 = CopyCursor(hCursor2);
SetSystemCursor(hCopyCursor2, (DWORD)IDC_ARROW); // Replace Arrow by Wait.
Question: Is the Arrow cursor of the system a Cross or a Wait cursor?
Answer: It's a Cross cursor.
The reason this happens is because you actually swap the underlying cursor data, not just some reference, so LoadCursor reads the replaced cursor data.
This also shows why it gets very confusing when you don't make a copy of the cursor first: then the two cursors are getting swapped globally.
I am trying to create a form in C# that is fully transparent, but will not allow clicks to go through it to the other windows below.
I have found two methods that were promising, but did not achieve the results I wanted.
The first is by setting the background color and transparency key to the same value. This gives me the transparent form, but clicking goes through.
this.BackColor = Color.Red;
this.TransparencyKey = Color.Red;
The other thing I tried is to set the opacity of the form to 1%. This creates the effects I wanted - almost. I get a 99% transparent form, but there is a slight color alteration to whatever is underneath the form. Since the app I am making is meant to be used in color-sensitive context (graphic design and such), even a tiny alteration of the colors is unacceptable. So I turn to you, dear SO. Can this be done?
I have found the solution, and I am sharing it with you guys as well.
The answer was quite simple: I set: this.TransparencyKey = Color.Lime;
And then I used a 1x1px Lime PNG as the background image. This also has the added benefit of not obscuring the form border and title bar. I will remove them later, but at the moment, it's useful to know where the form is located.
I found a solution to this completely by accident.
I wanted a click through transparent window and got it by using the answer in this SO question:
C# cursor highlighting/follower
That answer uses a transparency filter of LightGreen, but I thought that I might need to use that Color so I changed it to AliceBlue and click through stopped working. I switched back to LightGreen and it started working again.
Instead of trying to capture mouse actions on the transparent form, you can try just capturing mouse actions system-wide (clicks, moves) and handle them as you need.
This can be done in the following way (assuming the drawn-on form keeps maximized. If not, see the next paragraphs below):
Take screenshot of the current screen.
Create a form and use the screenshot as the background image.
Remove form title from the form, simply make it as a panel.
While the solution above solves what you want, you need to answer the question:
How will the user close the form he's drawing on?
If the form needs to be resized-moved - complicated version
However, if you want to resize this form (just noticed your edit with the new screenshots), then you need to cut the part of the taken screenshot and show it as the background of the form. But this goes farther then: you need to do it every time the form is resized or moved.
I personally would take the first (simpler) approach.
You can set hook to catch the mouse events.
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public class MouseStruct
{
public Point pt;
public int hwnd;
public int wHitTestCode;
public int dwExtraInfo;
}
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private int hHook;
public const int WH_MOUSE_LL = 14;
public static HookProc hProc;
public int SetHook()
{
hProc = new HookProc(MouseHookProc);
hHook = SetWindowsHookEx(WH_MOUSE_LL, hProc, IntPtr.Zero, 0);
return hHook;
}
public void UnHook()
{
UnhookWindowsHookEx(hHook);
}
//callback function, invoked when there is an mouse event
private int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
var MyMouseStruct = (MouseStruct)Marshal.PtrToStructure(lParam, typeof(MouseStruct));
if (nCode < 0)
{
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
else
{
switch wParam{
case (IntPtr)513:
//click
//do whatever you want
case (IntPtr)512:
//move
case (IntPtr)514:
//release
default:
}
Cursor.Position = MyMouseStruct.pt;
//stop the event from passed to other windows.
return 1;
}
}
More details at MSDN.
The application that I'm running needs to call a separate app to do some scanning. I'm calling the other application by starting a new System.Diagnostics.Process. Once I get that process, I call a method to give that application the focus. I've tried two different ways to give that external app the focus, but neither are working. Could someone help?
Here's the code:
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, uint windowStyle);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd,
IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
private static void GiveSpecifiedAppTheFocus(int processID)
{
try
{
Process p = Process.GetProcessById(processID);
ShowWindow(p.MainWindowHandle, 1);
SetWindowPos(p.MainWindowHandle, new IntPtr(-1), 0, 0, 0, 0, 3);
//SetForegroundWindow(p.MainWindowHandle);
}
catch
{
throw;
}
}
First scenario uses the ShowWindow and SetWindowPos methods, the other method uses the SetForegroundWindow method. Neither will work...
Am I using the wrong methods, or do I have an error in the code that I'm using? Thanks all!
Use SetWindowPos, but whenever you don't want the window to be the topmost anymore call it again with the second parameter set to -2 (HWND_NOTOPMOST) instead of -1(HWND_TOPMOST)
Try setting your process to the background ie: this.SendToBack(); This is just another solution, partial fix.
Problem
When you search for such question using google you get a lot of hits but all solutions assume you have at least one window.
But my question is just like I phrased it -- not assumptions at all. I can have a window, but I could have zero windows (because I didn't even show one or I just closed the last one). So in short the solution cannot rely on any widget or window -- the only thing is known, is there is a desktop (and app running, but it does not have any windows).
So the question is -- how to get the mouse position?
Background
I would like to show windows centered to mouse position. There is no such mode in WPF (there are only center to owner, or center to screen) so I have to do it manually. The missing piece is mouse position.
Edits
Thank you all, so now I have the first part of the solution -- raw position. Now there is a problem how to convert the data for WPF. I found such topic:
WPF Pixels to desktop pixels
but again, it assumes having some window.
Then I googled more and I found solution:
http://jerryclin.wordpress.com/2007/11/13/creating-non-rectangular-windows-with-interop/
the code includes class for scaling up/down coordinates relying only on info about desktop. So joining those two pieces, I finally get the solution :-). Thanks again.
Getting the Screen Coordinates:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
private void WritePoint(object sender, RoutedEventArgs e)
{
POINT p;
if (GetCursorPos(out p))
{
System.Console.WriteLine(Convert.ToString(p.X) + ";" + Convert.ToString(p.Y));
}
}
Converting Pixels to WPF Units:
[DllImport("User32.dll")]
static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
private Point ConvertPixelsToUnits(int x, int y)
{
// get the system DPI
IntPtr dDC = GetDC(IntPtr.Zero); // Get desktop DC
int dpi = GetDeviceCaps(dDC, 88);
bool rv = ReleaseDC(IntPtr.Zero, dDC);
// WPF's physical unit size is calculated by taking the
// "Device-Independant Unit Size" (always 1/96)
// and scaling it by the system DPI
double physicalUnitSize = (1d / 96d) * (double)dpi;
Point wpfUnits = new Point(physicalUnitSize * (double)x,
physicalUnitSize * (double)y);
return wpfUnits;
}
Putting both together:
private void WriteMouseCoordinatesInWPFUnits()
{
POINT p;
if (GetCursorPos(out p))
{
Point wpfPoint = ConvertPixelsToUnits(p.X, p.Y);
System.Console.WriteLine(Convert.ToString(wpfPoint.X) + ";" + Convert.ToString(wpfPoint.Y));
}
}
Two options:
Use System.Windows.Forms.Control.MousePosition, or p/invoke
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern bool GetCursorPos([In, Out] NativeMethods.POINT pt);
The first option already does the p/invoke for you. I'm not entirely sure it requires you have some UI splashed up, but I don't think so. Yes, its winforms and not wpf, but it really doesn't have anything to do with where its located at.
If you want to skip any dependencies on system.windows.forms.dll then check out more information about the second on pinvoke.net.
I stumbled over that thread while looking for a solution for the same problem. In the meantime, I found PointToScreen, which does not require any P/Invoke. The method is available on any Visual starting .NET 3.0 (and thus UIElement, Control, etc.) and an implementation would look like this:
protected void OnMouseLeave(object Sender, MouseEventArgs e) {
var relativePosition = e.GetPosition(this);
var screenPosition = this.PointToScreen(relativePosition);
}