|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Visual Basic Projects WritePrivateProfileString: Creating a Quiz Application Step 4: Building the Main Quiz Form |
|
Posted: | Tuesday August 24, 1999 |
Updated: | Monday December 26, 2011 |
Applies to: | VB4-32, VB5, VB6, and VB3, VB4-16 with appropriate declarations |
Developed with: | VB6, Windows NT4 |
OS restrictions: | None |
Author: | VBnet - Randy Birch |
Other project pages: | Step 1: Introduction and BAS
Module Step 2: Building the 'TopScores' Form Step 3: Building the Quiz Topic Selection Form Step 4: Building the Main Quiz Form Form Illustration Layouts Downloadable KidzQuiz INI file |
Related: |
WritePrivateProfileString: INI Files - The Basics WritePrivateProfileString: INI Files - Saving Entire Sections |
Prerequisites |
Steps 1, 2 and 3 completed. |
|
This
is the main form for the application, and contains all the logic to determine correct answers, show the correct one when wrong, load the data
from the ini file and call the parsing methods, and is responsible determining whether a high score worthy of adding to the list was
achieved, and if so calling the Top Scores form.
The first illustration shows the design-time layout with each control's name as its caption, while the illustration below shows it as it is in my project. Note that the two command buttons in the frame - cmdSubmit and cmdNext, are physically aligned in the illustration below. cmdSubmit should be set as the topmost control. And while there appears to be a ton'o'code below don't panic - a good portion of the text below are code comments.
|
Form Code: frmQuiz |
Add the final form to the project and name it frmQuiz.
On the form add three command buttons (cmdNew, cmdTopScores, and cmdQuit). Also add a series of labels (their names are not important) and set their captions as indicated above - 'Category', 'Question' and 'Your Score'. Now add three more auto-sizing labels that have their captions set in code - name those lbCategory, lbQuestionNo and lbScore and position appropriately beside the other labels you added. Add a frame to the form (frAnswers), and onto it draw the two command buttons (cmdSubmit and cmdNext). Position one overtop each other -- the code shows/hides the required button as needed. Also create, again inside the frame, five option buttons in a control array (optAnswers(0) through optAnswers(4). You can delete optAnswers(0 as its not used, or simply set its visible property to False. To test that you've correctly create the controls inside the frame, try moving the frame around. If you've created it correctly, all the frame's controls will move as well. If one or more don't move with the frame, you've created the controls on the from, so simply select them, cut, click anywhere inside the frame, paste, and position. Finally, inside the frame add two labels (lbQuestion and lbMessage) in which the question and the answer/prompt are displayed. You can add labels for the A/B/C/D captions as desired. Also, position the two command buttons overtop each other, assuring that cmdSubmit is the topmost button. There are two unseen controls on this form, shown in the yellow rectangle in the layout illustration above. The first is a timer - Timer1 - used exclusively when a wrong answer is given (see the cmdSubmit routine below). The other control is a command button that should be move out of view (but not made invisible!!), and given the name cmdDummy. Its not used to execute any code ... its sole purpose is to have a control off-screen to which focus can be set when resetting (de-selecting) the question option buttons so as not provide any clue or misleading information as to what might be the expected answer. Add the following to this form: |
|
Option Explicit '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved. ' Some pages may also contain other copyrights by the author. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Distribution: You can freely use this code in your own ' applications, but you may not reproduce ' or publish this code on any web site, ' online service, or distribute as source ' on any media without express permission. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'Constants for the questions array. By using variables, 'we make the array dynamic, and in doing so assure it 'doesn't occupy the 64k of memory normally reserved 'for form-level code and strings. ' 'The data is stored in the array as follows: ' - assume there are 15 questions ' - the first dimension always has 6 elements: ' element 1 - the correct answer index ' element 2 - the question ' element 3 - answer option A ' element 4 - answer option B ' element 5 - answer option C ' element 6 - answer option D ' - the second dimension of the array is always ' the index to the current question (of the 15 available). 'Therefore, in assigning the data to the array, 'the following methodology is used: 'Assuming the current question (currQuestion) was 1 ... ' Q1=3,What does the prefix pre mean?,after,seldom,before,rapid '...the data is assigned as follows : 'questions(Correct, currQuestion) = 3 'index to the correct response 'questions(Question, currQuestion) = "What does the prefix pre mean?" 'questions(AnswerA, currQuestion) ="after" 'questions(AnswerB, currQuestion) ="seldom" 'questions(AnswerC , currQuestion) ="before" 'questions(AnswerD, currQuestion) ="rapid" 'Since the indices of the answer option buttons are 'also 1 through 4, the code simply uses the selected 'index and compares it to the value stored at 'Correct'.. '(that's why optAnswers(0) was deleted or hidden and not used!) 'i.e. isRight = AnswerGiven = questions(Correct, currQuestion) 'The lower bounds of both dimensions of the questions array Private Const firstNo As Long = 1 'The upper bounds of the first dimension of the questions 'array. The upper bound of the second dimension is set 'in code via the BeginQuiz routine, and the value is 'determined according to the number of Quiz questions 'under the Quiz topic selected. Private Const lastNo As Long = 6 'Just to make keeping track of the item in the questions 'array currently being used a little easier, declare constants 'representing the info in each position. This will correspond 'to the way the data is stored in the ini file. Private Const Correct As Long = 1 Private Const Question As Long = 2 Private Const AnswerA As Long = 3 Private Const AnswerB As Long = 4 Private Const AnswerC As Long = 5 Private Const AnswerD As Long = 6 'finally, we need ... ' ThisQuestion: a form-level variable to track the current question ' TotalQuestions: a form-level variable to hold the total questions ' AnswerGiven: a form-level variable to hold the submitted answer ' TotalScore: a form-level variable for the current score. Private ThisQuestion As Long Private TotalQuestions As Long Private AnswerGiven As Long Private TotalScore As Long Private Sub cmdNew_Click() 'This sub displays the question category list, then 'loads the ini questions & answers, and begins the test 'the section in the ini file with the questions frmSelect.Show vbModal, Me If iniQuizSection = "" Then Exit Sub End If 'if no questions were loaded, in BeginQuiz, then 'TotalQuestions = 0 else TotalQuestions = no of 'questions loaded. TotalQuestions = BeginQuiz(iniQuizSection) If TotalQuestions Then 'reset remaining variables ThisQuestion = 0 TotalScore = 0 'update the labels with the startup data lbCategory = iniQuizSection lbQuestionNo = CStr(ThisQuestion) & " of " & CStr(TotalQuestions) lbScore = TotalScore 'show the first question ShowQuestion 'and enable the frame and buttons to 'allow answering frAnswers.Enabled = True End If End Sub Private Sub cmdNext_Click() 'when the Next Question button is pressed... ShowQuestion End Sub Private Sub cmdQuit_Click() 'we're done, so shut down Unload frmHiScores Erase Questions Unload Me End Sub Private Sub cmdSubmit_Click() 'this sub is called when "Am I Right ?" is pressed 'working variables Dim CorrectAnswer As Long Dim msg As String 'determine what the the correct answer should be CorrectAnswer = Val(Questions(Correct, ThisQuestion)) 'and compare to the selected answer 'AnswerGiven is set when the kids click an 'option button from the optAnswers_Click sub If AnswerGiven = CorrectAnswer Then 'got it correct, so say so, and update score lbMessage = "Correct !!" TotalScore = TotalScore + 1 lbScore = TotalScore 'do a quick check to determine if there are 'more questions to show. If CheckQuestionStatus = False Then frAnswers.Enabled = False Exit Sub 'nope, we're done End If 'show the "next question" button, and 'assure it is at the top of the ZOrder cmdNext.ZOrder 0 cmdNext.Visible = True Else 'wrong answer, so indicate the correct one 'Build the answer string to display. 'the correct answer's "a,b,c or d" can be determined by 'taking the chr$ value of 96 (the character just before ''a' in the ASCII chart), and adding the correct 'answer number (1 through 4), to give the 'characters a, b, c, d) 'This is the equivalent of using: ' if CorrectAnswer = 1 then msg = "a )" ' if CorrectAnswer = 2 then msg = "b )" ... etc msg = Chr$(96 + CorrectAnswer) & " ) " 'the correct answer strings begin at Questions() array 'position 3 (Const AnswerA = 3), but the answer 'option buttons begin at index 1, so we have to add 2 'to the answer to display the correct answer string. msg = msg & Questions(CorrectAnswer + 2, ThisQuestion) 'show the correct answer in a label, and call a little 'routine to highlight the correct answer option button 'in bold. lbMessage = "Sorry. The correct answer is " & msg HighlightAnswer CorrectAnswer 'do a quick check to determine if there are 'more questions to show. If CheckQuestionStatus = False Then frAnswers.Enabled = False Exit Sub 'done End If 'because they made a wrong answer, pause so they 'can read the correct answer before continuing. 'Incrementing the Interval below by 1000 adds approx. 1 second. Timer1.Interval = 3000 'pause about 2-3 seconds Timer1.Enabled = True 'turn on the timer. 'In the Timer1_Timer sub, once 'the interval set above has elapsed, 'it will shut itself off, and make the 'Next Question button visible. End If 'disable until a new answer is selected cmdSubmit.Enabled = False End Sub Private Sub cmdTopScores_Click() 'show the top scores dialog frmHiScores.Show 1 End Sub Private Sub Form_Load() 'centre this form on the screen Me.Move (Screen.Width - Me.Width) \ 2, (Screen.Height - Me.Height) \ 2 'disable the answer frame & buttons cmdSubmit.Enabled = False frAnswers.Enabled = False cmdNext.Visible = False 'set the name of the file containing the 'high scores' sHighScoreFile = App.Path & "\" & "qscores.dat" 'set the name of the file containing the questions sIniQFile = App.Path & "\" & "kidquiz.ini" 'have to force the form on-screen before 'setfocus can be used Show cmdNew.SetFocus End Sub Private Function BeginQuiz(iniQuizSection As String) As Long 'Step 1 - declare working variables for this sub Dim qiCount As Long 'counter for the "question item" Dim currQuestion As Long 'counter for the actual question in process Dim item As String 'value returned from the ini file, minus the trailing null Dim sKeyData As String 'key in the ini file currently being processed Dim ret As String 'string returned from the API call currently being processed Dim x As String 'extracted string Dim r As Long 'return var for API calls 'Step 2 - set starting variables for this routine 'update the current question counter counter variable is 0 'and erase the array containing any previous questions currQuestion = 0 Erase Questions 'Step 3 - assure that the specified ini file exists If Not FileExists(sIniQFile) Then MsgBox "The quiz file..." & vbCrLf & vbCrLf & _ sIniQFile & vbCrLf & vbCrLf & "...was not found.", _ vbExclamation Or vbOKOnly, "Quiz Error" BeginQuiz = False Exit Function End If 'Step 4 - get the keys in the ini file. Using this call 'removes the requirement of knowing and hard coding the 'actual keys, and the number of them. 'First, get all the item keys for the given section. 'In the ini file kidquiz.ini, this call returns all 'the items to the left of the = signs (Q1, Q2, Q3 etc), 'in other words the keys that we use later to retrieve 'the actual individual data. 'pad a string large enough for the returned string ret = Space(2048) 'and get the keys r = GetPrivateProfileString(iniQuizSection, _ 0&, _ "", _ ret, _ Len(ret), _ sIniQFile) 'if r > 0, then r = the number of characters 'in the returned string. With this info, we extract each 'key item individually, call GetPrivateProfileString again 'with that value to obtain the question and answers for each key. If r Then 'strip the terminating null character ret = Left(ret, r) 'and get each question & answer set Do Until ret = "" 'get 1 key item (ie Q1) item = ppStripItem(ret) 'and retrieve its related info by calling a 'GetPrivateProfileString 'wrapper' function sKeyData = ppGetItemsInfo(iniQuizSection, item, sIniQFile) 'make sure there is a valid string If Len(Trim(sKeyData)) > 0 Then 'at this point, we have a string (such as the first Q1 entry) ' " What does the prefix pre mean?,after,seldom,before,rapid " 'We now need to do another loop with this string, parsing out 'each item, and assigning it to the questions() array for 'later use. 'assure that the "question item" counter variable is 0 qiCount = 0 'update the current question counter by 1 currQuestion = currQuestion + 1 'and redim the questions() array for the new question & answer, 'preserving the present contents, if any ReDim Preserve Questions(firstNo To lastNo, _ firstNo To currQuestion) As String Do Until sKeyData = "" x = ppExtractItem(sKeyData) 'Again, assure x is a valid string If Len(Trim(x)) > 0 Then 'it is, so update the "question item" counter qiCount = qiCount + 1 Questions(qiCount, currQuestion) = x End If Loop End If Loop 'return the number of questions loaded BeginQuiz = currQuestion Else BeginQuiz = False 'some error happened, so return 0 End If End Function Private Sub ShowQuestion() 'This sub displays each question 'working variables Dim i As Long 'double check that this isn't the last question. If ThisQuestion < TotalQuestions Then 'update the current question counter ThisQuestion = ThisQuestion + 1 'hide the "next question" button cmdNext.Visible = False '1. reset the option button values to normal ' display mode, with nothing selected. '2. Show the question using the for..next counter. 'Because the answer strings begin at Questions() array 'position 3 (Const AnswerA = 3), we have to add 2 'to the loop index to display the correct answer string. For i = 1 To 4 optAnswers(i).Font.Bold = False optAnswers(i).Value = False optAnswers(i).Caption = Questions(i + 2, ThisQuestion) optAnswers(i).Enabled = True Next i 'disable the submit button until an 'option button is selected cmdSubmit.Enabled = False 'display the question lbQuestion.Caption = Questions(Question, ThisQuestion) 'display the question number lbQuestionNo.Caption = CStr(ThisQuestion) & " of " & CStr(TotalQuestions) 'and remove any present correct/wrong message lbMessage.Caption = "" Else 'This should never fire ... MsgBox "Counter Error in sub ShowQuestion ... an attempt was " & _ "made to display question #" & _ CStr(ThisQuestion) & " when there are only" & _ CStr(TotalQuestions) & " loaded.", _ vbExclamation Or vbOKOnly, "VBnet INI Demo - Kidz Quiz" End If 'assure focus is removed from all the option 'buttons by forcing focus onto a 'dummy' command 'button off-screen (maximize form to see it). cmdDummy.SetFocus End Sub Private Sub optAnswers_Click(Index As Integer) 'an answer was selected, so enable the submit button cmdSubmit.Enabled = True 'and set the form-level variable to the answer selected AnswerGiven = Index End Sub Private Sub HighlightAnswer(Correct As Long) 'Once an answer has been submitted, and it was 'deemed incorrect, this routine dims the wrong 'answers and highlights the correct one by bolding it. 'working variable Dim cnt As Long 'loop through the option buttons. If the 'loop counter matches the correct answer, 'switch it to bold text, and select it by 'setting its value to true, otherwise 'dim the wrong answer For cnt = 1 To 4 optAnswers(cnt).Value = cnt = Correct optAnswers(cnt).Enabled = cnt = Correct optAnswers(cnt).Font.Bold = cnt = Correct Next cnt End Sub Private Sub Timer1_Timer() 'turn off the timer Timer1.Enabled = False 'and show the "next question" button, 'assuring its at the top of the ZOrder cmdNext.ZOrder 0 cmdNext.Visible = True lbMessage = "" End Sub Private Function CheckQuestionStatus() As Long 'This first compares the current question '(ThisQuestion) with the TotalQuestions. 'If equal, it processes the score, and creates a 'custom message based on the score, and displays it. 'Additionally, it has a flag called GoodEnoughToAddToList, 'which defaults to true. However, based on how high a 'score was achieved, you can set any level to be false. 'Setting False will prevent the "Add Name to Top Scores" 'dialog from appearing at the end of the questioning. 'Perhaps this might be an incentive to work harder. 'working variables Dim msg As String Dim percentRight As Long Dim tutor As String Dim title As String Dim iconVal As String Dim GoodEnoughToAddToList As String If ThisQuestion = TotalQuestions Then 'we're done the quiz, so say so... 'determine the percentage correct based on 'the number of questions, and the number correct percentRight = (TotalScore / TotalQuestions) * 100 'create a generic first-part of the message msg = "The quiz is over." & vbCrLf & vbCrLf msg = msg & "You scored " & CStr(TotalScore) msg = msg & " out of a possible " & CStr(TotalQuestions) msg = msg & " points." & vbCrLf msg = msg & "That is " & CStr(percentRight) msg = msg & " percent correct." & vbCrLf & vbCrLf 'set a couple of default values- lets assume they're not fools title = "Kidz Quiz" GoodEnoughToAddToList = True 'and create the custom portion of the message Select Case percentRight Case Is > 90: tutor = "Congratulations .. Well done! Top 10 of the Class!" title = "Kidz Quiz Master!" iconVal = vbExclamation Case Is > 75: tutor = "Congratulations! You scored in the top 25." iconVal = vbExclamation Case Is > 65: tutor = "Well done. Maybe next time you'll make 100!" iconVal = vbInformation Case Is > 50: tutor = "Good try. Maybe next time you'll make 100!" iconVal = vbInformation Case Is > 25: tutor = "More work is needed this term to break 50." iconVal = vbQuestion GoodEnoughToAddToList = False Case Is <= 25: tutor = "You need to do a lot more work this term." iconVal = vbCritical GoodEnoughToAddToList = False End Select 'build the final message and show it msg = msg & tutor MsgBox msg, iconVal, "Quiz Machine" 'If they were GoodEnoughToAddToList, initiate the routines 'to add the name to the winner's list. If GoodEnoughToAddToList Then AddPlayerToList percentRight 'no more questions, so return false CheckQuestionStatus = False Else: CheckQuestionStatus = True 'still more questions End If End Function Private Function AddPlayerToList(percentRight As Long) 'Here we set a flag to tell the high scores 'form to display the Add Name dialog. Then we 'call the HiScores dialog. On showing, its 'Activate Sub has code that displays the 'Add Name dialog if GetHiScoreNameFlag is > 0. 'By passing the percentRight, the sub can 'use it to display in its congratulatory message GetHiScoreNameFlag = percentRight 'and just show the high scores dialog frmHiScores.Show vbModal End Function |
Comments |
This is the last project file. You can download a sample INI file from this link, or, give the exe version of this app a test drive (VB6 only). Place the sample ini file into the same folder as you project files, then give the app a run. |
|
|
|
|||||
|
|||||
|
|||||
Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved. |