Extra Material, Week 10 UsedRange property The worksheet object has a UsedRange property, which is the range from the top left to the bottom right of cells that have been used on the worksheet. Sub used() Dim mycell As Range For Each mycell In ActiveSheet.UsedRange mycell.Interior.Color = vbGreen Next mycell End Sub Working with two ranges in parallel The next code loops through two ranges in parallel. On the Exams worksheet there are two named ranges: named Marks, and Newmarks. Sub CompareRanges() Dim i As Integer Dim first As Range, second As Range Set first = Range("marks") Set second = Range("newmarks") For i = 1 To second.Count If second(i) > first(i) Then first(i).Interior.Color = vbYellow End If Next i End Sub The above code uses one For loop to compare the numbers in the ranges (it only colours the cell yellow if the number in newmarks is higher, but you could substitute a more useful function at this point. 1 Names The following examples are to be used with the Exams worksheet in week10.xls. Programs can be made easier by using Named Ranges. The Names collection is a member of the Application object or the Worksheet object. The Add method creates a name and adds it to the collection e.g. Names.Add Name:="ExamMarks", RefersTo:="=Exams!$A$2:$B$10" (Though there is the much simpler way Selection.Name="ExamMarks") The RefersTo property must be specified in A1 style notation, including dollar signs where appropriate. The macro recorder generates this code if you use Ctrl and * to select the current region. Sub Macro2() Selection.CurrentRegion.Select ActiveWorkbook.Names.Add Name:="ExamMarks", RefersToR1C1:= _ "=Exams!R1C1:R10C2" End Sub The RefersToR1C1 property is used to reference ranges in a different notation, e.g. R1C6 refers to the first row sixth column of the worksheet, i .e. cell F1. Therefore it is still an absolute reference. You can select a range with a statement like Range("ExamMarks").Select However, the macro recorder records the line above as Application.GoTo Reference:= "ExamMarks" The advantage of using GoTo rather than Select is that if you use GoTo, Excel has the capacity to ‘remember’ the address of the range you start from, therefore there is the potential to make a macro return to a given location on a worksheet. The command for this is Application.GoTo. Type in the following macro. It assumes the range A1:B10 on the worksheet Exams is a range named ExamMarks; the macro will run from anywhere in the workbook. What it does is loop through the exam marks and copies the higher ones and pastes to column F Sub ExtractHighScores() Dim i As Integer i = 1 Worksheets("Exams").Activate Range("ExamMarks").Columns(2).Select ActiveCell.Offset(1, 0).Select Do If ActiveCell.Value > 50 Then ActiveCell.Copy Application.Goto Reference:="R" & i & "C6" ActiveSheet.Paste i = i + 1 Application.CutCopyMode = False Application.Goto End If ActiveCell.Offset(1, 0).Select Loop Until ActiveCell = "" End Sub ‘select 2nd column of range ‘ select next cell down ‘ i is 1 to start with; concatenate i to build a reference to location where we want to paste. i must be incremented for next pass of loop ‘ returns to cell that was copied from If you want to copy and paste both the name and mark to the new location there is only one alteration to make. The line ActiveCell.Copy (line 9) becomes Range(Selection, Selection.End(xlToLeft)).Copy It’s useful to use F8 to step through this macro and watch it work. 2 Use the macro recorder to make a chart Click the Insert tab on the ribbon and start recording a macro. Click the Column button and choose the first 2D column chart in the window that follows. Stop recording, delete the chart, but keep the code. The code has potential for re-use if you change the reference to the range. The Excel 2007 code is shorter than what follows, but I’ve retained the code from the Chart Wizard so you can see the way to change the Chart Title and remove the legend. Sub MakeChart() Selection.CurrentRegion.Select Charts.Add ActiveChart.ChartType = xlColumnClustered ActiveChart.SetSourceData Source:=Sheets("Exams").Range("A1:B10"), PlotBy:= _ xlColumns ActiveChart.Location Where:=xlLocationAsObject, Name:="Exams" With ActiveChart .HasTitle = True .ChartTitle.Characters.Text = "Exam Marks" .Axes(xlCategory, xlPrimary).HasTitle = False .Axes(xlValue, xlPrimary).HasTitle = False End With ActiveChart.HasLegend = False End Sub We are close to the point of being able to extract the high exam scores, paste them to another region, and chart them. First though we need a routine that can interact with the user and define a named range. A procedure to interactively define a range Sub NameCurrentRegion() Dim r As Range Dim myName As String Set r = ActiveCell.CurrentRegion myName = InputBox("The current region is: " & r.Address _ & vbCr & "Enter name for this range: ", , "myRange") Names.Add Name:=myName, RefersTo:="=" & r.Address End Sub (Reference: Financial Modelling, S. Beninga, MIT Press) If say you manually replace the reference to Range("A1:B10") in the MakeChart macro with the range name HighScores you could now put it all together as follows: Sub ExtractAndChart() ExtractHighScores Application.Goto Reference:="R1C6" NameCurrentRegion MakeChart End Sub ‘ keyword Call is optional The limitation here is you would have to enter HighScores into the Input box during the running of NameCurrentRegion. See next page for how to remove this irritation. 3 Module level variables Use module level variables only when two or more subs need access to the same variable; otherwise it’s better practice to use variables local to the procedure they are in. To declare a module level variable, you declare it in the Declarations section (where the Option statements are), before any subs. You can use Dim as previously, but it is recommended you use the keywords Private or Public to determine the scope of the variable. Private variables are accessible only to the subs in the same module, Public variables are accessible to all the subs in the workbook. Here is a basic example using a global variable: Option Explicit Private x As Integer Sub one() x = 1 MsgBox x End Sub Sub two() x = x + 1 MsgBox x End Sub Sub runthisprog() Call one Call two End Sub Here’s how using a module level variable can make the programs run smoothly in the previous example. At the top of the module declare a module level variable: Option Explicit Public rName As String This variable can now be shared by all the subs in the module In the makechart code use the module level variable: ActiveChart.SetSourceData Source:=Sheets("Exams").Range(rName) In the namecurrentregion code assign the new range to the variable: rName = InputBox("The . . . Names.Add Name:=rName, RefersTo:="=" & r.Address 4 Forms/Named Ranges Example The following looks at more issues, including updating the name of a range after editing. The objects appear on the worksheet Data in the file week10.xls Design Issues Create a form with two text boxes, one to take a Customer ID, the other a Customer Name Automatically generate a Customer ID – the next number up set text box’s Enabled property to False to disallow editing Update the named range after input do we have a header row? looks better but complicates things some data below the named range which we don’t want to overwrite Existing ranges on the worksheet: CustIDs, Customers, VAT Appearance of the form: Code attached to Initialize event generates an autonumber: Private Sub UserForm_Initialize() txtCustID = WorksheetFunction.Max(Range("CustIDs")) + 1 End Sub Code for the Click event of the button: Private Sub cmdOK_Click() Dim Numrows As Long Numrows = Range("Customers").Rows.Count Range("Customers").Rows(Numrows + 1).Select ActiveCell.Value = txtCustName ActiveCell.Offset(0, -1) = CInt(txtCustID) ActiveWorkbook.Names.Add Name:="Customers", RefersToR1C1:= _ "=Data!R2C4:R" & Numrows + 2 & "C4" ActiveWorkbook.Names.Add Name:="CustIDs", RefersToR1C1:= _ "=Data!R2C3:R" & Numrows + 2 & "C3" Range("VAT").Insert Unload Me End Sub 5