Continuing the series of articles on Media Center development, this post introduces a command button with the standard Media Center look-and-feel:
A Command Button
Last time, we looked at a UI element for a page title. That was a good example of a non-interactive element. To get any real functionality in an add-on, we will need interactive elements; the most rudimentary of which is a simple command button.
This implementation was derived by reverse-engineering the built-in command button defined in Microsoft.MediaCenter.Shell.dll – that implementation relies on managed objects which cannot be accessed from third party code. My version removes those dependencies (and some lesser-used functionality) and updates the MCML-2006 (Vista) markup to MCML-2008 (Windows 7). While the markup for the built-in button cannot be used, the image and sound resources from ehres.dll can and are employed to provide a consistent look-and-feel.
Attributes of a Command Button
- Must be able to receive focus – we do this by setting Input.KeyInteractive and examining Input.KeyFocus.
- Must perform an action – we need to associate a <Command> with the button, and trigger the command when the button is clicked.
- Must have a text label – we can use the Description property on the command to provide this; conceptually, this is the preferred method (see the Explanation section for the reasoning).
- Can be disabled – the properties on the command also support this.
The Windows 7 Media Center look-and-feel dictates that command buttons:
- Have an empty background normally and a glass-style background when focused
- Change (brighten) the colour of their text when focused
- Play sounds when focused and when clicked (which vary according to the action performed)
- Display a steadily pulsing animation when focused
- Display an animated highlight when clicked
Buttons in Media Center, therefore, have a layout which divides content into the following, overlapping layers:
<me:Button> <Model> <Command Description="Button Text" /> </Model> </me:Button>
<me:Button Model="[SomeCommand]" />
It is considered best practice if you declare the <Command> object in the <Locals> or <Properties> section of your UI, rather than inline.
In the Media Center SDK, commands are objects on the View-Model which implement the ICommand interface or extend from the Command class. They can be declared either in MCML markup or in code (in the latter case, they must then be passed to the view). They represent actions that can be performed in the add-on, in a manner which does not dictate how they are represented at the view layer. Similarly, they do not actually dictate what occurs when they are invoked; it is left for that behaviour to be defined separately, either in the <Rules> section of the markup or in code. This disconnected representation allows you to change either the behaviour or the appearance of the command without affecting the other.
Below is an example of how a command might typically be implemented, showing its declaration, behaviour and attachment to a button:
<UI Name="ButtonUsage"> <Locals> <Command Name="MyCommand" Description="Perform Action" /> </Locals> <Rules> <Changed Source="[MyCommand.Invoked]"> <Actions> <Invoke Target="[SomeClass.SomeMethod]" /> </Actions> </Changed> </Rules> <Content> <me:Button Model="[MyCommand]" /> </Content> </UI>
In most places where an object path or inline constructor can be used in MCML, a resource URL can be used as an alternative. Most of the image/sound resources used by the standard Media Center look-and-feel are located in ehres.dll, a non-managed resource DLL. For such a DLL, resources are accessed using a URL as follows:
You can also reference your own resources from within managed assemblies. The syntax is:
The command button uses a number of images and sounds from Media Center and declares them as properties – you could override them when placing the button element in your own markup.
All of the images used for the button which are resizable make use of a special concept called Nine-Grid Rendering, whereby the original image is divided into 9 sections; when resizing, the 4 corner sections remain at their original size. The 4 outer sides scale in only one direction and the center section is the only region which scales in both directions. This technique is particularly useful for resizing images with some sort of border; in this case, the glass-style button graphic. Without this technique, the rounded borders would appear oversized, skewed and/or fuzzy. When declaring an <Image> object, the NineGrid attribute allows you to specify that Nine-Grid rendering is to be used when scaling, and where the left, top, right and bottom divider lines will be placed.
By instantiating ClickHandler in the markup for a UI element, you provide a mechanism for handling click events (mouse click, remote OK/Select key, keyboard enter key). You can examine the value of Clicking to determine whether the key/button is in the process of being clicked. (There are also more specific handlers such as KeyHandler which you can use for text entry, etc.)
The button introduces a number of new visual elements and layout types, as well as animation.
Please note that I will not be covering animation in this article – it is well documented in the SDK and not the focus of this section.
With respect to the layout shown in the diagram (near the top of this article), the various “layers” of the button are implemented as follows:
- Label and Background – As overlapping children inside a <Panel> element using the Anchor layout (the panel scales down to fit its content), docked to fill the available space within the root panel, factoring in margins for the label. The background introduces the <Graphic> element, which displays an image file as well as having the capacity to host visual elements of its own. Although larger and situated behind the label, the background graphic scales with the text.
- Click Animation – Uses the <Clip> element introduced in the previous article to make the animated highlight graphic fade seamlessly into the edges of the region. Uses the Fill layout to occupy all available space within the button. When shown or hidden, the highlight graphic plays a sweeping animation (Show and Hide type animations).
- Focus Overlay – This is designed to be a mostly-transparent image that sits at the top of the z-order and pulses slowly (an Idle type animation), again filling all of the available space.
The button element also introduces rules, which allow its visual and behavioural states to change in reaction to input (i.e. focus and clicking). The rules for the button simply do as indicated at the start of this article, however I shall introduce each of the rule types:
<Default> – Used to set a default value to apply to an object/element in the absence of any other rules. Particularly useful for assigning values to attributes of the UI element itself, such as Input. Used in this case to specify that the button element can receive key focus. (In Media Center, “key” focus means keyboard, remote control and mouse unless otherwise configured.)
<Binding> – Used to create a one-way binding, usually between a property or model item and a visual element. Used by the button implementation to enforce the enabled/disabled behaviour, show/hide the click animation, set the button size and ensure the text on the button always matches the command description.
<Rule> – Used to handle and react to focus and click events as outlined in the behavioural description of the button earlier in this article. A rule has conditions and actions:
- Typically, the <Equality> condition is used to react when objects have a specific value, whereas the <Modified> condition is used to react whenever a object’s value changes. Additionally, the modified condition can be used to handle events on objects in code (e.g. Command.Invoked).
- You can use actions to change object values (<Set>), invoke methods in code (<Invoke>) or play sounds/animations.
I should also point out some equivalence with rule elements; you will see both <Rule> elements (which tend to represent more complex behaviours) and shorthand forms:
|Shorthand Version||Equivalent To|
<Condition Source="[Source]" SourceValue="[Value]" Target="[Target]" Value="[Value]" />
<Rule> <Conditions> <Equality Source="[Source]" Value="[Value"] /> </Conditions> <Actions> <Set Target="[Target]" Value="[Value]" /> </Actions> </Rule>
<Condition Source="[Source]" SourceValue="[Value]"> <Actions> ... </Actions> </Condition>
<Rule> <Conditions> <Equality Source="[Source]" Value="[Value"] /> </Conditions> <Actions> ... </Actions> </Rule>
<Changed Source="[Source]"> <Actions> ... </Actions> </Changed>
<Rule> <Conditions> <Modified Source="[Source]" /> </Conditions> <Actions> ... </Actions> </Rule>
The most important rule to note is the one which invokes the command associated with the button; when the Invoked event on the ClickHandler is fired (“modified” in MCML), the Invoke method is called on the Command object. Combined with the other visual and behavioural rules, we have everything we need to start using command buttons.
In the next instalment, I want to demonstrate the <Repeater> and <Scroller> elements to show how to display a scrollable list of items from a data source, and then demonstrate how to track the selected item. In my network browsing add-on, this mechanism will be used to navigate through computers, shares and folders, and to select files.