代码演练
本部分将为您介绍示例客户端方案的一些源代码。您将看到如何在托管代码和本机代码中使用选择图片对话框和照相机捕获对话框。不过,本文的附带源代码中不包括本机代码示例。然后,您将看到如何使用其他包含媒体的应用程序,以及如何将媒体存储到数据库中。
选择图片(托管代码)
要将多媒体与企业应用程序集成,最重要的是能够使用已经存储在文件系统中的图片(包括照片)和视频。如果该设备没有配备内置照相机,您仍然可以使用该方法集成多媒体。例如,您可能使用单独的数字照相机来拍照或者捕获视频,然后使用红外线端口或蓝牙将照片或视频传输到 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 = tue;
process.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
}
正如在关于从本机代码使用多媒体播放器控件的讨论中提到的一样,该控件有许多可能性。您在独立媒体播放器中可以进行的任何操作在该控件中几乎都可以实现。示例包括使用播放列表、连接、远程媒体、播放器状态以及很多事件(如播放和结束流)。