Archive for the ‘Programming’ Category

Time for reports cards with spelling mistakes

Saturday, June 11th, 2011

Many schools create one text document for each student. They then ask the teacher to enter grades on the local school network. An example in point: my present school.

This has some obvious disadvantages:

  1. If the school’s network is down no grades can be entered. This just happened at my school. The server was down for three weeks.
  2. Since the data is collected in a text document and not in a database table analysis of the data is not feasible.
  3. It is difficult to change the layout or text on the report when grades have been entered.
  4. Two teachers can not enter grades on the same student at the same time.
  5. By mistake teachers may create havoc to the report layout or content.
  6. The teachers have to wade through all students to find the ones they teach. They may miss someone and/or enter grades under the wrong subject heading. The teachers have no way of knowing that they have entered all the grades.

Here are its major advantages:

  1. It is rather easy to create the empty reports using mail merge, or even by adding the students’ names manually and naming the text document with their name.
  2. Teachers’  grades  and tutors’ comments can be entered in the same document.

Better ways of doing this do exist, of course, and some of them does not cost a fortune, but more about that later. Now I want to deal with disadvantage 3 above:

It is difficult to change the layout or text on the report when grades have been entered.

155 empty report cards had been put on the local server when thee mistakes were noted.

  1. The school’s name is “Academy St James” (fictional), but had been entered as “Academy of St James”.
  2. The text “Absent:” had not been put in bold. “Lates:” was correctly entered in bold.
  3. “classwork” should have been “class work”.

If the empty reports were created by mail merge, (I don’t know how they were created and there was no time to find out), one could easily fix these mistakes in the source document and merge again with the data. But if the 155 reports were created manually, or if these mistakes had been discovered after some or all of the grades had been entered, then what?

An obvious solution would be to manually fix the errors in all 155 documents. This could take any time from one to three hours and afterwards one might not remember the capital of France.

A more attractive solution was to write a macro that would do all the corrections automatically and that could be used if/when other mistakes, changes were called for. Since the reports were Word documents I made a Word macro.

The first task was to reinstall MS Word 2003 on my netbook. I had just made an oath not to use MS Office again, but this was an emergency. (I am more than happy with the free Open Office.)

The report cards were divided into folders. One folder per tutor group. Each folder had a document for each students. About 15-25 documents per folder.

My macro would ask me for the folder and perform the changes on all the documents in the folder. I would run the macro for the seven folders.

To ask the user for a folder, as opposed to a file, requires quite a lot of instructions in VBA (Visual Basic for Applications). I found usable code quite easily on the internet.

I put this code in General – Declarations:

Private Declare Function SHGetPathFromIDList Lib “shell32.dll” _    Alias “SHGetPathFromIDListA” (ByVal pidl As Long, ByVal pszPath As String) As LongPrivate Declare Function SHBrowseForFolder Lib “shell32.dll” _    Alias “SHBrowseForFolderA” (lpBrowseInfo As BROWSEINFO) As LongPrivate Type BROWSEINFO ‘ used by the function GetFolderName    hOwner As Long    pidlRoot As Long    pszDisplayName As String    lpszTitle As String    ulFlags As Long    lpfn As Long    lParam As Long    iImage As LongEnd TypePublic SubmissionStatus As StringPublic ValidationStatus As StringPublic Comp As StringPublic FolderName As String

The line breaks did not work out, but you get the gist.

Then a function was needed:

Function GetFolderName(Msg As String) As String’ returns the name of the folder selected by the userDim bInfo As BROWSEINFO, path As String, r As LongDim X As Long, pos As Integer    bInfo.pidlRoot = 0& ‘ Root folder = Desktop    If IsMissing(Msg) Then        bInfo.lpszTitle = “Select a folder.”        ’ the dialog title    Else        bInfo.lpszTitle = Msg ‘ the dialog title    End If    bInfo.ulFlags = &H1 ‘ Type of directory to return    X = SHBrowseForFolder(bInfo) ‘ display the dialog    ’ Parse the result    path = Space$(512)    r = SHGetPathFromIDList(ByVal X, ByVal path)    If r Then        pos = InStr(path, Chr$(0))        GetFolderName = Left(path, pos – 1)    Else        GetFolderName = “”    End IfEnd Function

And finally a procedure:

Sub TargetFolderName()
FolderName = GetFolderName("Select the Folder to Scan...")
If FolderName = "" Then
    MsgBox "You didn't select a folder. Please try again."
    End
End If
End Sub

Puuh! All that is needed for the simple call:

Sub test01()
' ask the user for a folder
cfolder = GetFolderName("Please choose a folder")

The next step is to open each document in turn:

Dim fsSearch As FileSearchSet 
fsSearch = Application.FileSearch
With fsSearch
    .FileName = "*.doc"
    .LookIn = cfolder
    .Execute    
    If .FoundFiles.Count = 0 Then
      MsgBox "No Word documents were found in " + cfolder
    Else
      For i = 1 To .FoundFiles.Count
          Documents.Open FileName:=.FoundFiles(i)
          ' code for making changes to the document just opened
          ActiveDocument.Close Savechanges:=wdSaveChanges
      Next i
    End If
End With
MsgBox "fin"
End Sub

To make changes are easy. Just add code  below the comment in red above.

Change “Academy of St James” to “Academy St James”:

For Each myStoryRange In ActiveDocument.StoryRanges
    With myStoryRange.Find
       .Text = "Academy of St James"
       .Replacement.Text = "Academy St James" 
       .Wrap = wdFindContinue
       .Execute Replace:=wdReplaceAll 
     End With
Next myStoryRange

The code is most likely overkill, but it works. To put text in bold I used this code:

' set a word in bold
Selection.Find.ClearFormatting
With Selection.Find
            .Text = "Absent:"
            .Replacement.Text = ""
            .Forward = True
            .Wrap = wdFindContinue
            .Format = True
            .MatchCase = False
            .MatchWholeWord = False
            .MatchKashida = False
            .MatchDiacritics = False
            .MatchAlefHamza = False
            .MatchControl = False
            .MatchWildcards = False
            .MatchSoundsLike = False
            .MatchAllWordForms = False
End With
Selection.Find.Execute
Selection.Font.Bold = True

Again, the code has probably too many instructions, but that is a minor concern in this case.

Next post will try to put the Word documents online as GoogleDocs documents.

Renaming and copying deeply hidden files

Tuesday, May 25th, 2010

To get to the IB math past papers one has to drill down through lots of folders as shown above. Wouldn’t it be simpler for the math department if all the past papers were copied to one folder, along with their marking schemes, and renamed to show the month, year, and if they were question papers (QP) or marking schemes (MS)?

The question came from a math teacher and the solution was provided by vbscript. The code below took one minute to run. If the job were done manually it would have taken somewhat longer. :)

' May 24, 2010
' Jan Nordgreen
' Problem: Pull all math papers into one folder and rename them

Set filesys = CreateObject("Scripting.FileSystemObject")

dim f

' data
const folders = 23

dim folderarray(24)

'startfolder = "H:\bill_problem\"
'destinationfolder = "C:\bill\"

startfolder = "\\fs1\IB\IB Examination Papers and Markschemes\"
destinationfolder = "\\fs1\Departments\Mathematics\Teachers\IB\Past papers and marking schemes\"

folderarray(0) = "Nov 2009 Questions\Group 5\English"
folderarray(1) = "Nov 2008 Questions\Group 5\English"
folderarray(2) = "Nov 2007 Questions\Group 5\English"
folderarray(3) = "Nov 2006 Questions\Group 5\English"
folderarray(4) = "Nov 2002 Questions\NovQuestions\Group 5\Eng"
folderarray(5) = "May 2002 Questions\Group 5\Eng"
folderarray(6) = "Nov 2001 Questions\NovQuestions\Group 5\Eng"
folderarray(7) = "May 2009 Questions\Group 5\English"
folderarray(8) = "May 2008 Questions\Group 5"
folderarray(9) = "May 2007 Questions\Group 5 (English) Mathematics"
folderarray(10) = "May 2003 Questions\Group 5\Eng"
folderarray(11) = "Markschemes\May2000 Markscheme\Group 5"
folderarray(12) = "Markschemes\May2002 Markscheme\Group 5"
folderarray(13) = "Markschemes\May 2003 Markscheme\Group 5"
folderarray(14) = "Markschemes\May 2007 Markscheme\Group 5 (English) Mathematics"
folderarray(15) = "Markschemes\May 2008 Markscheme\Group 5"
folderarray(16) = "Markschemes\May 2009 Markscheme\Group 5\English"
folderarray(17) = "Markschemes\Nov2001 Markscheme\Group 5\Eng"
folderarray(18) = "Markschemes\Nov2002 Markscheme\Group 5"
folderarray(19) = "Markschemes\Nov 2006 Markscheme\Group 5\English"
folderarray(20) = "Markschemes\Nov 2007 Markscheme\Group 5\English"
folderarray(21) = "Markschemes\Nov 2007 Markscheme\Group 5\English"
folderarray(22) = "Markschemes\Nov 2008 Markscheme\Group 5\English"
folderarray(23) = "Markschemes\Nov 2009 Markscheme\Group 5\English"

' processing
for i = 0 to folders
  if instr(UCase(folderarray(i)), "QUESTIONS") > 0 then
    qp = "QP"
    cmonth =  left(folderarray(i),3)
    'wscript.echo cmonth
    cyear = mid(folderarray(i),5,4)
    'wscript.echo cyear
  else
    qp = "MS"
     cmonth =  mid(folderarray(i),13,3)
    'wscript.echo cmonth
    cyear = mid(folderarray(i),17,4)
    'wscript.echo cyear
  end if
  'wscript.echo qp
  cfolder = startfolder + folderarray(i)
  'wscript.echo cfolder
  if filesys.FolderExists(cfolder) then
    Set f = filesys.GetFolder(cfolder)
    for each objfile in f.files
      if instr(UCase(objfile.name), "MATH") > 0 then
            ' wscript.echo objfile.name
            newname = qp + " " + left(objfile.name,len(objfile.name)-4) + " " + cyear + " " + cmonth + ".pdf"
            wscript.echo newname
            filesys.CopyFile cfolder + "\" + objfile.name, destinationfolder + newname
      end if
    next
  end if
next

wscript.echo "fin"
wscript.quit

Lines starting with an apostrophe are comments. Since the folder structure was different for various years I had to store the folder names in an array.

Here is the end result:

‘ May 24, 2010
‘ Jan Nordgreen
‘ Problem: Pull all math papers into one folder and rename them

Set filesys = CreateObject(“Scripting.FileSystemObject”)

dim f

‘ data
const folders = 23

dim folderarray(24)

‘startfolder = “H:\bill_problem\”
‘destinationfolder = “C:\bill\”

startfolder = “\\fs1\IB\IB Examination Papers and Markschemes\”
destinationfolder = “\\fs1\Departments\Mathematics\Teachers\IB\Past papers and marking schemes\”

folderarray(0) = “Nov 2009 Questions\Group 5\English”
folderarray(1) = “Nov 2008 Questions\Group 5\English”
folderarray(2) = “Nov 2007 Questions\Group 5\English”
folderarray(3) = “Nov 2006 Questions\Group 5\English”
folderarray(4) = “Nov 2002 Questions\NovQuestions\Group 5\Eng”
folderarray(5) = “May 2002 Questions\Group 5\Eng”
folderarray(6) = “Nov 2001 Questions\NovQuestions\Group 5\Eng”
folderarray(7) = “May 2009 Questions\Group 5\English”
folderarray(8) = “May 2008 Questions\Group 5″
folderarray(9) = “May 2007 Questions\Group 5 (English) Mathematics”
folderarray(10) = “May 2003 Questions\Group 5\Eng”
folderarray(11) = “Markschemes\May2000 Markscheme\Group 5″
folderarray(12) = “Markschemes\May2002 Markscheme\Group 5″
folderarray(13) = “Markschemes\May 2003 Markscheme\Group 5″
folderarray(14) = “Markschemes\May 2007 Markscheme\Group 5 (English) Mathematics”
folderarray(15) = “Markschemes\May 2008 Markscheme\Group 5″
folderarray(16) = “Markschemes\May 2009 Markscheme\Group 5\English”
folderarray(17) = “Markschemes\Nov2001 Markscheme\Group 5\Eng”
folderarray(18) = “Markschemes\Nov2002 Markscheme\Group 5″
folderarray(19) = “Markschemes\Nov 2006 Markscheme\Group 5\English”
folderarray(20) = “Markschemes\Nov 2007 Markscheme\Group 5\English”
folderarray(21) = “Markschemes\Nov 2007 Markscheme\Group 5\English”
folderarray(22) = “Markschemes\Nov 2008 Markscheme\Group 5\English”
folderarray(23) = “Markschemes\Nov 2009 Markscheme\Group 5\English”

‘ processing
for i = 0 to folders
if instr(UCase(folderarray(i)), “QUESTIONS”) > 0 then
qp = “QP”
cmonth =  left(folderarray(i),3)
‘wscript.echo cmonth
cyear = mid(folderarray(i),5,4)
‘wscript.echo cyear
else
qp = “MS”
cmonth =  mid(folderarray(i),13,3)
‘wscript.echo cmonth
cyear = mid(folderarray(i),17,4)
‘wscript.echo cyear
end if
‘wscript.echo qp
cfolder = startfolder + folderarray(i)
‘wscript.echo cfolder
if filesys.FolderExists(cfolder) then
Set f = filesys.GetFolder(cfolder)
for each objfile in f.files
if instr(UCase(objfile.name), “MATH”) > 0 then
‘ wscript.echo objfile.name
newname = qp + ” ” + left(objfile.name,len(objfile.name)-4) + ” ” + cyear + ” ” + cmonth + “.pdf”
wscript.echo newname
filesys.CopyFile cfolder + “\” + objfile.name, destinationfolder + newname
end if
next
end if
next

wscript.echo “fin”
wscript.quit

Average of letters

Thursday, April 29th, 2010

Click the image above to see a bigger version.

If a student gets A, B, and Cs for effort in various classes how does one calculate the average effort grade?

One way is to convert A, B, and C to 1, 2, and 3 respectively, take the average, round off the average and translate 1, 2, and 3 back to A, B, and C.

Here is code for MS Access.

Public Function MyAvgLetter(ParamArray rValues()) As Variant
Dim dblSum As Double
Dim lngI As Long
Dim I As Long
lngI = 0
For I = 0 To UBound(rValues)
If Not (IsNull(rValues(I)) Or rValues(I) = “”) Then
dblSum = dblSum + Asc(UCase(Trim(rValues(I)))) – 64
lngI = lngI + 1
End If
Next I
MyAvgLetter = Null
If lngI = 0 Then Exit Function
MyAvg = dblSum / lngI
MyAvgLetter = Trim(Chr(Round(MyAvg + 64))) + ” (” + Trim(Str(Round(MyAvg, 1))) + “)”
End Function

And the call in the report is:

=MyAvgLetter([Math Eff],[Eng Eff],[EAL Eff],[Sci Eff],[Art Eff],[DT Eff],[Dra Eff],[Geo Eff],[His Eff],[ICT Eff],[Man Eff],[Mus Eff],[PE Eff],[Spa Eff],[ThL Eff],[ThS Eff])

Ugly yes, but this is what I was handed by the one who wanted the average calculated. I insisted in adding the numerical average as well. As you can see in the image above two students got average 1.5, but one got A and the other B. Reason being that one really got 1.49 and the other 1.51. If the numerical average is not shown one might believe that their averages were a grade apart while they really were 0.02 grades apart.

Macros makes life easier

Saturday, January 16th, 2010

The last two weeks my year 10 ICT students have designed a booking sheet for cinemas. The client (me) had these requirements:

Client’s requirements:
1. Simple to use and learn.
2. Should show all seats for a given performance (rows=23, seats in a row=20, you decide where the passageways are).
3. Use colour code to show seats taken (vacant or free, reserved, taken, price, backup seats)
4. Calculate:
a. # seats taken (total number and in %)
b. total income
New requirements:
1. The price of a seat depends on the location in the cinema and if the customer is a child or an adult.
2. Please protect the cells where one is not supposed to enter information so they will not be overwritten by mistake.
3. Add pie charts to show the various incomes.
4. The prices may change often so make it easy to change them.
5. The program should calculate the cost for each customer and the change they should receive.

1. Simple to use and learn.

2. Should show all seats for a given performance (rows=23, seats in a row=20, you decide where the passageways are).

3. Use colour code to show seats taken (vacant or free, reserved, taken, price, backup seats)

4. Calculate:

a. # seats taken (total number and in %)

b. total income

New requirements:

1. The price of a seat depends on the location in the cinema and if the customer is a child or an adult.

2. Please protect the cells where one is not supposed to enter information so they will not be overwritten by mistake.

3. Add pie charts to show the various incomes.

4. The prices may change often so make it easy to change them.

5. The program should calculate the cost for each customer and the change they should receive.

Today each student or student pair demonstrated (defended) their solution for their peers using the projector on the front computer. Many solutions were excellent, but I could see how macros could make them even more useful.

Macros and programming are not part of the syllabus, but even so on Monday I will tempt their intellectual taste buds with the following.

Sub costandchange

Sheet = thisComponent.Sheets(“Sheet1″)

income = Sheet.getCellRangeByName(“D4″)

oldincome = Sheet.getCellRangeByName(“D5″)

cost = income.value – oldincome.value

received = InputBox (chr(13)+ “The cost is ” + cost + “.” + chr(13) + chr(13)+ “Received:”,”Cinema”)

moneychange = val(received) – val(cost)

msgbox “Change = ” + received + ” – ” + cost + ” = ” + moneychange,,”Cinema”

oldincome.value = income.value

End Sub

The user enters a and c for adults and children and with countif() functions the total income is calculated in D4.

What happens when a new customer buys some tickets? Without a macro you have to type a and c in the cells/seats where they will sit and then type the number of as and cs to find the total cost and later the change they should receive.

With the macro above it is enough to mark the sheet with as and cs. The trick consists in having the total income in both D4 and D5. When as and cs are entered D4 changes, but D5 does not. D4-D5 gives therefore the total cost for the customer.

I found the code tips I needed here.