Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Enhancing the WPF Screen Capture Program with Window Borders

Pete Brown - 09 February 2011

In my previous post on creating a simple WPF Screen Capture program, I showed the basics of using the Win32 API and some WPF to take screen shots of other applications, regions on the screen, or the full screen. In this post, we'll enhance that sample to add a visible indicator of what window is being captured.

This example uses the full-screen almost-invisible WPF Window we created in the previous example. However, I've refactored the code and made a discrete window type. To the Window's xaml, I added the transparency properties as well as a red selection region.

<Window x:Class="PeteBrown.ScreenCapture.CaptureWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300"
WindowStyle="None"
ShowInTaskbar="False"
AllowsTransparency="True"
Topmost="True">
<Grid>
<Rectangle x:Name="WindowHighlight"
Visibility="Collapsed"
Stroke="Red"
StrokeThickness="3" />
</Grid>
</Window>

Those window style and transparency settings were set in code in the previous version.

The capture window itself is very similar to what we had in the previous example. However, I added two events (discussed below), and the code to handle the highlight. Here's the highlight code:

private IntPtr _hoveredHWnd = IntPtr.Zero;
void CaptureWindow_MouseMove(object sender, MouseEventArgs e)
{
Point current = e.GetPosition(this);
Point screenCoords = PointToScreen(current);

// this hide and show causes flicker, but using this
// approach, there's not much you can do about that.
// Caching the window rect doesn't help in the case of overlapping
// windows. One thing you could do is run through and cache *all*
// window rects. You could do some interesting things with that
// but unless you restrict yourself to top-level windows, you'll
// have a lot of rects to deal with
Hide();

IntPtr hWnd = User32.WindowFromPoint(new Win32Point((int)screenCoords.X, (int)screenCoords.Y));

Show();

if (hWnd != IntPtr.Zero)
{
// don't bother changing things if this is the same
// window we've been over
if (hWnd != _hoveredHWnd)
{
// get the dimensions of the window we're hovered over
Win32Rect rect = new Win32Rect();
User32.GetWindowRect(hWnd, out rect);

Point topLeft = PointFromScreen(new Point(rect.Left, rect.Top));


WindowHighlight.Width = rect.Width;
WindowHighlight.Height = rect.Height;
WindowHighlight.Margin = new Thickness(
topLeft.X,
topLeft.Y,
Width - rect.Width - topLeft.X,
Height - rect.Height - topLeft.Y);

WindowHighlight.Visibility = Visibility.Visible;
}
}
}

This code hides the window, calls the API call to get the hWnd under the mouse cursor, then, if the window isn't already selected, sizes and moves the red rectangle to highlight the bounds of that hWnd. To be clear, there is a fair amount of flicker when you keep the mouse relatively steady over the same window. I need to do something about that.

As I mentioned, the selection code itself is very similar to what we had before, but with the addition of WindowSelected and Canceled events.

private Point _mouseDown;
void CaptureWindow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_mouseDown = e.GetPosition(this);
}

void CaptureWindow_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point current = e.GetPosition(this);

if (Math.Abs(_mouseDown.X - current.X) < 10.0 &&
Math.Abs(_mouseDown.Y - current.Y) < 10.0)
{
// this is the window they want

Point screenCoords = PointToScreen(current);
Hide();

IntPtr hWnd = User32.WindowFromPoint(new Win32Point((int)screenCoords.X, (int)screenCoords.Y));
User32.SetForegroundWindow(hWnd);

SelectedHWnd = hWnd;

OnWindowSelected();
}
}

protected void OnWindowSelected()
{
if (WindowSelected != null)
WindowSelected(this, EventArgs.Empty);
}

The attached source code includes the cancel and event wire-up code as well.

I then modified the calling code in the main window to use the new window and sink the new events. Originally, I had this as a modal window with a regular DialogResult. However, due to the need to actually hide the window before making the Win32 API call (setting visibility or opacity wouldn't work), and re-show it, that doesn't work. So, I went from a modal approach to an event-driven approach.

In this partial screen shot (taken with normal print screen) you can see the red selection highlight around Visual Studio.

image

Note that as was the case in the previous version of the code, the calls here do not restrict you to the main window hWnd. I'll want to modify the code to do that for this specific scenario (where we're expanding the size to include the border and shadow), but still retain an "any hWnd" mode, as that's pretty useful as well.

image

The Clipboard functionality is still disabled. I'll address that in an upcoming post. I promise! :)

       

Source Code and Related Media

Download /media/73989/petebrown.screencapture2.zip
posted by Pete Brown on Wednesday, February 9, 2011
filed under:        

2 comments for “Enhancing the WPF Screen Capture Program with Window Borders”

  1. Alsays:
    I have a procedure in my code similar to yours. It works under Windows 7 but not in Vista (the reason to use Vista is that we have legacy systems, and by now we have to maintain such equipment). All the win32 calls are returning true (or at least not 0), but when I draw the captured image at OnRender, it does not show anything.

    Aditionally, I have checked that the bitmapsource can be saved (e.g. as a png file), and there is something in there. The content of the image file seems correct, but the alpha channel is set incorrectly. If the image file is open in a viewer that handles the alpha channel, it shows a transparent image. In contrast if it is open with Paint, the image is correctly visualized.


    I run your application (BTW really nice piece of work) and I checked that it behaves as in my code.

    Any idea about how to fix this issue?

    Thanks

Comment on this Post

Remember me