Option Strict On
Imports 
Microsoft.VisualBasic
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Web.UI
Imports System.Web.UI.Design

Namespace MikesControls

    
' The following declaration uses the new CompositeControl abstract class
    ' in 2.0, which gives you the following for free: INamingContainer, 
    ' EnsureChildControls, and design-time behavior.
    
Public Class QuizControl : Inherits CompositeControl
        
Private textAnswer As TextBox
        
Private labelQuizText As Label
        
Private layoutTable As Table
        
Private _answerValue As Integer

        Enum 
OperatorEnum
            Add
            Subtract
            Multiply
            Random
            
' No divide :-)
        
End Enum

        Enum 
QuizLayoutEnum
            Horizontal
            Vertical
        
End Enum

        Protected Overrides Sub 
CreateChildControls()
            
Me.Controls.Clear()

            textAnswer 
= New TextBox()
            textAnswer.ID 
"textAnswer"
            
textAnswer.Width = New Unit("30px")

            labelQuizText 
= New Label()
            labelQuizText.ID 
"labelQuizText"

            
labelQuizText.ForeColor = Me.ForeColor
            labelQuizText.Font.Bold 
= Me.Font.Bold
            labelQuizText.Font.Size 
= Me.Font.Size
            labelQuizText.Font.Names 
= Me.Font.Names

            
If Page.IsPostBack = False Then
                
labelQuizText.Text = Me.Question()
                ViewState(
"question").ToString()
            
Else
                
labelQuizText.Text ViewState("question").ToString()
            
End If

            
' This table does the layout
            
Dim row1 As New TableRow
            
Dim row2 As New TableRow
            
Dim cell1 As New TableCell
            
Dim cell2 As New TableCell

            
Dim table1 As New Table
            table1.BackColor 
= Me.BackColor
            table1.CellPadding 
= Me.CellPadding
            cell1.Controls.Add(labelQuizText)
            cell2.Controls.Add(textAnswer)
            row1.Cells.Add(cell1)
            table1.Rows.Add(row1)
            
If Me.QuizLayout QuizLayoutEnum.Horizontal Then
                
row1.Cells.Add(cell2)
            
Else
                
row2.Cells.Add(cell2)
                table1.Rows.Add(row2)
            
End If
            Me
.Controls.Add(table1)
        
End Sub

        Public Overrides ReadOnly Property 
Controls() As System.Web.UI.ControlCollection
            
Get
                
EnsureChildControls()
                
Return MyBase.Controls
            
End Get
        End Property

        
<Browsable(True)> Public Property QuizLayout() As QuizLayoutEnum
            
Get
                If 
ViewState("quizLayout"Is Nothing Then
                    Return 
QuizLayoutEnum.Horizontal
                
Else
                    Return CType
(ViewState("quizLayout"), QuizLayoutEnum)
                
End If
            End Get
            Set
(ByVal value As QuizLayoutEnum)
                ViewState(
"quizLayout"value
            
End Set
        End Property

        
<Browsable(True)> Public Property OperatorType() As OperatorEnum
            
Get
                If 
ViewState("operatorType"Is Nothing Then
                    Return 
OperatorEnum.Add
                
Else
                    Return CType
(ViewState("operatorType"), OperatorEnum)
                
End If
            End Get
            Set
(ByVal value As OperatorEnum)
                ViewState(
"operatorType"value
            
End Set
        End Property

        
<Browsable(True), DefaultValue(2)> Public Property CellPadding() As Integer
            Get
                If 
ViewState("CellPadding"Is Nothing Then
                    Return 
2
                
Else
                    Return CInt
(ViewState("CellPadding"))
                
End If
            End Get
            Set
(ByVal value As Integer)
                ViewState(
"CellPadding"value
            
End Set
        End Property

        
<Browsable(True), DefaultValue(12)> Public Property MaxValue() As Integer
            Get
                If 
ViewState("MaxValue"Is Nothing Then
                    Return 
12
                
Else
                    Return CInt
(ViewState("MaxValue"))
                
End If
            End Get
            Set
(ByVal value As Integer)
                ViewState(
"MaxValue"value
            
End Set
        End Property

        
<Browsable(True), DefaultValue(10)> Public Property MaxMultiplyValue() As Integer
            Get
                If 
ViewState("MaxMultiplyValue"Is Nothing Then
                    Return 
10
                
Else
                    Return CInt
(ViewState("MaxMultiplyValue"))
                
End If
            End Get
            Set
(ByVal value As Integer)
                ViewState(
"MaxMultiplyValue"value
            
End Set
        End Property

        
<Browsable(True), DefaultValue(1)> Public Property MinValue() As Integer
            Get
                If 
ViewState("MinValue"Is Nothing Then
                    Return 
1
                
Else
                    Return CInt
(ViewState("MinValue"))
                
End If
            End Get
            Set
(ByVal value As Integer)
                ViewState(
"MinValue"value
            
End Set
        End Property

        Public ReadOnly Property 
IsValid() As Boolean
            Get
                Dim 
userValue As String
                Dim 
tb As TextBox = CType(FindControl("textAnswer"), TextBox)
                
If tb Is Nothing Then
                    
userValue ""
                
Else
                    
userValue tb.Text
                
End If

                
' Convert known answer to string for comparison
                
If userValue ViewState("answerValue").ToString() Then
                    Return True
                Else
                    Return False
                End If
            End Get
        End Property

        Public Property 
QuestionFormat() As String
            Get
                Dim 
questionFormatString As String
                If 
ViewState("questionFormatString"Is Nothing Then
                    
questionFormatString "What is {0} {2} {1}?"
                
Else
                    
questionFormatString ViewState("questionFormatString").ToString()
                    
If questionFormatString "" Then
                        
questionFormatString "What is {0} {2} {1}?"
                    
End If
                End If
                Return 
questionFormatString
            
End Get
            Set
(ByVal value As String)
                ViewState(
"questionFormatString"value
            
End Set
        End Property

        
<Browsable(True)> Public ReadOnly Property Question() As String
            Get
                Dim 
questionString As String
                If 
ViewState("question"Is Nothing Then
                    
questionString CreateQuestion()
                    ViewState(
"question"questionString
                    
' The following is required to force the value to be stored in viewstate.
                    
ViewState.SetItemDirty("question"True)
                    
Return questionString
                
Else
                    Return CType
(ViewState("question"), String)
                
End If
            End Get
        End Property

        Public Sub 
Reset()
            
Dim question As String = CreateQuestion()
            
' I'm not sure if it's kosher to do this -- I'm putting the 
            ' question directly into viewstate. The read-only Question property 
            ' returns it to whoever asks for it. 
            
ViewState("question"question
            ViewState.SetItemDirty(
"question"True)

            
' The controls have already been created during EnsureChildControls, so 
            ' they have to be updated with new question information now.
            
CreateChildControls()

            
Dim tb As TextBox
            tb 
= CType(FindControl("textAnswer"), TextBox)
            tb.Text 
""

            
Dim As Label
            l 
= CType(FindControl("labelQuizText"), Label)
            l.Text 
question

        
End Sub

        Protected Function 
CreateQuestion() As String
            Dim 
num1, num2 As Integer

            
' This creates an integer based on the ASCII values of the characters in the
            ' control's ID (which of course must be unique on the page). By plugging this value into 
            ' the Random() constructor, we get different random numbers for each control on the 
            ' page. Relying on Ticks alone gets the same random numbers for all instances of the 
            ' control on the same page. I forget where I got this idea, but probably from someone's 
            ' blog, is a good guess. (Apologies to that person for not being able to credit them here.)
            
Dim idSum As Integer = 0
            
For As Integer = To Me.ID.Length - 1
                
idSum += Asc(Me.ID.Substring(i, 1))
            
Next
            Dim rnd As New 
Random(CType(System.DateTime.Now.Ticks Mod (System.Int32.MaxValue - idSum), Integer))

            
' Create the two numbers for the quiz. If this is a multiplication quiz, the numbers will
            ' be recreated based on the separate min/max for multiplication.
            
num1 = rnd.Next(Me.MinValue, Me.MaxValue)
            num2 
= rnd.Next(Me.MinValue, Me.MaxValue)
            
Dim questionReturn As String = ""
            
Select Case Me.OperatorType
                
Case OperatorEnum.Add
                    _answerValue 
num1 + num2
                    questionReturn 
= String.Format(Me.QuestionFormat, num1, num2, "+")
                
Case OperatorEnum.Multiply
                    questionReturn 
CreateMultiplyQuestion()
                
Case OperatorEnum.Subtract
                    
' Do a swap so the highest number is first
                    
If num1 < num2 Then
                        Dim 
temp As Integer = num1
                        num1 
num2
                        num2 
temp
                    
End If
                    
_answerValue num1 - num2
                    questionReturn 
= String.Format(Me.QuestionFormat, num1, num2, "-")
                
Case OperatorEnum.Random
                    
' I don't want to explicitly (re)set the current value of the
                    ' OperatorType property (e.g., by doing this logic first and then
                    ' setting Me.OperatorType before dropping into the big 
                    ' Select statement.) The property value should remain the same as 
                    ' what the developer has set it to.
                    
Dim allOperators As String = "*-+"
                    
Dim randomOperator As String = allOperators.Substring(rnd.Next(0, allOperators.Length), 1)
                    
Select Case randomOperator
                        
Case "+"
                            
_answerValue num1 + num2
                            questionReturn 
= String.Format(Me.QuestionFormat, num1, num2, "+")
                        
Case "-"
                            
' Do a swap so the highest number is first
                            
If num1 < num2 Then
                                Dim 
temp As Integer = num1
                                num1 
num2
                                num2 
temp
                            
End If
                            
_answerValue num1 - num2
                            questionReturn 
= String.Format(Me.QuestionFormat, num1, num2, "-")
                        
Case "*"
                            
questionReturn CreateMultiplyQuestion()
                    
End Select
                Case Else
                    
_answerValue num1 + num2
                    questionReturn 
= String.Format(Me.QuestionFormat, num1, num2, "+")
            
End Select
            
ViewState("answerValue"_answerValue
            
Return questionReturn
        
End Function

        Function 
CreateMultiplyQuestion() As String
            
' Note! This resets the num1 and num2 values to accommodate maxumum
            ' multplier property settings.
            
Dim num1, num2 As Integer
            Dim rnd As New 
Random(CType(System.DateTime.Now.Ticks Mod System.Int32.MaxValue, Integer))
            
If Me.MaxMultiplyValue >Then
                
num1 = rnd.Next(Me.MinValue, Me.MaxMultiplyValue)
                num2 
= rnd.Next(Me.MinValue, Me.MaxMultiplyValue)
            
Else
                
num1 = rnd.Next(Me.MinValue, Me.MaxValue)
                num2 
= rnd.Next(Me.MinValue, Me.MaxValue)
            
End If
            
_answerValue num1 * num2
            
Return String.Format(Me.QuestionFormat, num1, num2, "*")
        
End Function
    End Class

End Namespace

Colorized by: CarlosAg.CodeColorizer