|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionSometimes you might need to take snapshots of some Windows for a presentation or for a monitoring task. There are some articles about how to do it like Lim Bio Liong's article, but it uses old unmanaged C++ code, or it comes short when the target window is falling outside the desktop boundary. Hence I've created this C# application that allows capturing the specified Window and persisting it in a supported format file type. BackgroundIn order to capture a window you would need to get its handle and make use of the native win32 API calls to the bitmap handle that would be used by the managed code. There isn't much help in the FCL, so I had to import a lot of native calls. The site pinvoke.net is extremely helpful for such a task. Getting the window handle(s)If you knew the caption and/or the class name of the window you are looking for, then getting the window handle is trivial using the The internal UIApp(System.Diagnostics.Process proc)
{
_proc = proc;
_RealHWnd = IntPtr.Zero;
_windowHandles = new List<IntPtr>();
GCHandle listHandle = default(GCHandle);
try
{
if (proc.MainWindowHandle == IntPtr.Zero)
throw new ApplicationException
("Can't add a process with no MainFrame");
RECT MaxRect = default(RECT);//init with 0
if (IsValidUIWnd(proc.MainWindowHandle))
{
_RealHWnd = proc.MainWindowHandle;
return;
}
// the mainFrame is size == 0, so we look for the 'real' window
listHandle = GCHandle.Alloc(_windowHandles);
foreach (ProcessThread pt in proc.Threads)
{
Win32API.EnumThreadWindows((uint)pt.Id,
new Win32API.EnumThreadDelegate(EnumThreadCallback),
GCHandle.ToIntPtr(listHandle));
}
//get the biggest visible window in the current proc
IntPtr MaxHWnd = IntPtr.Zero;
foreach (IntPtr hWnd in _windowHandles)
{
RECT CrtWndRect;
//do we have a valid rect for this window
if (Win32API.IsWindowVisible(hWnd) &&
Win32API.GetWindowRect(hWnd, out CrtWndRect) &&
CrtWndRect.Height > MaxRect.Height &&
CrtWndRect.Width > MaxRect.Width)
{ //if the rect is outside the desktop, it's a dummy window
RECT visibleRect;
if (Win32API.IntersectRect(out visibleRect, ref _DesktopRect,
ref CrtWndRect)
&& !Win32API.IsRectEmpty(ref visibleRect))
{
MaxHWnd = hWnd;
MaxRect = CrtWndRect;
}
}
}
if (MaxHWnd != IntPtr.Zero && MaxRect.Width > 0 && MaxRect.Height > 0)
{
_RealHWnd = MaxHWnd;
}
else
_RealHWnd = proc.MainWindowHandle;
//just add something even if it's a bad window
}//try ends
finally
{
if (listHandle != default(GCHandle) && listHandle.IsAllocated)
listHandle.Free();
}
}
The list of the UI applications is created when this application starts. Also the applications listed in the combo box would have to be visible on the screen to be accounted for since they have size 0. The helper functions internal static bool IsValidUIWnd(IntPtr hWnd)
{
bool res =false;
if (hWnd == IntPtr.Zero || !Win32API.IsWindow(hWnd)
|| !Win32API.IsWindowVisible(hWnd))
return false;
RECT CrtWndRect;
if(!Win32API.GetWindowRect(hWnd, out CrtWndRect))
return false;
if (CrtWndRect.Height > 0 && CrtWndRect.Width > 0)
{// a valid rectangle means the right window is the mainframe
//and it intersects the desktop
RECT visibleRect;
//if the rectangle is outside the desktop, it's a dummy window
if (Win32API.IntersectRect(out visibleRect,
ref _DesktopRect, ref CrtWndRect)
&& !Win32API.IsRectEmpty(ref visibleRect))
res = true;
}
return res;
}
static bool EnumThreadCallback(IntPtr hWnd, IntPtr lParam)
{
GCHandle gch = GCHandle.FromIntPtr(lParam);
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(hWnd);
return true;
}
Capturing the window contentOnce we have the 'valid' mainframe handles, we can try to capture it using PInvoke heavily. private static Bitmap MakeSnapshot(IntPtr AppWndHandle,
bool IsClientWnd, Win32API.WindowShowStyle nCmdShow)
{
if (AppWndHandle == IntPtr.Zero || !Win32API.IsWindow(AppWndHandle) ||
!Win32API.IsWindowVisible(AppWndHandle))
return null;
if(Win32API.IsIconic(AppWndHandle))
Win32API.ShowWindow(AppWndHandle,nCmdShow);//show it
if(!Win32API.SetForegroundWindow(AppWndHandle))
return null;//can't bring it to front
System.Threading.Thread.Sleep(1000);//give it some time to redraw
RECT appRect;
bool res = IsClientWnd ? Win32API.GetClientRect
(AppWndHandle, out appRect): Win32API.GetWindowRect
(AppWndHandle, out appRect);
if (!res || appRect.Height == 0 || appRect.Width == 0)
{
return null;//some hidden window
}
// calculate the app rectangle
if(IsClientWnd)
{
Point lt = new Point(appRect.Left, appRect.Top);
Point rb = new Point(appRect.Right, appRect.Bottom);
Win32API.ClientToScreen(AppWndHandle,ref lt);
Win32API.ClientToScreen(AppWndHandle,ref rb);
appRect.Left = lt.X;
appRect.Top = lt.Y;
appRect.Right = rb.X;
appRect.Bottom = rb.Y;
}
//Intersect with the Desktop rectangle and get what's visible
IntPtr DesktopHandle = Win32API.GetDesktopWindow();
RECT desktopRect;
Win32API.GetWindowRect(DesktopHandle, out desktopRect);
RECT visibleRect;
if (!Win32API.IntersectRect
(out visibleRect, ref desktopRect, ref appRect))
{
visibleRect = appRect;
}
if(Win32API.IsRectEmpty(ref visibleRect))
return null;
int Width = visibleRect.Width;
int Height = visibleRect.Height;
IntPtr hdcTo = IntPtr.Zero;
IntPtr hdcFrom = IntPtr.Zero;
IntPtr hBitmap = IntPtr.Zero;
try
{
Bitmap clsRet = null;
// get device context of the window...
hdcFrom = IsClientWnd ? Win32API.GetDC(AppWndHandle) :
Win32API.GetWindowDC(AppWndHandle);
// create dc that we can draw to...
hdcTo = Win32API.CreateCompatibleDC(hdcFrom);
hBitmap = Win32API.CreateCompatibleBitmap(hdcFrom, Width, Height);
// validate
if (hBitmap != IntPtr.Zero)
{
// adjust and copy
int x = appRect.Left < 0 ? -appRect.Left : 0;
int y = appRect.Top < 0 ? -appRect.Top : 0;
IntPtr hLocalBitmap = Win32API.SelectObject(hdcTo, hBitmap);
Win32API.BitBlt(hdcTo, 0, 0, Width, Height,
hdcFrom, x, y, Win32API.SRCCOPY);
Win32API.SelectObject(hdcTo, hLocalBitmap);
// create bitmap for window image...
clsRet = System.Drawing.Image.FromHbitmap(hBitmap);
}
return clsRet;
}
finally
{
// release the unmanaged resources
if (hdcFrom != IntPtr.Zero)
Win32API.ReleaseDC(AppWndHandle, hdcFrom);
if(hdcTo != IntPtr.Zero)
Win32API.DeleteDC(hdcTo);
if (hBitmap != IntPtr.Zero)
Win32API.DeleteObject(hBitmap);
}
}
In case of success the return value is a managed Image object from the FCL. Saving the image in the specified formatSaving the image in any format supported by the .NET framework is very easy thanks to the FCL. private void btnSaveImage_Click(object sender, EventArgs e)
{ if(saveFileDialog1.ShowDialog()!=DialogResult.Cancel)
{ try
{ string ext = System.IO.Path.GetExtension
(saveFileDialog1.FileName).Substring(1).ToLower();
switch (ext)
{ case "jpg":
case "jpeg":
_pictureBox.Image.Save
(saveFileDialog1.FileName, ImageFormat.Jpeg);
break;
// .........code omitted for brevity
default:MessageBox.Show(this,
"Unknown format.Select a known one.", "Conversion error!",
MessageBoxButtons.OK, MessageBoxIcon.Error);
break;
}
}
catch (Exception ex)
{ MessageBox.Show(this, ex.Message, "Image Conversion error!",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}
HistoryThis is version 1.0.0.0 and it has been tested on Windows XP, on a single monitor graphic card. You might find some other cool things in this application like the system menu pop up, but that's outside the scope of the current topic. Enjoy!
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||