• MCML : First Contact (again)

    Okay, so we have created a (really simple) menu control, the next step is to wrap it all in a reusable control that you could use in all your pages / in another project. Doing so will require only 2 things :

    • manage keyboard and mouse input
    • move all the mcml code to a new UI

    Moving the control to its own UI element is not really difficult, all we need to remember is to add properties for the needed data.

    <Mcml 
        xmlns="http://schemas.microsoft.com/2006/mcml"
        xmlns:me="Me"
        xmlns:cor="assembly://MScorLib/System">
        <UI Name="MenuPage">
            <Locals>
                <Choice Name="TheCommands">
                    <Options>
                        <cor:String String="Command1" />
                        <cor:String String="Command2" />
                        <cor:String String="Command3" />
                    </Options>
                </Choice>
            </Locals>
            <Content>
                <ColorFill Content="LightGray" Layout="Center">
                    <Children>
                        <me:MyMenu MenuItems="[TheCommands]" />
                    </Children>
                </ColorFill>
            </Content>
        </UI>
    
        <UI Name="MyMenu">
            <Properties>
                <Choice Name="MenuItems" Choice="$Required" />
            </Properties>
            <Content>
                <Repeater Layout="VerticalFlow" Source="[MenuItems.Options]">
                    <Content>
                        <me:MyMenuItem 
                            Title="[RepeatedItem!cor:String]"
                            MyIndex="[RepeatedItemIndex]"
                            Commands="[MenuItems]"/>
                    </Content>
                </Repeater>
            </Content>
        </UI>
    ...
    </Mcml>

    Nothing difficult here : just cut & paste the Repeater into a new UI, add a property of type Choice and add a "reference" to the newly created control. Now let's add a little interactivity, shall we ?

    As I have hinted in the previous post, you just add keyboard and mouse interaction by adding rules to your object. One of the fundamental rules is :

            <Default Target="[Input.KeyInteractive]" Value="true" />
    

    which enables a UI to be keyboard-focusable. The actual process of finding which control will be the next one to gain focus when you press a key is left to MediaCenter itself, so don't bother with it. Whenever an item gains focus, the KeyFocus property of Input is changed from false to true, so you can easily add a rule for event-handling.

            <Condition Source="[Input.KeyFocus]" SourceValue="true">
                <Actions>
                    <Set Target="[Commands.ChosenIndex]" Value="[MyIndex.Value]" />
                    <Set Target="[CommandLabel.Font]" Value="Segoe UI, 24, Bold" />
                    <Set Target="[CommandLabel.Alpha]" Value="1" />
                </Actions>
            </Condition>

    For the MyMenuItem class, we just change the selected item (the chosen index from our list of commands) and the formatting of our label. This code is a little bit different from the one I'd shown in my previous post, but I won't explain here what makes this code easier to read but not as powerful as the other one.

    If you look at the SDK documentation, you'll see that the InputElement as quite a few properties, some of them beeing really interesting :

    • KeyFocusOnMouseEnter can be set to false if you want the focus not to be automatically taken by a control on mouse-over
    • MouseFocus is set to true when the mouse is over an element. (combined with the previous element, it allows you to create a hover effect like an html A/A:hover css rule)
    • KeyDeepFocus is set to true if the element or one of its children have the keyboard-focus
    • MouseDeepFocus is the same but for mouse-focus

    Next thing to do : add a click effect to our menu-item (the process of actually doing something will be covered in a future post). This is a little bit more difficult, as there's no EnterPressed or MouseClicked property on the Input element... For this basic interactivity, we'll use a local ClickHandler object, which as its name suggest, just reacts to a "click" (left mouse button, space and enter) and add rules to its Clicking property.

    <UI Name="MyMenuItem">
        ...
        <Locals>
            <ClickHandler Name="ClickHandler"/>
        </Locals>
        <Rules>
            ...
            <Condition Source="[ClickHandler.Clicking]" SourceValue="true">
                <Actions>
                    <Set Target="[CommandLabel.Color]" Value="Red" />
                </Actions>
            </Condition>
        </Rules>
        ...
    </UI>

    Now, we'll make it really re-usable by creating a new mcml file, and moving the two controls in it :

    MyMenu.mcml
    <Mcml xmlns="http://schemas.microsoft.com/2006/mcml" xmlns:me="Me" xmlns:cor="assembly://MScorLib/System"> <UI Name="MyMenu"> <Properties> <Choice Name="MenuItems" Choice="$Required" /> </Properties> <Content> <Repeater Layout="VerticalFlow" Source="[MenuItems.Options]"> <Content> <me:MyMenuItem Commands="[MenuItems]" Title="[RepeatedItem!cor:String]" MyIndex="[RepeatedItemIndex]"/> </Content> </Repeater> </Content> </UI> <UI Name="MyMenuItem"> <Properties> <Choice Name="Commands" Choice="$Required" /> <Index Name="MyIndex" Index="$Required"/> <cor:String Name="Title" String="$Required"/> <Color Name="ClickedColor" Color="Red"/> </Properties> <Locals> <ClickHandler Name="ClickHandler"/> </Locals> <Rules> <Default Target="[Input.KeyInteractive]" Value="true" /> <Default Target="[Input.KeyFocusOnMouseEnter]" Value="false" /> <Condition Source="[ClickHandler.Clicking]" SourceValue="true"> <Actions> <Set Target="[CommandLabel.Color]" Value="Red" /> </Actions> </Condition> <Condition Source="[Input.MouseFocus]" SourceValue="true"> <Actions> <Set Target="[CommandLabel.Alpha]" Value="0.75" /> </Actions> </Condition> <Condition Source="[Input.KeyFocus]" SourceValue="true"> <Actions> <Set Target="[Commands.ChosenIndex]" Value="[MyIndex.Value]" /> <Set Target="[CommandLabel.Font]" Value="Segoe UI, 24, Bold" /> <Set Target="[CommandLabel.Alpha]" Value="1" /> </Actions> </Condition> </Rules> <Content> <Text Alpha="0.50" Name = "CommandLabel" Content="[Title]" Color="White" Font="Segoe UI, 20"> </Text> </Content> </UI> </Mcml>

    In order to reuse this control (in the same project or another one), you'll need to :

    • add this file to your project,
    • add it in the resources.resx file
    • add the following line in any mcml file in which you want to use those controls (replacing MyAddin/MyDefaultNamespace )
        <Aggregate Source="resx://MyAddIn/MyDefaultNamespace.Resources/MyMenu" />
    

    In the next few posts, we'll try to make this menu a full fledged control by adding scrolling, commands, accessibility...

  • MCML : First Contact

    For the last view days, I've take some of my spare time to dwell into the new generation of Media Center Extensibility technologies. I've already blogged (and I will do it again with a little more details) about my "Lazy boy server monitoring" tool, but the subject for today is MCML (for Media Center Markup Language).

    MCML doesn't really have much in common with XAML (for WPF or WPF/e), which is sad. Having to learn one more description language and its associated bag of trick is pretty difficult, but the learning curve for MCML is not really long or even hard. In this post, I'll try to explain how to create a simple menu control with a few options (read without any need to scroll).

    After creating a new Media Center Presentation Layer Application, the project have a Default.mcml file that contains a UI element with some content. The UI element is really important in MCML for it's the root container ofevery graphical element you'll build - like a UserControl in asp.net but with the small difference that pages are also an UI element. Clearing the content from this element will bring you with a good starting point for the menu.

    <Mcml 
       xmlns="http://schemas.microsoft.com/2006/mcml"
               xmlns:me="Me">
       <UI Name="MenuPage">
          
       </UI>
    </Mcml>

    We'll start by adding a ColorFill in the MenuPage. This control is similar to a simple Border object in WPF and by default use all the available space in its container but have on advantage : it serves as a layout manager too (meaning that you can arrange its children in different ways : center them, make them flow vertically etc.).

    <Mcml 
       xmlns="http://schemas.microsoft.com/2006/mcml"
        xmlns:me="Me">
       <UI Name="MenuPage">
          <Content>
             <ColorFill Content="LightGray" Layout="Center">
                <Children>
                   
                </Children>
             </ColorFill>
          </Content>
       </UI>
    </Mcml>

    In MCML, there's nothing like a ListView control or any high-end controls. You'll have to go on with a simple Repeater control and a few tricks, but as you will see, there's nothing really complicated.

    MCML is as much a model/view paradigm as XAML is, so to get a list of menu items to display, we'll need three things :

    • a control to represent a menu-item,
    • a control to host those menu items
    • … and a list of menu-items

    For the last requirement, we'll keep it simple : MCML contains a special collection of objects (the Choice class), that handles most of the thing we'll need : a list of object, a property to know which one is currently selected and events when one of its property changes. As nothing can be that simple and easy, there's a catch in using this Class : by default, MCML does not know what a string (for example) is… Like WPF, you'll need to add a xml-namespace to reference the assembly which contains those types.

    As the Choice class is not a visual element, you won't be able to add it to a Content or Children property, but, that's not a big deal : the UI allows you to define local "variables".

    <Mcml 
       xmlns="http://schemas.microsoft.com/2006/mcml"
        xmlns:me="Me"
       xmlns:cor="assembly://MScorLib/System">
       <UI Name="MenuPage">
          <Locals>
             <Choice Name="TheCommands">
                <Options>
                   <cor:String String="Command1" />
                   <cor:String String="Command2" />
                   <cor:String String="Command3" />
                </Options>
             </Choice>
          </Locals>
          <Content>
             <ColorFill Content="LightGray" Layout="Center">
                <Children>
                   <Repeater Layout="VerticalFlow" Source="[TheCommands.Options]">
                      <Content>
                         <Text Content="[RepeatedItem!cor:String]" />
                      </Content>
                   </Repeater>
                </Children>
             </ColorFill>
          </Content>
       </UI>
    </Mcml>

    As you have certainly guessed, the [Something] notation allows to get the value of a variable (the !-notation is used to do some type casting). So we just created a RepeaterItems which displays the options from the local variable TheCommands, and for each value, displays a text. If you take this mcml snippet and run it into MCML Sampler, you should get something like :

    That's not yet amazing, but it will be ;).

    What we need to do next is to replace the simple Text element with something a little bit more complex (what I want to do is to make items become white when selected and red during a mouse-click/enter-key-press). The easiest way to create such a modern - or even futuristic interface - (:p) is to make a user-control (an new UI element) to display the menu item.

    <Mcml 
       xmlns="http://schemas.microsoft.com/2006/mcml"
        xmlns:me="Me"
       xmlns:cor="assembly://MScorLib/System">
       <UI Name="MenuPage">
          ...
       </UI>
       <UI Name="MyMenuItem">
           <Content>
             <Text Content="-not defined-" />
          </Content>
       </UI>
    </Mcml>
    

    We'll need a way to pass the data from the repeater-item to this control, and that's just what the Properties collection is for. These properties are used in the same way that local variables but can be set be the "caller" (parent) control.

    <Mcml 
       xmlns="http://schemas.microsoft.com/2006/mcml"
        xmlns:me="Me"
       xmlns:cor="assembly://MScorLib/System">
       <UI Name="MyMenuItem">
          <Properties>
             <Choice Name="Commands" Choice="$Required" />
             <Index Name="MyIndex" Index="$Required"/>
             <cor:String Name="Title" String="$Required"/>
             <Color Name="ClickedColor" Color="Red"/>
          </Properties>
          <Content>
             <Text Alpha="0.75" 
                Name = "CommandLabel" 
                Content="[Title]" 
                Color="White" 
                Font="Segoe UI, 20">
             </Text>
          </Content>
       </UI>
    </Mcml>
    

    The $Required notation specifies that there is no default value for this property and that the parent should always send a value. Now we just have to change the repeater control :

    ...
    <Repeater Layout="VerticalFlow" Source="[TheCommands.Options]">
       <Content>
          <me:MyMenuItem 
             Title="[RepeatedItem!cor:String]"
             MyIndex="[RepeatedItemIndex]"
             Commands="[TheCommands]"/>
       </Content>
    </Repeater>
    ...

    Okay, we're almost done. In order to get the selected item to be different from the others, we'll need to write some rules. Those rules are almost the equivalent of Triggers in WPF : you can specify a test (of the value of a property/variable) and if this test is true, have MCML apply some changes.

    The rule for displaying the selected item in a different way is :

    <UI Name="MyMenuItem">
       <Rules>
          <Condition Source="[Commands.ChosenIndex]" SourceValue="[MyIndex.Value]">
             <Actions>
                <Set Target="[CommandLabel.Font]" Value="Segoe UI, 24, Bold" />
                <Set Target="[CommandLabel.Alpha]" Value="1" />
             </Actions>
          </Condition>
       </Rules>
       ...
    </UI>

    It's rather simple : we just check that the ChosenIndex (from the Choice object) is equal to the Index of the current item, and if it's true, we set two properties to different values. If it's false, the default formatting will stay. If you run the mcml file now, the first element will stand-out, but you won't be able to change the selected element. For doing that, you'll need to add the following rule (we'll see more details about mouse/keyboard interaction in another post) :

    <UI Name="MyMenuItem">
       <Rules>
          <Default Target="[Input.KeyInteractive]" Value="true" />
          <Condition Source="[Input.KeyFocus]" SourceValue="true">
             <Actions>
                <Set Target="[Commands.ChosenIndex]" Value="[MyIndex.Value]" />
             </Actions>
          </Condition>
          ...
       </Rules>
       ...
    </UI>
    

    It basically tell the run-time that you control is focusable and that you want to change the ChosenIndex (to set it to the item's index) when an item as the focus. You can now run the sample and look at the amazing menu we have done :

    In the next post, we'll look at the way MediaCenter applications can handle keyboard and mouse input.

    Complete source

    <Mcml 
       xmlns="http://schemas.microsoft.com/2006/mcml"
        xmlns:me="Me"
       xmlns:cor="assembly://MScorLib/System">
       <UI Name="MenuPage">
          <Locals>
             <Choice Name="TheCommands">
                <Options>
                   <cor:String String="Command1" />
                   <cor:String String="Command2" />
                   <cor:String String="Command3" />
                </Options>
             </Choice>
          </Locals>
          <Content>
             <ColorFill Content="LightGray" Layout="Center">
                <Children>
                   <Repeater Layout="VerticalFlow" 
    Source="[TheCommands.Options]"> <Content> <me:MyMenuItem Title="[RepeatedItem!cor:String]" MyIndex="[RepeatedItemIndex]" Commands="[TheCommands]"/> </Content> </Repeater> </Children> </ColorFill> </Content> </UI> <UI Name="MyMenuItem"> <Properties> <Choice Name="Commands" Choice="$Required" /> <Index Name="MyIndex" Index="$Required"/> <cor:String Name="Title" String="$Required"/> <Color Name="ClickedColor" Color="Red"/> </Properties> <Rules> <Default Target="[Input.KeyInteractive]"
    Value="true" /> <Condition Source="[Input.KeyFocus]"
    SourceValue="true"> <Actions> <Set Target="[Commands.ChosenIndex]"
    Value="[MyIndex.Value]" /> </Actions> </Condition> <Condition Source="[Commands.ChosenIndex]"
    SourceValue="[MyIndex.Value]"> <Actions> <Set Target="[CommandLabel.Font]"
    Value="Segoe UI, 24, Bold" /> <Set Target="[CommandLabel.Alpha]"
    Value="1" /> </Actions> </Condition> </Rules> <Content> <Text Alpha="0.75" Name = "CommandLabel" Content="[Title]" Color="White" Font="Segoe UI, 20"> </Text> </Content> </UI> </Mcml>

  • Lazy-boy server monitoring (1)

    They say : there's no better techie than a lazy techie. Keeping that in mind, I've just put together a small Media Center Add-in which enables me to monitor my servers from my TV.

     

    This add-in (as you can see on the screenshot if your french is not too bad) shows a notification whenever one of my server have issues (the issues are collected by a MOM-like - but really smaller - service and sent to a special "maintenance" server). Clicking on "Afficher le détail" (show me the details) displays the list of errors.

    With this little project, we will see some of the extensibility features of Vista Media Center. For it to remain as simple as possible (and because I'm not yet fluent in MCML), I'll use an XBAP application to show the details and a small non-visual MCPL add-in for the notification.

    XBAP Application

    For this post, let's just say that this application exists, is written as a browser wpf application and looks like this : (I know that it is not really good looking and will try to make it prettier before I do another post about it)

     

    If you want to build a XBAP application for Media Center, there are a few things to remember : 

    • the layout and font-size should be chosen to be usable at 10-feet
    • display resolution and even format is the real unknown : 480p, 720i, 1080p or even a mere 800x600, the sheer number of different resolutions is frightening, so viewbox and dynamic layout are your friends
    • most of the time, your user won't have anything but the remote to use your application, so you'd better make it simple to use and forget the idea of complex data-entry
    • Media center displays (ugly) on screen-commands when the mouse is moved, so don't forget to make space for them

    The notification add-in

    The second project is what displays the notification bar. I've used the Windows Media Center Presentation Layer Application (Microsoft really have a way with names...) which came with the SDK. It provides all the necessary files (and much more) for this project :

    • an add-in class
    • a registration file
    • many other things that won't be used in this particular project

    As the add-in will run in background, the whole code will be found in the add-in class :

        public class MyAddin : IAddInModule, IAddInEntryPoint
        {
            public void Initialize(Dictionary<string, object> appInfo,
    Dictionary<string, object> entryPointInfo) { } public void Uninitialize() { } public void Launch(AddInHost host) {
    ... } }

    I didn't need to do anything when Initializing / Uninitializing, so only the Launch method will be used.

            public void Launch(AddInHost host)
            {
                if (!CheckSingleInstance())
                    return;




    _host = host; while (true) {
    NotiftIfErrors(); Thread.Sleep(30000); } }

    After ensuring that another instance of the add-in is not running - if you use a mutex to do such a test, remember to use a local mutex. If you don't, and use global mutex, the add-in may not run on an extender - the method simply enter an infinite loop which checks for errors and goes to sleep for 30 seconds. I did it quick and dirty (that while(true) is not the prettiest thing in the universe) but I'll try to find a nicer way. What is important is to know that the add-in - except for the call to Initialize and Uninitialize - only lasts as long as its Launch method. You may think that it's loaded and unloaded with Media Center, but that's not true. So say goodbye to timers and any other asynchronous method, and play it simple. (I'm not suggesting that you can't use any async methods, but remember that when the Launch method returns, the process will be terminated)

    The NotifyIfErrors method is not realy difficult to write :

            public void NotifyIfErrors()
            {
                int i = GetUnhandledEventCount();
                if ((i > 0 && (DateTime.Now - _dtLastEvent).TotalHours > 1) 
    || i > _lastCount) { _lastCount = i; _dtLastEvent = DateTime.Now; string imagePath = Path.GetTempPath()
    + Guid.NewGuid().ToString("N") + ".png"; Bitmap b = new Bitmap(1, 1); b.Save(imagePath, ImageFormat.Png); _host.MediaCenterEnvironment.DialogNotification(
    string.Format("{0} Evenements à traiter", lastCount), new object[] { "Afficher le detail" }, 30, imagePath, new DialogClosedCallback(LaunchDialog)); } }

    I won't bother you with the details of the GetUnhandledEventCount, which is pretty straightforward and makes web-service-calls. If there's more issues than the last time the add-in checked or if there is more than an hour since the last notification, a new one will be displayed.

    The DialogNotification method requires an image, so for the first version of this add-in, I generate an empty picture (when I said, that this was quick & dirty !). The second and last parameters of this method are important : one for specifying the buttons on the notification bar and the other to add a callback method ofr button-press handling.

            private void LaunchDialog(DialogResult dr)
            {
               // TBD
            }

    Before we can launch the XBAP, we'll need to put together an xml-file to register the application.

    This file contains an application definition and two entry points :

    • One for the notification add-in (placed in the Background category)
    • The other for the XBAP, registred in "More Program / Tasks"
    <application 
      title="MyAddin" 
      id="{EA231D54-38FE-4f8e-969F-077167EDB442}">
      <entrypoint 
         id="{78eb8987-7454-40fe-b7cb-bcc338d13587}" 
         addin="MyAddinPrj.MyAddIn, MyAddinPrj"
                  title="..."
                  description="..."
                  ImageUrl=".\AppIcon.png">
        <category category="Background"/>
      </entrypoint>
    
      <entrypoint 
      id="{4E6CDC30-7F35-4b48-AE4C-82F0234ECC24}"
      url="http://maintenance/admindeploy/admin.xbap"
      title="Issue Tracking"
      imageUrl="http://maintenance/admindeploy/admin.png">
        <category category="Settings\Tasks" />
      </entrypoint>
    </application>
    

    With the informations contained in this registration xml, the parameters to launch the XBAP - the application and entry point guids - are now known.

            private void LaunchDialog(DialogResult dr)
            {
                if (dr != DialogResult.Cancel)
                {
                    _host.MediaCenterEnvironment.LaunchEntryPoint(
                        new Guid("{EA231D54-38FE-4f8e-969F-077167EDB442}"),
                        new Guid("{4E6CDC30-7F35-4b48-AE4C-82F0234ECC24}"));
                }
            }

    Putting it all together

    Deploying this mini-project is not that simple... Due to security concerns - which don't allow a browser application to connect to any server but the one that host its files -, the XBAP goes to my maintenance server. For the MCPL add-in there are two valid destinations : the GAC or the \windows\ehome directory. I'm more in favor of the second, but if you're building a MSI to deploy your project, the GAC is certainly easier.