Reputation: 162
I have written below code, which works as intended. But I could probably make it more efficient, I believe that I've avoided using "select", but is there something else I should try to avoid?
The macro takes a couple of seconds to run, is there any way to make it quicker? When I run it, the screen "flickers" quite a lot, is there something I can do about that? The file is quite big, so maybe introducing something that stop calculations?
Sub ActivateUser(User As String)
Call LoadVariables
Call UnProtectSheet("Master")
LoadedUser = User
Dim Cell As Range
On Error Resume Next
For Each Cell In ShowSheets
Call UnProtectSheet(Cell.Value)
Worksheets(Cell.Value).Visible = IIf(Cell.Offset(1, 0) = "YES", True, False) 'moving down one row to check if it should be showing or not,
If Cell.Value = "Currencies" Then ' This is done to avoid hiding column A in the "currencies" sheet
Else
Worksheets(Cell.Value).Range("A:A").EntireColumn.Hidden = IIf(Cell.Offset(1, 0) = "YES", True, False) 'Showing or hiding column A
End If
Worksheets(Cell.Value).Range(FurthestColumn).EntireColumn.Hidden = IIf(Cell.Offset(1, 0) = "YES", False, True) 'Showing or hiding all other columns
If (User = "AdminEdit") Or (User = "adminedit") Or (User = "Adminedit") Or (User = "adminEdit") Then
Else
Call HideSpecificColumns(Cell.Value)
Call ProtectSheet(Cell.Value)
End If
Next Cell
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
ws.Activate
ActiveWindow.ScrollColumn = 1
ActiveWindow.ScrollRow = 1
Next ws
Call UnProtectSheet("Basic Data")
FirstTimeOpenToday = "Yes"
FirstTimeOpen = "Yes"
Call ProtectSheet("Basic Data")
Call HideSheet("LoginPage")
Sheets("Quotation").Select
Range("B1").Select
End Sub
Sub HideSpecificColumns(ws As String)
Dim Fixed1Sub As String
Dim Fixed2Sub As String
Dim SubToCall As String
'Can't contain any special characters, i'm only taking "-" and " " into account now.
Fixed1Sub = WorksheetFunction.Substitute(ws, " ", "")
Fixed2Sub = WorksheetFunction.Substitute(Fixed1Sub, "-", "")
SubToCall = Fixed2Sub & "Columns"
Application.Run SubToCall
End Sub
This is how I set "Global variables". So I only have to change these things in one place.
Global ShowSheets As Range
Global LoadedUser As Range
Global UsertypeListBoxNamesFirstValue As Range
Global UsertypeListBoxNamesCount As Integer
Global FurthestColumn As String
Global LockPassword As String
Global CombinationRange As Range
Global CustomerPlace As Range
Global ProjectNamePlace As Range
Global DesignNumberPlace As Range
Global SalesPersonPlace As Range
Global YourNamePlace As Range
Global FirstTimeOpen As Range
Global FirstTimeOpenToday As Range
Global BuildNumber As Range
Sub LoadVariables()
'General
'======================================================================================================================================================
LockPassword = "Admin123"
Set CombinationRange = Worksheets("Master").Range("HS8:HS20") ' Range whereas all correct login are to activate users.
Set FirstTimeOpen = Worksheets("Basic Data").Range("S4") ' Displays "Yes" or "No" depending on if this is the first time the files is opened
Set FirstTimeOpenToday = Worksheets("Basic Data").Range("S7") ' Displays "Yes" or "No" depending on if this is the first time the files is opened today.
Set BuildNumber = Worksheets("Basic Data").Range("N193")
'======================================================================================================================================================
'Macro to show/hide sheets based on User
'======================================================================================================================================================
Set ShowSheets = Worksheets("Master").Range("GR19:HL19") 'Pick the range with the name of the sheets, the range above the yes or no. (For the macro to hide and show sheets based on User)
Set LoadedUser = Worksheets("Master").Range("HN22") ' Cell with the name of the loaded user.
FurthestColumn = "B:KE" ' When not open, this field is what is hidden on every sheet, must be larger than the largest one.
'======================================================================================================================================================
'Loginsheet
'======================================================================================================================================================
Set UsertypeListBoxNamesFirstValue = Sheets("Master").Range("HO10") ' This should be the first cell that has a value of the list of 'User' to login with.
UsertypeListBoxNamesCount = Sheets("Master").Range("HO19") ' This should be the cell with the count of how many users there are.
'======================================================================================================================================================
'StartPage
'======================================================================================================================================================
Set CustomerPlace = Sheets("Quotation").Range("D5")
Set ProjectNamePlace = Sheets("Quotation").Range("I2")
Set DesignNumberPlace = Sheets("Quotation").Range("I4")
Set SalesPersonPlace = Sheets("Quotation").Range("I5")
Set YourNamePlace = Sheets("Quotation").Range("I6")
'======================================================================================================================================================
End Sub
This is some of my "general" formulas I use.
'Unprotecting and protecting sheets
'===================================================================
Sub UnProtectSheet(Sheet As String)
Call LoadVariables
Dim ws As Worksheet
Set ws = Sheets(Sheet)
ws.Unprotect LockPassword
End Sub
Sub ProtectSheet(Sheet As String)
Call LoadVariables
Dim ws As Worksheet
Set ws = Sheets(Sheet)
ws.Protect LockPassword, AllowFiltering:=True
End Sub
'===================================================================
'Hiding and unhiding sheets
'===================================================================
Sub UnHideSheet(Sheet As String)
Worksheets(Sheet).Visible = True
End Sub
Sub HideSheet(Sheet As String)
Worksheets(Sheet).Visible = False
End Sub
'===================================================================
'Opening StandardUserforms
'===================================================================
Sub OpenLoginForm()
Loginform.show
End Sub
Sub OpenStartForm()
Startpage.show
End Sub
'===================================================================
'Testing Attempts to login
'===================================================================
Function UnlockWall(Combination As String) As Boolean
Dim Key As Integer
Call LoadVariables
UnlockWall = False
If Combination = "/" Then
Exit Function
End If
On Error Resume Next
Key = WorksheetFunction.Match(Combination, CombinationRange, 0)
If Key = 0 Then
Else
UnlockWall = True
End If
End Function
'===================================================================
'First time opening and logging in to the workbook, do this:
'===================================================================
Sub PopulateWithStartPage(A As String, B As String, C As String, D As String, E As String)
Call LoadVariables
Call UnHideSheet("Quotation")
Call UnProtectSheet("Quotation")
YourNamePlace = A
ProjectNamePlace = B
DesignNumberPlace = C
SalesPersonPlace = D
CustomerPlace = E
Call UnHideSheet("Basic Data")
Call UnProtectSheet("Basic Data")
FirstTimeOpen = "Yes"
FirstTimeOpenToday = "Yes"
Call ProtectSheet("Basic Data")
End Sub
'===================================================================
I do also have these subs, which I use to hide specific columns on each sheet. I've not included all of these, but one sub exist per sheet I'm looping through in the "activateuser sub".
Sub MasterColumns() 'Can't contain any special characters, i'm only taking "-" and " " into account now.
'exit sub 'Turn on/off depending on if the sheets includes any columns that should be hidden
Dim ColumnArray(6) As String 'The number within the "()" is the maximum amount of column that can be hidden. May not be lower then used below.
ColumnArray(0) = "D:G" ' Change as per columns you want hidden, note that it has to be in the "X:X" format.
ColumnArray(1) = "J:M"
ColumnArray(2) = "P:S"
ColumnArray(3) = "W:AJ"
ColumnArray(4) = "AM:AP"
ColumnArray(5) = "AS:AW"
'ColumnArray(6) = ""
'ColumnArray(7) = ""
'ColumnArray(8) = ""
'ColumnArray(9) = ""
'ColumnArray(10) = ""
'ColumnArray(11) = ""
'ColumnArray(12) = ""
'ColumnArray(13) = ""
'ColumnArray(14) = ""
'ColumnArray(15) = ""
'ColumnArray(16) = ""
'ColumnArray(17) = ""
'ColumnArray(18) = ""
'ColumnArray(19) = ""
'ColumnArray(20) = ""
Dim i As Integer
For i = 0 To 5 ' ================== This has to be one less than above number
Sheets("Master").Range(ColumnArray(i)).EntireColumn.Hidden = True '=========== Change to correct sheet
Next i
End Sub
Sub BasicDataColumns() 'Can't contain any special characters, i'm only taking "-" and " " into account now.
Exit Sub 'Turn on/off depending on if the sheets includes any columns that should be hidden
End Sub
Sub ErrorFinderColumns() 'Can't contain any special characters, i'm only taking "-" and " " into account now.
Exit Sub 'Turn on/off depending on if the sheets includes any columns that should be hidden
End Sub
Upvotes: 1
Views: 1922
Reputation: 149295
The macro takes a couple of seconds to run, is there any way to make it quicker?
In that case, my advise is not to touch your macro. If it ain't broke, don't fix it. Most of us actually thrive to reach this "ecstatic state" :D
If it is just a learning excercise then yes, you can make these few changes and it will optimize your code. Not in terms of performance, but in terms of writing concise code. You can obvioulsy take care fo few minor tweaks like flicker etc.
Here are my suggestions
Avoid the reckless use of On Error Resume Next
. It is like telling Excel to shut up :). Instead do proactive programming. Pre-empt and handle the errors.
Switch off Events. However, whenever you are switching off events, use error handling to turn it back on, else if you get an error, the code will not run the next time. Also ensure that you do not tinker with user settings. Change as you need and then reset them when done as @PaulOgilvie suggested. To avoid the flicker you can use Application.ScreenUpdating = False
Here is an example
Sub Sample()
On Error GoTo Whoa
Dim PrevCalcMode As Integer
Dim PrevScrUp As Boolean
'~~> Get previous settings
PrevCalcMode = Application.Calculation
PrevScrUp = Application.ScreenUpdating
'~~> Set your new settings
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Application.ScreenUpdating = False
'
'~~> Your code
'
Letscontinue:
'~~> Reset to original settings
Application.EnableEvents = True
Application.ScreenUpdating = PrevScrUp
Application.Calculation = PrevCalcMode
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
Instead of using Global variable and then loading the range into those variable, use Named Ranges
For example, you do not need Set CombinationRange = Worksheets("Master").Range("HS8:HS20")
. You can name the range HS8:HS20
in Master
as CombinationRange
as shown in the link above and then use Range("CombinationRange")
in your code.
Instead of creating a Global variable Global LockPassword As String
and then setting it's value LockPassword = "Admin123"
, directly declare it as Public LockPassword As String = "Admin123"
at the very top.
Avoid calling procedures unnecessarily. It increases execution time. For example you are calling Call LoadVariables
in almost every sub.
Use With End With
. I will not go into details here. @Moosli has already covered that.
Upvotes: 2
Reputation: 3275
There are a few rules for VBA that you can use to make your code faster. And which you should always have in mind when writing new code.
Rule #1. Don't Copy and Paste
The Copy and Paste (or PasteSpecial) functions are slow. It is about 25 times faster to use the following to copy and paste values.
Range("A1:Z100").value = Range("A101:Z200").value
If you are doing it this way your Code will Probably work. There is maybe a problem with the Mamory if your are doing this on to many Rows.
Rule #2. Calculation
Normally, Excel will recalculate all cells, formulas and conditional formatting if a value in a cells or ranges have changed. This may cause your workbook to recalculate too often, which will slow down performance. You can prevent Excel from recalculating the workbook by using the statement:
Application.Calculation = xlCalculationManual
At the end of your code, you can set the calculation mode back to automatic with the statement:
Application.Calculation = xlCalculationAutomatic
Remember, though, that when the calculation mode is xlCalculationManual, Excel doesn't update values in cells. If your macro relies on an updated cell value, you must force a Calculate event, with the .Calculate method like Worksheets(1).Calculate
.
Rule #3. ScreenUpdating
As the colleagues have already mentioned in the comments, you can already get a lot out of this command.
The Problem with VBA is, every time VBA writes data to the worksheet it refreshes the screen image that you see. Refreshing the image is a considerable drag on performance. The following command turns off screen updates.
Application.ScreenUpdating = FALSE
At the end of the macro use the following command to turn screen updates back on.
Application.ScreenUpdating = TRUE
Rule #4 Ignore Events
If you have a Worksheet_Change event implemented for the Sheet1 of your workbook. Any time a cell or range is altered on the Sheet1, the Worksheet_Change event will run. So if you have a standard macro that manipulates several cells on Sheet1, each time a cell on that sheet is changed, your macro has to pause while the Worksheet_Change event runs. You can imagine how this behavior would slow down your macro.
Application.EnableEvents = False
At the end of your code, you can set the EnableEvents mode back to True with the statement:
Application.EnableEvents = True
Rule #5 With statement
When recording macros, you will often manipulate the same object more than once. You can save time and improve performance by using the With statement to perform several actions on a given object in one shot.
The With statement utilized in the following example tells Excel to apply all the formatting changes at one time:
With Range("A1").Font
.Bold = True
.Italic = True
.Underline = xlUnderlineStyleSingle
End With
Exmaple for the With Statement:
Sub Normal()
Application.ScreenUpdating = False
Dim StartTime As Double
Dim MinuteElapsed As String
StartTime = Timer
For i = 1 To 100
For y = 1 To 500
ActiveSheet.Cells(i, y).Value = i * y
ActiveSheet.Cells(i, y).Font.Bold = True
ActiveSheet.Cells(i, y).Font.Size = 16
ActiveSheet.Cells(i, y).Interior.Color = RGB(255, 255, 0)
Next y
Next i
MinuteElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
Application.ScreenUpdating = True
MsgBox (MinuteElapsed)
End Sub
This Code takes about 18 Seconds with my Computer.
Sub WithStatment()
Application.ScreenUpdating = False
Dim StartTime As Double
Dim MinuteElapsed As String
StartTime = Timer
For i = 1 To 100
For y = 1 To 500
With ActiveSheet.Cells(i, y)
.Value = i * y
.Font.Bold = True
.Font.Size = 16
.Interior.Color = RGB(255, 255, 0)
End With
Next y
Next i
MinuteElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
Application.ScreenUpdating = True
MsgBox (MinuteElapsed)
End Sub
The Same Code with the With Statment Takes about 15 to 16 Seconds on my Computer.
Getting into the habit of chunking actions into With statements will not only keep your macros running faster but also make it easier to read your macro code.
Upvotes: 2