This question has been asked for other languages, and even for those other languages, I have found their answers lacking in how to exactly do it, cleanly (no messed up screen repaints, etc..).
Is it possible to draw onto the Windows Desktop from C#? I am looking for an example if possible.
Try the following:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
class Program {
[DllImport("User32.dll")]
static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("User32.dll")]
static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);
static void Main(string[] args) {
IntPtr desktop = GetDC(IntPtr.Zero);
using (Graphics g = Graphics.FromHdc(desktop)) {
g.FillRectangle(Brushes.Red, 0, 0, 100, 100);
}
ReleaseDC(IntPtr.Zero, desktop);
}
}
You can try:
Graphics.FromHwnd(IntPtr.Zero)
You can see a real-world code example within https://uiautomationverify.codeplex.com/SourceControl/latest#UIAVerify/Tools/visualuiverify/utils/screenrectangle.cs
This draws a rectangle that will appear on the screen until the user chooses to remove it at an arbitrary position (wont be repainted over). It uses a windows form thats hidden/ appears as a popup.
This is the code behind the UIAVerify.exe tool in the current Windows SDK.
If you want to use the above, copy the following files into your project:
utils\screenboundingrectangle.cs
utils\screenrectangle.cs
win32\*
Might need to update namespaces accordingly + add references to System.Drawing + System.Windows.Forms
Then you can draw a rectangle with the following code:
namespace Something
{
public class Highlighter
{
ScreenBoundingRectangle _rectangle = new ScreenBoundingRectangle();
public void DrawRectangle(Rectangle rect)
{
_rectangle.Color = System.Drawing.Color.Red;
_rectangle.Opacity = 0.8;
_rectangle.Location = rect;
this._rectangle.Visible = true;
}
}
}
and
var rect = Rectangle.FromLTRB(100, 100, 100, 100);
var hi = new Highlighter();
hi.DrawRectangle(rect);
Related
I am trying to screen capture a rectangle based on the X,Y position of the mouse. I have a couple of issues with my code:
1.)I want the mouse cursor to be at the center of the rectangle(I don't need the cursor in the screen shot).
2.)The screenshots look ok on the first monitor but on the second monitor the screen shots seem to be offset. The reason it is offset is because monitor #2 has Scale And Layout setting of 125%.
Any help with this would be appreciated thanks.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows;
using System.Drawing;
using System.Drawing.Imaging;
namespace Test
{
public static class MouseScreenCapture
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(out POINT pt);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public static implicit operator System.Drawing.Point(POINT point)
{
return new System.Drawing.Point(point.X, point.Y);
}
}
static void Main(string[] args)
{
Start();
}
private static System.Threading.Timer _timer = null;
public static void Start()
{
_timer = new System.Threading.Timer(TimerCallback, null, 0, 5000);
}
private static void TimerCallback(Object o)
{
GetMousePositionAndScreenCapture();
}
public static void GetMousePositionAndScreenCapture()
{
POINT lpPoint ;
GetCursorPos(out lpPoint);
System.Drawing.Size sz = new System.Drawing.Size();
sz.Height = 100;
sz.Width = 100;
var bounds = new Rectangle(lpPoint, sz);
var image = new Bitmap(bounds.Width, bounds.Height);
using (var graphics = Graphics.FromImage(image))
{
graphics.CopyFromScreen(new System.Drawing.Point(bounds.Left, bounds.Top), System.Drawing.Point.Empty, bounds.Size);
}
image.Save(#"C:\temp\newss.jpeg", ImageFormat.Jpeg);
}
}
}
As far as issue one, where you want the arrow cursor to be at the center of screenshot, would appear fairly straight forward. If the size of the screenshot is 100x100 then it would appear that when you get the Point of the cursor that you would want to move the X value -50 and the Y value -50. Basically half the width and height of the screenshot rectangle size. This would put the cursor at the center of the screenshot rectangle.
The only thing you may want to look for is if the cursor’s X or Y positions are less than 50, in which case we would simply set the value to 0. Adding these changes to lpPoint should fix this. Something like…
lpPoint.X = lpPoint.X >= 50 ? lpPoint.X - 50 : 0;
lpPoint.Y = lpPoint.Y >= 50 ? lpPoint.Y - 50 : 0;
For the second issue I can only assume this may have something to do with possibly different resolutions of the monitors. I would think it would still work, as it appears to work in my small tests. However, this is speculation on my part. Can you show a screen shot of both monitors showing what you are describing?
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'm trying to make a screen capture manager & recorder, both screenshot & video, although the latter is not important for this question.
The user can select a process he/she wants to be captured and set a hotkey.
The screenshots work fine for the regular desktop and some games. However, when trying to capture a screenshot from some games (e.g Splinter Cell Blacklist), if these games are full-screen, and depending on Windows 7 Aero, the contents are black, or the desktop is showing with a small bar at the top left of the screen.
Here's my initial code:
public static Bitmap GetScreen()
{
Bitmap bmpScreenCapture = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
using (Graphics g = Graphics.FromImage(bmpScreenCapture))
{
g.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,
Screen.PrimaryScreen.Bounds.Y,
0, 0,
bmpScreenCapture.Size,
CopyPixelOperation.SourceCopy);
}
return bmpScreenCapture;
}
During my search I found a code project article:
http://www.codeproject.com/Articles/274461/Very-fast-screen-capture-using-DirectX-in-Csharp
And slightly modified the code
public Surface CaptureScreen()
{
Surface s = Surface.CreateOffscreenPlain(d, rc.Width, rc.Height, Format.A8R8G8B8, Pool.Scratch);
d.GetFrontBufferData(0, s);
Surface.ToFile(s, #"C:\test\img.jpg", ImageFileFormat.Jpg);
return s;
}
However, also that image comes out black.
Does anyone have any suggestions as to how this is done?
Use this code (which I found here) to disable Aero before taking any screenshots, then re-enable it.
public readonly uint DWM_EC_DISABLECOMPOSITION = 0;
public readonly uint DWM_EC_ENABLECOMPOSITION = 1;
[DllImport("dwmapi.dll", EntryPoint = "DwmEnableComposition")]
protected static extern uint Win32DwmEnableComposition(uint uCompositionAction);
public void ManageAero(bool a)
{
if (a)
Win32DwmEnableComposition(DWM_EC_ENABLECOMPOSITION );
if (!a)
Win32DwmEnableComposition(DWM_EC_DISABLECOMPOSITIO N);
}
I have this code where it makes the form always on top, transparent and click through.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace HyperBox
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.TopMost = true; // make the form always on top
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; // hidden border
this.WindowState = FormWindowState.Maximized; // maximized
this.MinimizeBox = this.MaximizeBox = false; // not allowed to be minimized
this.MinimumSize = this.MaximumSize = this.Size; // not allowed to be resized
this.TransparencyKey = this.BackColor = Color.Red; // the color key to transparent, choose a color that you don't use
// Set the form click-through
int initialStyle = GetWindowLong(this.Handle, -20);
SetWindowLong(this.Handle, -20, initialStyle | 0x80000 | 0x20);
}
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int SetParent(int hWndChild, int hWndNewParent);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(
[MarshalAs(UnmanagedType.LPTStr)] string lpClassName,
[MarshalAs(UnmanagedType.LPTStr)] string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr SetParent(
IntPtr hWndChild, // handle to window
IntPtr hWndNewParent // new parent window
);
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// draw what you want
e.Graphics.FillRectangle(Brushes.Red, new Rectangle((SystemInformation.WorkingArea.Width / 2) - 4, (SystemInformation.WorkingArea.Height / 2) - 20, 8, 40));
e.Graphics.FillRectangle(Brushes.Red, new Rectangle((SystemInformation.WorkingArea.Width / 2) - 20, (SystemInformation.WorkingArea.Height / 2) - 4, 40, 8));
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
IntPtr hwndf = this.Handle;
IntPtr hwndParent = FindWindow("chrome.exe", null);
SetParent(hwndf, hwndParent);
}
}
}
The problem is when I draw the graphics, it draws nothing. When the coordinates are around 100~ it works. But when it does the method above nothing happens. At all, not even a pixel. Could someone please explain why this is happening and or repost a fixed snippet, thank you. Layne
OnPaint is giving you a graphics object for your form, not the screen. You are filling rectangles based on the working area of the system, not the form. You will need to adjust your rectangle coordinates and position the form where you want your graphics to appear. A rectangle with a location of (0, 0) is the top-left corner of the form's client area. You should also be able to access that rectangle by calling ClientRectangle that is exposed on the base Form class.
Take a look at this question for drawing outside your form: Draw / Paint Outside Form
That should get you started in the right direction if you don't want to paint on your form, but it would probably be easier to reposition and resize your form as needed.
EDIT It would probably be wise to at least add a some sort of border while you debug your issue. This will help you see where the form is positioned and what monitor it is on. You can then check your numbers as you break point in OnPaint to make sure you are creating your rectangles correctly, however, making sure you are painting within the form's client area should fix your issue.
Could someone provide an example for drawing graphics without using Windows Forms? I have an app that doesn't have a console window or Windows form, but i need to draw some basic graphics (lines and rectangles etc.)
Hope that makes sense.
This should give you a good start:
[TestFixture]
public class DesktopDrawingTests {
private const int DCX_WINDOW = 0x00000001;
private const int DCX_CACHE = 0x00000002;
private const int DCX_LOCKWINDOWUPDATE = 0x00000400;
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetDCEx(IntPtr hwnd, IntPtr hrgn, uint flags);
[Test]
public void TestDrawingOnDesktop() {
IntPtr hdc = GetDCEx(GetDesktopWindow(),
IntPtr.Zero,
DCX_WINDOW | DCX_CACHE | DCX_LOCKWINDOWUPDATE);
using (Graphics g = Graphics.FromHdc(hdc)) {
g.FillEllipse(Brushes.Red, 0, 0, 400, 400);
}
}
}
Something like this?
using System.Drawing;
Bitmap bmp = new Bitmap(200, 100);
Graphics g = Graphics.FromImage(bmp);
g.DrawLine(Pens.Black, 10, 10, 180, 80);
The question is a little unfocused. Specifically - where do you want to draw the lines and rectangles? Generally speaking, you need a drawing surface, usually provided by a windows form.
Where does the need to avoid windows forms come from?
Are you using another kind of window?
For a windows form you could use code similar to this:
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
e.Graphics.DrawLine(new Pen(Color.DarkGreen), 1,1, 3, 20 );
e.Graphics.DrawRectangle(new Pen(Color.Black), 10, 10, 20, 32 );
}
}
}
You can generally do this with any object that lets you get a handle for a "Graphics" object (like a printer).
Right, the way i've done it is with a windows form, but make the background transparent, and then get rid of all the borders...
Thanks for the replys anyway..
J