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.

10 comments:

Noble Bell said...

Nice workaround. I have been looking for something like this. I am going to give it a shot and see how well it works on a TDS Nomad device.

jonfroehlich said...

Note that the helper function GetCameraCaptureDialogTitle(CameraCaptureDialog dlg) may need to be changed depending on your platform. On Cingular 2125 devices, the camera title is always appended by Video or Photo depending on its capture mode.

Noble Bell said...

I keep getting a compile error telling me that there is no function named "DestroyWindow". I have your code entered just like you have it posted. I do notice that you have a "FindWindow" method but not a "DestroyWindow Method". Thoughts?

jonfroehlich said...

Hi Noble,

Actually, I am P/Invoking both FindWindow and DestroyWindow. Do you see those two method definitions:

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

They work in concert together. Please let me know if you have any other issues :)

Noble Bell said...

Yep, I see those and they are also in my code, different namespace than WindowUtils though.

Here is my code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.WindowsMobile.Forms;
using System.Runtime.InteropServices;


namespace New_Camera_Test
{
public partial class Form1 : Form
{
[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);


CameraCaptureDialog ccd = new CameraCaptureDialog();
string windowTitle = "";

public Form1()
{
InitializeComponent();
InitializeCamera();
windowTitle = GetCameraCaptureDialogTitle(ccd);

}

private void InitializeCamera()
{
ccd.Owner = this;
ccd.Resolution = new Size(480, 640);
ccd.VideoTimeLimit = new TimeSpan(0, 0, 2);
ccd.Mode = Microsoft.WindowsMobile.Forms.CameraCaptureMode.Still;
ccd.StillQuality = Microsoft.WindowsMobile.Forms.CameraCaptureStillQuality.Normal;
ccd.Title = "Take Picture Now";

ccd.ShowDialog();

if (DialogResult.OK == ccd.ShowDialog())
{
System.IO.File.Move(ccd.FileName, "My Documents\\img.jpg");
this.Close();
Application.Exit();
}
ccd.Dispose();

IntPtr ptr = FindWindow(windowTitle);
if (ptr != IntPtr.Zero)
{
DestoryWindow(ptr);
}

}

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

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

Unknown said...

Hi, I want that CameraCaptureDialog should not go in Standby Mode after 15 seconds as it is going.
I want to extend this time.
Is it possible ?

CarlosS said...

Hi.

although my scenario is a bit diferent i'm using .NET Cf 3.5 and a htc 6500 WM 6.0 device. I've got the same issue with CameraCaptureDialog(). after some calls to ShowDialog it fails with the "unknow error...".

I've tried to find and destroy window, it works better but still fails sometimes like using the same instance to CameraCaptureDialog.

The solution for my problem is openenig the camera in a separate thread. This is my code.

private void takePicture()
{
try
{
CameraCaptureDialog cam2 = new CameraCaptureDialog();
DialogResult d = new DialogResult();
d = cam2.ShowDialog();


if (d == DialogResult.OK)
{
//this.pictureBox1.Image = new Bitmap(cam.FileName);
object[] oarray = new object[1];
oarray[0] = cam2.FileName;
this.Invoke(myDelegate, oarray);
cont++;
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}

}

private void buttonPicture_Click(object sender, EventArgs e)
{
ThreadStart thsCamera = new ThreadStart(takePicture);
thCamera = new Thread(thsCamera);
thCamera.Start();
}

Unknown said...

Sir, actually I want to know the code which gets activated when the camera is activated by the user and not by the code.After that the image get stored in the database.

Unknown said...

Hi,

I want the code which gets activated only when the camera of the windows mobile gets activated by the user and not by the code.And also I want the code for saving the images into the database.

Anonymous said...

Thanks.
FORCER FERMETURE CAMERA VB.NET
COMPACT FRAMEWORK

This code to VB.NET

Imports System.Runtime.InteropServices
--------------------------------

_
Public Shared Function DestroyWindow(ByVal hWnd As IntPtr) As IntPtr

End Function


_
Private Shared Function FindWindow( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As IntPtr
End Function

_
Private Shared Function FindWindowByClass( _
ByVal lpClassName As String, _
ByVal zero As IntPtr) As IntPtr
End Function

_
Private Shared Function FindWindowByCaption( _
ByVal zero As IntPtr, _
ByVal lpWindowName As String) As IntPtr
End Function

-----------------

Private Function GetCameraCaptureDialogTitle(ByRef dlg As CameraCaptureDialog) As String
'// My device work only with Pictures & Videos
Return "Pictures & Videos"
If (dlg.Mode = CameraCaptureMode.Still) Then

' Return String.Format("{0} [{1}]", dlg.Title, "Photo")

Else

' Return String.Format("{0} [{1}]", dlg.Title, "Video")
End If
End Function

-------------------------------
Function closeCdd() As Boolean
Dim ptr As IntPtr
ptr = FindWindowByCaption(IntPtr.Zero, title)
If ptr <> IntPtr.Zero Then
DestroyWindow(ptr)

End If

End Function

-------------------------

Key word :
close CameraCaptureDialog VB CF vb.net force close CameraCaptureDialog