The system consists of a main program, AutoTest, and two programs to enter new codes and new questions. The main program looks like this:
--------
Click Here to display the illustration
Click the Back button on your browser to return
--------
You've seen random number generation and dialogs before, so I'll get to the magic stuff immediately. (The entire program is downloadable at the end of the article.)
I load an XElement rather than an XDocument in this program because that just gives me the data I'm interested in. The code that does that is:
QBase = XElement.Load(BasePath)
Pretty simple. But XElement is one of the most useful objects in LINQ.
To find and display a random Q and A, I execute this LINQ code:
Dim QNum As Integer = _
Int((CInt(QBase.@QCount) * Rnd()) + 1)
Dim QandA = _
From q In QBase.<Q> _
Where q.@QNUM = CStr(QNum) _
Select q
Question.Text = QandA.<QText>.Value
Answer.Text = QandA.<QAns>.Value
Dim QT = _
From q In QCodes.<QTopicCodes>.<QTCode> _
Where q.@QTID = QandA.@QTID _
Select q
TText.Text = QT.Value
Notice that it's easy to get the QCount attribute from the root element. The "@" is VB.NET's syntax for "attribute". After that, I create a QandA collection and populate it with a LINQ query against the QBase XML.
Notice also that I'm using the new "implicit typing" in Framework 3.0 (no "As" clause). That can be a helpful confusion eliminator because the syntax of the "As" clause can be even tougher to remember. In this case, it would be:
Dim QandA As IEnumerable(Of XElement)
I then select one XElement (in the variable q) from the collection where the QNUM attribute in the XElement is equal to the QNum random integer just computed. A similar query gets the text to display for the attribute codes. The only difference is that I compare the attribute from the QCodes XML to the attribute from the QBase XML.
Where q.@QTID = QandA.@QTID
After that, it's just a matter of pulling the values from the XML elements and displaying them in the components on the form. But even here, the syntactic sugar can drive you up the wall. I select the question and answer text values with the statements:
Question.Text = QandA.<QText>.Value
Answer.Text = QandA.<QAns>.Value
This works because <QText> is the immediate descendent of QBase.<Q> in the XML. But this also works:
Question.Text = QandA...<QText>.Value
Answer.Text = QandA...<QAns>.Value
Microsoft's designers must have had a sugar high when they designed LINQ - especially the Visual Basic version. Because they put in all kinds of convenient ways to express things. In this case, the three dots will find <QText> and <QAns> no matter where they are beneath QBase.<Q>. I could have used LINQ's Lambda expressions instead of this query syntax too. Choices, choices!
The EnterCodes and EnterQuestions forms are used to add new elements to the respective XML documents. These provide a great example of VB.NET's exclusive "embedded expression" syntax. (If you're a C# programmer -- Too bad! -- They're not in C#. Yet.) The code that adds a new element to the XML document (in the case of QCodes) is:
Dim QTopicCodes = QCodes.<QTopicCodes>(0)
Dim TopicCode As XElement = _
<QTCode>
<%= TText.Text %>
<%= New XAttribute("QTID", TID.Text) %>
</QTCode>
QTopicCodes.Add(TopicCode)
The first and second lines are coded that way to show you two more syntactic sugar confusions. What, you may ask, is the "(0)" doing at the end of the first line?
That specifies the zero'th XElement of QTopicCodes. Which, in turn, implies that it is an XElement. Otherwise, it would be a Collection. (As we learned earlier, it would be type "IEnumerable(Of XElement)".) Specifying the zero'th element makes VB.NET's "implicit typing" type it as just an XElement. That's necessary because it has to be an XElement to be added to the document:
QTopicCodes.Add(TopicCode)
The code also runs if you code the first line as:
Dim QTopicCodes As XElement = QCodes.<QTopicCodes>
Both the element content and an attribute are created from embedded expressions:
<QTCode>
<%= TText.Text %>
<%= New XAttribute("QTID", TID.Text) %>
</QTCode>
If you download the code, you'll notice some other code that's really just there to make the form easier to use by displaying labels and blanking out textboxes.
The only code in the EnterQuestions form that isn't in this one is:
QBase.ReplaceAttributes(New XAttribute("QCount", CStr(QNum)))
LINQ is a very complete technology. If you need a method, it's probably there somewhere.
As I wrote initially, this is a start on a request received from a reader, but it's far from complete. For one thing, although you can add questions and codes, there's no way in the system to change or delete them. For another, the reader really wanted something that would print out a random selection of a bunch of questions based on a combination of topic, skill and objective attributes. This just makes a random selection of one question and doesn't use the attributes in making the selection.
My reader initially offered to pay me to code this. (But I decided the fun of coding it would be payment enough.) If they still want these enhancements, maybe I'll take them up on it.
Download the code by clicking here.

