在家庭的任何房间中,无论是作为家庭电脑还是增强家庭影院体验,Windows Media Center都能提供最佳的Windows体验。它集成了包括照片、音乐、电视等在内的家庭娱乐体验。通过连接家中和外出的设备,可以扩展娱乐体验。如果希望将应用程序集成到这种新的用户体验中,可能希望扩展现有的应用程序,以便在用户可能正在PC上观看电视时向用户显示通知。本文描述了一种向用户显示带有图像的通知的简单方法。
微软并没有在Media Center UI中直接添加从外部应用程序显示通知的方式,但他们提供了另一种可能的方式:可以向Media Center添加“背景插件”,这些插件将在Media Center UI运行时一直运行,并使用它来接收“外部世界”的消息。本文展示了如何使用这些背景插件向用户显示消息。
Michael Creasy在他的博客中描述了如何开发这些背景插件。可以在那里找到一个背景插件的基本示例。为了与正在运行的Media Center插件进行通信,使用了简单的WM_COPYDATA来发送要显示的文本和其他信息到插件。虽然有几种方式可以在两个不同的应用程序之间进行通信,但如果程序的部分是用不同的编程语言开发的,这种方式是最简单的。
为了概述所有组件和流程,准备了这个流程图:
示例由两个独立的部分组成:
在背景插件中的“技巧”是不仅仅从微软建议的基类派生这个插件(IAddInModule、IAddInEntryPoint、IDisposable),还从System.Windows.Forms.NativeWindow派生。这为提供了一个窗口句柄来挂钩并监听发送到这个窗口的任何Windows消息,并过滤WM_COPYDATA:
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_COPYDATA)
{
...
}
base.WndProc(ref m);
}
但是仅仅创建一个窗口句柄只是一半的工作:这个窗口必须被消息发送者找到。所以给这个不可见的窗口一个非常独特的窗口标题:
public mceDialogAddIn()
{
// Creating Window based on globally known name, and create handle
so we can start listening to Window Messages.
CreateParams Params = new CreateParams();
Params.Caption = " .: mceDialog.MessageServer :. ?>";
this.CreateHandle(Params);
}
这确保了可以使用FindWindow在MessageSender中找到这个窗口:
' Get TargetWindow handle
hwndTarget = FindWindow(vbNullString, " .: mceDialog.MessageServer :. ?>")
If (hwndTarget = 0) Then
' MCE addin-window not found
Exit Function
End If
VB 6中的消息发送者有一个重要功能,使用SendMessage将消息发送到插件:
Dim uCopyData As COPYDATASTRUCT
Dim sData As String
Dim strDelim As String
strDelim = "|"
' build string to send
' single parameters are separated by "|"
' the parameters:
' [0] = caption
' [1] = text to show
' [2] = image to display
' [3] = hide dialog automatically after these seconds
sData = strCaption & strDelim & strText & strDelim & _
strImage & strDelim & intAutoHide & strDelim
' Fill up the structure
With uCopyData
.cbData = LenB(sData)
.lpData = StrPtr(sData)
End With
Call SendMessage(hwndTarget, WM_COPYDATA, 0, uCopyData)
' hwndTarget is the window handle of the invisble
' Media Center addin window
为了简单起见,插件的数据只是用“|”分隔。这样做,可以在插件接收到数据时将其分割:
if (m.Msg == WM_COPYDATA)
{
// arguments are delimited by "|".
// the argument in order:
// [0] = caption
// [1] = text to show
// [2] = image to display
// [3] = hide dialog automatically after these seconds
COPYDATASTRUCT st = (COPYDATASTRUCT) Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));
string strData = Marshal.PtrToStringUni(st.lpData);
string strDelim = "|";
string[] args = strData.Split(strDelim.ToCharArray());
int intTimeOut = Int32.Parse(args[3]);
string strImage = args[2].Replace("\\", "\\\\");
if (!File.Exists(strImage))
// if image does not exist,
// do not pass to Dialog or it will not show up
strImage = "";
...
}
现在已经得到了想要的数据:在Media Center GUI的过程中。因为这个插件有对Microsoft.MediaCenter的引用,可以通过插件访问Media Center API。但是没有直接的方法从插件的某个点显示MCE风格的MessageBox... MessageBox函数在Media Center API中有一个类似的函数,即HostControl.Dialog(详见MSDN)。所以需要一个HostControl的句柄。一个HostControl的引用在函数void IAddInEntryPoint.Launch(AddInHost host)中传递到插件中。这个函数在Media Center启动插件时被调用。所以所要做的就是保存这个host的引用,以便以后使用它来显示Dialog:
void IAddInEntryPoint.Launch(AddInHost host)
{
mhcControl = host.HostControl;
...
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_COPYDATA)
{
...
Object[] oButtons = new Object[1];
oButtons[0] = 1;
// just an OK button
mhcControl.Dialog(args[1], args[0], oButtons, intTimeOut, false, strImage, null);
// mhcControl is our saved reference to a HostControl
}
base.WndProc(ref m);
}
一个非常重要的一点是,当执行离开IAddInEntryPoint.Launch函数时,背景插件将被Media Center卸载。所以如果希望插件保留在内存中(例如,等待像这样的特定消息),必须在Launch函数中“持有”执行,并且只有在想要被卸载时才将其返回给Media Center进程:
void IAddInEntryPoint.Launch(AddInHost host)
{
...
while (true)
{
// Yield processing till we sample for inactivity
// if we let the execution exit "Launch",
// the addin will be unloaded
// and is then unable to get messages...
Thread.Sleep(100);
System.Windows.Forms.Application.DoEvents();
}
}