|
|
Windows Media Center development hints, tips, tutorials and information
-
-
Microsoft are of the opinion that users of Media Center applications do not want 3rd party apps to look like or work like Media Center itself.
This is at odds with 99% of the feedback I've ever recieved as a Media Center developer, but I quote Charlie Owen from MS -
The pool of customers we listen to and try to satisfy is much larger than folks realize. 'Customers want a unified experience' is not supported by the broad data I see everyday.
- Chalie Owen, Microsoft (from the Media Center Sandbox)
Now, I'd like to ask a question simply for data-gathering purposes. Do you - as a Media Center user - want your applications to integrate completely with the Media Center experience, looking and working the same way, or would you prefer every application to have a unique look, feel and method of interaction, with a clear seperation of "Media Center Mainstream" and "3rd Party App"?
Microsoft has offered their reasoning why they don't want to give developers access to components of the system, and although I feel it limits their developer base, I don't feel too bad about that decision, because it does force users to really understand the MCML system. However, the statement that users out there don't WAN'T to have a consistent interface seems unlikely given the real-world feedback I've recieved.
So - I've posted a poll. Please, answer it yourself. And if you can, spread the word about the poll and encourage answers from other users of your Media Center PC - I want to get serious feedback from as many people as I can about this particular issue.
http://thedigitallifestyle.com/cs/forums/ShowThread.aspx?PostID=6021#6021
Reasons Why There Is Not A Unified Interface:
http://blog.retrosight.com/WhyOurLookFeelIsntAvailableToApplications.aspx
Arguments FOR a Unified Interface (My Personal Perspective, And Of Users I've Dealt With):
I know that for every family with a Media Center PC, there's usually 1 or 2 tech-savvy people who have no trouble picking up the basics of navigation in moments. But there is also almost always another 1-2 people who are technophobes, and simply will not access something that is unfamilliar to them. I also know that for every program I have ever written in Media Center, the first thing pointed out in any review or user feedback is "Make it look more like Media Center".
|
-
Ok - many people still have some trouble visualising their interfaces when it comes to writing their MCML. They don't know where to start, or what to do. I'm going to take a page from an MCML application and show you how to 'de-construct' it to figure out how it's made.
The basic trick is to imagine you are drawing the page with a vector graphics program. If you have Adobe Illustrator, Harvard Graphics, or even MS Word, you should be able to make some basic vector graphics. For those who don't have any of these programs, try the open-source project InkScape.
In vector programs, you only have some fairly simple primative objects - boxes, circles, text & graphics - to work with, in a very similar way to MCML. And most of the adjustments you can do to these objects is also similar to MCML, the most important being scaling and rotation.
So if you draw your interface in one of these vector programs, you almost naturally discover how to replicate them in the Media Center Markup Language.
Let's take a look at a basic screen from EMUCenter.
Step 1 - Isolating The Elements
The first thing to do is isolating each seperate element we see on the screen. If it's positioned independently - eg. it's doesn't seem to be physically part of another element on the screen - then we basically draw a 'box' around it. We will want to make a UI for each of these.
By looking at our screen, we can come up with several seperate elements.
Background Text (saying 'emucenter')
Filter Bar (top left corner)
Position Bar (bottom right corner)
Selected Game Info (bottom center, underneath the selected game)
Games
If you were drawing these in a vector program, you'd build these items on different layers or, to make them easier to move around and get their positioning right, you'd group them. Everywhere you would put a group or a layer, you build a Panel control, or even more likely a seperate UI.
So each of the sections we isolated above would make perfect UI elements.
Then you attack each UI as a completely seperate, indpendent creature. Remember at all times that there are only really three object types they could possibly be - Text, Graphic and ColorFill. Anything that isn't writing or a solid square of colour is almost certainly a Graphic object - it's as simple as that.
The Background Text:
The background element is self-evident - we've already looked at how to make these earlier.
The Position Bar:
The position bar LOOKS as if it's a single item, but with two numbers that change and a slash that stays where it is, you can actually build it out of three seperate Text objects. You can but them together by putting them in a HorzontalLayout.
The Filter Bar:
The filter bar is similar - single Text objects that appear in a HorizontalLayout, this time with a bit of spacing between them. You'll of course have to put these elements in a Scroller though, to make it scroll, and you'll need to set the LockedPosition, since the selection doesn't actually move - the selection stays still and the options move around it instead.
Selected Game Info:
Again, three Text objects. This time they are going DOWN the screen, so we need a VerticalLayout.
Games
No big surprise, it's like the filter bar - Graphic objects (one normal, one inverted and faded by a Clip object), repeated in a Scroller, with a HorizontalLayout.
Of course, there's more work in making the selection work correctly, making your scrollers and repeaters happy etc. But when you get down to it, it's just a handful of UI's, each with a few text objects or graphic objects. And if you had drawn it, you'd know exactly what you need to do to make the reflections work.
So if you can't visualise it without help, follow this guide and draw your interface in Illustrator, InkScape or it's friends first, and the solution will be much clearer, and you'll have a nice prototype to work off.
|
-
There is a bit of confusion out there about which rule to use in which situation when writing rules in your MCML application. I'll just quickly cover the scenarios here...
Default vs Binding Rules
To be honest, you can actually use a Binding rule as a Default rule fairly seamlessly, but it's not really required and adds an (admittedly very small) amount of overhead you don't really need.
The basic difference is that a Default rule is evaluated only ONCE when your application loads. It is never again referenced or used. Binding rules are re-evaluated every time the value of the Source changes. Although pretty insignificant, this means a little bit of memory or a little more time used every time that the property changes.
OK, it's a minor point - but worth bringing up. The second point is a bit more serious.
Changed vs Condition Rules
Some people are likewise confused between the Changed and Condition rules in MCML. The difference in these is pretty simple, but important to know.
A Condition rule can be and often is constantly re-evaluated by Media Center as long as the condition is true. This is excellent for when you are changing the appearance of an object, because you can tell Media Center to make objects visible, brighter, change their colour or text etc. However, because the rules are continually re-evaluated and re-run, there are some things you should NOT do in the Condition rule. You should avoid triggering any event, such as an animation or Invoking a function, that should only happen ONCE.
For this sort of one-shot event, such as mouse clicks, a Command object being invoked or reactions to events from your C# code, you should use the Changed rule.
Changed rules are only run once, when FirePropertyChanged is called. This means that it doesn't matter how long the user chooses to hold the mouse button down or the event remains in it's new value, the rule is only run the one time.
You can further specialise it by adding more conditions.
Let's look at a quick example. In Yougle, I have a "Feedback" section that tells the user what is current going on. When I start a particular function, I set a string to "Starting Embedded Playback", to let the user know that Yougle is working on playing back their media. This triggers the following condition...
<Condition Source="[Feedback.Text]" SourceValue="" ConditionOp="NotEquals">
<Actions>
<Set Target="[TextObject.Visible]" Value="true"/>
</Actions>
</Condition>
The above rule means that for as long as the Feedback's "Text" property anything OTHER than an empty string, the text will be visible.
But what if I want to do something a little more important? What if I wanted to Invoke a function at the same time, but only when the status actually changed to "Starting Embedded Playback"? Remember - this text value could also be any of a hundred other values, so we need to deal with this condition in particular.
If I put the code in a <Condition>, I could find that my function is Invoked several times. To avoid this, it's much better to put it in a <Changed> rule...
<Changed Source="[Feedback.Text]">
<Conditions>
<Equality Source="[Feedback.Text]" Value="Starting Embedded Playback"/>
</Conditions>
<Actions>
<Invoke Target="[MyObject.MyFunction]"/>
</Actions>
</Condition>
So there you go - using ONE property, but both Changed and Condition rules, you can react to events both when the value changes and while the value is set.
And we've also seen how we can further narrow down rules so that we can deal with a change to a specific value.
|
-
OK, although this isn't about GENERAL Media Center development and is more of a self-serving plug, I'm going to go ahead anyway and show you how to write a plugin for Yougle.
I get a lot of requests for particular video, audio and (to a lesser extent) picture sources. What many people don't know is that Yougle is extendible - 3rd party developers can write their own sources to add more content to Yougle.
So I'm going to show you the basics of creating the Yougle source to allow you to browse the Media Center Show podcasts.
Development of a Yougle source must be done on a Windows Vista installation that has Media Center included - you can use any .NET language, but our example with use C#, the language Yougle Vista itself was written in.
You also need to have Yougle itself installed.
Finally, create a new Class Library in the .NET language of your choice.
Now you need to tell Visual Studio that your program is going to be using some of the classes defined by the Yougle Vista application. You can do this by looking in the ClassWizard, finding the 'References' section, right clicking and choosing 'Add Reference...'.
The trickiest part of the whole process is coming up. For some reason that I'm sure makes a lot of sense but I don't understand myself, the "References" list in Visual Studio is NOT loaded from the set of assemblies you have in your Global Assembly Cache - they are instead loaded from a completely different location. There are a couple of methods you can use to resolve this particular issue, but the method found here - http://www.devtopics.com/adding-assemblies-to-the-visual-studio-add-reference-dialog/ - isn't bad. You can use this to point to the folder where you have installed Yougle Vista - or, if you can not find the Yougle Vista DLL, you can download it from http://www.push-a-button.com.au/community/index.php?action=tpmod;dl=item47 and put it wherever you like.
OK - now this is done, and we need to start actually writing our code.
It just so happens that there's an example project and example code on my website at http://www.push-a-button.com.au/community/index.php?topic=92.0 - this gives you the complete sample code for the Flickr picture source. So copy all of this code and paste it into your application.
A Yougle Source is basically just a single class - based on the YougleSource class - that you customise to load data from your own provider. There are a couple of functions we need to override to do this properly...
public class MediaCenterShowVid : YougleSource {
public MediaCenterShowVid() { //Set up all of the details of this source - description, name, picture etc. }
public override bool Load() { //Load all of the basic category, filter & sort information for the sliders at the top of the screen }
public override bool GetMedia(int page) { //Get a list of media (pictures, videos, audio streams etc.) }
public override string Play(string URL) { //If required, take the URL discovered in the GetMedia call and convert it into something that we can actually download } }
And that's it - override those 4 functions, and you're away!
So how do we do this in the case of the Media Center Show? Well it's actually pretty easy when you get right down to it. Let's do these in order...
The Constructor
Nothing too shocking here, really - you use the Load function to set all of the major information about your source, so that Yougle knows what to show in the list of sources.
public MediaCenterShowPodcast() { //Set up all of the details of this source - description, name, picture etc.
_Name = "The Media Center Show"; //The name of the source _Description = "Watch Ian Dixon's Media Center Show"; //The description of the source _Type = YougleSourceType.audio; //The type of media available (picture,video or audio) _Rating = YougleSourceRating.children; //The maximum audience rating _ImageURL = "http://thedigitallifestyle.com/images/albumcoversmall.png"; //A URL for an image to use
}
The Load Function
This function is responsible for loading all of the information that appears in the 'pivot bar' at the top of the screen - your sort orders, filters, channels and categories.
There are TWO pivot bars in Yougle Vista. The top one is called the CATEGORY bar. Each category has one or more OPTIONS to choose from, which are shown on the lower pivot bar.
As an example, 'Sort' can be a category - with options being 'by popularity', 'by date', 'by views' etc.
Where there are multiple categories, they work and are reset in a left-to-right manner. For example, let's say the first category was 'sort', the second was 'period' and the third was 'category'. In this case, you can effectively 'add' these three options together (eg. sort="By Popularity", period="Today" and Category="Animals") to create more complex queries - in this case, 'Show Me All Of The Animal Videos That Were Popular Today".
However, when you change a category value, all of the categories to the RIGHT of that one are reset. For example, if you had the earlier example and changed "period" to "week", the Category would reset to "All".
Anyway, all of this isn't particularly relevant to us, since we will be getting all of our information from the Media Center Show RSS feeds - and there is only one real sorting option, which is "By Date".
public override bool Load() { //Load all of the basic category information for the top of the screen
//There is only really one sort order - By Date. Add it. YougleCategorySet Cat = new YougleCategorySet(); Cat.Name = "sort"; Cat.Options.Add(new YougleOption("by date", "")); Categories.Add(Cat); return true; }
Why bother adding anything at all? Well, Media Center is a troublesome beast some times, and you MUST have at least ONE option in every list - so you have to have at least one category, and each category must have at least one option - or your source will crash.
The GetMedia Function
This function is the core of Yougle Vista - it gets the media we want to offer to the user. In this case, it will be offering the users the 40 most recent Media Center shows.
Each media item is stored in a "YougleMovie" object (yes, I really should have gotten around to renaming this, but it was originally a video-only product), and each YougleMovie object has some basic attributes, such as name, thumbnail image, description and one or more URLs.
Let's start the function...
public override bool GetMedia(int page) { Movies.Clear(); //ALWAYS do this
Now, we are getting all of our information from an RSS feed from FeedBurner. ( http://feeds.feedburner.com/TheDigitalLifestyleVideoShow ) - so the very first thing we need to do is actually download the thing...
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("http://feeds.feedburner.com/TheMediaCenterShowPodcast"); HttpWebResponse Response = (HttpWebResponse)Request.GetResponse(); TextReader Rdr = new StreamReader(Response.GetResponseStream()); String Content = Rdr.ReadToEnd();
This code downloads the current feed from Feedburner, and loads it into a String called 'Content'.
RSS feeds are simply XML documents, so we should be able to happily load this feed into a XMLDocument object...
XmlDocument Doc = new XmlDocument(); Doc.LoadXml(Content);
XmlNodeList Items = Doc.GetElementsByTagName("item");
foreach (XmlNode Vid in Items) { YougleMovie Movie = new YougleMovie();
Movie.Name = Vid.Attributes["title"].Value; Movie.Description = Vid.Attributes["pubDate"].Value; Movie.Thumbnail = http://thedigitallifestyle.com/images/albumcoversmall.png";
OK - now we have a small issue. We aren't given a DIRECT link to the video in it's own tag. Instead, it's embedded inside the Description tag. On the plus side though, it's the only ".mp3" file in the whole text. So perhaps with a regular expression, we can track it down. ( I use Expresso from Ultrapico, which is an excellent program for creating and debugging .NET style regular expressions, which are slightly different to the Unix/PERL style ones you may be familliar with - get it from http://www.ultrapico.com/Expresso.htm ).
So the regex that finds the 'mp3' file in the description will be something like...
http://thedigitallifestyle.com/audio/([^"]*?)"
Which means we can now use that regex to find the MP3 file.
undefinedhttp://thedigitallifestyle.com/audio/
Match Mch = Rg.Match(Vid.Attributes["Description"].Value); if (Mch.Success == true) Movie.URLs.Add(new YougleMovieURL(Mch.Groups[1].Value, "video/wmv", true)); Movies.Add(Movie); }
Of course, it would be much more efficient to create the actual Regex OUTSIDE the 'while' loop, to stop it needing to be created again and again. I've added it there simply because we are doing this step-by-step.
And finally for the GetMedia function, we return a value. We can return either TRUE or FALSE, with 'True' meaning that there is more content waiting to be downloaded (note how the GetMedia function is passed a 'page' parameter? This is so you can download media in seperate 'pages', the same way you view them on a web page). Since we only have access to a single RSS feed and can't move through it, there won't be any more pages to come - so we return FALSE.
return false;
}
The Play Function
The play function is basically there to help speed up the process of locating your media. Quite often, the direct link to the actual video file (the downloadable file, or the actual streaming address) isn't quite as straight forward as it could be. For example, YouTube don't actually publish the location of the .FLV files for their videos - they need to be sourced using special techniques or 3rd party tools ( such as http://www.keepvid.com ) to find the file.
It would take WAY too long to track down all of these direct links as part of the LoadMedia function, so the Play function is called immediately before Yougle is ready to play or download your video.
If we were writing the Media Center Show video source, we could end up having a more difficult job to do here - because we may have to get a link to the web page on either YouTube or MSN Soapbox for the episode. To translate that into a actual URL to either the video file (avi, flv, wmv or asx) would take a little more work. (Luckily, Ian always offers a Direct Download option too, so it should be easy to write a video downloader!)
In this case, we have a situation where we have the actual MP3 file for Media Player to use, so this is really easy.
public override string Play(string URL) { //Rebuild the original URL...
String UR = "http://thedigitallifestyle.com/audio/" + URL;
//'UR' now contains the actual address to the actual MP3 file, which Media Center will play... return UR; }
We could have just stored the full URL in our YougleMovie object and simply not overridden this member at all, but I wanted to show you how it works, for possible future projects.
And we are pretty much done!
If you have a site you want to work with, but you're having trouble getting direct links to the video or audio files (pictures are usually easy), you'll find that there are a number of websites that have the handy ability to translate web page URL's into video URL's...
http://max.subfighter.com/flv/downloader.php
http://www.keepvid.com
|
-
Here's an interesting little tidbit, thanks to Charlie Owen from Microsoft and the Media Center Sandbox.
If you are using a Repeater, you have the option of allowing it to either Wrap or Repeat when you get to the end of it. This is great for long lists, since you can simply jump back to the start or swing over the end or back to the start when you need to.
However, it causes some problems if you are using RepeatedItemIndex. The Value of the index does NOT loop around. So if you are at the first item (index 0) in a 20 item list, the item before it does NOT have an index of '20', as you'd expect if it wrapped around - it actually has an index of -1.
If you are trying to update the ChosenIndex property of a Choice object with this value, you just crashed your application. Congrats :)
BUT - RepeatedItemIndex has more than just 'Value', it also has a second property called SourceValue. THIS is the one you want to use, because the SourceValue is the actual index into the array.
So if you discover this problem, we now have a solution! Thanks to Charlie, for his help with this thread - http://discuss.mediacentersandbox.com/forums/thread/6183.aspx
|
-
-
Although this really isn't really very difficult from what's been explained before, some people have trouble with getting their controls to interact - to change one control based on another.
So I thought I'd spend a little time covering the basics, so that people who are having trouble can get a walk-through of how to do it.
The most common problem is in making a list of items appear based on the state of another list of items. A simple example of this is the menu in a program (press 'ALT' if you are using IE7 and you'll see what I'm talking about).
In this case, we have two menus - the contents of the sub-menu depends on what is selected in the main-menu.
Well, in previous blog posts we have already seen how to load an on-screen list using a Repeater in MCML and a Choice in our C# object. So all we need to do now is extend it slightly to involve TWO repeaters, and TWO Choice objects, then link them together.
So let's say we have to Choice objects, once called MainMenu and the other called SubMenu.
To display these on the screen, we need to create the UI's for the Repeater/Scroller combination (with the ScrollingData and ScrollingHandler of course, so we can handle the scrolling correctly - see the earlier blog posts if you don't quite follow).
So we have our MainMenuUI and SubMenuUI, which are the UI's containing our scroller and repeater. Now we build two UI's for the objects within the scrollers - let's call them MainMenuItem and SubMenuItem.
We give these Item UI's a property called Label
<Properties>
<cor:string name="Label" cor:string="$Required"/>
</Properties>
So, we have the following layout...
Base Item
MainMenuUI
MainMenuItems...
SubMenuUI
SubMenuItems
And finally, we load both items from our C# code - filling the ArrayListDataSets.
But - what about making them work WITH eachother? Well, that is done - like ALL communication between UI elements in MCML - via shared properties.
Now there are a variety of ways to do the next part of our task. I'm going to show you a simple method that will work both with and without a Choice object being used to load the data - this way, if you ever need to replicate this situation with a FIXED list of items, you'll still know how it's done. But just to cover the options, you could...
* Make your own subclass of Choice and use it instead of normal Choice objects for the content of your main menu. By overriding the OnChosenChanged member of the Choice class, your code will be automatically called whenever the ChosenIndex of the Choice object is changed.
* Make a Command object that is Invoked when a menu item is selected. By setting the description of the Command object to the Label of the MainMenuItem, you can then communicate not only that the selection has changed, but what the new selected item actually IS. Note that to make this work, the Command object must be shared between all of the UI components - it would be a property of the Base Item, and passed as properties through to all other UI elements.
* Invoke a function on your main class (which is what I'll be showing you).
So, we will be doing the last - and probably the simplest - of the techniques. The other methods also have their uses - the first one is completely seamless when done correctly and requires virtually no code on the MCML side, while the 2nd one is perfect if your C# code is not actually involved in the loading of more information (eg. if you are switching between fixed lists by changing the Visible property on sub-menus) because it's entirely contained in MCML, with no code on the C# side.
This technique requires both C# and MCML code, but is a little more intuitive.
Let's say our main class (the ModelItem class we do most of our C#->MCML interaction with) is called MainClass. What we can do is add a new member function, called NewMenuSelected.
public void NewMenuSelected(String MenuName);
This function takes a single parameter - the name of the menu that is currently selected. It just so happens that our UI already has a property called Label, which is the name of the menu. So all that is left is to get our MainClass object to the SubMenuUI. This means that both the SubMenuUI and SubItem elements need a property...
<Properties>
<a:MainClass name="MyClass" a:MainClass="$Required"/>
</Properties>
So now, when our MainMenuItem objects get the focus, we want it to call our function....
<Rules>
<Condition Source="[Input.KeyFocus]" SourceValue="true">
<Actions>
<Invoke Target="[MyClass.NewMenuSelected]" ManuName="[Label]"/>
</Actions>
</Condition>
</Rules>
This means that when our MainMenuItem gets the focus (meaning that it is the active menu we want to see) then we call our C# or VB object and load the new contents of our SubMenu.
public void NewMenuSelected(String MenuName)
{
_SubMenu.Clear();
switch (MenuName)
{
case "File":
_SubMenu.Options.Add("Open");
_SubMenu.Options.Add("Save");
_SubMenu.Options.Add("Save As");
break;
case "Edit":
_SubMenu.Options.Add("Copy");
_SubMenu.Options.Add("Cut");
_SubMenu.Options.Add("Paste");
break;
}
FirePropertyChanged("SubMenu");
}
So hopefully you've seen how these objects can be combined to control the contents of one repeater with another.
|
-
There are times when the response from a click is going to take a little while - fetching information from a web page, downloading a file, queuing a video...some of these things take time, and the user doesn't like to have their Media Center 'lock up' while they are waiting for it.
When this happens, it's time to bring out the good old Thread class once again, and start playing.
There are a couple of things you should be aware of though before you write your threaded application.
If you have some experience with Windows Forms applications (and I hope you do - it will make your Media Center development life much easier), you will know that it is impossible to change the text or the appearance of a control from any thread other than the thread the window was opened in (eg. the Application Thread).
The same rule applies to Media Center applications and the FirePropertyChanged function of ModelItem. If you are going to call FirePropertyChanged, it must be from the application thread, otherwise it will fail.
Note that if you call FirePropertyChanged from the wrong thread, you probably won't get an exception like you do with Windows Forms - the notification will be 'lost' and you may start getting erratic behaviour out of your application.
So how do you manage to change a property value then? Well, you create a member function that takes a single parameter of type 'object' (even if you don't USE the parameter, create it) and then call...
Application.DeferredInvoke(<function name>,<parameter>);
The 'Application' object is a global one, and the DeferredInvoke function tells Media Center and the .NET system to run the <function name> function in the APPLICATION's thread, next time that thread is available for a little extra work.
I personally like to make my work even easier, by using the following code in any property that I expect to be accessed from a thread other than the main application thread...
In The Object That Has Properties....
internal void FPC(object Val)
{
String Value = (String)Val;
if (Application.IsApplicationThread() == true)
FirePropertyChanged(Value);
else
{
Application.DeferredInvoke(FPC,Value);
}
}
The Actual Property Itself...
string _MyStringValue;
public string MyStringValue
{
get { return _MyStringValue; }
set
{
_MyStringValue = value;
FPC("MyStringValue");
}
};
Because the FPC (which is short for FirePropertyChanged) actually conforms to the format needed to pass to the DeferredInvoke function, it's actually recursive. If you try to set the MyStringValue property, FPC is called.
If FPC detects that it has been called from the application thread (by using the IsApplicationThread() command), it simply calls FirePropertyChanged. If it finds that it isn't in the application's thread, it calls itself again using DeferredInvoke. The 2nd time it runs, it WILL be in the right thread.
This greatly simplifies your programming, because you simply have to make sure that you ALWAYS use the publically exposed property name when you are accessing MyStringValue.
Unfortunately, while this protects you when playing with your own properties that are based on simple type (eg. Int32, String, Bool etc.) it won't help with any type that is already based on ModelItem.
These types - like IntRangedValue, Choice, EditableText, ArrayListDataSet etc. already use FirePropertyChanged internally. So you must ensure that you only actually interact with these classes in the correct Application thread.
|
-
A new website has come to my attention for Windows Media Center addins (and useful tools for a Media Center) - http://htpcresource.com/
This site may have existed for a while without me noticing, so this may not be big news for you. But it's got a growing collection of addins that may be of interest to you - you may want to check it out!
|
-
-
Oh, I've finally released the new version of EMUCenter, which has a few new bells and whistles. If people are interested, you can download it from my new community site at http://community.push-a-button.com.au (please register before you can download the programs).
EMUCenter is how I discovered the trick I covered in my last post.
There's also an testing version of the new version of Yougle available. It now supports pictures and has fixed a number of sources that had 'broken' due to site changes (or in Soapboxes case, the complete elimination of their site!).
EMUCenter Screenshots - taken by tom_son from TheGreenButton and the Push-A-Button forums.



|
-
This is a reasonably advanced topic, but since I've only just discovered the solution for it, I decided I may as well post it here so people who need the solution can use it.
Large scrollers can sometimes be a problem when you need to do large 'jumps' within them. The best example of this is the 'type-to-search' functionality that you find in the Music menu of Media Center. When you start to type the name of the album, artist etc. with either your keyboard or remote control, you will find that the selection jumps straight to the nearest matching item.
To replicate this in your own application can be troublesome.
Normally, Media Center only has two functions to use to control user navigation. The first is Input.NavigateInto. As we have seen before, every object has an 'Input' class associated with it. 'NavigateInto' requests that the current selection be changed to the object that you are Invoking the command on.
For example, if you had a button, putting the following command inside the UI for the button would result on it getting focus.
<Invoke Target="[Input.NavigateInto]"/>
For collections though, this isn't all that useful. If you wanted to select a particular item in a scroller, you'd need to put in some complex rules inside the item that check not only that you WANT the focus to change, but also compare the index of the item against the index that is being searched for. It's possible - but it's slightly ugly.
There's an easier function though. The Repeater item has a special function called NavigateIntoIndex (see http://msdn2.microsoft.com/en-us/library/bb189530.aspx ). This is a very similar function to NavigateInto, but this time allows you to pass an index number. The repeater will then set the focus to the appropriate index element.
NOTE: Be careful when setting up complex events with these particular functions. Unlike a Windows session, when navigation events like clicks and focus changes are all queued up, Media Center does not queue navigation events. So if you try to fire off two or more NavigateInto or NavigateIntoIndex calls at once, one or more of them may be 'lost'.
To work around this problem, use Timers to offset the calls to NavigateInto. For example, make the first one happen immediately and then make the others happen at 1/10th of a second intervals - or whatever works well for you.
There's one major drawback with NavigateIntoIndex and NavigateInto though. Scrollers and repeaters work together to optimise the objects on screen. Part of this optimisation is only creating the actual repeated item when it becomes nessicary to create it. So for example, if you have 100 items in your list but never scroll past the 10th item, the 100th item will never actually be created as an MCML object.
So let's say you were in your own version of the Music section, you were exploring 'Ace of Base' and decided that you wanted to look for 'ZZ Top'. If you had 100 different artists, but only 12 were on the screen, calling NavigateIntoIndex with a parameter of '100' simply wouldn't work, because as yet, the 100th index does not actually exist.
You can tweak the repeaters 'Prefetch' attribute - which chooses how many item the repeater 'pre-loads', but then you are enforcing a strict maximum possible length of your search, which not only means you are just delaying the problem, but pre-loading hundreds or even thousands of entries isn't just time consuming, it can crash your Media Center application.
So what's the trick? Well, it's not too difficult, it's just got some kinda sketchy documentation.
Each scroller has a ScrollingData attached. If you check out the documentation - http://msdn2.microsoft.com/en-us/library/bb189586.aspx - you'll find that it has a 'scroll' method. The documentation says that it takes a parameter that "is the amount to scroll". This is a little misleading. Does it mean in pixels? Pages? Centimeters?
Well, if you have focusable items in your scoller, the measurement is actually in focusable items. So by calling the Scroll method on the ScrollingData object with a parameter of 5, you will scroll forward five elements.
Because you are now moving the SCROLLER rather than trying to move the focus on the repeated item itself, the repeater now has the opportunity to create the MCML objects, which allow it to focus on them properly.
The only catch is that unlike NavigateIntoIndex, Scoll is a relative movement. So if you are on the 1st item, scrolling with a parameter of 4 will take you to the 5th item. If you are on the 6th position, a value of 4 will take you to the 10th item.
So if your repeater is based on a Choice object, remember to factor in the Choice.ChosenIndex as part of your calculation for the amount you need to scroll by.
|
-
Yes, it's been ages...and yes, it's still not another addition to the TriviaCenter application. I do promise I'll get back to it some day, but it's a case of "Too much to do, too little time to do it in!"
Anyway, I just thought I'd throw up a couple of posts for things that have been troublesome for me over the last few stages of working on the new release of EMUCenter.
The one visual element I've never really explored in the blog is the <Graphic> element. We are going to cover it in TriviaCenter, but I thought I'd cover some of the basics here, just in case someone needed them.
Like the <Text> element, the <Graphic> element has a 'Content' attribute. This attribute is an Image object, which is a class that is part of the Media Center UI class library.
However, Media Center is pretty good about converting the Image class from various other classes. If you point it towards an image resource (such as a PNG file on the hard drive or in your resource file), then it will load it for you from that resource.
For example...
< Graphic Name="Flare" Content="resx://MyAssembly/MyProject.Resources/Flare"/>
This creates an image, loaded from the 'Flare' PNG file located in the resources of our Media Center project.
To load an image from a dynamic source, you can simply use a string value and convert it to an image.
For example, if you had a C# class with a property called "MyImage", you could do the following...
<Graphic Name="Flare" Content="[MyClassInstance.MyImage!Image]"/>
The exclaimation mark is how you tell Media Center to treat the String returned by MyClassInstance.MyImage to an Image object.
You may end up wanting to rotate your image. One thing to remember here is that your rotation will occur AFTER you have physically placed the object with your layoutmanager - so for example, if you are using FormLayout, you have to position the graphic itself - and most importantly it's CenterPointPercent - in the correct location so that your rotation makes sure it ends up where it's supposed to be.
If you have trouble, rememeber to remove the rotation, reposition and try again. It can be very confusing to play with your Layout when the object is already rotated.
|
-
I've started a new Open Source project, in the hope that other people are interested in contributing to it.
Installing Media Center addins is a bit of an annoyance. You've got this really pretty Media Center interface, but for people who are trying to use it simply as a home-theatre experience, installing an addin means getting out the keyboard and mouse. Even worse, once you've got the thing installed, if there's an update or a patch for your addins, you've got to leave the Media Center experience to find, download and install them - and User Account Control will be jumping up, which means you can't automate the process from the addin itself.
This is where Addinistrator comes in. It's goal is to perform a number of functions that weren't addressed by the SDK because of it's focus. The SDK is great when it comes to creating your app, but Media Center isn't all that friendly when it comes to deploying and maintaining the app (although Charlie Owen and Aaron Stebner from Microsoft are making excellent improvements to the Media Center SDK applications to make installations much simpler).
Addinistrator will also allow you run-time access to your own Start menu strip (if you have one), meaning that you can re-order and even create new entries on-the-fly to suit your users needs.
Future features may include Start Strip management, a More Programs strip and, most importantly - the ability to browse for and install Media Center applications without the need to leave the Media Center experience.
If you want to contribute, please feel free to visit http://www.codeplex.com/addinistrator
|
-
If you're wondering what on earth I've been doing lately, I've been fiddling with some of my own projects lately.
http://www.push-a-button.com.au/downloads/BlindTimerSetup.msi - a 32 bit version of a simple poker game timer. It tells you the current blinds the current ante and how long until they next go up, with text-to-speech announcements of blind increases and warnings when they are getting near.
http://www.push-a-button.com.au/products/mce/vista/youglevista/ is the latest verison of Yougle (Yougle Vista) that has recently replaced the older Yougle 2 I linked people to on this website.
Yougle Vista currently is still in alpha - it has a number of flaws - but I thought people may want to play with it.
KNOWN BUGS:
1) Buffering for Flash Video files (such as YouTube, DailyMotion and Google Video feeds) is extremely slow - it can take quite some time for playback to start. If in doubt, press '#' to download the video rather than trying to stream it directly.
2) The X64 version of Yougle Vista can NOT play Flash Video files, so you will be unable to play videos from YouTube, DailyMotion and Google Video. This is because there is currently no FLV video splitter for 64 bit Windows, and although you are given a 32 bit copy of Windows Media Player (meaning it can use 32 bit video codecs) you only get a 64 bit version of Windows Media Center.
This may be resolved in the not-to-distant future, but right now it's going to cause problems.
3) Pressing the 'Back' to go from your video source (such as YouTube) to the Source Selection screen can cause Yougle to go slightly haywire and drop back to the Media Center start menu. I am aware of what causes this problem, but fixing it is causing me all kinds of pain.
[ for any interested developer, it is because of the fact that Choice objects raise an exception if you try to choose something outside the valid range. When I remove all of the sort entries from the list, but the user had selected anything other than the first one, the exception occurs without me having any ability to catch it, which throws the app back to the Start Menu. The resolution requires me to call two Invokes, one to force the focus of the sub-sort menu to go back to the first item, then a second one to bring focus BACK to the parent sort menu ]
Common Questions:
Q) Will it work on my 360?
A) To a limited degree. The MSN Soapbox source may very well work for you, but most others will not.
Q) Can you make it work BETTER on the 360?
A) I don't know. Perhaps Transcode360 will work...but since I don't yet have enough donations to purchase a 360 or any other Extender device, I simply don't know because I can't test this sort of thing.
Q) Can you add <insert provider her> to Yougle Vista?
A) Probably not (unless it's a REALLY good idea)...but YOU can.
When Yougle Vista goes from Alpha to Beta, I will release the SDK that allows you to quickly and easily write your own Yougle Vista sources in Visual C# Express or Visual Studio. The SDK will include complete samples of working sources, such as DivX Stage 6 and YouTube - you will be able to base your solutions on these samples.
 Yougle Vista Production Video Video: Yougle Vista Production Video
|
-
Another sandbox-related discussion (a new user, Jeff, is having some trouble wrapping his head around the MCML concept, so I thought I'd post the assistance I'm giving him).
This is about scope in MCML. I know I've covered this in the past, but this will just expand on my previous points.
Basically, this is all about the question of "I've got this nice little button UI - how do I change the text on it from another UI, or from C#?"
The first thing you need to understand is that each UI is a closed control - you can NOT access the various parts of one UI from another one. So although your button may contain a graphic, a text control and a bunch of other parts, only the button is actually going to be able to make changes to those components - nothing else can.
Every object can only contain rules that effect ITSELF or it's IMMEDIATE CHILDREN. It can't even access it's own grand-childen.
And if you don't quite follow the 'children' concept, it quite simple - if a UI contains another UI, that can be considered it's child. So - for example - your main UI contains your various panels that you are showing or hiding. Those panels contain buttons - those buttons would be the child of the panel, and the grandchild of the base UI.
Base
Menu
Button1, Button2, Button3 etc.
Panel 1
Button1, Button2, Button3
Panel 2
Button1, Button2, Button3
Each of these is a seperate UI, where Base is the first UI in the file, Menu is a main menu and Panel 1 and Panel 2 are panels containing buttons.
So, to cover this...
If you wanted to change the text inside your buttons, you could only do this with a <Set> tag inside the button itself. No other UI can access the internal parts (eg. text objects, graphics etc.) of your buttons.
If you wanted to set a property that changes a property in the FIRST element in a UI (which is almost always a panel or clip object) then you can do it from the PARENT. For example, Panel2 can change the Visible, Alpha, Scale, Position and other properties of each one of it's buttons.
If you want to do anything else, you will need to use a property value, such as a Choice, Command, cor:String, RangedValue or any other shared property, and you'll need to have rules inside the button UI to deal with the property changing.
|
-
Oh, another thing that came up in the forums that may work for you if you are playing with Media Center development.
If you back track a bit to our main menu, or to the list of answers in TriviaCenter, you will find that we load our list by filling an ArrayListDataSet with Strings.
But don't feel limited by that - it's commonly used in all of the examples, but it's worth remembering that you can put any class in the ArrayListDataSet - not just String.
Let's say that we wanted to add a picture to the front of every answer - either something to illustrate our answer, or maybe just an 'A', 'B', 'C', or 'D'.
Now we need a different picture for each element. We COULD try and do something that is just way too complicated, like looking up our images in a different data set - but this would be very time consuming and ugly. There's a much easier way.
Instead of loading our ArrayListDataSet with strings like we've been doing, we create our own new class (based on ModelItem, because it's going to have it's own properties and we want Media Center to be able to see any changes we make) and fill the dataset with THESE items instead.
public class MyCategory : ModelItem
{
public _Name;
public _Image;
public Name { get { return _Name; } }
public Image { get { return _Image; } }
}
Of course, we can also add constructors to make our life a little easier.
So when we add them to the ArrayListDataSet....
MySet.Add(new MyCategory("General","file:c:\program files\random stuff\general.png"));
MySet.Add(new MyCategory("Entertainment","file:c:\program files\random stuff\entertainment.png"));
And there's only one other thing we need to change in your MCML code. We can choose to either pass the WHOLE item to our button's UI, or we can set the Image and the Label individually using properties.
The properties would change from...
Label="[RepeatedItem!cor:String]" - which took our string and used it as our label, we now use...
Label="[RepeatedItem!me:MyClass.Name]" - to get the name of the category and
Label="[RepeatedItem!me:MyClass.Image]" - to get the filename of the picture.
|
-
I've been helping someone out on the Media Center Sandbox, and I thought one of his questions were fairly relevant to someone who is starting to play with MCML development and their own classes.
He was basically asking 'should the properties I use in my .NET program be the Media Center classes (like Choice, ArrayListDataSet etc.) or standard .NET components, such as List?
Some of these topics are covered in earlier posts, but this is just a summary for those who may be a little confused.
If you have a property that is going to return something complex - for example, another object that itself has properties - you should always use a Media Center type (if there is a suitable one) or build your own class based on ModelItem.
So if you are just trying to send a string or an integer, use the String and Integer types - these will be fine.
If you are choosing an element from a list, always use Choice one of the Ranged items. If you have a list of options, always use ArrayListDataSet, or a class of your own that is based on ModelItem.
But this rule only applies to the properties you expose to Media Center. You don't use them for internal values, since most of the ModelItem based classes are not thread safe and will cause exceptions should you use them outside the parent thread.
The reson for this is kinda complicated (see my previous posts for some information on how ModelItem works), but basically, 'ModelItem' allows you to notify Media Center that a property has changed, meaning all of your UI rules will be checked. Without basing your class on ModelItem and calling FirePropertyChanged where appropriate, changing a property in any complex object will mean that Media Center will simply miss it - your UI will never update properly.
So the basic rule is:
Simple Properties and Internal Variables: Standard .NET Classes
Complex Properties: Media Center Classes
|
-
Sorry, no new stage of TriviaCenter development this week. I'm starting to think that my new place was built ontop of an Aboriginal burial ground...or at the very least a large magnetic plate. My development machine has been fairly badly crippled by a hardware failure - my on-board USB has died on me and the computer has now slowed down to an absolute crawl whenever any I/O is happening.
So there's an upgrade/replacement motherboard in my not-to-distant future, but after a couple of expensive weeks, I'm going to have to wait until I get the cash together. Next week is the housewarming party, so I'm not entirely sure I'll get an update out then either, particularly with new Yougle changes happening, since it's died recently too due to YouTube site changes.
[EDIT: The problem is mostly fixed now. While the USB on my motherboard is still burnt out, I discovered the reason for the radical slowdown. One of my RAM modules was disconnected when I installed the USB card...which meant that my Vista was running with 512MB of memory. And I agree with the minimum specs on this one - Vista is completely unusable for development work with anything under 1GB]
Oh, and I've got some advice to software developers who are thinking of going the 'donation supported' route. If it's going to cost you money to distribute your software, DON'T DO IT. I've had over 10,000 downloads of the original Yougle with three donations, and 500 downloads of EMUCenter with none. The additional server costs alone have been over $200...so it looks like future products of mine may be coming with additional features that you can only unlock with a registration key.
|
-
Ian Dixon has kindly donated the developer forum to let us all discuss the topics I'm covering on this blog. So if you have any questions, want to discuss possible features for TriviaCenter or even post code and suggestions, please feel free to jump in.
And if you want a little bit of a chat about Media Center development in general, feel free to vent your frustrations or express your joy here. And of course any expressions of your new-found love for me will also be accepted ;)
Please read the 'Welcome Aboard' post though - this is not intended to be a replacement for the Media Center Sandbox forums. If you are going to ask a development question, please try to ask those that are based on the MCMLookalike libraries, TriviaCenter or my other posts here - if it's about something else, you'll get a much faster reply from the sandbox.
Feel like joining in? follow this link to the forums!
[Update: Link fixed] 
|
-
OK, we can now answer trivia questions and keep a score...but you've got to keep watching that darn bottom corner to see if you got it right or not!
So how about we add a little more feedback to the questions by having it obvious if you were right or wrong.
To do that, we are going to need another UI component. Let's start by making one for the 'Correct' response.
<UI Name="AnswerResponse">
<UI/>
In this case, we are going to make a large piece of text (saying 'Correct!') appear on the screen where our list of answers are. It will fade in and move 'towards' us, then fade back out.
So we build our content...
< Panel Name="MainPanel" Alpha="1" Visible="false" Layout="Center">
< Children>
< Text Color="Green" Content="Correct!" Font="Arial,50,Bold" />
</ Children>
</ Panel>
Basics done. Now, since we want to make this appear to 'zoom in', we will need an animation that scales the object from the middle. We can do this by setting it's CenterPointPercent property.
< Panel Name="MainPanel" Alpha="1" Visible="false" CenterPointPercent="0.5,0.5,0.5" Layout="Center">
Since we also only want this text to appear occasionally (rather than all the time) I've set the Visible property to false.
I've probably already explained this, but just to make sure you know...if an object is not Visible, then it does not interact with the keyboard and mouse. If an object has it's alpha set to 0, you won't be able to SEE it, but it can still block mouse events. So we want to make this object completely dissapear. In this case, we use Visible rather than Alpha.
Now, when a item changes from Visible="false" to Visible="true", the 'Show' animation is run. So let's create an animation for our panel that will fade the object in, enlarge it, then fade it out again.
< Panel Name="MainPanel" Alpha="1" Visible="false" CenterPointPercent="0.5,0.5,0.5" Layout="Center">
< Animations>
< Animation Type="Show">
< Keyframes>
< AlphaKeyframe Time="0" RelativeTo="Absolute" Value="0"/>
< ScaleKeyframe Time="0" RelativeTo="Absolute" Value="1,1,1"/>
< AlphaKeyframe Time="0.3" RelativeTo="Absolute" Value="1"/>
< AlphaKeyframe Time="2" RelativeTo="Absolute" Value="1"/>
< AlphaKeyframe Time="2.3" RelativeTo="Absolute" Value="0"/>
< ScaleKeyframe Time="2.3" RelativeTo="Absolute" Value="1.2,1.2,1.2"/>
</ Keyframes>
</ Animation>
</ Animations>
...
The entire animation will run (as you can see from the above 'Time' settings) 2.3 seconds.
Cute. But how do we trigger the thing to appear?
Well, there are a number of ways to do this. You could create a string that had three states - for example, "Right", "Wrong" and "Waiting", you could use a Choice object, you could do all kinds of things that are messy and complicated. But MCML has a special object designed to handle these kinds of 'event driven' situations.
The Command object can be used to make MCML objects do what you need them to do in response to an event. In this case, we are going to enter our C# code and create two Command objects - one for a correct answer and one for an incorrect answer.
Command _WasRight;
Command _WasWrong;
public Command WasRight
{
get { return _WasRight; }
}
public Command WasWrong
{
get { return _WasWrong; }
}
With 'Command' objects, we don't need to bother about 'FirePropertyChanged' or any of that business. To make things happen from our C# code, we need to Invoke them. This is very easy...they have a member function called Invoke.
So when we validate our answer in C#, we do the following...
if (Right)
{
_WasRight.Invoke();
}
else
{
_WasWrong.Invoke();
};
There you go - if the answer was correct, we Invoke the 'WasRight' and if was wrong, we Invoke the 'WasWrong' commands.
Now we need to react to them. Command objects work in a very similar way to the ClickHandler object. You use a Changed rule to see if anything has happened. Let's see how we do this to handle when the user has answered the question right...
< Changed Source="[game.WasRight.Invoked]">
< Actions>
& | |
|