Tuesday, September 11, 2007

Force Closing the CameraCaptureDialog

The camera API in .NET CF 2 offers a great improvement over .NET CF 1, which had no common interface to the device's camera (you needed to work with the OEM's directly to obtain a reference to their camera driver information). The CameraCaptureDialog class in .NET CF 2 can be used to capture still photographs or video (with or without audio) in a few lines of code. However, like many of the more "advanced" features in the .NET CF 2 library, not all of the OEMs have correctly implemented the managed camera functionality on their devices. For example, one common complaint on discussion boards is not being able to close the camera after invoking it with the CameraCaptureDialog.ShowDialog() method--CameraCaptureDialog.Dispose() does not work. Thus, on some devices, the camera application stays open sucking up memory and disturbing your window z-order even after calling Dispose().

The code below provides a fix to this issue. It relies on a FindWindow P/Invoke to grab the handle to the device's camera application and a DestroyWindow P/Invoke to force it to close. Note that the Cingular 2125 device (which is where I tested this code) always appends [Photo] or [Video] to the camera title (no matter what title you set yourself). Thus, I have a function called GetCameraCaptureDialogTitle that appends that right suffix depending on the capture mode (e.g., video or photo).

//Open the camera capture dialog
CameraCaptureDialog cameraCaptureDialog = new CameraCaptureDialog();
string windowTitle = GetCameraCaptureDialogTitle(cameraCaptureDialog);
DialogResult dr = cameraCaptureDialog.ShowDialog();
if (dr == DialogResult.OK)
{
string capturedFileName = cameraCaptureDialog.FileName;
//do stuff with file!
}
cameraCaptureDialog.Dispose();

//sometimes the camera capture dialog does not close automatically
//we look for the window title and force close it ourselves
IntPtr ptr = WindowUtils.FindWindow(windowTitle);
Debug.WriteLine(string.Format("Found window '{0}' with ptr={1} ", windowTitle, ptr));
if (ptr != IntPtr.Zero)
{
//force the camera closed
WindowUtils.DestroyWindow(ptr);
}

Here are the helper functions:

private string GetCameraCaptureDialogTitle(CameraCaptureDialog dlg)
{
if (
dlg.Mode == CameraCaptureMode.Still)
{
return string.Format("{0} [{1}]",
dlg.Title, "Photo");
}
else
{
return string.Format("{0} [{1}]",
dlg.Title, "Video");
}
}

[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr DestroyWindow(IntPtr hWnd);

[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

public static IntPtr FindWindow(string windowTitle)
{
return FindWindow(null, windowTitle);
}


Please e-mail me or post a comment if you have any questions. Also, I use this trick in the MyExperience tool--the source for which can be found here and is open sourced under the BSD license.

Finally, moving beyond CameraCaptureDialog, it would be nice if .NET CF provided events for when a new image or video is captured on the device (e.g., a NewMediaCapturedEvent would be cool to have at the SystemState level if not in the CameraCaptureDialog as well). Furthermore, .NET CF 2 does not allow you to take images/video automatically without user intervention. This feature might be useful for taking timer-based pictures (e.g., for those times when you want to take a self/group portrait but have no one around to take the picture for you). Marcus Perryman has C++ code that turns a Windows Mobile device into a wireless webcam using using DirectShow (see this post). It would be cool to have this fully fleshed out in managed code.