Allright - are you like me, and you've had enough of playing with this scroller? It's time to hang that up and start working on something REAL.
Our first step is to go to a new MCML page. This first page is handy, but it's now done it's job - the user can now choose a category. But to ask a question, we should have a new MCML page, to keep both parts nice and seperate (also, if we have them as two seperate pages, the 'back' button will then go from asking a question, to asking for a category).
To move to another page in an MCML application, we use the 'Navigate' action. 'Navigate' allows you to make Media Center load another MCML file. You specify the name of the file (normally a resource inside your C# assembly) to navigate to.
Navigate will normally go to the very first UI it finds within the target MCML file. However, if for some reason you want it to navigate to a particular UI, you can use '#<uiname>' at the end of the resource name. We won't be bothering with this.
For example, we will be creating a new MCML markup file called 'Questions.mcml'. To navigate to this page, we find the actions in our Click handler we created for our TextButton, and change it to...
<
Changed Source="[Clicker.Invoked]">
<
Actions>
<
PlayAnimation Target="[ButtonPanel]" Animation="animation://me:Clicked"/>
<
Invoke Target="[game.StartGame]"/>
<
Navigate Source="@resx://TriviaCenter/TriviaCenter.Resources/Questions" game="[game]"/>
</
Actions>
</
Changed>
The Navigate command above does two things. First, it tells Media Center to open the resource named 'Questions' that is part of our DLL file (we will add this later) and show us the very first UI it finds. The second part is that it sets the 'game' property of this new UI to our TriviaCenter object.
Using Navigate is just like creating a UI directly (eg. when we create a TextButton). You can pass it any parameters you like. In a little while, you'll see where and how we use the game property in our second page.
You may also notice that I've changed the name of the 'SomeoneClickedMe' function to 'StartGame'. This makes a lot of sense, since this function tells Media Center that you are now ready to answer questions from a category.
Speaking of changes to our C#, we had better discuss them.
As I said earlier on, I expect you to have a good general idea of C#. If you don't, I suggest you stop now, learn how to write some of your more simple Windows applications in C#, then come back to this.
When writing your C# code for Media Center development, it's often easier to write it in a Windows Forms application first. This allows you to use simple, well known and predicatable user interface elements, rather than causing yourself confusion where you don't know if it's your MCML or your C# at fault for problems/
I'm not going to walk you through every stage of the C# code - I'll just explain the very basics behind what I am trying to do here.
The first step is to load our data from a sensible location. That location will be found either in the local directory to the application, or the 'Application Data' directory. We will be using some quiz files sourced from an open source project - you can download them here - http://w3.misterhouse.net:81/mh/data/trivia/ (and they are included in the download).
Each file will represent a single category - Entertainment, Sports etc.
Now we need to look at what data we need to exchange between our two parts of the application - this is usually best done through a diagram or other visual aid. In this case, we are going to need to following key pieces of data to appear in our interface...
The Question
Four Multiple Choice Answers
The Current Score
The Total Number of Questions So Far.
So in our C# object, we need to create properties to deliver all of these things to the MCML page, so they can be reported.
private
String _Question;
Choice _Answers;
byte correctanswer;
public String Question
{
get { return _Question; }
}
public Choice Answers
{
get { return _Answers; }
}
...
...and I'm sure you get the rest. It's all pretty straight-forward by this stage in your progress through MCML development.
We also need a couple of different actions that the user can perform. These are...
Starting a New Game (StartGame)
Answering a Question (Answer)
Since for both of these actions, our user will be pressing a button (ie. the name of the category or the answer to the question), we are going to have to get that button press from the MCML Page to the C# Class. So we will need to create public functions for them...
public
void StartGame()
{
...
};
public
void Answer()
{
...
};
So we have already Navigated to a page - but so far, it doesn't exist! Let's create it now. This page is going to contain our trivia question and the answers. This will be where the bulk of our game is played, and you will find that we can re-use almost every part of our original page.
Let's create our new page using our old one as a template. Start out by right-clicking the 'Start Page.mcml' file in the Solution Explorer and choosing 'Copy', as pictured.
Then right click the 'Markup' folder and choose 'Paste' to make a new copy of our MCML page. Rename it - ideally, to 'Questions.mcml'.
Now, we dive into the MCML code. There is one vital thing we need to do first. Very early on in our first UI element, we create a new 'Local' - our TriviaGame object.
In this case, we don't need this to be a local variable - we have already created a TriviaGame object in our FIRST page, where you select the category. So in order to make the selection of the category move from one MCML page to the next, we will need to sent the TriviaGame object as a property, just like when we pass an object between two MCML elements that are in the one file.
So simply change the 'Local' tags to 'Properties' tags, and we are ready to continue.
<
Properties>
<
a:TriviaGame Name="game"/>
</
Properties>
You'll remember at the start that we set the 'game' property in our Navigate call. This is where we define that game property. Using this technique of passing our object as a property from page to page, we ensure that there is only ever a single TriviaCenter object - it is simply shared amongst all of the MCML pages.
Our BackgroundText object is still important - but lets do something a little more impressive and change it to the name of the category we chose. Because we passed our TriviaGame object from our original page, the category selected in the choice object will still be selected, so we can use this to get the name of the selected category.
<con:BackgroundText Label="[game.Categories.Chosen!cor:String]">
<
LayoutInput>
<
FormLayoutInput Right="Parent,.95" Top="Parent,.05"/>
</
LayoutInput>
</
con:BackgroundText>
Our trivia database only ever has questions with four multiple choice answers. We don't really need to worry about needing a Scroller object anymore - with four, we aren't going to fall off the page anymore. So we can get rid of all of that scroller-related code we have, and change it to a simple Panel.
<Panel Layout="VerticalFlow">
<
LayoutInput>
<
FormLayoutInput Left="Parent,0" Top="Parent,.45" Horizontal="Center"/>
</
LayoutInput>
<
Children>
<
Repeater Source="[game.Answers.Options]" Layout="VerticalFlow">
<
Layout>
<
FlowLayout Orientation="Vertical" ItemAlignment="Center"/>
</
Layout>
<
Content>
<
me:TextButton Label="[RepeatedItem!cor:String]" Indx="[RepeatedItemIndex]" game="[game]"/>
</
Content>
</
Repeater>
</
Children>
</
Panel>
And if you notice the bold text above, you'll also see where I've made another change. Rather than wanting to see the list of categories, I now want this repeater to have a list of the answers.
And since our TextButton class already takes care of focus and choices, our work is almost done! All we need to do is change the actions that the button performs when we click...since we aren't choosing a category this time, starting a new game seems a little silly.
<
Changed Source="[Clicker.Invoked]">
<
Actions>
<
PlayAnimation Target="[ButtonPanel]" Animation="animation://me:Clicked"/>
<
Invoke Target="[game.Answer]"/>
</
Actions>
</
Changed>
The Answer function (which we discussed earlier) is one we defined in our C# object to tell us that the user has chosen an answer. It will check to see if it correct, update the score and then load the next question.
Remember, since our buttons change the ChosenIndex properties of the Choice objects they are based on, your C# class already knows which of the categories or answers you have selected. That's why we don't need to pass any parameters to our Answer or StartGame functions...because we already have all of the information we need.
Finally, there's our friend the position indicator. Well, since we no longer have a scroller we certainly don't need this anymore...but there's one thing it COULD be useful for. How about for showing our current score?
So let's dive into the PositionIndicator. First, I'm going to rename it to a ScoreIndicator, just so I know what I'm doing (don't forget to change the code where you actually create the indicator in the first UI!)
There's only two things we need to do. Since we aren't using a choice object anymore to get the information for our indicator, we need to change the property that we pass it from the main UI. We want to instead show the total number of questions asked, and the total number of questions that were right. Both of these are stored in our TriviaGame object...so that is the property we need to pass to our new ScoreIndicator.
<
Properties>
<
a:TriviaGame Name="game"/>
</
Properties>
And we also need to change our Bindings. The 'game' object doesn't have Count or ChosenIndex properties, nor will we need the MathTransformer to add one to a property - this is a very simple object now, with two bindings...
<
Binding Target="[Position.Content]" Source="[game.CorrectQuestions!cor:String]"/>
<
Binding Target="[Value.Content]" Source="[game.TotalQuestions!cor:String]"/>
The last thing we need to do is make our 'Questions.mcml' page part of our assembly (or DLL file). To do this, double-click on the Resources.resx file.
At the top of the window that appears, there will be drop down menu that will probably default to 'Images'. If you left click it, you'll get a list of choices like the ones here. Choose 'Files'. This allows you to embed files into your DLL by making them part of your resource.
Simply left-click the 'Questions.mcml' file from the Solution Explorer and drag it into the list of Files.
There!. It's now part of your resource file, which will make sure that your C# code and your MCML interfaces can never be accidentally seperated.
And apart from putting in C# code to load the questions and fill the properties, we are done. That's IT. So after all that pain with the scroller, it's only a few fairly simple changes, a few extra properties and a little bit of behind-the-scenes C# code to go from a simple list of categories to a working trivia game.
Of course, there's HEAPS of work to do making this pretty...but now you can have a game of your own running in your Media Center...or can you? You've got it working fine in MCMLPad...but how do we get it into Media Center?
Tune in next time for the answer!
Code is here.
If you can't get it to run or your list of categories keeps coming up empty, create a folder called %APPDATA%/Steven Harding/TriviaCenter
You will need to copy the files ending in .DAT (the question and answer files) from the /bin/debug directory of TriviaCenter and copy them into the %APPDATA%/Steven Harding/TriviaCenter folder. This is where it looks for the trivia questions.