I'm building a WPF application in which I need to display document previews such as what is achievable with a DocumentViewer and DocumentPaginator. However, converting the report to XPS and loading it into a DocumentViewer has proven to be very slow when the report is large (as a common report I'll need to display is).
This lead me to start thinking that there is probably some way to start showing the first few pages of the report while the rest of the pages are being 'loaded' into the DocumentViewer -- basically loading/showing the pages as they're created.
Does anyone know if something like this is possible? And, if so, how would you suggest I get started trying to make it work? I've spent a few hours looking around online for a solution to display the report faster, but haven't come up with anything.
For the sake of full disclosure, in this case the report I need to display is being created in HTML. I know that I need to convert it to XPS in order to use the DocumentViewer, but I bring this up because if anyone has a fast way of displaying the HTML, please feel free to bring that up too. I can't use a WebBrowser control as I have to have the display in a 'print preview' type of mode. A good algorithm for deciding how to 'paginate' an HTML site would probably lead me to a solution to this problem as well as then I could create a custom control to display it. I'd use a DocumentPaginator, but then the outputted file is XPS and then I'm back to the DocumentViewer issue.
Again, any help is greatly appreciated. Thank you!
Ok, I think I've got something...
Once again I found a better URL to reference. This one wasn't loading for me straight up so I grabbed it from the Google cache: http://webcache.googleusercontent.com/search?q=cache:LgceMCkJBrsJ:joshclose.net/%3Fp%3D247
Define the IViewObject interface as described in each article:
[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);
void SetAdvise([In, MarshalAs(UnmanagedType.U4)] int aspects,
[In, MarshalAs(UnmanagedType.U4)] int advf,
[In, MarshalAs(UnmanagedType.Interface)] IAdviseSink pAdvSink);
void GetAdvise([In, Out, MarshalAs(UnmanagedType.LPArray)] int[] paspects,
[In, Out, MarshalAs(UnmanagedType.LPArray)] int[] advf,
[In, Out, MarshalAs(UnmanagedType.LPArray)] IAdviseSink[] pAdvSink);
}
Create an HtmlPaginator class that screenshots the browser's document (as described) but then crops it into pages / frames:
class HtmlPaginator
{
public event EventHandler<PageImageEventArgs> PageReady;
protected virtual void OnPageReady(PageImageEventArgs e)
{
EventHandler<PageImageEventArgs> handler = this.PageReady;
if (handler != null)
handler(this, e);
}
public class PageImageEventArgs : EventArgs
{
public Image PageImage { get; set; }
public int PageNumber { get; set; }
}
public void GeneratePages(string doc)
{
Bitmap htmlImage = RenderHtmlToBitmap(doc);
int pageWidth = 800;
int pageHeight = 600;
int xLoc = 0;
int yLoc = 0;
int pages = 0;
do
{
int remainingHeightOrPageHeight = Math.Min(htmlImage.Height - yLoc, pageHeight);
int remainingWidthOrPageWidth = Math.Min(htmlImage.Width - xLoc, pageWidth);
Rectangle cropFrame = new Rectangle(xLoc, yLoc, remainingWidthOrPageWidth, remainingHeightOrPageHeight);
Bitmap page = htmlImage.Clone(cropFrame, htmlImage.PixelFormat);
pages++;
PageImageEventArgs args = new PageImageEventArgs { PageImage = page, PageNumber = pages };
OnPageReady(args);
yLoc += pageHeight;
if (yLoc > htmlImage.Height)
{
xLoc += pageWidth;
if (xLoc < htmlImage.Width)
{
yLoc = 0;
}
}
}
while (yLoc < htmlImage.Height && xLoc < htmlImage.Width);
}
private static Bitmap RenderHtmlToBitmap(string doc)
{
Bitmap htmlImage = null;
using (var webBrowser = new WebBrowser())
{
webBrowser.ScrollBarsEnabled = false;
webBrowser.ScriptErrorsSuppressed = true;
webBrowser.DocumentText = doc;
while (webBrowser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
webBrowser.Width = webBrowser.Document.Body.ScrollRectangle.Width;
webBrowser.Height = webBrowser.Document.Body.ScrollRectangle.Height;
htmlImage = new Bitmap(webBrowser.Width, webBrowser.Height);
using (Graphics graphics = Graphics.FromImage(htmlImage))
{
var hdc = graphics.GetHdc();
var rect1 = new Rectangle(0, 0, webBrowser.Width, webBrowser.Height);
var rect2 = new Rectangle(0, 0, webBrowser.Width, webBrowser.Height);
var viewObject = (IViewObject)webBrowser.Document.DomDocument;
viewObject.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, hdc, ref rect1, ref rect2, IntPtr.Zero, 0);
graphics.ReleaseHdc(hdc);
}
}
return htmlImage;
}
}
Call it like so:
WebBrowser browser = new WebBrowser();
browser.Navigate("http://www.stackoverflow.com");
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
HtmlPaginator pagr = new HtmlPaginator();
pagr.PageReady += new EventHandler<HtmlPaginator.PageImageEventArgs>(pagr_PageReady);
pagr.GeneratePages(browser.DocumentText);
To test it I implemented a basic form with a button and a picture box and a List collection. I add pages to the collection as they're ready from the HtmlPaginator and use the button to add the next image to the picturebox.
The magic numbers are your desired width and height. I used 800x600 but you probably have different dimensions you want.
The downside here is you're still waiting for the WebBrowser to render the HTML but I really don't see how an alternate solution is going to reduce that time - something has to interpret and draw the HTML in the first place. Write your own web browser I guess. :)
I did try playing with IViewObject.Draw to see if I could just have it render the page frames directly rather than have the cropping loop, but it wasn't working for me.
Related
I'm trying to get the properties of a button but when I drag the spy++'s find window tool it doesn't reach the button (of caption "iniciar gravação" in that case) but rather the button grid parent, like this:
Why is that and how can I get the button properties? I tried with auto it info tool but I get the same behavior.
I tried get that button properties from C# code, like this:
public class WindowHandleInfo
{
private delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr lParam);
private IntPtr _MainHandle;
public WindowHandleInfo(IntPtr handle)
{
this._MainHandle = handle;
}
public List<IntPtr> GetAllChildHandles()
{
List<IntPtr> childHandles = new List<IntPtr>();
GCHandle gcChildhandlesList = GCHandle.Alloc(childHandles);
IntPtr pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(this._MainHandle, childProc, pointerChildHandlesList);
}
finally
{
gcChildhandlesList.Free();
}
return childHandles;
}
private bool EnumWindow(IntPtr hWnd, IntPtr lParam)
{
GCHandle gcChildhandlesList = GCHandle.FromIntPtr(lParam);
if (gcChildhandlesList == null || gcChildhandlesList.Target == null)
{
return false;
}
List<IntPtr> childHandles = gcChildhandlesList.Target as List<IntPtr>;
childHandles.Add(hWnd);
return true;
}
}
void printWindows(IntPtr parent, int tabLevel)
{
var w = new WindowHandleInfo(parent);
foreach (IntPtr h in w.GetAllChildHandles())
{
string caption = GetTextBoxText(h) ?? "not found";
Debug.Write(new string('\t', tabLevel));
Debug.WriteLine(caption);
printWindows(h, tabLevel + 1);
}
}
int GetTextBoxTextLength(IntPtr hTextBox)
{
// helper for GetTextBoxText
const int WM_GETTEXTLENGTH = 0x000E;
int result = SendMessage(hTextBox, WM_GETTEXTLENGTH, 0, IntPtr.Zero);
return result;
}
string GetTextBoxText(IntPtr hTextBox)
{
const int WM_GETTEXT = 0x000D;
int len = GetTextBoxTextLength(hTextBox);
if (len <= 0) return null; // no text
StringBuilder sb = new StringBuilder(len + 1);
SendMessage(hTextBox, WM_GETTEXT, len + 1, sb);
return sb.ToString();
}
using like this:
IntPtr parent = FindWindow(IntPtr.Zero, "my app title");
printWindows(parent, 0);
But I can't see anyhing related to Iniciar gravação button.
Qt UI controls (widgets, in Qt lexicon. Aka buttons, line edits, comboboxes, etc.) are not backed by native controls. They just steal the native UI look and feel from them and mimic it.
For this reason, you won't find those controls using some of those "spy" tools -- you may still find them through accessibility, though (*).
Or, you can inspect your application using other Qt-specific tools, such as GammaRay for debugging or Squish for UI testing.
(*) controls shipped with Qt are accessibile, but if someone manually reimplements something that looks like a button and doesn't provide it with accessibility then you can't do much about that.
I am trying to make an external map for a computer game.
Therefore I have made a Forms Application with a picture box, that contains my map image.
Now I want to draw little squares onto the map using GDI. I allready got that working using Graphics.DrawRectangle.
Now I want to update the position of the rectangle every 0.2s.
How do I do that?
My current source (i wnt to replace the button with an auto-update):
public partial class Form1 : Form
{
//choords local player
int localX;
int localY;
int running;
const int Basex = 0x05303898;
const int Basey = 0x05303894;
const string Game = "ac_client";
//map drawing
Pen aPen = new Pen(Color.Black);
Graphics localp;
//choords enemy
//permission to read process memory
const int PROCESS_VM_READ = 0x0010; //needed for reading memory
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(int hProcess,
int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
if (Process.GetProcessesByName(Game).Length > 0)
{
Process process = Process.GetProcessesByName(Game)[0];
IntPtr procHandle = OpenProcess(PROCESS_VM_READ, false, process.Id);
int bytesRead = 0;
byte[] buffer = new byte[24]; //'Hello World!' takes 12*2 bytes because of Unicode
// 0x0046A3B8 is the address where I found the string, replace it with what you found
ReadProcessMemory((int)procHandle, Basex, buffer, buffer.Length, ref bytesRead);
localX = BitConverter.ToInt32(buffer, 0);
LBlocalx.Text = Convert.ToString(Math.Ceiling(Convert.ToDecimal(localX)));
ReadProcessMemory((int)procHandle, Basey, buffer, buffer.Length, ref bytesRead);
localY = BitConverter.ToInt32(buffer, 0);
LBlocaly.Text = Convert.ToString(Math.Ceiling(Convert.ToDecimal(localY)));
localp = pictureBox1.CreateGraphics();
localp.DrawRectangle(aPen, (Convert.ToInt32(Convert.ToString(Math.Ceiling(Convert.ToDecimal(localX))))/1000), (Convert.ToInt32(Convert.ToString(Math.Ceiling(Convert.ToDecimal(localY))))/1000), 10, 10);
}
else
{
MessageBox.Show("Error! Process not running.");
}
}
How about you store two time variables(DateTime) one that has the time when you started checking for that 2 second difference, another with current time and on the beginning of every iteration you verify if the difference of both times is 2 seconds. Remember, the first variable is the one that has the time when the difference was 2 seconds or when you first started checking for that difference.
You could also use the Timer class and set a timer that on ever 2secs do something.
Timer class reference:
https://msdn.microsoft.com/en-us/library/system.timers.timer%28v=vs.110%29.aspx
I know how to print an image using PrintDocument. However, i want to print my Image using the default windows print function. Like when you right click an image and click print, the dialog comes up that allows you to set size choose printer etc. Does anyone know how to achieve this in C#? Do i have to use WINAPI ?
Cheers
Edit:
I'm talking about this print dialog.
You can launch that dialog with the Process class.
private void button1_Click(object sender, EventArgs e)
{
string fileName = #"C:\Development\myImage.tif";//pass in or whatever you need
var p = new Process();
p.StartInfo.FileName = fileName;
p.StartInfo.Verb = "Print";
p.Start();
}
The simple approach with launching a new process using verb "print" is not working on Windows XP at all (it opens Windows Picture and Fax Viewer instead of the Printing Wizard). Also it does not work as intended on Windows 10 (at first run the Default app chooser for images is opened, then the default photo viewer is opened).
The correct approach would be using CLSID_PrintPhotosDropTarget COM object.
My code is in C++ (and ATL) but I hope you could translate it in C#.
I jast pass file names, but AFAIK you can pass picture itself directly without writing it on disk implementing IDataObject interface.
bool DisplaySystemPrintDialogForImage(const std::vector<CString>& files, HWND hwnd) {
static const CLSID CLSID_PrintPhotosDropTarget ={ 0x60fd46de, 0xf830, 0x4894, { 0xa6, 0x28, 0x6f, 0xa8, 0x1b, 0xc0, 0x19, 0x0d } };
CComPtr<IShellFolder> desktop; // namespace root for parsing the path
HRESULT hr = SHGetDesktopFolder(&desktop);
if (!SUCCEEDED(hr)) {
return false;
}
CComPtr<IShellItem> psi;
CComPtr<IDataObject> pDataObject;
std::vector<LPITEMIDLIST> list;
for (const auto& fileName : files) {
PIDLIST_RELATIVE newPIdL;
hr = desktop->ParseDisplayName(hwnd, nullptr, const_cast<LPWSTR>(static_cast<LPCTSTR>(fileName)), nullptr, &newPIdL, nullptr);
if (SUCCEEDED(hr)) {
list.push_back(newPIdL);
}
}
if (!list.empty()) {
hr = desktop->GetUIObjectOf(hwnd, list.size(), const_cast<LPCITEMIDLIST*>(&list[0]), IID_IDataObject, 0, reinterpret_cast<void**>(&pDataObject));
if (SUCCEEDED(hr)) {
// Create the Photo Printing Wizard drop target.
CComPtr<IDropTarget> spDropTarget;
hr = CoCreateInstance(CLSID_PrintPhotosDropTarget, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spDropTarget));
if (SUCCEEDED(hr)) {
// Drop the data object onto the drop target.
POINTL pt = { 0 };
DWORD dwEffect = DROPEFFECT_LINK | DROPEFFECT_MOVE | DROPEFFECT_COPY;
spDropTarget->DragEnter(pDataObject, MK_LBUTTON, pt, &dwEffect);
spDropTarget->Drop(pDataObject, MK_LBUTTON, pt, &dwEffect);
return true;
}
}
}
return false;
}
This works for me:
internal static class ShellHelper
{
[ComImport]
[Guid("00000122-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDropTarget
{
int DragEnter(
[In] System.Runtime.InteropServices.ComTypes.IDataObject pDataObj,
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
int DragOver(
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
int DragLeave();
int Drop(
[In] System.Runtime.InteropServices.ComTypes.IDataObject pDataObj,
[In] int grfKeyState,
[In] Point pt,
[In, Out] ref int pdwEffect);
}
internal static void PrintPhotosWizard(string p_FileName)
{
IDataObject v_DataObject = new DataObject(DataFormats.FileDrop, new string[] { p_FileName });
MemoryStream v_MemoryStream = new MemoryStream(4);
byte[] v_Buffer = new byte[] { (byte)5, 0, 0, 0 };
v_MemoryStream.Write(v_Buffer, 0, v_Buffer.Length);
v_DataObject.SetData("Preferred DropEffect", v_MemoryStream);
Guid CLSID_PrintPhotosDropTarget = new Guid("60fd46de-f830-4894-a628-6fa81bc0190d");
Type v_DropTargetType = Type.GetTypeFromCLSID(CLSID_PrintPhotosDropTarget, true);
IDropTarget v_DropTarget = (IDropTarget)Activator.CreateInstance(v_DropTargetType);
v_DropTarget.Drop((System.Runtime.InteropServices.ComTypes.IDataObject)v_DataObject, 0, new Point(), 0);
}
}
i want to disable the scrolling feature of richtextbox in c#. i just want to make richtextbox to allow user to enter only in its size area, means no vertical scrolling for user. just like MS-word or open Office Pages.thanx in advance.
You should override WndProc and block WM_SETFOCUS.
protected override void WndProc(ref Message m)
{
if(m.Msg != WM_SETFOCUS)
base.WndProc(ref m);
}
Here is a tutorial about this : How to: C# - Prevent RichTextBox from auto scrolling
This worked for me.
First thing as you may have seen in other posts you need access to user32.dll from C#.
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hwndLock,Int32 wMsg,Int32 wParam, ref Point pt);
We need to make some constant declaration to make the SendMessage calls properly.
private const int WM_USER = 0x400;
private const int EM_HIDESELECTION = WM_USER + 63;
private const int WM_SETREDRAW = 0x000B;
private const int EM_GETSCROLLPOS = WM_USER + 221;
private const int EM_SETSCROLLPOS = WM_USER + 222;
Then, some public static methods to be used whenever we need to stop scrolling.
public static void Suspend(Control control)
{
Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgSuspendUpdate);
}
public static void Resume(Control control)
{
// Create a C "true" boolean as an IntPtr
IntPtr wparam = new IntPtr(1);
Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgResumeUpdate);
control.Invalidate();
}
public static Point GetScrollPoint(Control control) {
Point point = new Point();
SendMessage(control.Handle, EM_GETSCROLLPOS, 0, ref point);
return point;
}
public static void SetScrollPoint(Control control, Point point)
{
SendMessage(control.Handle, EM_SETSCROLLPOS, 0, ref point);
}
The Suspend method stops the Control to make a redraw on the screen. The Resume method restarts redraws on the screen for the given Control.
The GetScrollPoint method gets the actual Point where the scroll caret is located. The SetScrollPoint puts the scroll caret at the given point.
How to use these methods? First, given a Control you need to stop autoscroll, make the call to Suspend, then to GetScrollPoint, (make what you need to do with the control, like highlight or append text) then SetScrollPoint and finally Resume.
In my case, I wanted to copy the entire line of a RichTextBox at any time when the cursor moves from line to line. (Doing so produce a scroll on long lines).
This is my working method:
private int intLastLine = -1;
private void richTextBoxSwitch_SelectionChanged(object sender, EventArgs e)
{
try
{
if (this.richTextBoxSwitch.TextLength > 0)
{
ControlBehavior.Suspend(this.richTextBoxSwitch);
Point point = ControlBehavior.GetScrollPoint(this.richTextBoxSwitch);
int intSelectionStartBackup = this.richTextBoxSwitch.SelectionStart;
int intSelectionLengthBackup = this.richTextBoxSwitch.SelectionLength;
int intCharIndex = this.richTextBoxSwitch.GetFirstCharIndexOfCurrentLine();
int intLine = this.richTextBoxSwitch.GetLineFromCharIndex(intCharIndex);
this.richTextBoxSwitch.SuspendLayout();
if (intLastLine != intLine)
{
intLastLine = intLine;
int intLength = this.richTextBoxSwitch.Lines[intLine].Length;
this.richTextBoxSwitch.Select(intCharIndex, intLength);
this.richTextBoxSwitch.BackColor = ColorMessageBackground;
strData = this.richTextBoxSwitch.SelectedText;
this.textBoxMessageSelected.Text = strData.Trim();
this.richTextBoxSwitch.Select(intSelectionStartBackup, intSelectionLengthBackup);
}
this.richTextBoxSwitch.ResumeLayout();
ControlBehavior.SetScrollPoint(this.richTextBoxSwitch, point);
ControlBehavior.Resume(this.richTextBoxSwitch);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
Hope this helps!
What is the best way to take screenshot of a web page?
At the moment I just start an selenium instance of firefox and using winapi bring it to the front and make a screenshot.
I ask similar question already.
There is two points:
Slowness.
If any window occurently gets higher than our web browser's window, this window will imprint in our screenshot.
Is there any method to take screenshot more 'programmly'?
Here is some code I use now:
class FirefoxDriverEx : FirefoxDriver
{
public Process GetFirefoxProcess()
{
var fi = typeof(FirefoxBinary).GetField("process", BindingFlags.NonPublic | BindingFlags.Instance);
return fi.GetValue(this.Binary) as Process;
}
}
Here is the code illustrating process of taking screenshot itself:
using (FirefoxDriverEx driver = new FirefoxDriverEx())
{
driver.Navigate().GoToUrl(url);
var process = driver.GetFirefoxProcess();
if (process != null)
{
var screenCapture = new ScreenCapture();
Win.SetForegroundWindow(process.MainWindowHandle.ToInt32());
}
}
Right now, I'm thinking about some manager that will control a queue of windows to take the screenshots from.
Question edit.
I'm not looking for a solution to just get screenshot 'in memory' and return it back to HTTP stream. So any ways to save screenshot and save it to file and then get it from there is very ambiguous for that purpose.
Question edit #2.
I forgot to mention. Needed screenshot should be made as it seen by user. So, screenshot should have browser window and a site inside of web browser window's bounds. I can't find any way to change mode of taking a screenshot in WebDriver of selenium. WebDriver just take screenshot of a page without any browser window.
I'd recommend getScreenshotAs. It gets even the 'out of view' part of the screen.
Here is some sample code in gr0ovy.
import java.io.IOException
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import java.text.SimpleDateFormat
import org.openqa.selenium.Capabilities
import org.openqa.selenium.TakesScreenshot
import org.openqa.selenium.WebDriverException
import org.openqa.selenium.remote.CapabilityType
import org.openqa.selenium.remote.DriverCommand
import org.openqa.selenium.remote.RemoteWebDriver
import org.openqa.selenium.OutputType
import org.openqa.selenium.WebDriver
public class Selenium2Screenshot {
private WebDriver driver
private String browserType
private boolean skipScreenshots
public Selenium2Screenshot(WebDriver webDriver, String browserType, boolean skipScreenshots) {
this.driver = webDriver
this.browserType = browserType
this.skipScreenshots = skipScreenshots
}
public void takeScreenshot(String filenameBase) {
if (!skipScreenshots) {
Date today
String formattedDate
SimpleDateFormat formatter
Locale currentLocale
File scrFile
currentLocale = new Locale("en", "US")
formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", currentLocale)
today = new Date()
formattedDate = formatter.format(today)
String filename = getUiAutomationDir() + filenameBase + "_" + browserType + formattedDate + ".png"
Log.logger.info("Screenshot filename = " + filename)
try {
scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE)
JavaIO.copy(scrFile.getAbsolutePath(), filename)
} catch (Exception e) {
Log.logger.error(e.message, e)
}
} else {
Log.logger.info("Skipped Screenshot")
}
}
private String getUiAutomationDir()
{
String workingDir = System.getProperty("user.dir")
Path workingDirPath = Paths.get(workingDir)
String returnString = workingDirPath.toString() + "\\"
return returnString
}
}
Edited on 8/1/12:
Get application handle code. I am surely duplicating code that is on stackoverflow several times, but hopefully this is not the exact same code as in other posts :-)
public static IntPtr FindWindowByPartialCaption(String partialCaption)
{
var desktop = User32.GetDesktopWindow();
var children = EnumerateWindows.GetChildWindows(desktop);
foreach (var intPtr in children)
{
var current = GetText(intPtr);
if (current.Contains(partialCaption))
return intPtr;
}
return IntPtr.Zero;
}
[DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowProc lpEnumFunc, IntPtr lParam);
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
return GetChildWindows(parent, false);
}
public static List<IntPtr> GetChildWindows(IntPtr parent, bool reverse)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
if (reverse)
{
List<IntPtr> resultList = result.Reverse<IntPtr>().ToList();
return resultList;
}
else
return result;
}
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
// You can modify this to check to see if you want to cancel the operation, then return a null here
return true;
}
}
http://www.pinvoke.net/ is also a great resource.
http://msdn.microsoft.com/en-us/library/windows/desktop/dd162869(v=vs.85).aspx
I personally love this API. Create a bitmap with width and height calculated from the returned rectangle of GetWindowRect API and for HDC parameter use (for example):
thebitmap.GetHdc()
You should be fine.
Edit: also check this.
Btw, you can take screenshot of any window you like, even if they fall back.(note that this will not work for minimized windows. However, if you really need, there are some way arounds for that too.)
If you're looking for a programmatic way to get a screenshot of the main window of a given process, here is a function that does it:
public static Bitmap TakeScreenshot(Process process)
{
// may need a process Refresh before
return TakeScreenshot(process.MainWindowHandle);
}
public static Bitmap TakeScreenshot(IntPtr handle)
{
RECT rc = new RECT();
GetWindowRect(handle, ref rc);
Bitmap bitmap = new Bitmap(rc.right - rc.left, rc.bottom - rc.top);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
PrintWindow(handle, graphics.GetHdc(), 0);
}
return bitmap;
}
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);
[DllImport("user32.dll")]
private static extern bool PrintWindow(IntPtr hWnd, IntPtr hDC, int flags);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
Unfortunately, on Aero-equipped OS (Vista/Win7/Win8) it will not capture the full transparent border. The usual transparent border will be blacked instead. Maybe it's enough for what you're trying to acomplish.
I've been using webshotcmd (the paid version is also command line) in a production app for years. It can be configured to wait for the page to load, to wait n seconds after page load, etc. It uses Internet Explorer and works on Windows. Starts pretty fast (in my experience, the msie activex has always been instant to load).
Other than the above, I would recommend something based on a Webkit libray, it would be so much smaller than Firefox, and would start very fast (wkhtmltoimage is for now only available on Linux, but when it will be available for Windows, I would go for it - also command line). Right now just google for webkit screenshot (the huge number of available screenshotters using webkit makes me believe using that DLL would be easy to port to C#).
Edit: Considering your 2nd edit, take a look at Chrome Screen Capture source.
To try it, the extension is available in the store/extension gallery.
I was able to accomplish this by copying the window (piece by piece) into a bitmap that is set to the size of the ScrollRectangle for my webBrowser control. While it is certainly not the most elegant way of achieving this goal, I wanted to share the code in case anyone might be able to use it. Once I had something that was mostly working, I was then able to add some args, and I can now execute this utility from the command line:
Executable_Path URL Filename
/// <summary>
/// This method is called to start the process of copying the webpage to the bitmap
/// this should be called after the page has fully loaded (use DocumentCompleted event to determine
/// if the page has completed loading if calling from the command line.)
/// </summary>
private void copyWebpageToImage()
{
//these two vars will house the current position in the bmp file (starting at 0,0)
int currXPosition = 0;
int currYPosition = 0;
//we need to set the height and width of our bitmap to the scrollrectangle of the webbrowser document object
int width = webBrowser1.Document.Body.ScrollRectangle.Width;
int height = webBrowser1.Document.Body.ScrollRectangle.Height;
//instantiate the bitmap
bm = new Bitmap(wd, ht);
//Instantiate our graphics object
Graphics gfx = Graphics.FromImage((Image)bm);
//this point is used throughout the process, and helps to determine where the form is at on the screen
Point formPoint = Form1.ActiveForm.Location;
formPoint.X = formPoint.X + webBrowser1.Location.X;
formPoint.Y = formPoint.Y + webBrowser1.Location.Y;
formPoint.X = formPoint.X + 8; //offsets for my form (may be different for yours)
formPoint.Y = formPoint.Y + 33; //offsets for my form
//begin our recursive call that will stop when it reaches the end of the page
copyEverythingToBitmap(bm, currXPosition, currYPosition, formPoint, gfx);
}
private void copyEverythingToBitmap(Bitmap bm, int currXPosition, int currYPosition, Point formPoint, Graphics gfx)
{
//check to see if currXPosition and currYPosition are both 0, if so we just began, call the zero copy method
if (currXPosition == 0 && currYPosition == 0)
{
performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
}
//if the current x position is less than the total width of the scrollrectangle - the width of the webbrowser,
//then we need to scroll the window, and copy the contents, y stays the same
else if (currXPosition < bm.Width - webBrowser1.Width)
{
AlterXPosition(bm, ref currXPosition, ref currYPosition, ref formPoint, gfx);
}
//if we are no longer at the zero, zero, and we cannot increase the x position anymore,
//then we need to scroll the window down and copy the contents, x is reset back to zero
else if(currYPosition < bm.Height - webBrowser1.Height)
{
currYPosition = currYPosition + webBrowser1.Height - 20;
currXPosition = 0;
performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
}
}
/// <summary>
/// The name of this method is slightly misleading. It inherently means that X is zero.
/// </summary>
private void performZeroCopy(Bitmap bm, int currXPosition, int currYPosition, Point formPoint, Graphics gfx)
{
webBrowser1.Document.Window.ScrollTo(currXPosition, currYPosition);
gfx.CopyFromScreen(formPoint, new Point(currXPosition, currYPosition), new Size(webBrowser1.Width - 20, webBrowser1.Height - 20));
if (currXPosition < bm.Width - webBrowser1.Width)
{
AlterXPosition(bm, ref currXPosition, ref currYPosition, ref formPoint, gfx);
}
else if(currYPosition < bm.Height - webBrowser1.Height)
{
currYPosition = currYPosition + webBrowser1.Height - 20;
currXPosition = 0;
performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
}
}
private void AlterXPosition(Bitmap bm, ref int currXPosition, ref int currYPosition, ref Point formPoint, Graphics gfx)
{
currXPosition = currXPosition + webBrowser1.Width - 20;
webBrowser1.Document.Window.ScrollTo(bm.Width - currXPosition, currYPosition);
gfx.CopyFromScreen(formPoint, new Point(bm.Width - currXPosition - 3, currYPosition), new Size(webBrowser1.Width - 20, webBrowser1.Height - 20));
if (currXPosition + webBrowser1.Width < bm.Width)
{
//we still have not traversed the full width of the page, call to alterxposition again...
}
else
{
copyEverythingToBitmap(bm, currXPosition, currYPosition, formPoint, gfx);
}
}
private void saveImageToFile(string p)
{
bm.Tag = DateTime.Now;
bm.Save(p, ImageFormat.Jpeg);
}