Programowanie w MCML

Wstęp

edytuj

Windows Media Center Markup Language (MCML) jest językiem opartym o specyfikację XML przeznaczonym do tworzenia aplikacji dla Windows Media Center. Choć wykorzystuje składnię XML, to jednak nie jest kompatybilny z żadnym dotychczas opublikowanym językiem korzystającym z podobnych rozwiązań. Istnieją różne teorie dlaczego Microsoft nie zastosował już istniejących, lecz nie będziemy ich ani przytaczać ani wyjaśniać. Przyjmujemy za fakt, że tylko w tym języku można tworzyć aplikacje interfejsu użytkownika w MCE i spróbujemy wyjaśnić jak to się robi. Istniejąca dokumentacja do MCML jest słabo dostępna i najczęściej nie wyjaśnia wszystkich aspektów programowania, co nakłoniło mnie do pisania tego podręcznika.

Co to jest MCML?

edytuj

Krąży wiele błędnych przekonań czym jest właściwie MCML. Tak na prawdę nie jest to język programowania. Można powiedzieć, że jest to znacznikowy język prezentacji obiektów multimedialnych do tworzenia interfejsu użytkownika w Microsoft Windows Media Center. Wykazuje duże podobieństwo do tak znanego języka znacznikowego jak HTML, a w ogólności do wszystkich opierających się na definicji XML. Zastosowanie języka znacznikowego wynika z budowy samego Windows Media Center.

Jak działa Windows Media Center?

edytuj

MCE oparte jest na architekturze "Model/View". W jego przypadku "Model" to zbiór danych i usług, zaś "View" to zbiór formularzy, w których te dane mogą być ukazywane i procesy odpowiedzialne za to. I znów widać tu podobieństwo do stron HTML. W ich przypadku "Model" jest umieszczony na serwerze WWW a "View" to przeglądarka. W MCE obie te funkcje zwykle pełni ta sama maszyna, choć rozdział istnieje w przypadku stosowania Media Center Extender-a na przykład na XBox-sie. MCML zatem definiuje nam coś na kształt strony WWW, a dane do niej dostarczane są z innego poziomu, możliwego do napisania w "normalnych" jezykach programowania. W przeglądarkach WWW definicja formularza jest przekazywana z serwera, w przypadku MCE definicja musi zostać zarejestrowana w systemie operacyjnym jako assembly (jako obiekt w katalogu %SystemRoot%\assembly) oraz w samym MCE (w odpowiedniej gałęzi registry). Kwestią rejestracji zajmiemy się w dalszych rozdziałach. Podczas tworzenia kodu MCML korzystać będziemy z załączonej do MCE aplikacji do testowania oprogramowania o nazwie McmlPad (%SystemRoot%\eHome\McmlPad.exe), która emuluje (nie do końca) środowisko MCE a nie wymaga rejestracji programu.

Środowisko pracy programisty

edytuj

Opisywane tu przykłady wynikają z programów tworzonych w następującym środowisku:

  • System operacyjny Microsoft Windows 7 Professional PL 32bit (wystarczy dowolona wersja Windows 7, także 64bitowa)
  • Microsoft Visual Studio 2010 - Visual C# (wystarczy Ms Visual C# Express od wersji 2006)
  • Microsoft Media Center SDK w wersji 6 dla Windows 7

Potrzebne w dalszych pracach:

Materiały źródłowe

edytuj

W pracy nad programami dla MCE korzystałem z następujących materiałów źródłowych:

Struktura programu

edytuj

Właściwości programu w Ms Visual C#

edytuj

Zakładając, że jako startowy wzorzec programu wybieramy "empty program C#" musimy dokonać pewnych zmian w jego właściwościach by odpowiadał assembly dla MCE.

  1. Target Framework: Nie może być typu Client Profile, przy czym sugeruję w chwili obecnej (2011.11) nie używanie większej wersji niż 3.5
  2. Output Type: Class Library
  3. Post-built events Command Line: %windir%\eHome\McmlVerifier.exe -verbose -assemblyredirect:"$(FullyQualifiedOutputPath)" -directory:"$(ProjectDir)Markup"
  4. Debug/Start external program: WINDIR\eHome\McmlPad.exe
  5. Debug/Start options/Command line arguments: -load:"resx://ASSEMBLY_NAME/DEFAULT_NAMESPACE.Resources/Start" -assemblyredirect:"PROJECT_DIR\bin\Debug" -markupredirect:"resx://ASSEMBLY_NAME/DEFAULT_NAMESPACE.Resources/,file://PROJECT_DIR\Markup\,.mcml"
  6. Debug/Start options/Working Directory: WINDIR\eHome
  7. Referencje:
    1. WINDIR\eHome\Microsoft.MediaCenter.dll
    2. WINDIR\eHome\Microsoft.MediaCenter.UI.dll

Parametry WINDIR, ASSEMBLY_NAME, DEFAULT_NAMESPACE i PROJECT_DIR ustawiamy zgodnie z fizycznie istniejącymi na naszym komputerze.

Ważną rzeczą jest też utworzenie danych Assembly Information a w szczególności nadanie GUID. W przeciwnym razie nie będzie tworzony obiekt assembly.

Dlaczego ustawienia są takie a nie inne wyjaśnię później. Obecnie chodzi o szybkie przejście do programowania, co "tygrysy lubią najbardziej...".

Jak wspomnialem, MCML opisuje układ "strony-View" wyświetlanej jako interfejs użytkownika w MCE. Obiekty udostępniane przez tę stronę są przekazują pobrane dane z poziomu "Model" nie tylko w sposób wizualny ale także np. przez dźwięk. Bazą "View" jest ekran MCE, przy czym nie jest on torżsamy z ekranem komputera. Jest to wnętrze okna wyświetlanego przez proces ehshell.exe, które najczęściej rozciągnięte jest na cały ekran komputera bez żadnych ramek. Obecnie stosowana wersja ehshell.exe umożliwia obsługę TYLKO JEDNEGO ekranu. Na etapie obecnym nie można tworzyć aplikacji MCML obsługujących więcej niż jeden monitor, choć możliwe jest wykonanie takiego oprogramowania innymi metodami.

Ekran jest powierzchnią roboczą o stałej proporcji szerokości do wysokości. Stosowane są proporcje 4:3 lub 16:9 tak jak w przypadku telewizorów. Nie znam w tej chwili metody zdefiniowania innych proporcji, choć być może takie istnieją. Powierzchnia robocza ma tło w kolorze czarnym (RGB:0,0,0) i nie ma zdefiniowanych wymiarów. Oznacza to, że w zasadzie nie można mówić o ilości pikseli ani w szerokości ani wysokości. W rzeczywistości oczywiście te piksele istnieją ale są zależne od sprzętu. Natomiast u podstaw MCML tkwi założenie oderwania się od sprzętu. Zdefiniowany "View" powinien być tak samo wyświetlany na różnym sprzęcie lub choćby w różnych romiarach okna procesu ehshell.exe. W większości przypadków zatem gdy będziemy mówić o położeniu obiektów lub ich rozmiarze na ekranie, będziemy mieć na myśli połóżenie lub rozmiar relatywny. Piksele zastosujemy tylko w szczególnych przypadkach.

Markup document

edytuj

Na ekranie możemy wyświetlić "dokument znacznikowy (markup document)", w którym umieścimy jeden lub wiele "interfejsów użytkownika (UI elements)" a w nich dowolną liczbę różnych obiektów przeznaczonych do prezentacji. Prócz interfejsu użytkownika można w dokumencie zawrzeć także definicje obszarów nazw (namespaces) oraz definicje pomocnicze (nie prezentowane bezpośrednio).

Dokument znacznikowy jest przechowywany przed kompilacją w pliku tekstowym z rozszerzeniem .MCML. Jego struktura pokazana jest poniżej:

<Mcml definicje_obszarów_nazw >
  ... definicje pomocnicze ...
  <UI Name="Interfejs 1" >
   ... obiekty ...
  </UI>
    <UI Name="Interfejs 2" >
   ... obiekty ...
  </UI>
 ...
  <UI Name="Interfejs N" >
   ... obiekty ...
  </UI>
</Mcml>

Kod startowy

edytuj

W momencie startu aplikacji pisanej dla MCE musi ona wiedzieć który dokument MCML oraz który interfejs użytkownika musi zostać wyświetlony jako pierwszy. Musi ona także posiadać jakieś powiązania z zapleczem "Model". Programista musi utworzyć kod określający powyższe wymagania, który potem kompilowany jest do biblioteki dll (Class Library), która jest rejestrowana jako assembly dla MCE lub wywoływana przez McmlPad. Klasa startowa tworzona jest w oparciu o klasy interfejsów IAddInModule i IAddInEntryPoint zawarte w obszarze nazw Microsoft.MediaCenter.Hosting definiowanych w bibliotece (i assembly) Microsoft.MediaCenter.dll (katalog %SystemRoot%\eHome). Musi ona posiadać precedurę Launch(AddInHost), która wykonywana jest w momencie rozpoczęcia prezentacji interfejsu. Przykładowy program napisany w C# pokazany jest poniżej:

 using System.Collections.Generic;
 using Microsoft.MediaCenter.Hosting;
 
   public class MyAddIn : IAddInModule, IAddInEntryPoint
   {
       private static HistoryOrientedPageSession s_session;
 
       public void Initialize(Dictionary<string, object> appInfo, Dictionary<string, object> entryPointInfo)
       {
       }
 
       public void Uninitialize()
       {
       }
 
       public void Launch(AddInHost host)
       {
           s_session = new HistoryOrientedPageSession();
           s_session.GoToPage("resx://ASSEMBLY_NAME/DEFAULT_NAMESPACE.Resources/Start");        
       }
   }

Linia "s_session.GoToPage("resx://ASSEMBLY_NAME/DEFAULT_NAMESPACE.Resources/StartMCML");" nakazuje wyświetlenie pierwszego interfejsu użytkownika w dokumencie StartMCML.mcml. Proszę pamiętać o zamianie ASSEMBLY_NAME i DEFAULT_NAMESPACE na realne wartości naszego projektu. Pozostałymi obiektami używanymi w programie zajmiemy się później.

Obiekty podstawowe

edytuj

Interfejs użytkownika składa się z pewnej ilości (co najmniej jednego) obiektu podstawowego. Z połączeń tych elementów można utworzyć inne ale tym zajmiemy się później. Do obiektów podstawowych zaliczamy:

  • Obiekty prezentowane (widzialne), wśród których są:
    • Text (wyświetla tekst, z możliwością formatowania)
    • Colorfill (wyświetla prostokąt, wypełniony kolorem)
    • Graphic (wyświetla obraz typu typu bitmapy, jpg czy PNG)
  • Obiekty grupujące:
    • Panel (umożliwia operowanie grupą obiektów w jednym kontekście)
    • Clip (jak wyżej, przy czym określa się pole widzenia)
    • Scroller (jak wyżej, ale umożliwia się przesuwanie obiektów przez pole widzenia)
  • Obiekty strukturalne:
    • Repeater (umożliwia operowanie listami obiektów)
  • Inne (o których wspomnimy później):
    • NowPlaying
    • Host
    • Video

Opis obiektów podany będzie w rozdziale Typy MCML.

Programujemy

edytuj

Hello World

edytuj

Spróbujemy teraz napisać nasz pierwszy program, korzystający z MCML. Otwórzmy Visual C# i utwórzmy nowy pusty projekt. Zmieńmy jego właściwości tak jak zaznaczono to w sekcji Właściwości programu w Ms Visual C#. Utwórzmy w naszym projekcie dwa podkatalogi: Code (gdzie będziemy przechowywać kod w C#) oraz Markup (gdzie będziemy przechowywać dokumenty MCML). W podkatalogu Code utwórzmy nowy obiekt typu Class o nazwie Launch.cs i skopiujmy do niego zawartość kodu startowego podanego wcześniej. W podkatalogu MCML utwórzmy nowy obiekt typu Text file o nazwie Start.mcml (rozszerzenie .txt zmieniamy na .mcml). Otwieramy (przez dwukrotne kliknięcie) plik Start.mcml. Wklejamy poniższy tekst:

<Mcml xmlns="http://schemas.microsoft.com/2006/mcml" >
 <UI Name="HelloWorld">
   <Content>
      <Text Content="Hello World" Color="White" />
   </Content>
 </UI>
</Mcml>

Do projektu dodajemy nowy obiekt typu Resources File o nazwie Resources.resx. Wybieramy w nim kontener "Files" (Ctrl+5) i przeciagamy weń nasz plik Start.mcml. Zapiszmy teraz nasz projekt (Ctrl+Shift+L) i skompilujmy go (Ctrl+Shift+B). W oknie output powinniśmy uzyskać coś podobnego do poniższego:

------ Rebuild All started: Project: Project1, Configuration: Debug x86 ------
 Project1 -> PROJECT_DIR\bin\Debug\Project1.dll
 Microsoft (R) MediaCenter Markup Language (MCML) Verifier version 1.00
 Copyright (C) Microsoft Corporation 2006. All rights reserved.
  
 file://PROJECT_DIR\Markup\Start.mcml - OK!
 No errors detected.
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========

Po uruchomieniu skompilowanego programu (F5) uzyskamy następujący efekt:

 
Hello World

Analiza programu

edytuj

Prześledźmy teraz jak został zinterpretowany nasz program.

  1. Utworzona została instacja obiektu MyAddIn (z definicji z pliku Launch.cs).
  2. Metoda Launch(AddInHost host) wywołała dokument MCML o nazwie Start.mcml (powiązanie poprzez Resources.resx).
  3. W dokumencie MCML mamy zdefiniowany tylko jeden interfejs użytkownika o nazwie HelloWord i on zostaje wyświetlony.
  4. Interfejs zawiera obiekt tekstowy wyświetlający tekst Hello World w kolorze białym. Musimy wyspecyfikować kolor, gdyż domyślnym kolorem dla wszystkich obiektów oraz tła jest kolor czarny. Nasz tekst nie byłby widoczny.

Definicja rozpoczynająca się od słowa "xmlns" wskazuje, że korzystamy z obszaru nazw mcml w wersji 2006.

Typy MCML

edytuj

Główny znacznik dokumentu MCML.

Składnia

<Mcml
   xmlns:namespace="resource"
/>

Definiuje interfes użytkownika, sposób w jaki jest wyświetlany, jak się zachowuje i jak współdziała z kodem lub obiektami danych.

Składnia

<UI
   BaseUI="string"
   Flippable="{true | false}"
   Name="string"
>
   <Content />
   <Locals />
   <Properties />
   <Rules />
</UI>

Definiuje obiekt grupujący inne obiekty - kontener, w celu łatwiejszej manipulacji zawartymi w nim obiektami.

Składnia

<Panel
   Alpha="float"
   AnimationsEnabled="{true | false}"
   CenterPointOffset="Vector3"
   CenterPointPercent="Vector3"
   ColorFilter="Color"
   DebugOutline="Color"
   FocusOrder="int"
   Layout="{Anchor | Center | Dock | Fill | Form | Grid | HorizontalFlow | Rotate | Scale | VerticalFlow}"
   LayoutInput="LayoutInput"
   Margins="Inset"
   MaximumSize="Size"
   MinimumSize="Size"
   MouseInteractive="{true | false}"
   Name="string"
   Navigation="NavigationPolicies enumeration"
   Padding="Inset"
   Rotation="Rotation"
   Scale="Vector3"
   TouchInteractive="{true | false}"
   Visible="{true | false}"
>
   <Animations />
   <Children />
   <Layout />
   <LayoutInput />
</Panel>

Wyświetla tekst i umożliwia jego formatowanie.

Składnia

<Text
   Alpha="float"
   AnimationsEnabled="{true | false}"
   BackColor="Color"
   CenterPointOffset="Vector3"
   CenterPointPercent="Vector3"
   Color="Color"
   ColorFilter="Color"
   Content="string"
   DebugOutline="Color"
   FadeSize="float"
   FocusOrder="int"
   Font="Font element"
   HorizontalAlignment="{Center | Far | Near}"
   Layout="{Anchor | Center | Dock | Fill | Form | Grid | HorizontalFlow | Rotate | Scale | VerticalFlow}"
   LayoutInput="LayoutInput"
   Margins="Inset"
   MaximumLines="int"
   MaximumSize="Size"
   MinimumSize="Size"
   MouseInteractive="{true | false}"
   Name="string"
   Navigation="NavigationPolicies enumeration"
   Padding="Inset"
   Rotation="Rotation"
   Scale="Vector3"
   TouchInteractive="{true | false}"
   Visible="{true | false}"
   WordWrap="{true | false}"
>
   <Animations />
   <Children />
   <Layout />
   <LayoutInput />
</Text>