Best way to take screenshot of a web page - c#

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);
}

Related

Is there a way to change the color of the NonClientFrameEdges or make them invisible (particularly with WPF/.NET)?

Goal Summary
I'm trying to make a WPF window with a custom window caption (so I can paint the title bar and add controls there). I've been successful in doing this with the WPF WindowChrome class, but it introduces a lot of bugs. I've been able to work around most of them by setting the WindowChrome NoneClientFrameEdges property to any value other than None, but this introduced a new bug in the process. There's an unsightly ~1px thick border where the NonClientFrameEdge property is set. You can see it in the video, but it's very faint. I want to either set the color to transparent or find a way to disable rendering it entirely. The problem is that I can't actually remove the NonClientFrameEdge because it's required to fix the bugs I mentioned earlier (more details below).
Detailed explanation of the problem (as best as I can understand it)
From what I understand, a standard window is separated into at least two parts: the client area and the non-client area. The client area is the part of the window you can easily customize, where you add your controls and other window content. The non-client area is the part of the window that includes the frame, which consists of the resize borders and the caption. Under normal conditions you can’t modify the non-client area.
The WindowChrome class in WPF provides an easy way to customize the non-client area of a WPF window. This enables you to design your own caption and place controls there like you see in modern enterprise apps such as Visual Studio and Microsoft Office. The problem is that using WindowChrome causes many bugs. Here’s a list of the ones I’ve found so far:
When you minimize the window from a maximized state and hover over it in the taskbar, the resulting window preview has ~8 px of empty space on the top and top-left sides. If you click it to bring it to the front, it snaps back to the proper space. Even Visual Studio and Microsoft Office have this bug. (caused by having GlassFrameThickness value of 0 set in WindowChrome class)
The transparent glass rectangle effect you get when hovering over the aero peek button for “peek at desktop” no longer works. You just see empty space, no outline of the app. (caused by having GlassFrameThickness value of 0 set in WindowChrome class)
The window is jittery when resizing from any corner other than the bottom right. I’ve read this is because resizing from these corners forces a position change of the window. (caused by having NonClientFrameEdges value of 0, at least 1 edge needs to be set to avoid this)
The window is distorted and blurry during the focus zoom effect when selecting a minimized window from task view. Even many enterprise apps have this problem. (Adobe Photoshop, Visual Studio, and several game launchers, to name a few). (caused either by GlassFrameThickness value of 0, NonClientFrameEdges value of None, or a combination of the two)
Hacky solution and video before and after
I noticed that issue 1 and 2 can be fixed by setting the GlassFrameThickness to a non-zero value. Issues 3 and 4 can be fixed by setting the NonClientFrameEdges property of the WindowChrome class to any value other than None. This video shows all of the problems listed above, along with how they look before and after changing that property: https://www.youtube.com/watch?v=z7O28aEPygg
Note: The video ends abruptly due to my poor editing skills, but it should show all the necessary issues. You will have to look very closely at the bottom of the app in the second half of the video to see the 1px grey/white NonClientFrameEdge, but it's noticeable. This is what I'm trying to fix.
The problem is that setting NonClientFrameEdges to any value other than None literally adds a 2-3 px edge to your window, and it's visually obvious. Setting GlassFrameThickness to 1 on the same edge as the NonClientFrameEdge reduces the visibility significantly, but it's still a noticeable 1px or so eyesore. For example if NonClientFrameEdge is set to Bottom, GlassFrameThickness should be set to "0, 0, 0, 1".
Minimal Example
You can reproduce the problem by creating a .NET or .NET Core WPF project and adding the following code to the window's XAML view file:
<WindowChrome.WindowChrome>
<WindowChrome GlassFrameThickness="0 0 0 1" CornerRadius="0" CaptionHeight="38" UseAeroCaptionButtons="False" ResizeBorderThickness="5" NonClientFrameEdges="Bottom" />
</WindowChrome.WindowChrome>
Here's a Git Repo with a minimal project for convenience if you want it to test but don't feel like typing all the code. It includes some extra stuff like the boilerplate for the ViewModel, command firing, buttons for min/max/close, and a button to add a border for testing (makes things like the resize jitter more apparent). It also includes a hook into WndProc with Pinvoke just in case you want to experiment with the WindowsAPI: https://github.com/cjfcode/WindowProject
The key to solving this is to wire up a window hook with a handler for the WM_NCCALCSIZE (0x83) and WM_NCPAINT (0x85) messages.
WM_NCPAINT will allow you to remove the single pixel bottom border by calling DwmExtendFrameIntoClientArea. In the code below I've wrapped that call in a method called RemoveFrame.
WM_NCCALCSIZE will allow you to change the size of the client area of the window restoring the extra space that WindowChrome set by using GlassFrameThickness="0,0,0,1" and NonClientFrameEdges="Bottom".
I've wrapped up this functionality into a XAML behavior.
Here is final code that will solve your issue:
WindowChromeLoadedBehavior
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Shell;
using Microsoft.Xaml.Behaviors;
namespace WpfApp1
{
public class WindowChromeLoadedBehavior : Behavior<FrameworkElement>
{
private Window window;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnLoaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
window = Window.GetWindow(AssociatedObject);
if (window == null) return;
Task.Delay(5).ContinueWith(_ =>
{
Dispatcher.Invoke(() =>
{
var oldWindowChrome = WindowChrome.GetWindowChrome(window);
if (oldWindowChrome == null) return;
var newWindowChrome = new WindowChrome
{
CaptionHeight = oldWindowChrome.CaptionHeight,
CornerRadius = oldWindowChrome.CornerRadius,
GlassFrameThickness = new Thickness(0, 0, 0, 1),
NonClientFrameEdges = NonClientFrameEdges.Bottom,
ResizeBorderThickness = oldWindowChrome.ResizeBorderThickness,
UseAeroCaptionButtons = oldWindowChrome.UseAeroCaptionButtons
};
WindowChrome.SetWindowChrome(window, newWindowChrome);
});
});
var hWnd = new WindowInteropHelper(window).Handle;
HwndSource.FromHwnd(hWnd)?.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case NativeMethods.WM_NCPAINT:
RemoveFrame();
handled = false;
break;
case NativeMethods.WM_NCCALCSIZE:
handled = false;
var rcClientArea = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
rcClientArea.Bottom += (int)(WindowChromeHelper.WindowResizeBorderThickness.Bottom / 2);
Marshal.StructureToPtr(rcClientArea, lParam, false);
var retVal = IntPtr.Zero;
if (wParam == new IntPtr(1))
{
retVal = new IntPtr((int)NativeMethods.WVR.REDRAW);
}
return retVal;
}
return IntPtr.Zero;
}
private void RemoveFrame()
{
if (Environment.OSVersion.Version.Major >= 6 && NativeMethods.IsDwmAvailable())
{
if (NativeMethods.DwmIsCompositionEnabled() && SystemParameters.DropShadow)
{
NativeMethods.MARGINS margins;
margins.bottomHeight = -1;
margins.leftWidth = 0;
margins.rightWidth = 0;
margins.topHeight = 0;
var helper = new WindowInteropHelper(window);
NativeMethods.DwmExtendFrameIntoClientArea(helper.Handle, ref margins);
}
}
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public static RECT Empty;
public int Width => Math.Abs(Right - Left);
public int Height => (Bottom - Top);
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RECT(RECT rcSrc)
{
Left = rcSrc.Left;
Top = rcSrc.Top;
Right = rcSrc.Right;
Bottom = rcSrc.Bottom;
}
public RECT(Rectangle rectangle) : this(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom)
{
}
public bool IsEmpty
{
get
{
if (Left < Right)
{
return (Top >= Bottom);
}
return true;
}
}
public override string ToString()
{
if (this == Empty)
{
return "RECT {Empty}";
}
return string.Concat("RECT { left : ", Left, " / top : ", Top, " / right : ", Right, " / bottom : ", Bottom, " }");
}
public override bool Equals(object obj)
{
return ((obj is RECT) && (this == ((RECT)obj)));
}
public override int GetHashCode()
{
return ((Left.GetHashCode() + Top.GetHashCode()) + Right.GetHashCode()) + Bottom.GetHashCode();
}
public static bool operator ==(RECT rect1, RECT rect2)
{
return ((((rect1.Left == rect2.Left) && (rect1.Top == rect2.Top)) && (rect1.Right == rect2.Right)) && (rect1.Bottom == rect2.Bottom));
}
public static bool operator !=(RECT rect1, RECT rect2)
{
return !(rect1 == rect2);
}
static RECT()
{
Empty = new RECT();
}
}
}
}
WindowChromeHelper
using System;
using System.Runtime.InteropServices;
using System.Windows;
namespace WpfApp1
{
public static class WindowChromeHelper
{
public static Thickness LayoutOffsetThickness => new Thickness(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
/// <summary>
/// Gets the properly adjusted window resize border thickness from system parameters.
/// </summary>
public static Thickness WindowResizeBorderThickness
{
get
{
var dpix = GetDpi(GetDeviceCapsIndex.LOGPIXELSX);
var dpiy = GetDpi(GetDeviceCapsIndex.LOGPIXELSY);
var dx = GetSystemMetrics(GetSystemMetricsIndex.CXFRAME);
var dy = GetSystemMetrics(GetSystemMetricsIndex.CYFRAME);
// This adjustment is needed since .NET 4.5
var d = GetSystemMetrics(GetSystemMetricsIndex.SM_CXPADDEDBORDER);
dx += d;
dy += d;
var leftBorder = dx / dpix;
var topBorder = dy / dpiy;
return new Thickness(leftBorder, topBorder, leftBorder, topBorder);
}
}
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
private static float GetDpi(GetDeviceCapsIndex index)
{
var desktopWnd = IntPtr.Zero;
var dc = GetDC(desktopWnd);
float dpi;
try
{
dpi = GetDeviceCaps(dc, (int)index);
}
finally
{
ReleaseDC(desktopWnd, dc);
}
return dpi / 96f;
}
private enum GetDeviceCapsIndex
{
LOGPIXELSX = 88,
LOGPIXELSY = 90
}
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(GetSystemMetricsIndex nIndex);
private enum GetSystemMetricsIndex
{
CXFRAME = 32,
CYFRAME = 33,
SM_CXPADDEDBORDER = 92
}
}
}
NativeMethods
using System;
using System.Runtime.InteropServices;
namespace WpfApp1
{
public static class NativeMethods
{
public const int WM_NCCALCSIZE = 0x83;
public const int WM_NCPAINT = 0x85;
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("dwmapi.dll", PreserveSig = false)]
public static extern bool DwmIsCompositionEnabled();
[DllImport("kernel32", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public int leftWidth;
public int rightWidth;
public int topHeight;
public int bottomHeight;
}
private delegate int DwmExtendFrameIntoClientAreaDelegate(IntPtr hwnd, ref MARGINS margins);
public static int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins)
{
var hModule = LoadLibrary("dwmapi");
if (hModule == IntPtr.Zero)
{
return 0;
}
var procAddress = GetProcAddress(hModule, "DwmExtendFrameIntoClientArea");
if (procAddress == IntPtr.Zero)
{
return 0;
}
var delegateForFunctionPointer = (DwmExtendFrameIntoClientAreaDelegate)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(DwmExtendFrameIntoClientAreaDelegate));
return delegateForFunctionPointer(hwnd, ref margins);
}
public static bool IsDwmAvailable()
{
if (LoadLibrary("dwmapi") == IntPtr.Zero)
{
return false;
}
return true;
}
internal enum WVR
{
ALIGNTOP = 0x0010,
ALIGNLEFT = 0x0020,
ALIGNBOTTOM = 0x0040,
ALIGNRIGHT = 0x0080,
HREDRAW = 0x0100,
VREDRAW = 0x0200,
VALIDRECTS = 0x0400,
REDRAW = HREDRAW | VREDRAW
}
}
}

Find out which Windows Explorer window was clicked to start my executable.

I am working on a console executable that may run in multi monitor environment.
It may be started by double clicking on the Exe file name inside Windows Explorer.
I want to move the console to the same monitor as the Windows Explorer window that started it. Is there any way to do it?
I was able to get parent process and get MainWindowHandle.
But it will not give me the correct window. It will give me the first Explorer window. See code below. SetupConsoleWindow is the main function.
internal static class NativeMethods
{
internal const int SWP_NOSIZE = 0x0001;
[DllImport("kernel32.dll")]
internal static extern bool AllocConsole();
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
internal static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetConsoleWindow();
}
public static Process GetParentProcess()
{
var currentProcessName = Process.GetProcessById(Process.GetCurrentProcess().Id).ProcessName;
var processesByName = Process.GetProcessesByName(currentProcessName);
for (var index = 0; index < processesByName.Length; index++)
{
string processIndexdName = index == 0 ? currentProcessName : currentProcessName + "#" + index;
var processId = new PerformanceCounter("Process", "ID Process", processIndexdName);
if ((int)processId.NextValue() == Process.GetCurrentProcess().Id)
break;
}
var parentPerformanceCounter = new PerformanceCounter("Process", "Creating Process ID", currentProcessName);
Process parentProcess = Process.GetProcessById((int)parentPerformanceCounter.NextValue());
return parentProcess;
}
public static void SetupConsoleWindow()
{
NativeMethods.AllocConsole();
var parentProcess = GetParentProcess();
if (parentProcess != null)
{
IntPtr consoleWindowHandle = NativeMethods.GetConsoleWindow();
Screen parentScreen = Screen.FromHandle(parentProcess.MainWindowHandle);
Rectangle monitor = parentScreen.WorkingArea;
NativeMethods.SetWindowPos(consoleWindowHandle, 0, monitor.Left, monitor.Top, 0, 0, NativeMethods.SWP_NOSIZE);
}
}
So what you want to do is launch your program on the same monitor as the Windows Explorer window that started it. Doing this the way you currently intend to may be technically possible, but it would be cumbersome and flaky. It would involve enumerating all the open windows, figuring out which ones are Explorer windows, then for each of those which folder is being viewed and then which file is currently selected. You would then compare the name of that file with your program's name and, if multiple such windows are found, choose the most top-level one. You would have to hope that the file did not somehow get deselected between the double click and your main().
This process is well documented here. For reference, here is what the code to get the currently selected item looks like in C++:
#include <shlobj.h>
#include <exdisp.h>
TCHAR g_szPath[MAX_PATH];
TCHAR g_szItem[MAX_PATH];
void CALLBACK RecalcText(HWND hwnd, UINT, UINT_PTR, DWORD)
{
HWND hwndFind = GetForegroundWindow();
g_szPath[0] = TEXT('\0');
g_szItem[0] = TEXT('\0');
IShellWindows *psw;
if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
IID_IShellWindows, (void**)&psw))) {
VARIANT v;
V_VT(&v) = VT_I4;
IDispatch *pdisp;
BOOL fFound = FALSE;
for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
V_I4(&v)++) {
IWebBrowserApp *pwba;
if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
HWND hwndWBA;
if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
hwndWBA == hwndFind) {
fFound = TRUE;
IServiceProvider *psp;
if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
IShellBrowser *psb;
if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
IID_IShellBrowser, (void**)&psb))) {
IShellView *psv;
if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
IFolderView *pfv;
if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
(void**)&pfv))) {
IPersistFolder2 *ppf2;
if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
(void**)&ppf2))) {
LPITEMIDLIST pidlFolder;
if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
lstrcpyn(g_szPath, TEXT("<not a directory>"), MAX_PATH);
}
int iFocus;
if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
LPITEMIDLIST pidlItem;
if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
IShellFolder *psf;
if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
(void**)&psf))) {
STRRET str;
if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
SHGDN_INFOLDER,
&str))) {
StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);
}
psf->Release();
}
CoTaskMemFree(pidlItem);
}
}
CoTaskMemFree(pidlFolder);
}
ppf2->Release();
}
pfv->Release();
}
psv->Release();
}
psb->Release();
}
psp->Release();
}
}
pwba->Release();
}
pdisp->Release();
}
psw->Release();
}
InvalidateRect(hwnd, NULL, TRUE);
}
However, you may be making this more complicated than it needs to be. Consider this instead:
Get the current mouse coordinates with Cursor.Position (or GetCursorPos)
Get the monitor on which the mouse cursor is present
Move your window to that monitor

How to define 'CreateDIB' function or reproduce its purpose in C#?

I'm trying to translate this C++ code from this MSDN reference, to C# or VB.NET:
case WM_DWMSENDICONICTHUMBNAIL:
{
// This window is being asked to provide its iconic bitmap. This indicates
// a thumbnail is being drawn.
hbm = CreateDIB(HIWORD(lParam), LOWORD(lParam));
if (hbm)
{
hr = DwmSetIconicThumbnail(hwnd, hbm, 0);
DeleteObject(hbm);
}
}
break;
At first sight seems simple, but I need help to understand about the CreateDIB function, I don't know what means and which is the purpose of that function, I can't find info about, and also I can't find it inside the Windows SDK header files, nothing of nothing.
Where is defined that function?, It is necessary to follow good practices in that C++ example?, how to declare it from C#, or which is the .NET equivalent for that unmanaged function?.
I found the CImageAllocator.CreateDIB which I'm not sure whether it reffers to that, but the parameters of that function does not corresponds to a kind of CreateDIB(width, height) like I seen in this other MSDN code, so probablly is not the same function, and also it is a directshow thing...
Well, this is the current translation I did, it works, but I'm worried about possible memory issues because the lack of CreateDIB function or its equivalent managed member:
Case WM_DWMSENDICONICTHUMBNAIL
Dim hwnd As IntPtr = Process.GetCurrentProcess().MainWindowHandle
Dim dWord As Integer = m.LParam.ToInt32()
Dim maxWidth As Short = BitConverter.ToInt16(BitConverter.GetBytes(dWord), 2)
Dim maxHeight As Short = BitConverter.ToInt16(BitConverter.GetBytes(dWord), 0)
Using img As Image = Bitmap.FromFile("C:\Image.jpg")
Using thumb As Bitmap = CType(img.GetThumbnailImage(maxWidth, maxHeight, Nothing, Nothing), Bitmap)
Dim hBitmap As IntPtr = thumb.GetHbitmap()
Dim hresult As Integer = NativeMethods.DwmSetIconicThumbnail(hwnd, hBitmap, 0)
If (hresult <> 0) Then
' Handle error...
' Throw Marshal.GetExceptionForHR(hresult)
End If
NativeMethods.DeleteObject(hBitmap)
End Using
End Using
That's way too much effort expended to set the thumbnail image. Just keep a copy of the bitmap on your window, and draw it when you need to.
public partial class Form1 : Form
{
[DllImport("Dwmapi.dll")]
static extern int DwmSetIconicThumbnail(IntPtr hWnd, IntPtr hbmp, uint dwSITFlags);
[DllImport("Dwmapi.dll")]
static extern int DwmSetWindowAttribute(IntPtr hWnd, uint dwAttribute, IntPtr pvAttribute, uint cbAttribute);
const uint WM_DWMSENDICONICTHUMBNAIL = 0x0323;
const uint DWMWA_FORCE_ICONIC_REPRESENTATION = 7;
const uint DWMWA_HAS_ICONIC_BITMAP = 10;
Size thumbSize = new Size(30, 30);
Bitmap thumbImage = new Bitmap(30, 30);
object sync = new object();
public Form1()
{
InitializeComponent();
using (Graphics g = Graphics.FromImage(this.thumbImage))
{
g.Clear(Color.Blue);
g.DrawRectangle(Pens.Black, new Rectangle(new Point(0, 0), this.thumbSize));
}
this.HandleCreated += Form1_HandleCreated;
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_DWMSENDICONICTHUMBNAIL)
{
lock (this.sync)
{
int x = (int)((m.LParam.ToInt32() >> 16) & 0xffff);
int y = (int)(m.LParam.ToInt32() & 0xffff);
if (this.thumbSize != new Size(x, y))
{
this.thumbSize = new Size(x, y);
this.UpdateBitmap();
}
DwmSetIconicThumbnail(this.Handle, thumbImage.GetHbitmap(), 0);
}
}
base.WndProc(ref m);
}
void UpdateBitmap()
{
lock (this.sync)
{
this.thumbImage = new Bitmap(this.thumbSize.Width, this.thumbSize.Height);
using (Graphics g = Graphics.FromImage(this.thumbImage))
{
g.Clear(Color.Blue);
g.DrawRectangle(Pens.Black, new Rectangle(new Point(0, 0), this.thumbSize));
//or: g.DrawImage() with stretching specified.
}
}
}
private void Form1_HandleCreated(object sender, EventArgs e)
{
IntPtr val = Marshal.AllocHGlobal(4);
Marshal.WriteInt32(val, 1);
DwmSetWindowAttribute(this.Handle, DWMWA_FORCE_ICONIC_REPRESENTATION, val, 4);
DwmSetWindowAttribute(this.Handle, DWMWA_HAS_ICONIC_BITMAP, val, 4);
Marshal.FreeHGlobal(val);
}
}
C# since the tags on the question list it.

"Fast" Displaying in a DocumentViewer

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.

richTextBox.DrawToBitmap Does Not Draw Containing Text?

If I have a richTextBox and run DrawToBitmap on it, it doesn't draw any of the text inside of the richTextBox.
Bitmap b = new Bitmap(rtb.Width, rtb.Height);
inputControl.DrawToBitmap(b, new Rectangle(0, 0, b.Width, b.Height));
Is there any way to fix this?
I know this is relatively old, but a working solution that I found at http://www.windows-tech.info/3/8ffaf21eed5de2d4.php:
public static Bitmap RtbToBitmap(RichTextBox rtb)
{
rtb.Update(); // Ensure RTB fully painted
Bitmap bmp = new Bitmap(rtb.Width, rtb.Height);
using (Graphics gr = Graphics.FromImage(bmp))
{
gr.CopyFromScreen(rtb.PointToScreen(Point.Empty), Point.Empty, rtb.Size);
}
return bmp;
}
This thread came up second in Google. Seems to have exactly what you want. Because I imagine you're using this inside your function from this question Accepting Form Elements As Method Arguments?, it's probably best to do something like this.
if(inputControl is RichTextBox)
{
//do specifc magic here
}
else
{
//general case
}
You can check for a Control containing RichTextBox recursively
bool ContainsOrIsRichTextBox(Control inputControl)
{
if(inputControl is RichTextBox) return true;
foreach(Control control in inputControl.Controls)
{
if(ContainsOrIsRichTextBox(control)) return true;
}
return false;
}
I haven't compiled this, and there's a way of doing it without risking a StackOverflowException, but this should get you started.
From the MSDN Library article for RichTextBox.DrawToBitmap():
This method is not relevant for this class.
A crummy way to say that the native Windows richedit control doesn't support WM_PRINT. Taking a screen shot is an option, Novikov gave you a link to my answer.
For what it's worth, the later version of the RichTextBox control supports the DrawToBitmap method properly; it also improves performance and has more features.
internal class RichTextBox5: RichTextBox
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern IntPtr LoadLibrary(string lpFileName);
protected override CreateParams CreateParams
{
get
{
CreateParams cparams = base.CreateParams;
if (LoadLibrary("msftedit.dll") != IntPtr.Zero)
{
cparams.ClassName = "RICHEDIT50W";
}
return cparams;
}
}
}
I found a related answer here: how to print Rich text box contents on any device contenxt with proper formatting?
I changed this to render my off screen RichTextBox to a bitmap. This way I could create a bitmap off screen and then send it to OpenGL.
// Convert the unit used by the .NET framework (1/100 inch)
// and the unit used by Win32 API calls (twips 1/1440 inch)
private const double anInch = 14.4;
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct CHARRANGE
{
public int cpMin; // First character of range (0 for start of doc)
public int cpMax; // Last character of range (-1 for end of doc)
}
[StructLayout(LayoutKind.Sequential)]
private struct FORMATRANGE
{
public IntPtr hdc; // Actual DC to draw on
public IntPtr hdcTarget; // Target DC for determining text formatting
public RECT rc; // Region of the DC to draw to (in twips)
public RECT rcPage; // Region of the whole DC (page size) (in twips)
public CHARRANGE chrg; // Range of text to draw (see earlier declaration)
}
private const int WM_USER = 0x0400;
private const int EM_FORMATRANGE = WM_USER + 57;
[DllImport("USER32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
/// <summary>
/// Render the specified RichTextBox onto the specified bitmap
/// </summary>
/// <param name="textBox">RichTextBox to render</param>
/// <param name="bitmap">Bitmap to render the RichTextBox onto</param>
public void RenderToBitmap(RichTextBox textBox, Bitmap bitmap)
{
// Set area to render to be entire bitmap
RECT rect;
rect.Left = 0;
rect.Top = 0;
rect.Right = (int)(bitmap.Width * anInch);
rect.Bottom = (int)(bitmap.Height * anInch);
Graphics g = Graphics.FromImage(bitmap);
IntPtr hdc = g.GetHdc();
FORMATRANGE fmtRange;
fmtRange.chrg.cpMin = textBox.GetCharIndexFromPosition(new Point(0,0));
fmtRange.chrg.cpMax = textBox.GetCharIndexFromPosition(new Point(bitmap.Width,bitmap.Height));
fmtRange.hdc = hdc; // Use the same DC for measuring and rendering
fmtRange.hdcTarget = hdc;
fmtRange.rc = rect;
fmtRange.rcPage = rect;
IntPtr lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lparam, false);
// Render the control to the bitmap
SendMessage(textBox.Handle, EM_FORMATRANGE, new IntPtr(1), lparam);
// Clean up
Marshal.FreeCoTaskMem(lparam);
g.ReleaseHdc(hdc);
}
I tested the methods above, and whenever I load the saved Bitmap into an ImageViewer(Like Paint), it would have the SaveFileDialog-UI faded into the background of the text. Luckily, I found an easy fix:
SaveFileDialog bfsd = new SaveFileDialog();
var rtb = richTextBox1;
bfsd.Filter = "Bitmap (*.bmp)|*.bmp|All Files (*.*)|*.*";
bfsd.Title = "Save your text as a Bitmap File";
rtb.Update(); // Ensure RTB fully painted
Bitmap bmp = new Bitmap(rtb.Width, rtb.Height);
using (Graphics gr = Graphics.FromImage(bmp))
{
gr.CopyFromScreen(rtb.PointToScreen(Point.Empty), Point.Empty, rtb.Size);
}
if (bfsd.ShowDialog()==DialogResult.OK)
{
Draw:
try
{
bmp.Save(bfsd.FileName);
bmp.Dispose();
}
catch (Exception)
{
DialogResult dr = MessageBox.Show("An error ocurred while attempting to save your Image...", "Error! Error!", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
if (dr == DialogResult.Retry)
{
goto Draw;
}
else if (dr == DialogResult.Cancel)
{
return;
}
}
That way, it paints the picture before you even press Save(Don't worry, it won't actually save the image until you press Save)
Pressing Cancel doesn't effect the process, because when you press the Button or MenuStripItem to save it, it will update & re-paint it :)
I implemented a try-catch method, so that it will catch an error if one occurs, rather than the app just (Not Responding)
The catch method is a Retry Button
It will catch the error, and give you the choice to either Cancel the whole Operation, or Retry
I used a goto to be able to just rewind, and make another attempt to save the file, rather than having the SaveFileDialog appear again.
I hope this helps you :)

Categories