Capturing webpage as image in c#, ensuring javascript rendered elements are visible - c#

I am trying to capture the following page using standard c# .net code. I've searched around for people's various methods, most of which involve instantiating a browser object and using a draw to bitmap method. However, none of these pick up the contents of the chart on this page:
http://www.highcharts.com/demo/combo-dual-axes
Perhaps the javascript doesn't have time to run, but adding Thread.Sleep(x) hasn't assisted.
This commercial component captures it correctly, but I'd rather avoid requiring an additional dependency in my project and paying $150 when the other solutions are sooo close!.
Anyone find their solution renders this correctly?

You have possibly tried IECapt. I think it is the right way to go. I created a modified version of it and use a timer instead of Thread.Sleep it captures your site as expected.
------EDIT------
Here is the ugly source. Just Add a reference to Microsoft HTML Object Library.
And this is the usage:
HtmlCapture capture = new HtmlCapture(#"c:\temp\myimg.png");
capture.HtmlImageCapture += new HtmlCapture.HtmlCaptureEvent(capture_HtmlImageCapture);
capture.Create("http://www.highcharts.com/demo/combo-dual-axes");
void capture_HtmlImageCapture(object sender, Uri url)
{
this.Close();
}
File1
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.IO;
namespace MyIECapt
{
public class HtmlCapture
{
private WebBrowser web;
private Timer tready;
private Rectangle screen;
private Size? imgsize = null;
//an event that triggers when the html document is captured
public delegate void HtmlCaptureEvent(object sender, Uri url);
public event HtmlCaptureEvent HtmlImageCapture;
string fileName = "";
//class constructor
public HtmlCapture(string fileName)
{
this.fileName = fileName;
//initialise the webbrowser and the timer
web = new WebBrowser();
tready = new Timer();
tready.Interval = 2000;
screen = Screen.PrimaryScreen.Bounds;
//set the webbrowser width and hight
web.Width = 1024; //screen.Width;
web.Height = 768; // screen.Height;
//suppress script errors and hide scroll bars
web.ScriptErrorsSuppressed = true;
web.ScrollBarsEnabled = false;
//attached events
web.Navigating +=
new WebBrowserNavigatingEventHandler(web_Navigating);
web.DocumentCompleted += new
WebBrowserDocumentCompletedEventHandler(web_DocumentCompleted);
tready.Tick += new EventHandler(tready_Tick);
}
public void Create(string url)
{
imgsize = null;
web.Navigate(url);
}
public void Create(string url, Size imgsz)
{
this.imgsize = imgsz;
web.Navigate(url);
}
void web_DocumentCompleted(object sender,
WebBrowserDocumentCompletedEventArgs e)
{
//start the timer
tready.Start();
}
void web_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
//stop the timer
tready.Stop();
}
void tready_Tick(object sender, EventArgs e)
{
try
{
//stop the timer
tready.Stop();
mshtml.IHTMLDocument2 docs2 = (mshtml.IHTMLDocument2)web.Document.DomDocument;
mshtml.IHTMLDocument3 docs3 = (mshtml.IHTMLDocument3)web.Document.DomDocument;
mshtml.IHTMLElement2 body2 = (mshtml.IHTMLElement2)docs2.body;
mshtml.IHTMLElement2 root2 = (mshtml.IHTMLElement2)docs3.documentElement;
// Determine dimensions for the image; we could add minWidth here
// to ensure that we get closer to the minimal width (the width
// computed might be a few pixels less than what we want).
int width = Math.Max(body2.scrollWidth, root2.scrollWidth);
int height = Math.Max(root2.scrollHeight, body2.scrollHeight);
//get the size of the document's body
Rectangle docRectangle = new Rectangle(0, 0, width, height);
web.Width = docRectangle.Width;
web.Height = docRectangle.Height;
//if the imgsize is null, the size of the image will
//be the same as the size of webbrowser object
//otherwise set the image size to imgsize
Rectangle imgRectangle;
if (imgsize == null) imgRectangle = docRectangle;
else imgRectangle = new Rectangle() { Location = new Point(0, 0), Size = imgsize.Value };
//create a bitmap object
Bitmap bitmap = new Bitmap(imgRectangle.Width, imgRectangle.Height);
//get the viewobject of the WebBrowser
IViewObject ivo = web.Document.DomDocument as IViewObject;
using (Graphics g = Graphics.FromImage(bitmap))
{
//get the handle to the device context and draw
IntPtr hdc = g.GetHdc();
ivo.Draw(1, -1, IntPtr.Zero, IntPtr.Zero,
IntPtr.Zero, hdc, ref imgRectangle,
ref docRectangle, IntPtr.Zero, 0);
g.ReleaseHdc(hdc);
}
//invoke the HtmlImageCapture event
bitmap.Save(fileName);
bitmap.Dispose();
}
catch
{
//System.Diagnostics.Process.GetCurrentProcess().Kill();
}
if(HtmlImageCapture!=null) HtmlImageCapture(this, web.Url);
}
}
}
and File2
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
namespace MyIECapt
{
[ComVisible(true), ComImport()]
[GuidAttribute("0000010d-0000-0000-C000-000000000046")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IViewObject
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int Draw(
[MarshalAs(UnmanagedType.U4)] UInt32 dwDrawAspect,
int lindex,
IntPtr pvAspect,
[In] IntPtr ptd,
IntPtr hdcTargetDev,
IntPtr hdcDraw,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcBounds,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcWBounds,
IntPtr pfnContinue,
[MarshalAs(UnmanagedType.U4)] UInt32 dwContinue);
[PreserveSig]
int GetColorSet([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [In] IntPtr ptd,
IntPtr hicTargetDev, [Out] IntPtr ppColorSet);
[PreserveSig]
int Freeze([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [Out] IntPtr pdwFreeze);
[PreserveSig]
int Unfreeze([In, MarshalAs(UnmanagedType.U4)] int dwFreeze);
}
}

Thread.Sleep will simply suspend the thread your web browser is running on - how do you expect it to render anything when it is suspended? :)
Instead, you need to allow the thread to process work. You can achieve this with a combination of Thread.Sleep(0) and Application.DoEvents(), with something like the following:
DateTime finish = DateTime.Now.AddSeconds(3);
while (DateTime.Now < finish) {
Application.DoEvents();
Thread.Sleep(0);
}

#L.B , thank you for the help!
Just an FYI for anyone wanting to run it in a class library,
WebBrowser needs to Single Threaded Apartment, so do something like this:
var t = new Thread(InitAndDo); //InitAndDo would have your code creating the webbrowser object etc...
t.SetApartmentState(ApartmentState.STA);
t.Start();
Then the Gotcha, after the navigate call is done, add this line of code so that you get the completed navigation event:
web.Navigate(Url);
Application.Run();

I created a nuget package for this purpose
https://github.com/dcumin39/RenderHighCharts/wiki

Related

WebBrowser Html Document to Image

I'm trying to make image of webpage, but some pages shows me as white page.
In Registry editor browse \HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION\
and add there this:
WindowsFormsApp1.exe with decimal value 11000
WindowsFormsApp1.vshost.exe with decimal value 11000
Here is my code:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
Dictionary<Uri, Bitmap> browserShots = new Dictionary<Uri, Bitmap>();
WebBrowser browser = new WebBrowser();
public Form1()
{
InitializeComponent();
browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
}
//=========================================MADE BY JIMY====================================
private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var browser = sender as WebBrowser;
if (browser.ReadyState != WebBrowserReadyState.Complete) return;
var bitmap = WebBrowserExtender.DrawContent(browser);
if (bitmap != null)
{
if (!browserShots.ContainsKey(browser.Url))
browserShots.Add(browser.Url, bitmap);
else
{
browserShots[browser.Url]?.Dispose();
browserShots[browser.Url] = bitmap;
}
// Show the Bitmap in a PictureBox control, eventually
pictureBox1.Image = browserShots[browser.Url];
}
}
public class WebBrowserExtender
{
public static Bitmap DrawContent(WebBrowser browser)
{
if (browser.Document == null) return null;
Size docSize = Size.Empty;
Graphics g = null;
var hDc = IntPtr.Zero;
try
{
docSize.Height = (int)((dynamic)browser.Document.DomDocument).documentElement.scrollHeight;
docSize.Width = (int)((dynamic)browser.Document.DomDocument).documentElement.scrollWidth;
docSize.Height = Math.Max(Math.Min(docSize.Height, 32750), 1);
docSize.Width = Math.Max(Math.Min(docSize.Width, 32750), 1);
var previousSize = browser.ClientSize;
browser.ClientSize = new Size(docSize.Width, docSize.Height);
var bitmap = new Bitmap(docSize.Width, docSize.Height, PixelFormat.Format32bppArgb);
g = Graphics.FromImage(bitmap);
var rect = new RECT(0, 0, bitmap.Width, bitmap.Height);
hDc = g.GetHdc();
var view = browser.ActiveXInstance as IViewObject;
view.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, hDc, ref rect, IntPtr.Zero, IntPtr.Zero, 0);
browser.ClientSize = previousSize;
return bitmap;
}
catch
{
// This catch block is like this on purpose: nothing to do here
return null;
}
finally
{
if (hDc != null) g?.ReleaseHdc(hDc);
g?.Dispose();
}
}
[ComImport]
[Guid("0000010D-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IViewObject
{
void Draw(uint dwAspect, int lindex, IntPtr pvAspect, [In] IntPtr ptd,
IntPtr hdcTargetDev, IntPtr hdcDraw, ref RECT lprcBounds,
[In] IntPtr lprcWBounds, IntPtr pfnContinue, uint dwContinue);
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left, int top, int width, int height)
{
Left = left; Top = top; Right = width; Bottom = height;
}
}
}
//=========================================MADE BY JIMY====================================}
private void button1_Click(object sender, EventArgs e)
{
browser.Navigate(textBox1.Text, null, null, "User-Agent: User agent");
}
}
}
In order to print the Html content of a WebBrowser Control, there are a few points that need to be considered:
We need to use the WebBrowser's DocumentCompleted event to determine when the current Document is loaded and rendered
A single Document may (will) contain more that one sub-Document, usually contained inside Frames/IFrames. Each IFrame contains its own Document: when a Document contained in an IFrame is loaded, the DocumentCompleted is reaised. This means that the event can and will be raised multiple times when the WebBrowser navigates to a URL.
The notes here explain more: How to get an HtmlElement value inside Frames/IFrames?
The managed properties of the WebBrowser don't always reflect the DOM's real values. For example, the actual dimensions of the Html Document, when the rendering is completed, are not reflected anywhere, so we need to get those measures from the DOM ourselves. The current DOM rendered dimensions are referenced by:
[WebBrowser].Document.DomDocument.documentElement.scrollHeight;
[WebBrowser].Document.DomDocument.documentElement.scrollWidth;
See: Measuring Element Dimension and Location with CSSOM in Windows Internet Explorer
The WebBrowser Control DrawToBitmap() method is derived from Control but it's not actually implemented as we could expect. The same applies to other Controls: the RichTextBox is known to print blank content when this method is used.
A Html Document may be larger than the maximum Size supported by a Bitmap. There is also a more subtle memory limit: the Bitmap object needs to store its content in a contiguous memory space, so the limit in Size of a Bitmap is actually hard to pre-determine and may cause exceptions when we might not expect it.
The WebBrowser control's Emulation Feature must be set to Internet Explorer 11. See:
How can I get the WebBrowser control to show modern contents?
Web browser control emulation issue (FEATURE_BROWSER_EMULATION)
To proceed, first subscribe to DocumentCompleted event of the WebBrowser Control.
A Dictionary<Uri, Bitmap> is used here to store the Bitmap representing the Html content of URLs visited in a session.
When the DocumentCompleted event is raised, we add a new element to the Dictionary when the current URL has never been visited before.
If the Uri is already stored, we updated the related Bitmap object, so only the most recent snapshot of a Html Document is present in the collection.
I'm using a support class to handle the Bitmaps creation and to declare the native COM Interface used to generate the Bitmap from the current ISurfacePresenter.
Since the WebBrowser control is forced to use VIEW_OBJECT_COMPOSITION_MODE_LEGACY as the CompositionMode for all sites, the internal GetPrintBitmap method calls the IViewObject Interface Draw() method in this situation, so do we.
To print the content (all the content) of the current Html Document, call the
DrawContent(WebBrowser browser) static method of the WebBrowserExtender class:
Dictionary<Uri, Bitmap> browserShots = new Dictionary<Uri, Bitmap>();
private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var browser = sender as WebBrowser;
if (browser.ReadyState != WebBrowserReadyState.Complete) return;
var bitmap = WebBrowserExtender.DrawContent(browser);
if (bitmap != null) {
if (!browserShots.ContainsKey(browser.Url)) {
browserShots.Add(browser.Url, bitmap);
}
else {
browserShots[browser.Url]?.Dispose();
browserShots[browser.Url] = bitmap;
}
// Show the Bitmap in a PictureBox control, eventually
[PictureBox].Image = browserShots[browser.Url];
}
}
The WebBrowserExtender support class:
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class WebBrowserExtender
{
public static Bitmap DrawContent(WebBrowser browser)
{
if (browser.Document == null) return null;
Size docSize = Size.Empty;
Graphics g = null;
var hDc = IntPtr.Zero;
try {
docSize.Height = (int)((dynamic)browser.Document.DomDocument).documentElement.scrollHeight;
docSize.Width = (int)((dynamic)browser.Document.DomDocument).documentElement.scrollWidth;
var screenWidth = Screen.FromHandle(browser.Handle).Bounds.Width;
docSize.Width = Math.Max(Math.Min(docSize.Width, screenWidth), 1);
docSize.Height = Math.Max(Math.Min(docSize.Height, 32750), 1);
var previousSize = browser.ClientSize;
browser.ClientSize = new Size(docSize.Width, docSize.Height);
var bitmap = new Bitmap(docSize.Width, docSize.Height, PixelFormat.Format32bppArgb);
g = Graphics.FromImage(bitmap);
var rect = new RECT(0, 0, bitmap.Width, bitmap.Height);
hDc = g.GetHdc();
var view = browser.ActiveXInstance as IViewObject;
view.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, hDc, ref rect, IntPtr.Zero, IntPtr.Zero, 0);
browser.ClientSize = previousSize;
return bitmap;
}
catch {
// This catch block is like this on purpose: nothing to do here
return null;
}
finally {
if (hDc != null) g?.ReleaseHdc(hDc);
g?.Dispose();
}
}
[ComImport]
[Guid("0000010D-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IViewObject
{
void Draw(uint dwAspect, int lindex, IntPtr pvAspect, [In] IntPtr ptd,
IntPtr hdcTargetDev, IntPtr hdcDraw, ref RECT lprcBounds,
[In] IntPtr lprcWBounds, IntPtr pfnContinue, uint dwContinue);
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left, int top, int width, int height)
{
Left = left; Top = top; Right = width; Bottom = height;
}
}
}
This is how it works:
The full Document is captured. Of course, the Bitmap can also be limited to a specific maximum/minimum size, to capture just a section of the Html Document.
:
Sample WinForms Project on Google Drive.
try to set User Agent like this
browser.Navigate(url, null, null, "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0");

How can I get rid of the Title Bar of a Window when taking a screenshot of it

So right now I am taking a screenshot of an application by doing this:
using (var bitmap = new Bitmap(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height))
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(new Point(Screen.PrimaryScreen.WorkingArea.Left, Screen.PrimaryScreen.WorkingArea.Top), new Point(0, 0), Screen.PrimaryScreen.WorkingArea.Size);
bitmap.Save(#"C:\Users\Neoray\Desktop\Test.png", ImageFormat.Bmp);
}
This works fine enough but the problem is that I need to get rid of the Title Bar that is in there (doesn't matter if its afterwards by altering the image or when taking the screenshot itself).
Hope someone can help me with this.
As noted in the comments, this method will return the ClientArea of a Process Window, excluding the Caption (Title Bar) only when the Window actually has a Caption. Not all applications do (in WinForms sense).
Also, no guarantee whatsoever that it can work with a maximized Window which is rendered using DirectX APIs.
In this case, it would be necessary to implement a completely different method.
Anyway, it should work correctly with the sample image you're showing.
The overloaded main method accepts both a Process Name and Process ID.
Parameters:
string processName or int processId:
Specify the process friendly Name (the one shown in Task Manager, it's not case sensitive) or the Process Id (also available in Task Manager).
It can be easily modified to also accept the Window Title.
bool clientAreaOnly:
Switch. Lets you specify whether you want a screenshot of the ClientArea section only or the whole Windows bounds.
float dpi:
You can define the Dpi resolution of the final Bitmap.
Sample usage:
Takes a screenshot of the Main Window of a process with Name [Some Process Name]
Instructs to only consider the ClientArea (the Window Caption and Borders will be excluded)
Specifies that the Bitmap resolution should be 120Dpi
Saves a Bitmap in PNG format, named ScreenShot.png, in [Some Path]
using (var bitmap = ProcessWindowScreenshot("[Some Process Name]", true, 120)) {
bitmap.Save(#"[Some Path]\ScreenShot.png", ImageFormat.Png);
}
Sample procedure:
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.InteropServices;
public Bitmap ProcessWindowScreenshot(string processName, bool clientAreaOnly, float dpi) {
using (var process = Process.GetProcessesByName(processName)
.FirstOrDefault(p => p.MainWindowHandle != IntPtr.Zero)) {
if (process == null) return null;
return GetProcessWindowScreenshot(GetProcessWindowRect(process.Id, clientAreaOnly), dpi);
}
}
public Bitmap ProcessWindowScreenshot(int processId, bool clientAreaOnly, float dpi) {
return GetProcessWindowScreenshot(GetProcessWindowRect(processId, clientAreaOnly), dpi);
}
private Bitmap GetProcessWindowScreenshot(Rectangle window, float dpi)
{
var bitmap = new Bitmap(window.Width, window.Height, PixelFormat.Format32bppArgb);
bitmap.SetResolution(dpi, dpi);
using (var g = Graphics.FromImage(bitmap))
{
g.InterpolationMode = InterpolationMode.High;
g.CopyFromScreen(window.Location, Point.Empty, window.Size, CopyPixelOperation.SourceCopy);
return bitmap;
};
}
private Rectangle GetProcessWindowRect(int processId, bool isClientArea)
{
IntPtr hWnd = Process.GetProcessById(processId).MainWindowHandle;
if (isClientArea) {
return GetWindowClientRectangle(hWnd);
}
else {
return GetWindowRectangle(hWnd);
}
}
Windows API declarations and helpers methods:
[UIPermission(SecurityAction.Demand, Action = SecurityAction.Demand, Window = UIPermissionWindow.AllWindows)]
public static Rectangle GetWindowRectangle(IntPtr handle) {
NativeMethods.GetWindowRect(handle, out RECT rect);
return rect.ToRectangle();
}
[UIPermission(SecurityAction.Demand, Action = SecurityAction.Demand, Window = UIPermissionWindow.AllWindows)]
public static Rectangle GetWindowClientRectangle(IntPtr handle) {
NativeMethods.GetClientRect(handle, out RECT rect);
NativeMethods.ClientToScreen(handle, out POINT point);
return rect.ToRectangleOffset(point);
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
public Rectangle ToRectangleOffset(POINT p) => Rectangle.FromLTRB(p.x, p.y, Right + p.x, Bottom + p.y);
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
internal static class NativeMethods
{
[DllImport("User32.dll", SetLastError = true)]
internal static extern bool ClientToScreen(IntPtr hWnd, out POINT point);
[DllImport("User32.dll", SetLastError = true)]
internal static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
[DllImport("User32.dll", SetLastError = true)]
internal static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
}

Simulate Windows Dragging Effect in a Winform FlowLayoutPanel

I'm currently simulating the windows multiple selection rectangle when the user is dragging the mouse. To synchronize our understanding, this picture shows the effect I want to simulate:
Now I want to simulate this effect on a FlowLayoutPanel with some controls inside.
So far I am managed to get the effect almost done:
What I did here was putting a unfocused border-less semi-transparent (half the opacity) form on top the main form. To get the border simulated, I handled SizeChanged and Paint to draw the border.
However, this solution sometimes flickers, as in the owner border couldn't get cleared on-time:
I have tried using double buffering on the cover form by setting DoubleBuffer to true, and override CreateParam to set WM_EX_COMPOSITED, but neither works.
My question is: How to reduce this artifact?
Thanks a lot!
My code:
For the cover form:
public partial class CoverForm : Form
{
public CoverForm()
{
InitializeComponent();
BackColor = Color.CadetBlue;
FormBorderStyle = FormBorderStyle.None;
SizeChanged += (s, e) => Invalidate();
Paint += (s, e) =>
{
e.Graphics.Clear(BackColor);
using (var pen = new Pen(Color.DodgerBlue))
{
e.Graphics.DrawRectangle(pen, 1, 1, Size.Width - 2, Size.Height - 2);
}
};
}
protected override bool ShowWithoutActivation
{
get { return true; }
}
}
For the main form:
public Form1()
{
InitializeComponent();
// mainPanel is the panel that simulates the dragging effect
mainPanel.MouseDown += (s, e) =>
{
_isMouseDown = true;
_startPosition = e.Location;
coverForm.Location = mainPanel.PointToScreen(e.Location);
coverForm.Show();
};
mainPanel.MouseUp += (s, e) =>
{
_isMouseDown = false;
coverForm.Hide();
};
mainPanel.MouseMove += CoverPanelMouseMoveHandler;
DoubleBuffered = true;
}
~Form1()
{
if (coverForm != null && !coverForm.IsDisposed)
{
coverForm.Dispose();
}
}
# region Dragging Effect
private void CoverPanelMouseMoveHandler(object sender, MouseEventArgs e)
{
if (_isMouseDown)
{
_curPosition = e.Location;
// find the dragging rectangle
var rect = CreateRect(_curPosition, _startPosition);
coverForm.Size = rect.Size;
coverForm.Location = mainPanel.PointToScreen(rect.Location);
foreach (Control control in mainPanel.Controls)
{
// logic to get button backcolor changed
}
mainPanel.Invalidate(true);
}
}
Update
I have tried to override OnPaint and put my drawing there, but it gave even worse result: the old paints wouldn't get erased:
Code I modified for cover form:
public partial class CoverForm : Form
{
public CoverForm()
{
InitializeComponent();
BackColor = Color.CadetBlue;
FormBorderStyle = FormBorderStyle.None;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.Clear(BackColor);
using (var pen = new Pen(Color.FromArgb(255, 0, 0, 255)))
{
e.Graphics.DrawRectangle(pen, 0, 0, Size.Width - 1, Size.Height - 1);
}
}
protected override bool ShowWithoutActivation
{
get { return true; }
}
}
Update 2
Actually the problem I am facing is about drawing above a FlowLayoutPanel, not a normal Panel. The reason I put Panel before was I was seeking answer for my flickering 2-layers design. But since someone approach the problem by adding control to the panel to get it drawn above all controls, I would like to point this out: adding control to a panel would be trivial, but FlowLayoutPanel will auto-align the newly added control to the next available position, which may screw up the expected effect.
Video Demo of the Solution: Remember to Switch to 1080p
Recorded in a VM on a crappy machine. So kinda slow.
You are getting those artifacts because you're doing a combination of 3 things all at once.
The two big ones are moving the form to another location and resizing the form. It also doesn't help if the form is semi transparent :) To get a better understanding of what I mean, just open VS2013 up and resize the window very quickly (at the top-left corner, and run in random directions really fast), you will see that around the edges it can't keep up. And yes, you will get different results when you're resizing from a different position around the window (just think about it for a minute and you will figure it out).
Aybe, provided a pretty clever solution but it doesn't allow you to see through it or see if any updates to the panel....since it basically just copies the last output to a bitmap and uses that as a back buffer (much like what you assume someone might do when doing the selection thing in a paint program).
If you really want to do it with an overlay form and keep it semi-transparent then you will need to eliminate those three things if you don't want artifacts.
The code requires quite a bit of WIN32 knowledge.... lucky for you Microsoft has already done the hard part. We are going to enable per pixel transparency in your cover frame by using the PerPixelAlphaForm by Microsoft (you can google it) I will paste the code here. It basically just creates a Window with a Style of WS_EX_LAYERED. Keeps a Backbuffer which is AlphaBlended with the screen (simple huh?).
/******************************** Module Header ********************************\
Module Name: PerPixelAlphaForm.cs
Project: CSWinFormLayeredWindow
Copyright (c) Microsoft Corporation.
This source is subject to the Microsoft Public License.
See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
All other rights reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
\*******************************************************************************/
#region Using directives
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
#endregion
namespace CSWinFormLayeredWindow
{
public partial class PerPixelAlphaForm : Form
{
public PerPixelAlphaForm()
{
InitializeComponent();
}
protected override CreateParams CreateParams
{
get
{
// Add the layered extended style (WS_EX_LAYERED) to this window.
CreateParams createParams = base.CreateParams;
createParams.ExStyle |= WS_EX_LAYERED;
return createParams;
}
}
/// <summary>
/// Let Windows drag this window for us (thinks its hitting the title
/// bar of the window)
/// </summary>
/// <param name="message"></param>
protected override void WndProc(ref Message message)
{
if (message.Msg == WM_NCHITTEST)
{
// Tell Windows that the user is on the title bar (caption)
message.Result = (IntPtr)HTCAPTION;
}
else
{
base.WndProc(ref message);
}
}
/// <summary>
///
/// </summary>
/// <param name="bitmap"></param>
public void SelectBitmap(Bitmap bitmap)
{
SelectBitmap(bitmap, 255);
}
/// <summary>
///
/// </summary>
/// <param name="bitmap">
///
/// </param>
/// <param name="opacity">
/// Specifies an alpha transparency value to be used on the entire source
/// bitmap. The SourceConstantAlpha value is combined with any per-pixel
/// alpha values in the source bitmap. The value ranges from 0 to 255. If
/// you set SourceConstantAlpha to 0, it is assumed that your image is
/// transparent. When you only want to use per-pixel alpha values, set
/// the SourceConstantAlpha value to 255 (opaque).
/// </param>
public void SelectBitmap(Bitmap bitmap, int opacity)
{
// Does this bitmap contain an alpha channel?
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
{
throw new ApplicationException("The bitmap must be 32bpp with alpha-channel.");
}
// Get device contexts
IntPtr screenDc = GetDC(IntPtr.Zero);
IntPtr memDc = CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr hOldBitmap = IntPtr.Zero;
try
{
// Get handle to the new bitmap and select it into the current
// device context.
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
hOldBitmap = SelectObject(memDc, hBitmap);
// Set parameters for layered window update.
Size newSize = new Size(bitmap.Width, bitmap.Height);
Point sourceLocation = new Point(0, 0);
Point newLocation = new Point(this.Left, this.Top);
BLENDFUNCTION blend = new BLENDFUNCTION();
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = (byte)opacity;
blend.AlphaFormat = AC_SRC_ALPHA;
// Update the window.
UpdateLayeredWindow(
this.Handle, // Handle to the layered window
screenDc, // Handle to the screen DC
ref newLocation, // New screen position of the layered window
ref newSize, // New size of the layered window
memDc, // Handle to the layered window surface DC
ref sourceLocation, // Location of the layer in the DC
0, // Color key of the layered window
ref blend, // Transparency of the layered window
ULW_ALPHA // Use blend as the blend function
);
}
finally
{
// Release device context.
ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
SelectObject(memDc, hOldBitmap);
DeleteObject(hBitmap);
}
DeleteDC(memDc);
}
}
#region Native Methods and Structures
const Int32 WS_EX_LAYERED = 0x80000;
const Int32 HTCAPTION = 0x02;
const Int32 WM_NCHITTEST = 0x84;
const Int32 ULW_ALPHA = 0x02;
const byte AC_SRC_OVER = 0x00;
const byte AC_SRC_ALPHA = 0x01;
[StructLayout(LayoutKind.Sequential)]
struct Point
{
public Int32 x;
public Int32 y;
public Point(Int32 x, Int32 y)
{ this.x = x; this.y = y; }
}
[StructLayout(LayoutKind.Sequential)]
struct Size
{
public Int32 cx;
public Int32 cy;
public Size(Int32 cx, Int32 cy)
{ this.cx = cx; this.cy = cy; }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ARGB
{
public byte Blue;
public byte Green;
public byte Red;
public byte Alpha;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc,
Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteObject(IntPtr hObject);
#endregion
}
}
OK, that should eliminate your semi-transparent problem. Remember to get rid of the override of the WndProc (you won't need it). Set Double-Buffer to false and TopMost to true.
Now to eliminate the other two problems. I hope you thought of a way of doing it....but I will give you my solution. Always keep the PerPixelAlphaForm the size of your MainForm. Same location, Same SIZE. :) And resize the PerPixelAlphaForm's backbuffer bitmap to the same size as well. When you do it this way, all you have to do is redraw the Selection Rectangle. Why? because it overlays the entire MainForm perfectly.
So basically
`OnMouseDown` = Save initial point of mouse, show the Cover layer
`OnMouseMove` = clear the PerPixelAlphaForm bitmap, draw your rectangle
call SelectBitmap again update the form
`OnMouseUp` = hide the Cover layer (or whatever you want to do)
I personally have all this hook up to the Control-Key
To clear the PerPixelAlphaForm we need to do in a certain way. Give all values an Alpha of 0.
public void ClearBackbuffer()
{
Graphics g = Graphics.FromImage(_reference_to_your_backbuffer_);
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
SolidBrush sb = new SolidBrush(Color.FromArgb(0x00, 0x00, 0x00, 0x00));
g.FillRectangle(sb, this.ClientRectangle);
sb.Dispose();
g.Dispose();
}
Video Demo of the Solution: Remember to Switch to 1080p
If you need more help, let me know I can find some time to rip the code out of the larger program. But it seems to me you're the kind of person that likes tinkering with stuff :D
EDIT : using an additional PictureBox and Bitmap makes the whole thing working
The following Panel draws a rectangle without flickering:
internal sealed class MyPanel : Panel
{
private readonly PictureBox _pictureBox;
private Bitmap _bitmapContent;
private Bitmap _bitmapForeground;
private Point? _point1;
private Point? _point2;
public MyPanel()
{
DoubleBuffered = true;
_pictureBox = new PictureBox();
}
protected override void OnSizeChanged(EventArgs e)
{
if (_bitmapForeground != null) _bitmapForeground.Dispose();
_bitmapForeground = new Bitmap(Size.Width, Size.Height);
if (_bitmapContent != null) _bitmapContent.Dispose();
_bitmapContent = new Bitmap(Size.Width, Size.Height);
_pictureBox.Size = Size;
_pictureBox.Image = _bitmapForeground;
base.OnSizeChanged(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
_point1 = e.Location;
DrawToBitmap(_bitmapContent, new Rectangle(0, 0, Size.Width, Size.Height));
SetControlsVisibility(false);
Controls.Add(_pictureBox);
base.OnMouseDown(e);
}
private void SetControlsVisibility(bool visible)
{
IEnumerable<Control> ofType = Controls.OfType<Control>();
foreach (Control control in ofType)
{
control.Visible = visible;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
Controls.Remove(_pictureBox);
SetControlsVisibility(true);
_point1 = null;
_point2 = null;
Refresh();
base.OnMouseUp(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_point1 != null)
{
_point2 = e.Location;
if (_point1 != null && _point2 != null)
{
Point p1 = _point1.Value;
Point p2 = _point2.Value;
int x1 = p1.X;
int y1 = p1.Y;
int x2 = p2.X;
int y2 = p2.Y;
int xmin = Math.Min(x1, x2);
int ymin = Math.Min(y1, y2);
int xmax = Math.Max(x1, x2);
int ymax = Math.Max(y1, y2);
using (Graphics graphics = Graphics.FromImage(_bitmapForeground))
{
graphics.DrawImageUnscaled(_bitmapContent, 0, 0, _bitmapContent.Width, _bitmapContent.Height);
graphics.DrawRectangle(Pens.Red, new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin));
}
_pictureBox.Refresh();
}
}
base.OnMouseMove(e);
}
}
However, the rectangle will be below the controls, not sure why ...

C# - Form user button click, get next window clicked by user

This shouldnt be a difficult question, but it is difficult to google the question and get the idea across.
The problem is simple: I have a windows form where the user presses a button, then, it will wait on the user to click another window. It stores that selected window information for manipulation later (specifically the dimensions).
How can I get the active window of the next user click after a button is pressed?
Thanks
You need to get the foreground window.
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetWindowRect(IntPtr hWnd, out Rectangle lpRect);
Rect rect = new Rect ();
GetWindowRect(GetForegroundWindow(), out rect);
//calculate width and height from rect
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
Size size = new System.Drawing.Size(width, height);
g.CopyFromScreen(new Point(rect.Left, rect.Top), Point.Empty, size);
}
bitmap.Save("C://test.jpg", ImageFormat.Jpeg);
}
[StructLayout(LayoutKind.Sequential)]
public struct Rect {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
I found most of the code in these two answers on SO. Modifed it to suit your question
Capture window
Find window width and height
Interested by your question i have created this small screen capture app.
It has strange workarounds:
Timer used to capture mouse position outside winform is strange but was easier to implement than using Global System Hooks . You might try to use lib from link or implement it by yourself. You wouldn't need than to use Timer and what is more important you could drop this constant reactivation of form.
I have found somewhere in SE info about overriding CreateParams but i can't find link to it anymore it allows you to click trough form (i think that not all of added params are neccessery but as i said i lost link :) ).
Only visible part of window is captured + all windows overlapping it (probably sinc u got a windowHandle to it u might try to show/activate it somehow).
For some windows it gets controls inside window.
Provided app is probably very unprofessional and unsafe and might blow up your computer so beware ;P
Used form is borderless with opacity set to 80%.
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.Runtime.InteropServices;
namespace WindowInfo
{
public partial class CurrentWindow : Form
{
Rectangle GDIrect = new Rectangle(0, 0, 100, 100);
[DllImport("user32.dll")]
public static extern IntPtr WindowFromPoint(Point lpPoint);
[DllImport("user32.dll")]
public static extern bool GetCursorPos(out Point lpPoint);
[DllImport("user32.dll")]
private static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public CurrentWindow()
{
InitializeComponent();
}
protected override CreateParams CreateParams
{
get
{
CreateParams baseParams = base.CreateParams;
baseParams.ExStyle |= (int)(
0x00080000 |
0x08000000 |
0x00000080 |
0x00000020
);
return baseParams;
}
}
public static IntPtr GetWindowUnderCursor()
{
Point ptCursor = new Point();
GetCursorPos(out ptCursor);
return WindowFromPoint(ptCursor);
}
public Bitmap CaptureScreen()
{
var result = new Bitmap(this.DisplayRectangle.Width, this.DisplayRectangle.Height);
using (var g = Graphics.FromImage(result))
{
g.CopyFromScreen(this.Location.X, this.Location.Y, 0, 0, this.DisplayRectangle.Size);
}
return result;
}
private void timer1_Tick(object sender, EventArgs e)
{
IntPtr windowHandle = GetWindowUnderCursor();
Rect rect = new Rect();
GetWindowRect(windowHandle, ref rect);
GDIrect = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
this.Location = new Point(GDIrect.Left, GDIrect.Top);
this.Size = GDIrect.Size;
this.Activate();
}
private void CurrentWindow_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == 'c')
{
this.Visible = false;
Bitmap bmp = CaptureScreen();
bmp.Save(Application.StartupPath + "\\example.png");
this.Visible = true;
}
else if (e.KeyChar == 'x')
{
this.Close();
}
}
}
}
U might add it to your app and run after button click, it should work but i have tested it only separately. Good luck :).

How to get the position of the tracker of a .NET TrackBar?

I am building a Windows Forms applicaton in C# and have a TrackBar on my Form. How can I compute the (pixel) position of the tip of the tracker? I would like to draw a line from there to another point on my form.
Additionally I also would like to compute the lowest and highest possible x position of the tip.
The native Windows control that is wrapped by TrackBar has an awkward restriction, you cannot get the positions for the first and last tic marks. You can however get the display rectangles for the channel and the slider. This class returns them:
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
class MyTrackBar : TrackBar {
public Rectangle Slider {
get {
RECT rc = new RECT();
SendMessageRect(this.Handle, TBM_GETTHUMBRECT, IntPtr.Zero, ref rc);
return new Rectangle(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
}
}
public Rectangle Channel {
get {
RECT rc = new RECT();
SendMessageRect(this.Handle, TBM_GETCHANNELRECT, IntPtr.Zero, ref rc);
return new Rectangle(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
}
}
private const int TBM_GETCHANNELRECT = 0x400 + 26;
private const int TBM_GETTHUMBRECT = 0x400 + 25;
private struct RECT { public int left, top, right, bottom; }
[DllImport("user32.dll", EntryPoint = "SendMessageW")]
private static extern IntPtr SendMessageRect(IntPtr hWnd, int msg, IntPtr wp, ref RECT lp);
}
Here is a sample usage in a form, it draws a line from the first tick mark to the slider pointer:
private void myTrackBar1_ValueChanged(object sender, EventArgs e) {
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
var chan = this.RectangleToClient(myTrackBar1.RectangleToScreen(myTrackBar1.Channel));
var slider = this.RectangleToClient(myTrackBar1.RectangleToScreen(myTrackBar1.Slider));
e.Graphics.DrawLine(Pens.Black, chan.Left + slider.Width / 2, myTrackBar1.Bottom + 5,
slider.Left + slider.Width / 2, myTrackBar1.Bottom + 5);
}

Categories

Resources