How i can speedUP my events based procedure? - performance

i have huge problem with my event procedure, it takes ages to run when i want to change more than few cells at once. How it works, well when user changes data in cell the Worksheet_Change adds comments, but first the Worksheet_SelectionChange updates informations for user (i have sumifs in different worksheet where it calculates ACT date for 12 months, and then it display via camer tool on active worksheet).
In know that problem is cuz of constant looping through events.... duno what to do ?!
Thx for help!
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim cell As Range
ActiveSheet.Unprotect Password:="xyz"
For Each cell In Target
If cell.Row > 21 And cell.Column > 9 Then
If cell.Comment Is Nothing Then
cell.AddComment Now & " - " & cell.Value & " - " & Application.UserName
Else
If Val(Len(cell.Comment.Text)) > 255 Then
cell.Comment.Delete
cell.AddComment
cell.Comment.Text _
Now & " - " & cell.Value & " - " & Application.UserName, 1 _
, False
Else
cell.Comment.Text _
vbNewLine & Now & " - " & cell.Value & " - " & Application.UserName, Len(cell.Comment.Text) + 1 _
, False
End If
End If
cell.Comment.Shape.TextFrame.AutoSize = True
End If
Next cell
ActiveSheet.Protect Password:="11opkLnm890", AllowFiltering:=True
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim RowNumber As Long, i As Long
Dim MaxRowNumber As Long
MaxRowNumber = Range("A9").Value
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
RowNumber = Target.Row
Set sh_AUXILIARY_PT = ThisWorkbook.Worksheets("AUXILIARY_PT")
If Target.Row > 21 And Target.Row < MaxRowNumber Then
sh_AUXILIARY_PT.Range("AA4").Value = Cells(RowNumber, 1).Value
sh_AUXILIARY_PT.Range("AB4").Value = Cells(RowNumber, 2).Value
sh_AUXILIARY_PT.Range("AC4").Value = Cells(RowNumber, 3).Value
sh_AUXILIARY_PT.Range("AD4").Value = Cells(RowNumber, 4).Value
For i = 14 To 25
sh_AUXILIARY_PT.Cells(8, i).Value = Cells(RowNumber, i - 4).Value
Next i
End If
Application.EnableEvents = True
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

well, you may consider assigning your collection range to an Array and then loop through since Arrays are much faster.

Related

Excel-VBA: High performance sheet update

I have a working code that updates cell entries in Sheet "B" with reference to entries in Sheet "A". However, the code is slow (the tables are really huge). Can anybody show me how to do it more efficiently?
Sub UpdateList()
Dim i As Integer
Dim c As Range
Application.ScreenUpdating = False
For i = 1 to 1000
For Each c in Sheets("A").Range("C8:C10000").Cells
If c.value = i Then
Sheets("B").Range("D" & i) = Sheets("B").Range("D" & i).value & "; " _
& Sheets("A").Range("X" & c.Row).value
End if
Next c
Next i
Application.ScreenUpdating = True
End
I am curious what I could do better. Thanks!

VBA row operations take too long to execute

So I have the script below in Execl VB that goes through the rows and deletes the ones that don't contain a certain keyword.
Sub Main()
RowsDeleted = 0
Keyword = "COLA"
For i = 2 to ActiveSheet.UsedRange.Rows.Count
If InStr(Cells(i, 1).Value, Keyword) = 0 Then
Rows(i).Delete
RowsDeleted = RowsDeleted + 1
i = i - 1
End If
Next i
MsgBox("Rows Deleted: " & RowsDeleted)
End Sub
The problem is that this script takes a very long time to execute (around 8 minutes for ~73000 entries). Why is that and how would I go about improving it?
no offense to other answer, but this will only help with troubleshooting.
what you need to do is remove the the line of code
Rows(i).Delete
inside the (potentially) long running For loop is what is causing the slow down.
you need to re-write it like this...
Sub Main()
RowsDeleted = 0
Keyword = "COLA"
Dim rng As Excel.Range
Dim arr() As Variant
Dim str As String
arr = ActiveSheet.UsedRange
Dim R As Long
For R = 1 To UBound(arr, 1) ' First array dimension is rows.
If InStr(arr(R, 1), Keyword) = 0 Then
If str <> "" Then
str = "," & str
End If
str = str & arr(R, 1).Address
End If
Next R
Set rng = ActiveSheet.Range(str)
RowsDeleted = rng.Rows.Count
rng.Delete
MsgBox ("Rows Deleted: " & RowsDeleted)
End Sub
It takes ages may due to formulas in your cells that are going to be deleted.
What you should do is to turn off auto calculation and Clear the contents of that row before delete. Also you should start from bottom up!
Try this:
Sub Main()
Dim lMode As Long
' Store initial state of Calculation mode
lMode = Application.Calculation
' Change to Manual Calculation
Application.Calculation = xlCalculationManual
' Disable screen update
Application.ScreenUpdating = False
RowsDeleted = 0
Keyword = "COLA"
' Start from bottom up!
For i = ActiveSheet.UsedRange.Rows.Count To 2 Step -1
If InStr(Cells(i, 1).Value, Keyword) = 0 Then
Rows(i).ClearContents
Rows(i).Delete
RowsDeleted = RowsDeleted + 1
End If
Next i
' Restore screenupdate and calculation mode
Application.ScreenUpdating = True
Application.Calculation = lMode
MsgBox ("Rows Deleted: " & RowsDeleted)
End Sub
Here could be something to look at,
It filters Column A for cells <>"Cola" and clears them
It then sorts column A so the blank cells in column A are now at the bottom
It then deletes the blank rows.
Not knowing the setup of the OP's ws, there may have to be adjustments made.
On my sample sheet I use 81,000 rows, when I run the code it takes about 5 seconds.
Sub SomeDeleteCode()
Dim Rws As Long, Rng As Range, nwRw As Long
Rws = Cells(Rows.Count, "A").End(xlUp).Row
Application.ScreenUpdating = 0
Application.Calculation = xlCalculateManual
Columns("A:A").AutoFilter Field:=1, Criteria1:="<>*Cola*"
Set Rng = Range(Cells(2, 1), Cells(Rws, 1)).SpecialCells(xlCellTypeVisible)
Rng.ClearContents
ActiveSheet.AutoFilterMode = 0
Columns(1).Sort Key1:=Range("A1"), Header:=xlYes
nwRw = Cells(Rows.Count, "A").End(xlUp).Row
Range(Range("B" & nwRw + 1), Range("B" & nwRw + 1).End(xlDown)).EntireRow.Delete
Application.Calculation = xlCalculationAutomatic
End Sub
Amend your code to look like this:
Sub Main()
On Error Goto ErrHandler
Application.ScreenUpdating = False
RowsDeleted = 0
Keyword = "COLA"
For i = ActiveSheet.UsedRange.Rows.Count to 2
If InStr(Cells(i, 1).Value, Keyword) = 0 Then
Rows(i).Delete
RowsDeleted = RowsDeleted + 1
' i = i - 1 ' -- manually changing the loop counter is a bad idea
End If
Next i
MsgBox("Rows Deleted: " & RowsDeleted)
EndSub:
Application.ScreenUpdating = True
exit sub
ErrHandler:
' Error handling here
resume EndSub
End Sub
The error handler is required to ensure that the ScreenUpdating is restored, even in case of an error.

vb6 Compare times in list with current time

I'm making form where i need to enter the time's for each period like.
Start time - End time
Start time - End time
Start time - End time
Those record's could be saved in listbox as in my example. On another form i have 3 labels
CurrentTime, TimePassed, TimeLeft, and a Timer which ticks in interval 1second. So the time in timepassed ticking up, time left ticking down, and current time show correct time while application is opened.
In final it looks like this
Private Sub cmdAdd_Click()
Call addp
End Sub
Function addp(Optional ByVal t As String)
Dim s As String, e As String
If t = "" Then s = Trim(InputBox("Start time (hh:mm AM/PM):")) Else: s = Trim(Split(t, "-")(0))
If s = "" Or InStr(1, s, " AM") = 0 And InStr(1, s, " PM") = 0 Then Exit Function
If chk(s) = False Then Exit Function
If t = "" Then e = Trim(InputBox(s & "-?" & vbCrLf & "End time:")) Else: e = Trim(Split(t, "-")(1))
If e = "" Or InStr(1, e, " AM") = 0 And InStr(1, e, " PM") = 0 Then Exit Function
If e = s Then Exit Function
If chk(e) = False Then Exit Function
If Format$(Split(s, "-")(0), "hh:mm AM/PM") > Format$(Split(e, "-")(0), "hh:mm AM/PM") Then Exit Function
If lstPeriods.List(0) <> "" Then
If Format$(Split(lstPeriods.List(lstPeriods.ListCount - 1), "-")(1), "hh:mm AM/PM") > Format$(Split(s, "-")(0), "hh:mm AM/PM") Then Exit Function
End If
lstPeriods.AddItem lstPeriods.ListCount + 1 & ". " & s & "-" & e
If frmMain.lblPeriod.Caption = "" Then Call snd(s & "-" & e, lstPeriods.ListCount)
End Function
For check
Function chk(ByVal st As String) As Boolean
st = Replace$(Replace$(st, " AM", ""), " PM", "")
If UBound(Split(st, ":")) <> 1 Then Exit Function
For i = 0 To 1
If IsNumeric(Split(st, ":")(i)) = False Then Exit Function
If Len(Split(st, ":")(i)) <> 2 Then Exit Function
If Split(st, ":")(i) < 0 Then Exit Function
Next
If Split(st, ":")(0) > 12 Then Exit Function
If Split(st, ":")(1) > 59 Then Exit Function
chk = True
End Function
The solution i gave is the only solution which beginner as i I had. And i know it's confusing and very slow. There is no way this can be finished by using trim/split/format because it require for a lot of modification.
Searching for easier solution.
Sp i need to compare the current time on computer with the time person enetered in textbox/listbox how can i do that .
To Run this code you need to add 5 Controls to the Form1
lblSystemTime = Label Control
lblTimeLeft = Label Control
lblTimePassed = Label Control
lblPeriod = Label Control
tmrSystemTime = Timer Control
Dim Periods()
Private Sub Form_Load()
Periods = Array( _
"06:00 PM-07:00PM", _
"07:01 PM-08:00PM", _
"09:00 PM-10:00PM", _
"1AM-2AM" _
)
End Sub
Private Sub tmrSystemTime_Timer()
lblSystemTime.Caption = FormatDateTime(Now, vbLongTime)
Dim OnPeriod As Integer
OnPeriod = GetPeriod()
If OnPeriod < 0 Then
lblTimeLeft.Caption = vbNullString
lblTimePassed.Caption = vbNullString
lblPeriod.Caption = "Unknown Period"
Else
lblPeriod = CStr(OnPeriod + 1) & ". period"
lblTimeLeft.Caption = "Time Left: " & Format( _
DateAdd("s", _
DateDiff("s", _
CDate(lblSystemTime.Caption), _
CDate(Split(Periods(OnPeriod), "-")(1))), _
CDate("0") _
), _
"nn:ss" _
)
lblTimePassed.Caption = "Time Passed: " & Format( _
DateAdd("s", _
DateDiff("s", _
CDate(Split(Periods(OnPeriod), "-")(1)), _
CDate(lblSystemTime.Caption)), _
CDate("0") _
), _
"nn:ss" _
)
End If
End Sub
Private Function GetPeriod() As Integer
Dim ICount As Integer
For Each Pr In Periods
If CDate(Split(Pr, "-")(0)) <= CDate(lblSystemTime.Caption) And _
CDate(Split(Pr, "-")(1)) >= CDate(lblSystemTime.Caption) Then
GetPeriod = ICount
Exit Function
End If
ICount = ICount + 1
Next
GetPeriod = -1
End Function
Here is something which may help:
Private Sub TimeCheck(byval int_TimeNow as Integer)
dim intTimeThen as string 'set a variable to compare for the THEN time
dim intTimeNow as string 'set a variable to compare for the NOW time
dim intDiff as string ' set a variable for the difference in time
intTimeNow = int_TimeNow 'just to ensure we don't mess with any data
intTimeThen = val(txtTimeThen.text) 'get the numeric value of the time, the system will
'convert it from a string to the value here.
intDiff = intTimeThen - intTimeNow 'Do the math
lstTimeEvents.additem timevalue(intDiff) 'write it to a listbox
End Sub
I realize this is not the coding convention you are using, but it should demonstrate what you are looking for to some degree.

Speeding Up Code that Removes Hidden Rows on a Sheet

Below I have some code that I have written. It is compeletely effective and gives no errors. However, it is very, very slow. The sub takes a given sheet with a table on it and checks for hidden rows. If all the rows are hidden, it deletes the sheet. If not, then it deletes all the hidden rows.
This is run in another sub, where all things like screenupdating and events are disabled.
I have researched common ways to speed up code (here: How to improve the speed of VBA macro code?, here: http://www.databison.com/how-to-speed-up-calculation-and-improve-performance-of-excel-and-vba/, and here: http://www.ozgrid.com/VBA/SpeedingUpVBACode.htm), but haven't been able to apply too many of them.
Please take a look and let me know what you think I could do to speed this up. If there are any other proper coding mistakes I have made, please let me know those as well.
Thanks!
Sub RhidRow(ByVal count4 As Double) 'count 4 is the total number of possible rows
Dim count6, count1, count9 As Double 'counters to be used
count6 = 2 'begin on row two
count1 = 0 'check for visible rows counter
With ActiveSheet
While count6 < count4
DoEvents
Application.StatusBar = "Checking row " & count6 & " of " & count4 & "."
If Range("A" & CStr(count6)).EntireRow.Hidden = False Then
count1 = count1 + 1 'if there was a visible row, then add one
End If
count6 = count6 + 1 'move to next row to check
Wend
Range("N7") = count6 'so I can hand check results
If count1 = 0 Then 'if there were no visible rows, then set Z1 to 1 and exit
Range("Z1").Value = 1 'to error check in another sub. if Z1=1, then delete
Exit Sub
End If
count6 = 2 'start on row 2
count9 = 1 'count 9
While count9 < count4 'while the row is less than the count of the total rows
DoEvents
Application.StatusBar = count6 & " or " & count9 & " of " & count4
If Range("A" & CStr(count6)).EntireRow.Hidden = True Then
Range("A" & CStr(count6)).EntireRow.Delete 'if row is hidden, delete
Else
count6 = count6 + 1 'if it is not hidden, move to the next row
End If
count9 = count9 + 1 'show what row it is on in the status bar
Wend
End With
End Sub
I have made the change suggested in the comments and gotten rid of ActiveSheet. The speed was unaffected.
Sub RhidRow(ByVal count4 As Double, shtO As Object) 'count 4 is the total number of possible rows
Dim count6, count1, count9 As Double 'counters to be used
count6 = 2 'begin on row two
count1 = 0 'check for visible rows counter
With shtO
While count6 < count4
DoEvents
Application.StatusBar = "Checking row " & count6 & " of " & count4 & "."
If Range("A" & CStr(count6)).EntireRow.Hidden = False Then
count1 = count1 + 1 'if there was a visible row, then add one
End If
count6 = count6 + 1 'move to next row to check
Wend
Range("N7") = count6 'so I can hand check results
If count1 = 0 Then 'if there were no visible rows, then set Z1 to 1 and exit the sub
Range("Z1").Value = 1 'this is used to error check in another sub. if Z1 is 1, then the sheet is deleted
Exit Sub
End If
count6 = 2 'start on row 2
count9 = 1 'count 9
While count9 < count4 'while the row is less than the count of the total rows
DoEvents
Application.StatusBar = "Deleting hidden rows. " & count6 & " or " & count9 & " of " & count4 & " done."
If Range("A" & CStr(count6)).EntireRow.Hidden = True Then
Range("A" & CStr(count6)).EntireRow.Delete 'if the row is hidden, delete it
Else
count6 = count6 + 1 'if it is not hidden, move to the next row
End If
count9 = count9 + 1 'show what row it is on in the status bar
Wend
End With
End Sub
Maybe something like this:
Sub RhidRow(ByVal count4 As Double) 'count 4 should be a Long, not Double
Dim count1 As Long 'counters to be used
Dim ws As Worksheet
Dim rngVis As Range
Dim rngDel As Range
Set ws = ActiveSheet
On Error Resume Next
Set rngVis = ws.Range("A2:A" & count4).SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If rngVis Is Nothing Then
ws.Range("Z1").Value = 1
Else
For count1 = count4 To 2 Step -1
If ws.Rows(count1).Hidden = True Then
If rngDel Is Nothing Then
Set rngDel = ws.Rows(count1)
Else
Set rngDel = Union(rngDel, ws.Rows(count1))
End If
End If
Next count1
If Not rngDel Is Nothing Then
Application.DisplayAlerts = False
Intersect(rngDel, rngDel.ListObject.DataBodyRange).Delete 'if row is hidden, delete
Application.DisplayAlerts = True
End If
End If
End Sub
This might be a bit quicker:
Sub RowKleaner()
Dim rBig As Range, r As Range, rDelete As Range
ActiveSheet.UsedRange
Set rBig = Intersect(ActiveSheet.UsedRange, Range("A:A"))
Set rDelete = Nothing
For Each r In rBig
If r.EntireRow.Hidden = True Then
If rDelete Is Nothing Then
Set rDelete = r
Else
Set rDelete = Union(rDelete, r)
End If
End If
Next r
If Not rDelete Is Nothing Then
rDelete.EntireRow.Delete
End If
End Sub
the below will delete the sheet (or flag I left the logic for you to decide) if all rows are hidden, or will delete only the hidden rows if not:
Dim rngData As Range, rngVisible As Range, rngHidden As Range
Set rngData = Range("C8:H20")
Set rngVisible = rngData.SpecialCells(xlCellTypeVisible)
Set rngHidden = Range("A:A")
If (rngVisible Is Nothing) Then
' delete sheet or flag
Else
' invert hidden / visible
rngHidden.Rows.Hidden = False
rngVisible.Rows.Hidden = True
' delete hidden and show visible
rngData.SpecialCells(xlCellTypeVisible).Delete
rngVisible.Rows.Hidden = False
End If

How can I make my code running faster? Copy cells from one sheet to another

I've created this code which is copying all values from 'Sheet1' - starting in A2 cell to the first empty row of column 1 in Sheet2.
In case that more cells are needed to be copied, it is running quite long. Is there a possibility to make it run faster?
Thanks
Sub CopyCells()
Dim CopyRow As Long
CopyRow = Sheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Row 'find last first empty cell in destination sheet
'Sheets("Sheet1").Range("A2").Copy Destination:=Sheets("Sheet2").Range("A" & CopyRow + 1)
Call turn_on_off(False)
For I = 2 To Sheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row
Sheets("Sheet1").Range("A" & I).Copy Destination:=Sheets("Sheet2").Range("A" & CopyRow + I - 1)
Next I
Call turn_on_off(True)
End Sub
Public Sub turn_on_off(mode As Boolean)
With Application
.Calculation = IIf(mode = True, xlCalculationAutomatic, xlCalculationManual)
.ScreenUpdating = mode
End With
End Sub
There is no need to use loop:
Sub CopyCells()
Dim CopyRow As Long
Dim lastrow As Long
Dim sh1 As Worksheet, sh2 As Worksheet
Call turn_on_off(False)
Set sh1 = ThisWorkbook.Worksheets("Sheet1")
Set sh2 = ThisWorkbook.Worksheets("Sheet2")
lastrow = sh1.Cells(Rows.Count, 1).End(xlUp).Row
CopyRow = sh2.Cells(Rows.Count, 1).End(xlUp).Row
sh2.Range("A" & CopyRow + 1).Resize(lastrow - 1).Value = _
sh1.Range("A2:A" & lastrow).Value
Call turn_on_off(True)
End Sub
Range.Value=Range.Value is much faster then Copy/Paste, however it copies only values (without formatting). If you need to copy formatting as well, change Range.Value=Range.Value part to:
sh1.Range("A2:A" & lastrow).Copy Destination:=sh2.Range("A" & CopyRow + 1)

Resources