代码演练
本部分将为您介绍示例客户端方案的一些源代码。您将看到如何在托管代码和本机代码中使用选择图片对话框和照相机捕获对话框。不过,本文的附带源代码中不包括本机代码示例。然后,您将看到如何使用其他包含媒体的应用程序,以及如何将媒体存储到数据库中。
选择图片(托管代码)
要将多媒体与企业应用程序集成,最重要的是能够使用已经存储在文件系统中的图片(包括照片)和视频。如果该设备没有配备内置照相机,您仍然可以使用该方法集成多媒体。例如,您可能使用单独的数字照相机来拍照或者捕获视频,然后使用红外线端口或蓝牙将照片或视频传输到 Pocket PC。为了满足该需要,Windows Mobile 5.0 平台中包括了一个现成的对话框,该对话框可以在托管 (.NET Compact Framework) 代码中作为"Microsoft.WindowsMobile.Forms"命名空间中的 SelectPictureDialog 类使用。
要创建前面图 9 中显示的文件选择屏幕,可以使用以下代码示例。
private void fromFileMenuItem_Click(object sender, EventArgs e)
{
SelectPictureDialog selectPictureDialog = new SelectPictureDialog();
selectPictureDialog.Owner = this;
selectPictureDialog.Title = "Select Exhibit Photo or Video";
selectPictureDialog.CameraAccess = false;
selectPictureDialog.Filter = "All files|*.*";
if(selectPictureDialog.ShowDialog() == DialogResult.OK &&
selectPictureDialog.FileName.Length > 0)
{
fileExtension = Path.GetExtension(selectPictureDialog.FileName);
File.Copy(selectPictureDialog.FileName, fileName());
if(fileExtension.ToLower() == ".jpg")
pictureBox.Image = new Bitmap(fileName());
else
{
ComponentResourceManager resources =
new ComponentResourceManager(typeof(ExhibitForm));
pictureBox.Image = ((System.Drawing.Image)
(resources.GetObject("pictureBox.Image")));
}
}
}
在上面的代码中,您可以看到该对话框的所有者设置为具有该标题的当前窗口。CameraAccess 属性指示照相机是否应该从该对话框窗口使用。如果该属性设置为 true 且照相机可用,则可以在文件列表中看到照相机符号。当选择照相机符号时,将显示一个 CameraCaptureDialog 对话框,这样就可以照相或者制作视频记录。请注意,因为 SelectPictureDialog 对话框能够链接到 CameraCaptureDialog 对话框(虽然该示例中未显示它),所以这的确就是从文件系统和内置照相机中启用包含的媒体而需进行的最小集成。
Filter 属性定义当应用程序显示文件列表时将应用的搜索筛选器。该属性的默认值为"Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF"。
上述代码中接下来要发生的是对话框的实际呈现。呈现通过 ShowDialog 方法进行,该方法直至对话框关闭时才停止执行。通常,ShowDialog 方法返回一个值,指示对话框的关闭方式。如果用户按设备上的 Action 键或点击 OK,该对话框返回 DialogResult.OK。如果选择一张图片,则 FileName 属性包括所选文件的名称。如果这两个条件都满足,则保存所选文件的扩展名,将该文件复制到示例的媒体文件夹。如果所选文件是照片(在本示例中,只有 JPEG 文件可用于照片),使用所选图片更新 Exhibit 屏幕上的图片框 (pictureBox)。如果所选文件是视频,默认图像(图 12 显示的放大的媒体播放器文档符号)从应用程序资源加载。
fileName 方法的代码如下所示。
private string fileName()
{
return Common.Values.MediaPath + Path.DirectorySeparatorChar +
exhibitID.ToString() + fileExtension;
}
上述代码创建了展示的完整文件名,该文件名是展示的标识(唯一标识符)和所选文件的文件扩展名的组合。所有媒体文件都存储在一个公共文件夹 (Common.Values.MediaPath) 中。
图 15 显示 SelectPictureDialog 类的完整定义。

图 15. SelectPictureDialog 类
因为 Filter 属性可以包括多个筛选器,所以可以使用 FilterIndex 属性设置默认筛选器索引。如,如果 Filter 属性设置为"Bitmap Files|*.bmp|JPEG Files|*.jpg|GIF Files|*.gif",FilterIndex 设置为 1,则该对话框将搜索 JPEG 文件。
除了已经提到的属性,还可以用 InitialDirectory 属性选择一个初始文件夹来搜索图片。可以将 LockDirectory 属性设置为 true 以防止用户更改文件夹。可以使用 SortOrder 属性设置找到的文件的初始排序,还可以将该属性设置为根据日期、名称或大小按升序和降序进行排序。可以使用 ShowDrmContent 属性指定受数字版权管理(Digital Rights Management,DRM)保护的文件是否应该显示在对话框中。如果受 DRM 保护的文件得到保护,则它们不会被转发,只有当 ShowForwardLockedContent 属性设置为 true 时,它们才会显示在该对话框中。
选择图片(本机代码)
要在本机代码中使用上一部分中描述的对话框,可以使用具有 OPENFILENAMEEX 结构的 GetOpenFileNameEx 函数,如下所示。
TCHAR szFile[MAX_PATH];
OPENFILENAMEEX ofnex = {0};
ofnex.lStructSize = sizeof(ofnex);
ofnex.hwndOwner = g_hWnd;
ofnex.lpstrFile = szFile;
ofnex.nMaxFile = sizeof(szFile) / sizeof(szFile[0]);
ofnex.lpstrFilter = TEXT("All Files (*.*)\0*.*\0");
ofnex.lpstrTitle = TEXT("Select Exhibit Photo or Video");
ofnex.ExFlags = OFN_EXFLAG_THUMBNAILVIEW;
ofnex.lpstrInitialDir = NULL;
if(GetOpenFileNameEx(&ofnex))
{
// The selected file name is in szFile
}
上述代码中的所有者窗口是主应用程序窗口(全局定义为 g_hWnd)。请注意,筛选器的各部分用 NULL 字符分隔。使用 OFN_EXFLAG_THUMBNAILVIEW 以缩略图格式显示 ListView 控件,ExFlags 成员可以包括 OFN_EXFLAG_DETAILSVIEW 标志以详细信息格式显示 ListView。其他选项是 OFN_EXFLAG_HIDEDRMPROTECTED 和 OFN_EXFLAG_HIDEDRMFORWARDLOCKED,前者用于排除受 DRM 保护的文件的显示,后者用于排除无法转发的受 DRM 保护的文件。
捕获照片和视频(托管代码)
尽管许多移动设备早已配备了内置照相机,但刚开始时缺乏对使用这些照相机的开发支持。几乎没有制造商制造可以从托管代码使用的照相机,甚至本机 API 都难以找到。对于那些希望将媒体集成到应用程序中的企业开发人员而言,唯一可用的选择是进行文件级集成(如前面图片选择对话框的讨论中描述的那样)。
有了 Windows Mobile 5.0 软件,所有一切都改变了。现在定义了设备制造商将支持的通用照相机 API。此外,为了满足那些使用 .NET Compact Framework 构建应用程序的企业开发人员的需要,Windows Mobile 5.0 软件还包括了一个现成的对话框。该对话框名为 CameraCaptureDialog,可在"Microsoft.WindowsMobile.Forms"命名空间中找到它。
要创建前面图 14 中显示的照片捕获屏幕,可以使用以下代码示例。
private void photoMenuItem_Click(object sender, EventArgs e)
{
CameraCaptureDialog cameraCaptureDialog = new CameraCaptureDialog();
cameraCaptureDialog.Owner = this;
cameraCaptureDialog.Title = "Take Exhibit Photo";
cameraCaptureDialog.Mode = CameraCaptureMode.Still;
if(cameraCaptureDialog.ShowDialog() == DialogResult.OK &&
cameraCaptureDialog.FileName.Length > 0)
{
fileExtension = Path.GetExtension(cameraCaptureDialog.FileName);
File.Copy(cameraCaptureDialog.FileName, fileName());
pictureBox.Image = new Bitmap(fileName());
}
}
CameraCaptureDialog 类的工作方式与前面讨论的 SelectPictureDialog 类非常类似。在上述代码中,该对话框的所有者设置为具有该标题的当前窗口。Mode 属性指示照相机捕获对话框应该用于拍照还是进行视频录制。在本例中,用户需要一张照片 (CameraCaptureMode.Still)。对话框通过 ShowDialog 方法显示,该方法返回一个指示对话框关闭方式的值。如果用户点击 OK,则对话框返回 DialogResult.OK。如果拍了一张照片,FileName 属性包括新建的图片(照片)文件的名称。如果这两个条件都满足,则保存所选文件的扩展名,将该文件复制到示例的媒体文件夹,使用所选图片更新 Exhibit 屏幕上的图片框 (pictureBox)。
要使用同一个对话框捕获视频,使用以下代码示例。
private void videoMenuItem_Click(object sender, EventArgs e)
{
CameraCaptureDialog cameraCaptureDialog = new CameraCaptureDialog();
cameraCaptureDialog.Owner = this;
cameraCaptureDialog.Title = "Take Exhibit Video";
cameraCaptureDialog.Mode = CameraCaptureMode.VideoWithAudio;
if(cameraCaptureDialog.ShowDialog() == DialogResult.OK &&
cameraCaptureDialog.FileName.Length > 0)
{
fileExtension = Path.GetExtension(cameraCaptureDialog.FileName);
File.Copy(cameraCaptureDialog.FileName, fileName());
ComponentResourceManager resources =
new ComponentResourceManager(typeof(ExhibitForm));
pictureBox.Image = ((System.Drawing.Image)
(resources.GetObject("pictureBox.Image"))); ;
}
}
请注意,上面的代码与捕获照片的代码非常类似,但是 Mode 属性却设置为捕获带有声音的视频记录 (CameraCaptureMode.VideoWithAudio)。Mode 属性也可以设置为捕获没有声音的视频 (CameraCaptureMode.VideoOnly)。此外,默认图像(图 12 中显示的放大的媒体播放器文档符号)从该应用程序的资源加载到图片框中。
图 16 显示 CameraCaptureDialog 类的完整定义。

图 16. CameraCaptureDialog 类
除了已经提到的属性,如果在使用 ShowDialog 方法显示该对话框之前设置 DefaultFileName 属性,该名称将用作新照片或视频的文件名。可以使用 InitialDirectory 属性指定捕获的文件(照片或视频)将存储到何处。要请求捕获的照片或视频的分辨率,将 Resolution 属性设置为一个 Size 实例,如下所示。
cameraCaptureDialog.Resolution = new Size(320, 240);
使用 StillQuality 属性设置该照片的压缩级别。在 CameraCaptureStillQuality 枚举中,High 选项意味着低压缩率用于保持照片的高质量。Low 选项与其相反(结果产生具有较低质量的高压缩)。Normal 选项产生介于 High 和 Low 选项之间的质量。
使用 VideoTimeLimit 属性设置新视频的最大记录时间。默认值为零,即没有时间限制。
最后一个属性是 VideoTypes,它可用于选择要捕获哪种类型的视频。基本上,在基于 Windows Mobile 5.0 的设备上,可以使用两种不同的视频类型 - 多媒体消息传递服务(Multimedia Messaging Service,MMS)和 Windows Media Video (WMV)。CameraCaptureVideoTypes 枚举提供可能的值。使用 Messaging 录制 MMS 视频,使用 Standard 录制 WMV 视频。还有一个 All 枚举值,用于使 Resolution 属性确定要录制的视频类型。如果使用 All 选项且 Resolution 高度和宽度为零,则再次使用上次使用的分辨率。您还需要设置 Messaging 和 Standard 枚举值的 Resolution 属性。
捕获照片和视频(本机代码)
如果想在本机代码中使用相同的对话框(如前所述),可以使用具有 SHCAMERACAPTURE 结构的 SHCameraCapture 函数,如下所示。
HRESULT hResult;
SHCAMERACAPTURE shcc;
ZeroMemory(&shcc, sizeof(shcc));
shcc.cbSize = sizeof(shcc);
shcc.hwndOwner = g_hWnd;
shcc.pszTitle = TEXT("Take Exhibit Video");
shcc.Mode = CAMERACAPTURE_MODE_VIDEOWITHAUDIO;
shcc.VideoTypes = CAMERACAPTURE_VIDEOTYPE_ALL;
if(S_OK == SHCameraCapture(&shcc))
{
// The selected file name is in shcc.szFile
}
上述代码中的所有者窗口是主应用程序窗口(全局定义为 g_hWnd)。Mode 成员的其他值是针对照片的 CAMERACAPTURE_MODE_STILL(默认值)和针对无声音视频的 CAMERACAPTURE_MODE_VIDEOONLY。VideoTypes 成员的其他值是针对 WMV 视频的 CAMERACAPTURE_VIDEOTYPE_STANDARD 以及针对 MMS 视频的 CAMERACAPTURE_VIDEOTYPE_MESSAGING。当使用后两个值之一时,nResolutionWidth 或 nResolutionHeight 成员均不能为零。
查看照片和视频
要打开前面图 11(图片查看器)和图 13(媒体播放器)中显示的屏幕,可以使用以下代码。
private void viewMenuItem_Click(object sender, EventArgs e)
{
Process process = new Process();
process.StartInfo.FileName = fileName();
process.StartInfo.Verb = "Open";
process.StartInfo.UseShellExecute = true;
prcess.Start();
}
使用"System.Diagnostics"命名空间的 Process 类为特定文件新建一个进程。请注意,如果 UseShellExecute 属性设置为 true,文件名可以是与具有默认打开操作的可执行文件相关联的任何文件类型。在基于 Windows Mobile 5.0 的 Pocket PC 上,.jpg 扩展名与图片和视频查看器可执行文件 (pimg.exe) 相关联,.wmv 扩展名与媒体播放器可执行文件 (wmplayer.exe) 相关联。
将媒体保存到数据库
在前面的示例中,媒体保存在一个文件中,该文件复制到特定的(媒体)文件夹以便稍后与服务器同步。媒体(文件)信息也可以通过如下代码示例的方式存储在数据库中。
FileStream fs = File.Open(fileName(), FileMode.Open);
byte[] imageData = new byte[fs.Length];
fs.Read(imageData, 0, imageData.Length);
fs.Close();
SqlCeCommand cmd = cn.CreateCommand();
cmd.CommandText = "UPDATE Exhibit SET Media=?" +
" WHERE ExhibitID='" + exhibitID + "'";
SqlCeParameter param = cmd.Parameters.Add("p0", SqlDbType.Image);
param.Value = imageData;
cmd.ExecuteNonQuery();
将文件读入一个字节数组,然后该数组用于更新数据库中正确的展示行。在上述代码中,保存媒体数据的数据库列 (Media) 被假定为 image 类型。请注意,要使用外部程序(pimg.exe 和 wmplayer.exe)播放媒体,需要将媒体再次写入到一个文件中,然后才能调用外部程序。
将媒体文件存储在文件系统中还是存储在数据库中,这取决于应用程序的要求。这两项技术都有优缺点。将媒体文件存储在文件系统中可以使录制、播放和操作更简单,并且能更容易地分离数据和媒体的存储。例如,数据库可以位于设备上较快的内存中从而提高访问性能,而媒体则存储在存储卡上,这样就可以存储更多信息,但访问速度较慢。然而,当这些文件需要与服务器同步时,就需要自定义解决方案(如 HTTP 上载和 XML Web 服务的附件)。如果媒体存储在数据库中,那么每次需要播放或操作时,都需要将媒体提取到一个文件中。但是这一同步操作较容易,因为数据库同步(如 Microsoft SQL Server Mobile Edition 中的远程数据访问和合并复制)和 XML Web 服务同步(SOAP 上的 DataSet)都支持该数据传输。任何情况下,您都应该考虑在设备上本地压缩媒体,以及当媒体在设备和服务器间传输时压缩媒体这两方面。
多媒体之外的代码重点
既然已经演练了示例应用程序中实现的多媒体功能,那么您可以再演练其他一些代码,因为从企业应用程序角度看,这是值得研究一下的。例如,使用 .NET Compact Framework 2.0 命名空间"Microsoft.Win32"中的 Registry 类获取注册表设置,如下代码示例所示。
string registryKey = @"SOFTWARE\Microsoft\MsdnSamples\Inspection";
RegistryKey key = Registry.LocalMachine.CreateSubKey(registryKey);
string userName = key.GetValue("UserName", "<default>").ToString();
要保存相同的注册表设置,使用以下代码。
RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey, true);
key.SetValue("UserName", userName);
该代码示例的另一个非常有趣的部分是数据访问。首先是用户界面实现,接着是用来填充 Type 组合框的代码(如前面的图 5 所示)。
try
{
DataTable dt;
using(InspectionHandler inspectionHandler = new InspectionHandler())
dt = inspectionHandler.GetAllTypes().Tables[0];
DataRow dr = dt.NewRow();
dr["TypeName"] = "<All types>";
dr["InspectionTypeID"] = Guid.Empty;
dt.Rows.InsertAt(dr, 0);
typeComboBox.DisplayMember = "TypeName";
typeComboBox.ValueMember = "InspectionTypeID";
typeComboBox.DataSource = dt;
typeComboBox.SelectedIndex = 0;
}
catch(Exception)
{
MessageBox.Show("Could not load inspection types!", this.Text);
}
通过检查处理程序类实例 (inspectionHandler) 检索具有所有类型的数据表 (dt)。第一行先添加到数据表中,然后它作为数据源添加到组合框中。该检查处理程序中方法 (GetAllTypes) 的实现如下所示。
public DataSet GetAllTypes()
{
return SqlHelper.ExecuteDataset(cn, CommandType.Text,
"SELECT * FROM InspectionType", "InspectionType");
}
SqlHelper 类实际上可以算是 Data Access Application Block(包括在 OpenNETCF 的 Application Blocks 1.0 中)的增强版。Application Blocks 1.0 是桌面计算机应用程序块的一个端口。方法 (ExecuteDataSet) 获取以下参数:数据库连接(SqlCeConnection 类型的 connection)、命令类型 (commandType)、语句 (commandText) 以及返回的表的名称 (tableName)。此处使用的重载如下代码示例所示。
public static DataSet ExecuteDataset(SqlCeConnection connection,
CommandType commandType, string commandText, string tableName)
{
DataSet ds = ExecuteDataset(connection, commandType, commandText,
(SqlCeParameter[])null);
ds.Tables[0].TableName = tableName;
return ds;
}
调用的另一个重载按以下代码示例的方式实现。
public static DataSet ExecuteDataset(SqlCeConnection connection,
CommandType commandType, string commandText,
params SqlCeParameter[] commandParameters)
{
SqlCeCommand cmd = new SqlCeCommand();
bool mustCloseConnection = false;
PrepareCommand(cmd, connection, (SqlCeTransaction)null, commandType,
commandText, commandParameters, out mustCloseConnection );
SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
DataSet ds = new DataSet();
ds.Locale = CultureInfo.InvariantCulture;
da.Fill(ds, 0, 100, "Table");
cmd.Parameters.Clear();
if(mustCloseConnection)
connection.Close();
return ds;
}
创建一个 SQL Mobile 命令 (SqlCeCommand) 实例 (cmd) 并准备执行它。然后,创建一个数据适配器 (da) 和一个数据集 (ds),该数据适配器用于使用数据填充数据集。注意如何才能指定只返回前 100 行。这种限制可能是个好点子;很少需要一个大的结果列表,一方面是因为对性能的影响,也因为在移动设备上浏览大数据集的效率并不太高。所有参数都与该命令对象分离,这样它们才可以再次使用。最后,返回创建的数据集。
您按照以下代码示例所示准备该命令。
private static void PrepareCommand(SqlCeCommand command,
SqlCeConnection connection, SqlCeTransaction transaction,
CommandType commandType, string commandText, SqlCeParameter[]
commandParameters, out bool mustCloseConnection )
{
if(connection.State != ConnectionState.Open)
{
mustCloseConnection = true;
connection.Open();
}
else
mustCloseConnection = false;
command.Connection = connection;
command.CommandText = commandText;
command.CommandType = commandType;
if(transaction != null)
command.Transaction = transaction;
if(commandParameters != null)
AttachParameters(command, commandParameters);
return;
}
如果连接尚未打开,则将其打开,根据这些参数配置该命令。有关 SqlHelper 的实现的更多信息,请参阅本文的下载示例代码以及 Data Access Application Block 文档。
对于任何企业开发人员,大大改进对 .NET Compact Framework 2.0 和 Visual Studio .NET 2005 中的数据访问支持不仅仅是受欢迎这么简单的。您可以直接从开发工具中使用数据库这一事实,将大大提高工作效率。典型的开发过程如下所示:在 SQL Server Management Studio 中创建 SQL Server Mobile Edition 数据库文件(sdf 扩展名)。也可以使用该工具添加所有表和数据。然而,您也可以从 SQL Server Management Studio 中的 Server Explorer 窗口连接到 SQL Server Mobile Edition 数据库,可以在其中添加表和数据、测试查询以及类似的项。进行调试时,您可以部署相同的数据库文件以及该应用程序。还有许多其他新增的数据访问功能有待您去探究。
媒体播放器控件
在许多情况下,将标准媒体播放器作为单独的进程启(如前面所示)可能会满足业务需求,但如果需要更多地控制视频播放,则可以使用媒体播放器控件。媒体播放器控件(版本 10)包括在 Windows Mobile 5.0 软件中,它使开发人员能够将媒体播放器用作自己应用程序的一个自定义控件。
宿主媒体播放器控件(本机代码)
媒体播放器控件是一个常规 ActiveX 控件(.ocx 文件),熟悉 COM 的本机开发人员使用它时不会有很多问题。首先,您应该查看 Windows Media Player Mobile 代码示例,这些示例包括用于宿主媒体播放器控件的代码。然而,因为这些示例是针对本机智能设备开发的上一代工具 (Microsoft eMbedded Visual C++) 而编写的,所以本文的下载代码示例包括尝试通过 eMbedded Visual C++ Upgrade Wizard for Visual Studio 2005 Beta 2 方法转换媒体播放器示例 (CEWMPHostML) 的结果。
宿主窗口和该控件的创建如下代码示例所示。
CAxWindow m_wndView;
CComPtr<IWMPPlayer> m_spWMPPlayer;
CComPtr<IConnectionPoint> m_spConnectionPoint;
DWORD m_dwAdviseCookie;
RECT rcClient;
GetClientRect(&rcClient);
m_wndView.Create(m_hWnd, rcClient, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
if(NULL == m_wndView.m_hWnd)
goto FAILURE;
CComPtr<IAxWinHostWindow> spHost;
HRESULT hr = m_wndView.QueryHost(&spHost);
if(FAILMSG(hr))
goto FAILURE;
hr = spHost->CreateControl(CComBSTR(
_T("{6BF52A52-394A-11d3-B153-00C04F79FAA6}")), m_wndView, 0);
if(FAILMSG(hr))
goto FAILURE;
hr = m_wndView.QueryControl(&m_spWMPPlayer);
if(FAILMSG(hr))
goto FAILURE;
CComWMPEventDispatch *pEventListener = NULL;
hr = CComWMPEventDispatch::CreateInstance(&pEventListener);
CComPtr<IWMPEvents> spEventListener = pEventListener;
if(FAILMSG(hr))
goto FAILURE;
CComPtr<IConnectionPointContainer> spConnectionContainer;
hr = m_spWMPPlayer->QueryInterface(__uuidof(IConnectionPointContainer),
(void**)&spConnectionContainer);
if(FAILMSG(hr))
goto FAILURE;
hr = spConnectionContainer->FindConnectionPoint(__uuidof(IWMPEvents),
&m_spConnectionPoint);
if(FAILMSG(hr))
goto FAILURE;
m_dwAdviseCookie = 0;
hr = m_spConnectionPoint->Advise(spEventListener, &m_dwAdviseCookie);
if(FAILMSG(hr))
goto FAILURE;
全局声明(前四行)之后,控件宿主窗口 (m_wndView) 作为主应用程序窗口 (m_hWnd) 的子窗口(并且具有和客户端区域相同的大小)进行创建。然后,检索宿主窗口的接口引用 (spHost) 并用它来创建媒体播放器控件。GUID(对应于与版本无关的 ProgID WMPlayer.OCX)用于创建该控件,从该宿主窗口检索该控件的实际接口引用 (m_spWMPPlayer)。最后,使用侦听所有类型事件的事件调度程序 (pEventHandler) 建立事件处理。请注意每个调用上的大量错误处理,您在调试和测试时将从中大大受益。
下一个主要操作是用户选择使用菜单打开某个文件,如下代码示例所示。
CFileOpenDlg dlgOpen;
if(dlgOpen.DoModal(m_hWnd) == IDOK)
{
HRESULT hr = m_spWMPPlayer->put_URL(_T(dlgOpen.m_bstrName);
if(FAILMSG(hr))
return 0;
}
return 0;
显示一个对话框 (dlgOpen),用户可从中选择文件名。如果用户点击 OK 关闭该对话框,则媒体播放器的 URL 属性设置为在该对话框中输入的名称。然后媒体播放器控件播放该文件。
该事件处理以如下方式实现。
HRESULT CWMPEventDispatch::Invoke(DISPID dispIdMember, REFIID riid,
LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams,
VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo,
unsigned int FAR* puArgErr)
{
if (!pDispParams)
return E_POINTER;
if (pDispParams->cNamedArgs != 0)
return DISP_E_NONAMEDARGS;
HRESULT hr = DISP_E_MEMBERNOTFOUND;
switch(dispIdMember)
{
case DISPID_WMPCOREEVENT_OPENSTATECHANGE:
OpenStateChange(pDispParams->rgvarg[0].lVal);
break;
case DISPID_WMPCOREEVENT_PLAYSTATECHANGE:
PlayStateChange(pDispParams->rgvarg[0].lVal);
break;
case DISPID_WMPCOREEVENT_AUDIOLANGUAGECHANGE:
AudioLanguageChange(pDispParams->rgvarg[0].lVal);
break;
case DISPID_WMPCOREEVENT_STATUSCHANGE:
StatusChange();
break;
// Here comes another 40 events
case DISPID_WMPOCXEVENT_MOUSEUP:
MouseUp(pDispParams->rgvarg[3].iVal,
pDispParams->rgvarg[2].iVal, pDispParams->rgvarg[1].lVal,
pDispParams->rgvarg[0].lVal);
break;
}
return( hr);
}
对于每个事件调度标识 (dispIdMember),调用一个单独的方法来操作该事件。正如您在上述代码中所看到的,另外 40 个事件在下载代码示例中实现。媒体播放器控件发布的广泛的属性、方法和事件集,使其成为进行媒体播放时的重点考虑对象。
宿主媒体播放器控件(托管代码)
尽管 .NET Compact Framework 2.0 包括对 COM 互操作性的支持,但是没有对 ActiveX 控件的内置支持。然而,多亏我的同事 Alex Feinman,他提供了一种在托管代码中使用 ActiveX 控件的方式,您将在他的文章 (Hosting ActiveX Controls in Compact Framework 2.0 Applications) 中找到在托管代码中使用 ActiveX 控件的方式。通过使用该技术并在前面的示例基础上进行生成,图 17 显示具有集成视频播放的 Exhibit 屏幕在 Smartphone 版本的示例应用程序中可能的外观。

图 17. 通过媒体播放器控件进行的集成播放。
正如在 Pocket PC 示例(如图 10 所示)中一样,可以编辑展示名称,但此处播放可以从同一屏幕启动。
通过下面的设计器代码开始创建窗体的代码。
private AxWMPLib.AxWindowsMediaPlayer windowsMediaPlayer;
this.windowsMediaPlayer = new AxWMPLib.AxWindowsMediaPlayer();
this.windowsMediaPlayer.Location = new System.Drawing.Point(0, 28);
this.windowsMediaPlayer.Name = "windowsMediaPlayer";
this.windowsMediaPlayer.Size = new System.Drawing.Size(176, 152);
this.windowsMediaPlayer.TabIndex = 0;
this.windowsMediaPlayer.Text = "windowsMediaPlayer";
正如您所看到的,当 ActiveX 控件的包装准备就绪时,可以像处理任何托管控件那样处理该控件。在前面的代码示例基础上生成,该媒体播放器控件可以按以下方式使用展示视频加载。
windowsMediaPlayer.URL = fileName();
URL 属性可以是指向流式源或远程文件的 URL,也可以是本地文件系统中的文件。Menu 命令(Play、Pause 和 Stop)的后台代码如下代码示例所示。
private void playMenuItem_Click(object sender, EventArgs e)
{
windowsMediaPlayer.Ctlcontrols.play();
}
private void pauseMenuItem_Click(object sender, EventArgs e)
{
windowsMediaPlayer.Ctlcontrols.pause();
}
private void stopMenuItem_Click(object sender, EventArgs e)
{
windowsMediaPlayer.Ctlcontrols.stop();
}
与该媒体播放器的基本交互不是非常困难,设置事件处理程序的方式也不会更困难。就像任何其他托管控件一样,以如下方式添加事件处理程序。
this.windowsMediaPlayer.StatusChange +=
new System.EventHandler(windowsMediaPlayer_StatusChange);
然后,该事件处理程序按如下方式实现。
void windowsMediaPlayer_StatusChange(object sender, System.EventArgs e)
{
string status = windowsMediaPlayer.status;
// Do something with status string
}
正如在关于从本机代码使用多媒体播放器控件的讨论中提到的一样,该控件有许多可能性。您在独立媒体播放器中可以进行的任何操作在该控件中几乎都可以实现。示例包括使用播放列表、连接、远程媒体、播放器状态以及很多事件(如播放和结束流)。
微软最近推出下一代移动设备操作系统Windows Mobile 5。Windows Mobile 5在用户体验方面做了很多改善,但更多的改进还是在应用程序编程接口方面。在这篇文章中,我们会向您介绍Windows Mobile 5在开发方面的一些新特性,其中包括3D图形编程、新控件、新的API函数等。
开发工具
Windows Mobile 5的开发工具是Visual Studio 2005。如果要开发Windows Mobile 5的应用程序,还需要装Windows Mobile 5的SDK和ActiveSync 4.0。
我们要在Windows Mobile 5设备上开发出托管应用程序就需要借助.NET Compact Framework 2.0的强大功能。.NET Compact Framework 2.0在1.0的基础上做了较大的改进,为我们提供了更多的用户控件,比如DateTimePicker、OpenFileDialog、WebBrowser、LinkLabel和Notification等控件。这些控件都是在.NET CF 1.0中所没有提供的,但是因为他们在实际开发过程中会经常用到,到了.NET CF 2.0中,这些功能终于被加了进来。
在数据访问方面,.NET CF 2.0支持SQL Mobile 2005的访问支持。SQL Mobile 2005的功能增强了很多,支持多用户同时访问数据库,也支持在PC上创建SQL Mobile数据库。Windows Mobile 5的Smartphone版本也支持SQL Mobile数据库了。在XML支持方面,.NET CF 2.0支持XML的Schema、Serialization、XPath等。
在远程访问方面,.NET CF 2.0支持MSMQ和.NET Remoting,而在对Socket支持方面,IPv6已经得到了很好的支持。
.NET CF 2.0还有一个重要的特性:支持COM互操作性。我们可以在.NET程序中可以访问COM组件,也可以将Callback函数传递给COM组件。但是我们不能调用ActiveX控件。
在C++移动设备程序开发方面,Visual Studio 2005支持MFC 8.0、ATL 8.0和标准C++库8.0。我们也可以在Windows Mobile 2003设备上支持.NET CF 2.0,但必须要将.NET CF 2.0的运行库安装到设备上。
用户界面

Windows Mobile 5的Pocket PC用户界面发生了很大改变。为了和Smartphone界面类似,Pocket PC的程序菜单被改成了左右两个,在实际设备上也添加了两个硬件按键和这两个菜单相对应。这样的变化可以让用户方便地通过硬件按键操作应用程序,而这样的修改也便于应用程序在Pocket PC和Smartphone之间的移植。为了保持兼容性,Pocket PC仍然支持多于两个一级菜单的菜单结构,但是新开发的程序建议还是采用新的菜单结构。
Windows Mobile 5的应用程序很方便地支持Notification通知机制。当应用程序发生改变的时候,我们可以通过Notification方式来通知用户。
下面我们就来通过一个示例,来了解一下.NET CF应用中如何使用Notification控件。我们首先来创建一个Windows Mobile 5的应用程序。打开Visual Studio 2005,选择File-New-Project,我们选择Visual C#中的Pocket PC Magneto,来创建一个Windows Mobile 5 Pocket PC应用程序。需要提醒的是在创建项目前,必须安装好Windows Mobile 5的SDK。
在创建好项目后,我们在界面编辑器中,为窗体添加MainMenu和Notification两个控件。我们在MainMenu的一个子菜单的响应函数中添加下面的代码:
private void menuItem5_Click(object sender, EventArgs e)
{
StringBuilder HTMLString = new StringBuilder();
HTMLString.Append("<html><body>");
HTMLString.Append("Submit data?");
HTMLString.Append("<form method=\'GET\' action=notify>");
HTMLString.Append("<input type='submit'>");
HTMLString.Append("<input type=button name='cmd:2' value='Cancel'>");
HTMLString.Append("</body></html>");
//Set the Text property to the HTML string.
notification1.Text = HTMLString.ToString();
notification1.Caption = "Notification Demo";
notification1.Critical = false;
// Display icon up to 10 seconds.
notification1.InitialDuration = 10;
notification1.Visible = true;
}
Notification控件支持HTML格式的文本,所以我们的示例创建了两个Input控件。然后设置Notification控件的Visible属性为true就可以了。

编译执行后的效果如上图所示。我们可以看到,Notification已经从Windows Mobile 2003的气泡型窗体变成了从下边出现的形式。输入法图标也从右下角移到了中间。
Microsoft.WindowsMobile.Forms
Windows Mobile 5为开发者提供了Microsoft.WindowsMobile.Forms类库,该类库为用户提供了调用各种系统功能的对话框。目前该类库为我们提供了三种自定义对话框,分别是提供照相机功能的CameraCaptureDialog、提供选择联系人的ChooseContactDialog和提供选择图片的SelectPictureDialog。
我们用一个示例来演示Microsoft.WindowsMobile.Forms类库的功能。首先来创建一个Windows Mobile 5的应用程序,平台类型可以是Pocket PC或Smartphone。为了使用Microsoft.WindowsMobile.Forms类库,我们需要首先添加引用。我们在解决方案资源管理器里右击项目名称,在右键菜单中选择添加引用。我们在添加引用对话框中,我们选择“Microsoft.WindowsMobile.Forms”。

我们在菜单的响应函数中添加ChooseContactDialog的使用。
private void menuItem1_Click(object sender, EventArgs e)
{
ChooseContactDialog contactPicker = new ChooseContactDialog();
contactPicker.Title = "Choose a Contact below:";
contactPicker.ChooseContactOnly = true;
contactPicker.ShowDialog();
}
其实我们看到ChooseContactDialog的方法十分简单,只需要设置Title后,调用ShowDialog方法就可以显示出下边的对话框。


下边我们来添加对SelectPictureDialog的引用。
SelectPictureDialog pickerDialog = new SelectPictureDialog();
pickerDialog.Filter = "Image Files(*.BMP;*.JPG)|*.BMP;*.JPG";
pickerDialog.InitialDirectory = @"\My Device\My Images";
pickerDialog.Title = "Select an image file";
pickerDialog.ShowDialog();
我们首先来设置SelectPictureDialog的过滤器,允许显示JPG和BMP的图片;然后设置初始文件夹;最后调用ShowDialog方法。
下边是SelectPictureDialog显示的效果。


电话、短信和电子邮件
在Windows Mobile 2003中,如果要在.NET程序中拨打电话的话,就需要通过/Invoke来调用API。到了Windows Mobile 5中,Microsoft.WindowsMobile.Telephony类库为我们提供了拨打电话的功能。
在使用这个功能之前,我们必须添加Microsoft.WindowsMobile.Telephony引用。调用电话功能的代码如下:
Phone phone = new Phone();
phone.Talk("1234567");
程序运行效果如下:

Phone类的方法只有一个Talk方法,所以也只能简单的进行电话拨打。我们如果想实现一些复杂的功能,比如监视拨入的电话等,还需要去调用TAPI来实现。
发送短信息和发送电子邮件都需要Microsoft.WindowsMobile.PocketOutlook类库的支持。PocketOutlook是一个很复杂的命名空间,包括对很多系统功能的支持,而支持发短信的类是SmsMessage。
public void SmsMessageSend()
{
SmsMessage smsMessage = new SmsMessage();
//Set the message body and recipient.
smsMessage.Body = "Would you like to meet for lunch?";
smsMessage.To.Add(new Recipient("John Doe", "2065550199"));
smsMessage.RequestDeliveryReport = true;
//Send the SMS message.
smsMessage.Send();
return;
}
我们可以看到SmsMessage的Body属性是SMS短信的内容,而To属性,则是收件人的姓名和电话号码,因为支持多个收件人,所以在添加收件人的时候需要调用To属性的Add方法。RequestDeliveryReport属性是一个Bool值,设置是否要求得到发送报告。最后调用SmsMessage的Send方法。
发送Email的代码与发送短信息的代码大致相似,但是需要使用的是EmailMessage类。
public void EmailSend()
{
EmailMessage message = new EmailMessage();
message.Subject = "The picture you requested";
message.BodyText = "Attached is the picture we discussed";
Recipient client = new Recipient("John","john@test.com");
message.To.Add(client);
Attachment image = new Attachment("\test.jpg");
message.Attachments.Add(image);
message.Send("ActiveSync");
}
EmailMessage的Subject属性是电子邮件的标题,BodyText属性是邮件正文。To属性中可以添加收件人的名称和地址。我们还可以在Attachments属性中添加附件。最后我们调用Send方法。
联系人、约会和任务
Windows Mobile中有三个很重要的程序:联系人、约会和任务。我们可以使用Microsoft.WindowsMobile.PocketOutlook类库,来调用系统中的联系人、约会和任务信息。我们可以通过自己的程序添加信息。
我们先来看一下Microsoft.WindowsMobile.PocketOutlook命名空间中很重要的一个类OutlookSession。该类的一个对象就表示了一个Pocket Outlook 对象模块,以前我们需要调用POOM实现的功能,现在可以通过PocketOutlook来实现。
| 属性 | 说明 |
|---|
| Appointments | 得到约会(Calendar)目录的信息。 |
| Contacts | 得到联系人目录信息。 |
| EmailAccounts | 得到Email帐号的集合。 |
| SmsAccount | 得到SMS帐号的信息。 |
| Tasks | 得到任务目录信息的集合。 |
在使用OutlookSession的时候要先创建一个OutlookSession的对象,然后创建相应的对象并添加到OutlookSession相应属性中去。我们来看下边的例子。
OutlookSession session = new OutlookSession();
// 添加约会
Appointment appointment = new Appointment();
appointment.Subject = "test";
appointment.Body = "testtest";
appointment.Start = DateTime.Now;
appointment.End = new DateTime(2005, 7, 17, 16, 25, 0);
session.Appointments.Items.Add(appointment);
// 添加联系人
Contact contact = new Contact();
contact.FirstName = "John";
contact.LastName = "Lee";
contact.CompanyName = "Microsoft";
contact.Email1Address = "John@test.com";
contact.MobileTelephoneNumber = "1234567";
contact.IM1Address = "John@hotmail.com";
session.Contacts.Items.Add(contact);
// 添加任务
Task task = new Task();
task.Subject = "task1";
task.Body = "task1 body";
session.Tasks.Items.Add(task);
我们创建Appointment、Contact和Task对象,然后设置相应的属性,然后添加到OutlookSession对象的相应的属性中。
其他新特性
Windows Mobile 5除了支持上面的特性外,还支持一些其他的新特性。比如在图象显示方面,Windows Mobile 5支持DirectX 3D Mobile。.NET应用程序可以通过Microsoft.WindowsMobile.DirectX类库来调用D3D Mobile,而C++应用程序可以通过COM接口的方式来调用。
在Windows Mobile设备上普遍使用的GPS全球定位系统也得到了更好的支持。Windows Mobile 5提供了GPS Intermediate Driver,使应用程序可以使用统一的API接口来调用GPS系统。
Windows Mobile 5提供了一个叫做“ExitWindowsEx”的函数,允许软件重启操作系统。Pocket PC使用该函数可以重启系统,而Smartphone支持关机和重启两种功能。
在数据库方面,SQL Mobile虽然被广泛使用,但是因为SQL Mobile没有被安装到ROM中。所以如果需要SQL Mobile就需要占用一部分RAM内存空间,对于一些轻量级的应用就显得不是很合适了。所以Windows Mobile中还包括一个轻量级的数据库EDB,该数据库是CEDB的升级。
写在最后
在这篇文章里,我们领略了Windows Mobile 5为开发者提供的新特性。很多常用的特性都被加入到了API中。但是我们还要看到,许多新特性对于复杂的应用来说,还比较简单。如果想实现比较复杂的功能,还需要依靠自定义控件来实现。
最后也希望这篇文章能够帮助开发者们了解Windows Mobile 5的开发新特性。