Introduction
Windows XP Media Center Edition offers the best Windows experience in any room in your home, whether you�re looking for a family computer or to enhance your home theater. Enjoy integrated home entertainment experiences including photos, music, TV, and more. Connect with devices around the home, and on the go that extends your entertainment (see Microsoft's Media Center website for details).
If you want to integrate your application into this new user experience, you might want to expand your existing application to show a notification to the user who is probably just watching TV on the PC. This article describes a simple way to show a notification with an image to the user.
Background
Microsoft did not add a direct way to show notifications in the Media Center UI from an external application, but they made another way possible: you can add "Background Add-ins" to the Media Center which will run all the time the Media Center UI is running, and use it to receive messages from the "outer world". This article shows how to use these background add-ins to open a simple way to show a message to the user.
Michael Creasy describes how to develop these background add-ins. In his blog (see here) you can find a basic sample of a background add-in.
To communicate to this running Media Center add-in, I use a simple WM_COPYDATA
to send the texts to show and other information to the add-in. There are several ways of communication between two different applications, but this way is the easiest one to use if your program parts are developed in different programming languages.
To give an overview of all the components and the flow, I have prepared this flowchart:
Using the code
The sample consists of two separate parts:
- A simple Visual Basic 6 program that demonstrates how to send some information to the Media Center add-in with
WM_COPYDATA
.
The reason I chose VB 6 here is that this article has its roots in TapiRex, a shareware program I develop that shows CallerID on incoming calls, which also uses this technique to display a CallerID and some other information in the Media Center UI (see www.cbuenger.com for details).
- A background add-in for Media Center 2005 that is automatically loaded by the Media Center when it starts and which is listening to incoming
WM_COPYDATA
messages displays the containing texts with the Media Center API functions.
This part has to be compiled with the .NET Framework 1.0, because Microsoft's Media Center only accepts add-ins to be in .NET 1.0. The source code contains a simple batch file that demonstrates how to compile the add-in without the Visual Studio IDE.
Let's get into the details: The "trick" in the background add-in is to derive this whole add-in not only from the base classes Microsoft proposes for add-ins (IAddInModule
, IAddInEntryPoint
, IDisposable
) but also from System.Windows.Forms.NativeWindow
. This gives us a window handle to hook on and listen for any Windows messages sent to this window and filter for WM_COPYDATA
:
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_COPYDATA)
{
...
}
base.WndProc(ref m);
}
But just creating a window handle is only half way: this window has to be found by our MessageSender. So we just give this invisible window a very unique window title:
public mceDialogAddIn()
{
CreateParams Params = new CreateParams();
Params.Caption = "<? .: mceDialog.MessageServer :. ?>";
this.CreateHandle(Params);
}
This ensures that we will find this window in our MessageSender using FindWindow
:
hwndTarget = FindWindow(vbNullString, "<? .: mceDialog.MessageServer :. ?>")
If (hwndTarget = 0) Then
Exit Function
End If
The MessageSender in VB 6 has one important function, to send the message to the add-in using SendMessage
:
Dim uCopyData As COPYDATASTRUCT
Dim sData As String
Dim strDelim As String
strDelim = "�"
sData = strCaption & strDelim & strText & strDelim & _
strImage & strDelim & intAutoHide & strDelim
With uCopyData
.cbData = LenB(sData)
.lpData = StrPtr(sData)
End With
Call SendMessage(hwndTarget, WM_COPYDATA, 0, uCopyData)
For simplicity, the data for the add-in is just separated by "�". Doing this, we can split up the data when it is received by the add-in:
if (m.Msg == WM_COPYDATA)
{
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 = "";
...
}
Now we have the data right where we wanted it: in the process of Media Center GUI. Because this add-in has a reference to Microsoft.MediaCenter
, we have access to the Media Center API from our add-in. But there is no direct way to show a MCE style MessageBox
from just some point in the add-in... The MessageBox
-function has a similar function in Media Center API which is HostControl.Dialog
(see MSDN for details). So we need a handle to a HostControl
. A reference to a HostControl
is passed in to the add-in in the function void IAddInEntryPoint.Launch(AddInHost host)
. This function is called when Media Center launches the add-in on start. So all we have to do is save this reference to host
for later usage to show the 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;
mhcControl.Dialog(args[1], args[0], oButtons, intTimeOut,
false, strImage, null);
}
base.WndProc(ref m);
}
Points of Interest
One very important thing to know is that a background add-in will be unloaded by Media Center when the execution leaves the IAddInEntryPoint.Launch
function. So if you want an add-in to stay in memory (for example, to wait for specific messages like this one), you have to "hold" the execution in the Launch
function and only give it back to the Media Center process if you want to be unloaded:
void IAddInEntryPoint.Launch(AddInHost host)
{
...
while (true)
{
Thread.Sleep(100);
System.Windows.Forms.Application.DoEvents();
}
}
The call to DoEvents
is just there to let the thread handle the WndProc
function to get a chance to filter incoming messages.
More
This example only shows a one-way "communication" from an external application to a background add-in. If you want to let your application know which button in the dialog box was clicked by the user, you have to establish a connection back from the add-in to your application. This could be done the same way (with WM_COPYDATA
) or in any other way you like.