Silverlight slideshow with remote control and windows taskbar support

Source code for this article is available at Silverlight Remote Control Source Code

Native Extensions For Microsoft Silverlight is a collection of COM automation based runtime libraries and Silverlight libraries which allow elevated trusted out of browser applications to utilize windows platform feature. For example you can build applications which use Sensor API, Speech API, react to remote control, integrate with windows 7 taskbar or listen to windows messages sent to the host window. In this post I will build a simple slideshow application to demonstrate some of the capabilities of the library.

The application allows users to choose images from their computer and control the slideshow with remote control as well as with taskbar buttons. Building the slideshow itself is very easy so I will briefly explain how it works: When the user selects pictures a timer is started. When the timer tick event is fired it fades out current image, switches to the next image and fades it in. The user is also able to stop the slideshow and manually switch between the images. The buttons can be also triggered with remote control and taskbar buttons.

Responding to remote control buttons

According to remote control documentation to respond to remote control you need to listen to various windows messages. For example, let’s implement pause/resume when APPCOMMAND_MEDIA_PAUSE or APPCOMMAND_MEDIA_PLAY commands are received. To interpret these commands we need to catch WM_APPCOMMAND message.

public MainPage()
{
    InitializeComponent();

    WindowMessageInterceptor.Current.WindowMessage += MessageReceived;
    WindowMessageInterceptor.Current.AddMessageIntercept(NativeMethods.WM_APPCOMMAND);
}

As you have probably guessed MessageReceived is a method which will be called when the host window receives WM_APPCOMMAND message. The handler will also receive message details such as wParam and lParam. To get the command which triggered the message we need to process lParam by calling GET_APPCOMMAND_LPARAM Macro. The macro is defined in Winuser.h as

#define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK))

HIWORD is another macro defined in Windef.h as

#define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff))

The corresponding code in C# looks like this

public static int GET_APPCOMMAND_LPARAM(int lParam)
{
    return (short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK);
}

private static int HIWORD(int lParam)
{
    return ((lParam >> 16) & 0xffff);
}

We are now ready to implement the MessageReceived handler:

private void MessageReceived(object sender, WindowMessageEventArgs e)
{
    if (e.Message == NativeMethods.WM_APPCOMMAND)
    {
        int cmd = NativeMethods.GET_APPCOMMAND_LPARAM(e.lParam);
        switch (cmd)
        {
            case NativeMethods.APPCOMMAND_MEDIA_PLAY:
                  ResumeSlideShow();
                  break;
            case NativeMethods.APPCOMMAND_MEDIA_PAUSE:
                  PauseSlideShow();
                  break;
        }
    }
}

Let’s add support for navigating between the images using left and right buttons on the controller. These buttons don’t generate WM_APPCOMMAND message. Instead they trigger WM_KEYDOWN message which Silverlight supports out of the box. This means that to respond to these buttons we need to simply respond to KeyDown event:

private void SlideShowKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Left)
    {
        PreviousImage();
    }

    if (e.Key == Key.Right)
    {
        NextImage();
    }
}

Adding support for taskbar buttons

The first step in adding support for taskbar buttons is to add the actual buttons. First we create the buttons and than instruct the taskbar to show them:

private void CreateTaskbarButtons()
{
    for (int i = 0; i < 4; i++)
    {
        var thumbbarButton = TaskbarButton.Current.CreateThumbbarButton((uint)(i + 1));
        thumbbarButton.Flags = THUMBBUTTONFLAGS.THBF_ENABLED;

        thumbbarButton.ImageDataType = ButtonImageDataType.PNG;
        var resourceInfo = Application.GetResourceStream(new Uri(string.Format("images/{0}.png", i), UriKind.Relative));
        using (var br = new BinaryReader(resourceInfo.Stream))
        {
            thumbbarButton.Image = br.ReadBytes((int)resourceInfo.Stream.Length);
        }

        taskbarButtons.Add(thumbbarButton);
     }

    taskbarButtons[0].Tooltip = "First Image";
    taskbarButtons[1].Tooltip = "Previous Image";
    taskbarButtons[2].Tooltip = "Next Image";
    taskbarButtons[3].Tooltip = "Last Image";

    TaskbarButton.Current.ShowThumbbarButtons();
}

You have probably notices that the code snippet is creating buttons but there are no event handlers to respond when they are clicked. Instead we need to use WindowMessageInterceptor class again and subscribe to CommandMessage event. The event is raised in response to WM_COMMAND message which is generated when a taskbar button is clicked. Apart from subscribing to event we need to call AddCommandMessageIntercept method and specify in which control notification code we are interested it. This way the event will be raised only for that particular notification code. In case of taskbar buttons the notification code is THBN_CLICKED.

WindowMessageInterceptor.Current.CommandMessage += CommandMessage;
    WindowMessageInterceptor.Current.AddCommandMessageIntercept(NativeMethods.THBN_CLICKED);

When the event handler is called it will receive an instance of CommandMessageEventArgs class specifying notification code and control identifier. The control identifier corresponds to the ButtonID we passed to CreateThumbbarButton method when we created the buttons. This way we can identify which button caused the event to fire.

private void CommandMessage(object sender, CommandMessageEventArgs e)
{
    if (e.NotifyCode != NativeMethods.THBN_CLICKED)
    {
        return;
    }

    switch (e.ControlID)
    {
        case 1:
            FirstImage();
            break;
        case 2:
            PreviousImage();
            break;
        case 3:
            NextImage();
            break;
        case 4:
            LastImage();
            break;
    }
}
Avatar
Giorgi Dalakishvili
World-Class Software Engineer