Edit: The original question was a long one with many wild guesses. I have cut it back to the remaining mysteries..
I've been struggling and puzzling all day now and guess I ought to present my problem to the community.
It originated from the post called Screenshot method generates black images. The original poster wants to continuously take screenshots of his program, which includes a WebBrowser, every n seconds even when the user is logged off.
When the user is logged out he has no screen anymore. Therefore any attempt to read the screen will fail. If using a window handle the result is a black box, when using CopyFromScreen a GDI-error exception.
But the program window is still there and using DrawToBitmap
works fine even when the user is logged out.
Here are the conditions and the remaining issues:
The user must not touch/click the WebBrowser
in any way. If he does by, say, Scrolling, Clicking, Navigating the subsqeuent DrawToBitmap
calls result in an empty box.
While the WebBrowser
remains untouched, it is enough to do a Refresh
before the next DrawToBitmap call.
After touching it it is neccessary to load the URL again by doing a webBrowser1.Url = new Uri(URLpath);
When navigating the new URL must be stored to do this. I do that in the Navigated event.
No matter what, DrawToBitmap
fails (with the empty box) if the webpage contains a <input type="text" ..> field
.
By vandalising the DocumentText
with a Replace("<input", "<in_put");
this could be healed, but without further tricks this will lose the CSS sheets..
To test it throw two Buttons, a Label, a Timer, a Combobox and a WebBrowser on a Form
and copy the code; change the filepath to a folder that fits your setup and watch..:
public Form1()
{
InitializeComponent();
this.button1.Click += new System.EventHandler(this.button1_Click);
this.button2.Click += new System.EventHandler(this.button2_Click);
this.button1.Text = "Start";
this.button2.Text = "Stop";
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
this.comboBox1.Items.AddRange(new object[] {
"https://stackoverflow.com/questions",
"http://webcam.zirndorf.de/marktplatz/gross.jpg"});
scapeRect = this.ClientRectangle;
webBrowser1.Url = new Uri("https://stackoverflow.com/questions");
this.comboBox1.SelectedIndexChanged +=
new System.EventHandler(this.comboBox1_SelectedIndexChanged);
}
Rectangle scapeRect = Rectangle.Empty;
int imgIndex = 0;
int urlIndex = 0;
private void button1_Click(object sender, EventArgs e)
{
timer1.Interval = 10 * 1000; // every 10 seconds
timer1.Start();
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Stop();
}
private void timer1_Tick(object sender, EventArgs e)
{
imgIndex ++;
label1.Text = imgIndex .ToString();
webBrowser1.Url = new Uri(comboBox1.Text); // this works almost always
//webBrowser1.Refresh(); // this works only if the WB is 'untouched'
string filename = "d:\scrape\AB_sos_Screen" + imgIndex .ToString("000") + ".png";
Bitmap bmp = new Bitmap(scapeRect.Width, scapeRect.Height);
this.DrawToBitmap(bmp, scapeRect);
bmp.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
bmp.Dispose();
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.Text != "") webBrowser1.Url = new Uri(comboBox1.Text);
}
private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
if (!comboBox1.Items.Contains(e.Url.ToString()))
urlIndex = comboBox1.Items.Add(e.Url.ToString());
else
urlIndex = comboBox1.Items.IndexOf(e.Url.ToString());
if (urlIndex >= 0) comboBox1.SelectedIndex = urlIndex;
button1.Focus();
}
I can now navigate almost freely and the screen scraping keeps working - except for pages with textual input fields like e.g. the Users or the Tags pages.
I wonder if anybdoy can reproduce ..?
Or explain??
Or have I been just 'ghost hunting' after all and the thing is simply unreliable???
Final Edit:
While it would have been nice to get an explanation, getting a working solution probably has to be good enough.. The OP has found code that uses a PrintWindow
call to user32.dll
and solves all problems. It works while being logged off, works with Refreshing
even after clicking in the WebBrowser
and scrapes all pages, including those with textual input fields. Here is my version of it:
using System.Runtime.InteropServices;
//...
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
public Bitmap CaptureControl(Control ctl)
{
//Bitmap bmp = new Bitmap(ctl.Width, ctl.Height); // includes borders
Bitmap bmp = new Bitmap(ctl.ClientRectangle.Width, ctl.ClientRectangle.Height); // content only
using (Graphics graphics = Graphics.FromImage(bmp))
{
IntPtr hDC = graphics.GetHdc();
try { PrintWindow(ctl.Handle, hDC, (uint)0); }
finally { graphics.ReleaseHdc(hDC); }
}
return bmp;
}
This can capture a Form or a Control with or without Borders.
See Question&Answers more detail:
os