I'm writing a code that takes the inputs from a user form and populates different worksheets with the data. I store the data from the user form into variables (string or double) but when I populate the worksheets with the information, the macro gets very slow. It takes about 4 seconds to populate each row with my code. I can't figure out myself how to make the code more efficient.
The data is to be pasted in different worksheets (below the code for one worksheet). Within each worksheet, the data is pasted always within the same row (always the first empty line). The columns are fixed and I cannot change them. I have to paste each variable into a particular column, leaving the other columns unchanged.
Dim ordSheet As Worksheet
Set ordSheet = ThisWorkbook.Sheets("Orders")
ord_cNorder = 1
ord_cDate = 2
ord_cNarticles = 5
ord_cImpBI = 6
ord_cImpTax = 7
ord_cSellingCh = 8
ord_cShippingBI = 9
ord_cShippingTax = 10
ord_cCustomer = 11
ord_cCountry = 12
ord_cCity = 13
ord_cNotes = 14
With ordSheet
ord_writeLine = ordSheet.Cells(Rows.Count, 1).End(xlUp).Row + 1
If Me.CBx_Cont = False Then
.Cells(ord_writeLine, ord_cNorder).Value = Norder
.Cells(ord_writeLine, ord_cDate).Value = dmyDate
.Cells(ord_writeLine, ord_cNarticles).Value = Narticles
.Cells(ord_writeLine, ord_cImpBI).Value = ImpBI
.Cells(ord_writeLine, ord_cImpTax).Value = ImpTax
.Cells(ord_writeLine, ord_cSellingCh).Value = SellingCh
.Cells(ord_writeLine, ord_cShippingBI).Value = ShippingBI
.Cells(ord_writeLine, ord_cShippingTax).Value = ShippingTax
.Cells(ord_writeLine, ord_cCustomer).Value = Customer
.Cells(ord_writeLine, ord_cNotes).Value = Notes
.Cells(ord_writeLine, ord_cCountry).Value = Country
.Cells(ord_writeLine, ord_cCity).Value = City
Else
.Cells(ord_writeLine, ord_cImpBI).Value = ImpBI
.Cells(ord_writeLine, ord_cImpTax).Value = ImpTax
.Cells(ord_writeLine, ord_cShippingBI).Value = ShippingBI
.Cells(ord_writeLine, ord_cShippingTax).Value = ShippingTax
End If
EDIT 04.Jan.21: Improved but still slow
I changed the code with the suggestions from #bugdrown and #Tim Williams:
Using With ordSheet.Rows(ord_writeLine) instead of With ordSheet
Populating a variant array with the userform data and looping through the array to fill the cells
Deactivating screen updating
Application.ScreenUpdating = False
Dim ordSheet As Worksheet
Set ordSheet = ThisWorkbook.Sheets("Orders")
Dim ord_writeLine As Integer
ord_writeLine = ordSheet.Cells(Rows.Count, 1).End(xlUp).Row+1
Dim ordCols As Variant
Dim ordVals As Variant
Dim ordColsCont As Variant
Dim ordValsCont As Variant
ordCols = Array(1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
ordVals = Array(Norder, dmyDate, Narticles, ImpBI, ImpTax, SellingCh, ShippingBI, ShippingTax, Customer, Country, City, Notes)
ordColsCont = Array(6, 7, 9, 10)
ordValsCont = Array(ImpBI, ImpTax, ShippingBI, ShippingTax)
With ordSheet.Rows(ord_writeLine)
If CBx_Cont = False Then
For i = 0 To 11 'I set a timer here to test
.Cells(ordCols(i)).Value = ordVals(i)
Next i
Else
For i = 0 To 3
.Cells(ordColsCont(i)).Value = ordValsCont(i)
Next i
Narticles = Narticles + .Cells(5).Value - 1
.Cells(5).Value = Narticles
End If
Application.ScreenUpdating = True
The total time for this code is now slightly below 3 seconds (25% improvement) but still feels too long.
I put a timer within the if loop and I identified the bottleneck being the first 2 iterations (with aprox. 1.2 seconds each).
The first 2 iterations correspond to Norder (integer) and dmyDate (date).
I can't figure out how to fix it.
Related
I have slow performing procedure, it's job is to format sheets in workbook. Mainly to adjust row heights and column widths, apply number formats for columns, plane freezes and to remove autofilters. It is long, and I have deleted several similar parts to shorten it for stackoverflow, but there are no big loops, excel workbook has only several sheets and I have tried to identify slow performing part with timer, but without luck. Any ideas? Is any of these operations extra slow? It takes around 1 minute and 20 seconds to format sheets. It is quite similar to other procedures that are working with data in cells, seems to long, for cosmetic adjustments.
'Procedure to format sheets
Private Sub FormatSheets()
Dim ShHead(1 To 22) As Variant
Dim ShHead2(1 To 19) As Variant
Dim i As Long
Dim Sh As Worksheet
'Creates array of column Headers for sheets "Data", "Process", "Delete"
ShHead(1) = "BizReg_UUK": ShHead(2) = "VDVV_UUK1": ShHead(3) = "VDVV_UUK"
ShHead(4) = "VDVV_NMK": ShHead(5) = "BizReg_Nos": ShHead(6) = "VDVV_Nos"
ShHead(7) = "BizReg_NACE1_2_red": ShHead(8) = "VDVV_NACE_2_red": ShHead(9) = "Nace maiņa"
ShHead(10) = "Nace maiņas avots": ShHead(11) = "BizReg_LKV": ShHead(12) = "VDVV_LKV"
ShHead(13) = "AVG Apgr.": ShHead(14) = "AVG Darb.": ShHead(15) = "VDVV_Adr"
ShHead(16) = "Struktūras": ShHead(17) = "Sākums": ShHead(18) = "Beigas"
ShHead(19) = "Nodarbošanās": ShHead(20) = "NACE": ShHead(21) = "Change it!"
ShHead(22) = "Reason"
'Creates header for sheets "NoResult", "Result"
For i = 1 To 19
If i = 1 Then
ShHead2(i) = ShHead(i)
Else
ShHead2(i) = ShHead(i + 3)
End If
Next
'Loops all sheets in workbook and removes filters, if they exist before data are processed
For Each Sh In ThisWorkbook.Worksheets
If Sh.AutoFilterMode = True Then
Sh.AutoFilterMode = False
End If
Next Sh
'Formating sheet "Result"
With ThisWorkbook.Sheets("Result")
'Clears whole sheet
.UsedRange.Clear
'Text in first row set to bold
.Range("A4:S4").Font.Bold = True
'Creates filter
.Range("A4:S4").AutoFilter
'Writes headers
.Range("A4:S4").Value2 = ShHead2
'Sets width of columns for differnet columns
.Columns("A").ColumnWidth = 10
.Columns("B").ColumnWidth = 25
.Columns("C").ColumnWidth = 30
.Columns("D:E").ColumnWidth = 4
.Columns("F").ColumnWidth = 10
.Columns("G:I").ColumnWidth = 2
.Columns("J").ColumnWidth = 8
.Columns("K").ColumnWidth = 5.5
.Columns("L").ColumnWidth = 35
.Columns("M").ColumnWidth = 3
.Columns("N:O").ColumnWidth = 6
.Columns("P").ColumnWidth = 20
.Columns("Q").ColumnWidth = 20
.Columns("R").ColumnWidth = 5
.Columns("S").ColumnWidth = 40
'Wraps text in column
.Columns("L").WrapText = True
.Columns("S").WrapText = True
'Sets formats for columns containing numbers
.Columns("A").NumberFormat = "#"
.Columns("D:E").NumberFormat = "#"
.Columns("F").NumberFormat = "m/d/yyyy"
.Columns("J").NumberFormat = "### ### ###"
.Range("G:G").HorizontalAlignment = xlCenter
.Range("Q:Q").HorizontalAlignment = xlLeft
'Sets height for all rows
.Rows("1:1048576").RowHeight = 15
End With
'Goes to sheet and cell
Application.Goto ThisWorkbook.Sheets("Result").Range("A5")
'Freezes panes
ActiveWindow.FreezePanes = False
'Freezes panes
ActiveWindow.FreezePanes = True
Application.StatusBar = "Sheet /Result/ formated!"
'Formating sheet "NoResult"
With ThisWorkbook.Sheets("NoResult")
'Clears whole sheet
.UsedRange.Clear
'Text in first row set to bold
.Range("A4:S4").Font.Bold = True
'Creates filter
.Range("A4:S4").AutoFilter
'Writes headers
.Range("A4:S4").Value = ShHead2
'Sets width of columns for differnet columns
.Columns("A").ColumnWidth = 10
.Columns("B").ColumnWidth = 25
.Columns("C").ColumnWidth = 30
.Columns("D:E").ColumnWidth = 4
.Columns("F").ColumnWidth = 10
.Columns("G:I").ColumnWidth = 2
.Columns("J").ColumnWidth = 8
.Columns("K").ColumnWidth = 5.5
.Columns("L").ColumnWidth = 35
.Columns("M").ColumnWidth = 3
.Columns("N:O").ColumnWidth = 6
.Columns("P").ColumnWidth = 20
.Columns("Q").ColumnWidth = 20
.Columns("R").ColumnWidth = 5
.Columns("S").ColumnWidth = 40
'Wraps text in column
.Columns("L").WrapText = True
.Columns("S").WrapText = True
'Sets formats for columns containing numbers
.Columns("A").NumberFormat = "#"
.Columns("D:E").NumberFormat = "#"
.Columns("F").NumberFormat = "m/d/yyyy"
.Columns("J").NumberFormat = "### ### ###"
.Range("G:G").HorizontalAlignment = xlCenter
.Range("Q:Q").HorizontalAlignment = xlLeft
'Sets height for all rows
.Rows("1:1048576").RowHeight = 15
End With
'Goes to sheet and cell
Application.Goto ThisWorkbook.Sheets("NoResult").Range("A5")
'Freezes panes
ActiveWindow.FreezePanes = False
'Freezes panes
ActiveWindow.FreezePanes = True
Application.StatusBar = "Sheet /NoResult/ formated!"
'====================
'Procedure that adds finishing touches at end of procedure
Call EndProcedure
'====================
End Sub
I have a code that sorts through thousands of lines in a spreadsheet and when it finds a row that has a specific match in two different columns, it returns a value in a third column. However this UDF is used thousands of times and with each running thousands of loops, its very slow. Is there a way to speed up or make this more efficient?
Dim SearchSheet As Worksheet
Dim PN As Integer
Dim MdlCol As Integer
Dim Mdl As String
Dim Result As Integer
Dim FinalRow As Integer
Dim i As Integer
Application.Volatile True
Select Case True
Case Number < WorksheetFunction.CountA(Sheet2.Range("A:A")) And Model = "1A"
Set SearchSheet = Sheet2
PN = 3
MdlCol = 5
Mdl = "1A"
Result = 30
Case Number < WorksheetFunction.CountA(Sheet2.Range("A:A")) And Model = "1B"
Set SearchSheet = Sheet2
PN = 3
MdlCol = 6
Mdl = "1B"
Result = 30
Case Number < WorksheetFunction.CountA(Sheet2.Range("A:A")) And Model = "1C"
Set SearchSheet = Sheet2
PN = 3
MdlCol = 7
Mdl = "1C"
Result = 30
Case Number >= WorksheetFunction.CountA(Sheet2.Range("A:A")) And Model = "1A"
Set SearchSheet = Sheet3
PN = 2
MdlCol = 18
Mdl = "-1A"
Result = 80
Case Number >= WorksheetFunction.CountA(Sheet2.Range("A:A")) And Model = "1B"
Set SearchSheet = Sheet3
PN = 2
MdlCol = 19
Mdl = "-1B"
Result = 80
Case Number >= WorksheetFunction.CountA(Sheet2.Range("A:A")) And Model = "1C"
Set SearchSheet = Sheet3
PN = 2
MdlCol = 20
Mdl = "-1C"
Result = 80
End Select
FinalRow = WorksheetFunction.CountA(SearchSheet.Range("A:A")) + 10
For i = 2 To FinalRow
If SearchSheet.Cells(i, PN) = PartNumber And SearchSheet.Cells(i, MdlCol) = Mdl Then
If SearchSheet.Cells(i, Result).Value = "X" Then
CalibrationRequired = "Y"
Else
CalibrationRequired = SearchSheet.Cells(i, Result).Value
End If
Exit For
End If
Next i
End Function ```
I would suggest:
put LastARow=WorksheetFunction.CountA(SearchSheet.Range("A:A")) once at the start and re-use LastARow rather than repeating the COUNTA many times.
Instead of looping down to final row and looking at each cell in turn, get all the data into a variant array and loop on that
Avoid the VBE UDF slowdown bug by initiating calculation from VBA
I'm trying to create a loop that pulls in the value on the second image.
Image 1 -
Image 2 -
Here is how you could do it, with two embedded loops.
Sub CopyStates()
Dim StateRow%
Dim CopyRow%
Dim FruitRow%
Dim FruitMaxRow%
'these three constants define in which columns your data can be found
Const STATE_COL = 1
Const COPY_COL = 6
Const FRUIT_COL = 7
'these constant define the start row of your data
Const DATA_START_ROW_S = 3 'start row for states
Const DATA_START_ROW_P = 2 'start row for fruits and copy
FruitMaxRow = DATA_START_ROW_P
StateRow = DATA_START_ROW_S
'first, check how many fruits you have
While Cells(FruitMaxRow, FRUIT_COL) <> ""
FruitMaxRow = FruitMaxRow + 1
Wend
'initialize: begin copying at the first empty row where you had fruits
CopyRow = FruitMaxRow
'loop through all the states
While Cells(StateRow, STATE_COL) <> ""
'loop through all the fruits
For FruitRow = DATA_START_ROW_P To FruitMaxRow - 1
'copy the state and fruit
Cells(CopyRow, COPY_COL) = Cells(StateRow, STATE_COL)
Cells(CopyRow, FRUIT_COL) = Cells(FruitRow, FRUIT_COL)
CopyRow = CopyRow + 1
Next FruitRow
StateRow = StateRow + 1
Wend
End Sub
I am trying to run the below VBA code and it is running. However the results are not as expected in some cases
For example
I have a column of values, the script looks at the whole column chooses the smallest value and add +1 to the output.
However sometimes it is skipping some small values in this original column and taking higher values and I cannot understand why it is doing that
I have attached the excel macro workbook here:
https://drive.google.com/open?id=1IAHalTjWoTqRT10mgF6bu5l4VB1HgCfv
The output sheet is called: "Loop"
The macro can be run from sheet: "Instruction"
Option Explicit
Sub Loop_Forecast()
Dim Onglet_Loop As String
Dim First_Ligne, First_Col_Fct, Col_SKU, Col_Inventory, Col_Monthly_Demand, Col_Coverage As Integer
Dim i, Last_ligne, Ligne_Smallest As Long
Dim Qty_Fct_Origin, Qty_Fct_Remain, Compteur, Smallest_Coverage As Variant
'Fige l'écran pendant la suppression des lignes
Application.ScreenUpdating = False
Onglet_Loop = "Loop"
Worksheets(Onglet_Loop).Activate
First_Ligne = 3
Last_ligne = Cells(First_Ligne, 1).End(xlDown).Row
Qty_Fct_Origin = Cells(1, 2).Value
Col_SKU = 1
Col_Inventory = 2
Col_Monthly_Demand = 3
Col_Coverage = 4
First_Col_Fct = 5
Compteur = 0
Do While Compteur < Qty_Fct_Origin
Compteur = Compteur + 1
Smallest_Coverage = Cells(First_Ligne, Col_Coverage).Value
Ligne_Smallest = First_Ligne
For i = First_Ligne + 1 To Last_ligne
If Cells(i, Col_Coverage).Value < Smallest_Coverage Then
Smallest_Coverage = Cells(i, Col_Coverage).Value
Ligne_Smallest = i
End If
Next i
Cells(Ligne_Smallest, First_Col_Fct).Value = Cells(Ligne_Smallest, First_Col_Fct).Value + 1
Loop
'Faire plein de choses qui affectent le contenu des cellules
Application.ScreenUpdating = True
End Sub
I have attached an image of the output I am receiving. As can be seen it is skipping some smaller values i.e 4 which should be priority and then jumping to 5
(EDIT: To clarify, I'm running Excel 2013, so Microsoft's date picker isn't available.)
I'm trying to code a simple date picker - it'll be tidier when it's done, it's just big for simplicity while I build it - and everything populates as it should:
Me.Combo_Year.List = wsLU.Range("Date_Years").Value
Me.Combo_Month.List = wsLU.Range("Date_Months").Value
Me.Combo_Day.List = wsLU.Range("Date_Days31").Value
However, there are two instances where I'd like to set default values for the year, month and day comboboxes. For the times I'm using spin buttons, where a simple .Value statement sets them to 12 noon in the _Initialize. But neither .Value nor .Text works for the comboboxes:
Me.Combo_Year.Text = Year(Now()) ' Doesn't work
Me.Combo_Month.Text = Month(Now()) ' Doesn't work
Me.Combo_Day.Text = Day(Now()) ' Doesn't work
Me.Spin_Hour.Value = 12 ' Works fine
Me.Spin_Minute.Value = 0 ' Works fine
Similarly, when I try to set the date to a lower value when a month with fewer days is selected (to avoid returning the 31st of February, for instance), both .Value and .Text prove unhelpful again:
Is there any way to reliably set a default value and later change the value of a combobox in code? Am I missing something hugely obvious?
EDIT: For reference, the full code for the relevant parts of the form (UpdatePreview just updates the preview date above the OK button) as requested:
--------------------------------------------------------------------------------
Option Explicit
--------------------------------------------------------------------------------
Private Sub UserForm_Initialize()
Dim wsLU As Worksheet, wbV As Workbook
Set wbV = ActiveWorkbook
Set wsLU = wbV.Worksheets("General Lookups")
Me.Combo_Year.List = wsLU.Range("Date_Years").Value
Me.Combo_Month.List = wsLU.Range("Date_Months").Value
Me.Combo_Day.List = wsLU.Range("Date_Days31").Value
Me.Combo_Minute.AddItem 0
Me.Combo_Minute.AddItem 30
' Tried putting the date numbers via variables instead of direct, with various data types
Dim TestYear As String, TestMonth As String, TestDay As String
TestYear = Year(Now())
TestMonth = Month(Now())
TestDay = Day(Now())
Lab_T_Year.Caption = TestYear
Lab_T_Month.Caption = TestMonth
Lab_T_Day.Caption = TestDay
'Me.Combo_Year.Text = TestYear ' If these lines are commented out the form will load, though without the comboboxes prepopulated
'Me.Combo_Month.Text = TestMonth ' If these lines are commented out the form will load, though without the comboboxes prepopulated
'Me.Combo_Day.Text = TestDay ' If these lines are commented out the form will load, though without the comboboxes prepopulated
' Original code; tried this both with and without various Format types.
'Me.Combo_Year.Value = Format(Year(Now()), "0000")
'Me.Combo_Month.Value = Format(Month(Now()), "00")
'Me.Combo_Day.Value = Format(Day(Now()), "00")
Me.Spin_Hour.Value = 12
Me.Combo_Minute.Value = 0 ' Switched the minute spinner to a combobox as the client wanted to just pick half hours (00/30) instead of minutes
UpdatePreview ' Updates date and time preview, works fine.
End Sub
--------------------------------------------------------------------------------
Private Sub Combo_Year_Change() ' Combo_Month_Change has an equivalent sub that essentially mirrors this one
Dim wsLU As Worksheet, wbV As Workbook
Set wbV = ActiveWorkbook
Set wsLU = wbV.Worksheets("General Lookups")
Dim iMonthNo As Integer, iYearNo As Long, iMaxDate As Integer
' Set number of days based on month
iMonthNo = Me.Combo_Month.ListIndex + 1
iYearNo = Me.Combo_Year.Value
If iMonthNo = 1 Or iMonthNo = 3 Or iMonthNo = 5 Or iMonthNo = 7 Or iMonthNo = 8 Or iMonthNo = 10 Or iMonthNo = 12 Then
Me.Combo_Day.List = wsLU.Range("Date_Days31").Value
iMaxDate = 31
ElseIf iMonthNo = 4 Or iMonthNo = 6 Or iMonthNo = 9 Or iMonthNo = 11 Then
Me.Combo_Day.List = wsLU.Range("Date_Days30").Value
iMaxDate = 30
ElseIf iMonthNo = 2 Then
Me.Combo_Day.List = wsLU.Range("Date_Days28").Value
iMaxDate = 28
' Leap year div by 4
If iYearNo / 4 = Int(iYearNo / 4) And Not (iYearNo / 100 = Int(iYearNo / 100)) Then Me.Combo_Day.List = wsLU.Range("Date_Days29").Value
If iYearNo / 4 = Int(iYearNo / 4) And Not (iYearNo / 100 = Int(iYearNo / 100)) Then iMaxDate = 29
' Leap year div by 400
If iYearNo / 4 = Int(iYearNo / 4) And iYearNo / 400 = Int(iYearNo / 400) Then Me.Combo_Day.List = wsLU.Range("Date_Days29").Value
If iYearNo / 4 = Int(iYearNo / 4) And iYearNo / 400 = Int(iYearNo / 400) Then iMaxDate = 29
End If
' Code to attempt to change the date down if Month is switched to one with fewer days. It doesn't work.
If Me.Combo_Day.Value > iMaxDate And iMonthNo > 0 And Not Me.Combo_Day.Value = "" Then Me.Combo_Day.Value = iMaxDate
UpdatePreview ' Updates date and time preview, works fine.
End Sub
THINGS WOT HAVEN'T WORKED:
Adding Microsoft's own date picker (not available in Excel 2013)
Installing the supplemental date picker as suggested by Microsoft (can't assume it'll be available on users' computers)
Attempts to directly set the comboboxes' .Text or .Value properties through VBA, regardless of data type used.
Attempts with dates directly (=Month(Now())), through variables (=sNowMonth), or by list index (Me.Combo_Month.Text=Me.Combo_Month.List(Month(Now())-1)).
I've been hunting around for a solution for this since last week. Every possibility I've found has been for older versions of Office. Can anyone help?
To answer your question directly, then you could make a date variable and then convert that to a text string:
Dim txtNowYear As String
Dim txtNowMonth As String
Dim txtNowDay As String
txtNowYear = Year(Now())
txtNowMonth = Month(Now())
txtNowDay = Day(Now())
Me.Combo_Year.Text = txtNowYear
Me.Combo_Month.Text = txtNowMonth
Me.Combo_Day.Text = txtNowDay
But depending on what you intend to use it for, it might be smarter to just change the input format to .Date
Go to Tools, Additional Control, select Microsoft Monthview Control 6.0 (SP6) And insert a date-picker in your form.
PS: A similar approach should be able to handle your second issue.