Introduction
I have often wanted to replicate the look and feel of another application for whatever reason, and it is a pain trying to get good graphics to match. This little app is just a compilation of other code samples I found and a little tweaking of my own, but I never found anything that does just what it does, so posting it up here for other people. It just lets you type the name of a window visible on the desktop, and then enumerates all the child windows of that window, and will extract images from them if found, and show them. Then, you can save them. I also made a feature to extract an animated GIF from those, although it just steals one frame at a time and then reassembles them together since I could not find a way to directly extract the resource. I just get a capture of the image for each window. It is very simple, very hacky, but it works nicely, and finally helped me get what I wanted in several cases. Hope it helps!
Note: The executable is attached above for those who cannot take time to read the description, so you can just click and play to see what it does. If you want to open the project, it requires Visual Studio 2008. You can download a trial of 2008 for absolutely no money whatsoever.
Background
Several other people's publically posted code is included in the snippets. Sorry I don't know who they are. The GIF Encoder and Animator was written by somebody else on here.
Using the code
It is pretty self explanatory and has just a few functions, so you can just download and use or copy parts of it as useful to you. Sorry if my explanation is sparse, but I figured for the few people searching and needing this, having something is better than having nothing, and when I was searching, I couldn't find it.
Anyway, to explain the code a bit, it uses FindWindowEx
from Win32 to get the first window with a title matching the string you put in the textbox.
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
string lpszClass, string lpszWindow);
public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.Dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern IntPtr EnumChildWindows(IntPtr parentHandle,
Win32Callback callback, IntPtr lParam);
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
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();
}
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);
return true;
}
No error checking at all, and just done for demonstration purposes. Then, it uses the Win32 EnumChildWindows
with the declared delegate EnumWindowProc
to enumerate all the child "windows" from that window, which is all the buttons, controls, images, etc., on the form. It just plugs the handle value from each of these into a listbox control. Then, when you click the listbox control, it gets the handle you clicked on and uses the Graphics
.NET object generated from the handle and copies the contents of the child window, BitBlt
ing them into the HDC for a bitmap, subsequently assigned to a PictureBox
's Image
property. This all occurs in the very short ShowImgFromHandle()
function:
private const int SRCCOPY = 0x00CC0020;
private const int CAPTUREBLT = 0x40000000;
[DllImport("gdi32.dll")]
private static extern bool BitBlt(
IntPtr hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
IntPtr hdcSrc,
int nXSrc,
int nYSrc,
int dwRop
);
private void ShowImgFromHandle(IntPtr hdlChild)
{
RECT rct;
GetWindowRect(hdlChild, out rct);
Rectangle wndRct = rct;
Graphics gr = Graphics.FromHwnd(hdlChild);
pictureBox1.Width = wndRct.Width;
pictureBox1.Height = wndRct.Height;
if (wndRct.Height == 0 || wndRct.Width == 0)
MessageBox.Show("This child window doesn't have a valid image.");
else
{
lblResourceInfo.Text = "Width: " + wndRct.Width.ToString() +
", Height: " + wndRct.Height.ToString();
Bitmap bmp = new Bitmap(wndRct.Width, wndRct.Height);
Graphics grBmp = Graphics.FromImage(bmp);
IntPtr bmpHdc = grBmp.GetHdc();
BitBlt(bmpHdc, 0, 0, wndRct.Width, wndRct.Height,
gr.GetHdc(), 0, 0, CAPTUREBLT | SRCCOPY);
grBmp.ReleaseHdc();
pictureBox1.Image = bmp;
}
}
Finally, if you click Save Bitmap, the SaveBmp
function just uses the PictureBox
's Image.Save
to save it to file.
private void SaveBmp(string FileName)
{
File.Delete(FileName);
System.Drawing.Imaging.ImageFormat imgfmt = ImageFormat.Bmp;
pictureBox1.Image.Save(FileName, imgfmt);
}
Using the AnimatedGifEncoder
, LZWEncoder
, and NeuQuant
classes I took from another project on this site that was cool, I made a Save Animated GIF button. This just calls the ShowImgFromHandle
function above 15 times in 1s, and uses the code from the AnimatedGif
class to capture each of those images into an animated GIF it creates, just one screenshot at a time. It sets the interval to the same period of the capture (1s), so there is no jumpiness in the final GIF. I have no idea how the AnimatedGifEncoder
works, just took it directly from the other project and appreciate that it was posted. It worked great.
private void SaveAnimatedGif(string FileName)
{
String outputFilePath = FileName;
AnimatedGifEncoder e = new AnimatedGifEncoder();
e.Start(outputFilePath);
e.SetDelay(1000/15);
e.SetRepeat(0);
for (int i = 0; i < 15; i++)
{
ShowImgFromHandle((IntPtr)listBox1.Items[listBox1.SelectedIndex]);
System.Threading.Thread.Sleep(1000 / 15);
e.AddFrame(pictureBox1.Image);
}
e.Finish();
}
I do hope this helps some people. This example is not meant to be a read-to-go app, just posted to show a technique that worked for me when I could not find anything else that could.
Good luck, and hope it is helpful to you.