Subtracting in loops associated with variables - visual-studio

I am trying to make a loop that keeps subtracting from the initial amount of each item until the amount is less than the price of an item. It is also supposed to show the last item purchased before the amount was less than the price of an item. Cars cost $310,000, rings cost $12,500, Diamonds cost $150,000 and chocolate costs $51. The items are in a file on my computer, and the following is an example of what it should look like.
Sample Input:
350000
Car
Ring
Diamond
Car
Chocolate
Diamond
Sample Output:
Ring
$27, 500 left
For some reason, the value I get the wrong value when I subtract, but I can't figure out why. I've declared the prices for each item and checked multiple times to make sure they are correct, and I've checked my code, but I still don't know why I get the wrong output.
Private Sub btnS_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnS.Click
Dim inFile As StreamReader = New StreamReader("serena.txt")
'Declare the varibles
Dim variableName As String
' the current items from the file
Dim amount As Integer
Dim price As Integer
amount = Val(inFile.ReadLine())
Do
'read in the words
variableName = inFile.ReadLine()
'determine each item's price
If variableName = "car" Then price = 310000
If variableName = "ring" Then price = 12500
If variableName = "diamond" Then price = 150000
If amount >= price Then amount = amount - price
Loop Until amount < price
'output the results
Me.lblOutput.Text = variableName & _
vbNewLine & "Serena has " & Format(amount, "currency") & " left"
End Sub

Take a close look at the COMMENTS I've placed into the code below:
Private Sub btnS_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnS.Click
Dim fileName As String = Path.Combine(My.Computer.FileSystem.SpecialDirectories.MyDocuments, "serena.txt")
Dim inFile As New StreamReader(fileName)
Dim itemToPurchase As String
Dim lastThingBought As String = "{ Nothing Bought }" ' what if they can't afford anything?
Dim balance As Integer
Dim price As Integer
Dim somethingBought As Boolean
balance = Val(inFile.ReadLine())
Do
somethingBought = False ' we don't know yet if we can purchase the next item
'read in the words
itemToPurchase = inFile.ReadLine().ToLower().Trim() ' make sure it has no spaces and is lowercase to match below
'determine each item's price
Select Case itemToPurchase
Case "car"
price = 310000
Case "ring"
price = 12500
Case "diamond"
price = 150000
Case "chocolate" ' FYI: you were missing chocolate in your code
price = 51
Case Else ' it could happen!
MessageBox.Show(itemToPurchase, "Unknown Item!")
lblOutput.Text = "Error in file"
Exit Sub
End Select
If balance >= price Then
somethingBought = True
balance = balance - price
' we need to store the last thing purchased,
' because "itemToPurchase" gets replaced with the thing you couldn't afford
lastThingBought = itemToPurchase
End If
' Need to check for the end of file being reached...
' ...they may purchase everything in the file and still have money left!
Loop While Not inFile.EndOfStream AndAlso somethingBought
'output the results
Me.lblOutput.Text = lastThingBought &
vbNewLine & "Serena has " & Format(balance, "currency") & " left"
End Sub

Related

How to calculate the minute in DTPICKER vb6

I'm creating a payroll system I want to calculate the minute late of Employee using two dtpicker
Dtpicker1 is for time in and Dtpicker2 for Timeout
Private Sub calc_Click()
oras = DateDiff("n", DTPicker1, DTPicker2)
Text1.Text = oras
End sub
If all employees are working the same amount of hours (8 hours/day for example):
Private Sub calc_Click()
Dim iWorkdayHours As Integer
Dim iMinutesWorked As Integer
Dim iMinutesLate As Integer
' Get the amount of minutes between two dates
iMinutesWorked = DateDiff("n", DTPicker1, DTPicker2)
' Get number of hours employee should have worked
iWorkdayHours = 8
iMinutesLate = (iWorkdayHours * 60) - iMinutesWorked
If iMinutesLate > 0 Then
Text1.Text = iMinutesLate & " minutes late."
Else
Text1.Text = "On time."
End If
End Sub
If employees have different shift lengths, you can update iWorkdayHours.

How can run the following code on multiple Excel sheets?

I have a code which I would like to use on multiple sheets, except one sheet. But applying the code to alle sheets is also fine.
Here is the code that I would like to adjust. I am have currently applied it to Excel 2011 in OS X , but I would like to use it for Excel 2010 in Windows.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then
Dim the_selection As String
Dim month_in_review As String
the_selection = Sheet1.Range("A1")
Dim Rep As Integer
For Rep = 2 To 379
the_column = GetColumnLetter_ByInteger(Rep)
month_in_review = Sheet1.Range(the_column & "1")
If the_selection = month_in_review Then
Sheet1.Range(the_column & ":" & the_column).EntireColumn.Hidden = False
Else
Sheet1.Range(the_column & ":" & the_column).EntireColumn.Hidden = True
End If
Next Rep
End If
End Sub
In the module I have the following code:
Public Function GetColumnLetter_ByInteger(what_number As Integer) As String
GetColumnLetter_ByInteger = ""
MyColumn_Integer = what_number
If MyColumn_Ineger <= 26 Then
column_letter = ChrW(64 + MyColumn_Integer)
End If
If MyColumn_Integer > 26 Then
column_letter = ChrW(Int((MyColumn_Integer - 1) / 26) + 64) & ChrW(((MyColumn_Integer - 1) Mod 26) + 65)
End If
GetColumnLetter_ByInteger = column_letter
End Function
If you're asking for one sheet to detect the change in cell "A1" and then to hide/unhide columns on multiple sheets then the prior answers to your question will serve you nicely.
If, on the other hand, you're asking to detect a change in cell "A1" on any sheet and then to hide/unhide columns on just the changed sheet, then the code below will work for you. It accesses the Workbook_SheetChanged event at Workbook level.
A few points about your code:
You can reference cells using their integer or address values with the .Cell property, so Sheet1.Cells(1, 1) is the same as Sheet1.Cells(1, "A"). The same applies to the .Columns property. So there's no real need to convert your integer values to a string. See #Florent B's answer for a good example of this.
Wherever possible, minimise looping sheet interactions as these are very time-consuming. So rather than loop through the columns and hide/unhide each one individually, you could assign them to ranges within your loop and then hide/unhide the ranges all in one go at the end of your loop. If you must interact with the sheet on each iteration of your loop, then set the Application.ScreenUpdating property to false before the start of your loop. There's an example of this property in the sample code below.
Put this in your Workbook module:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const TARGET_ADDRESS As String = "A1"
Dim cell As Range
Dim hiddenCols As Range
Dim unhiddenCols As Range
Dim selectedMonth As String
Dim monthInReview As String
Dim c As Integer
'Ignore event if not a target worksheet
If Sh.Name = "Not Wanted" Then Exit Sub
'Ignore event if not in target range
Set cell = Target.Cells(1)
If cell.Address(False, False) <> TARGET_ADDRESS Then Exit Sub
'Criteria met, so handle event
selectedMonth = CStr(cell.Value)
For c = 2 To 379
Set cell = Sh.Cells(1, c)
monthInReview = CStr(cell.Value)
'Add cell to hidden or unhidden ranges
If monthInReview = selectedMonth Then
If unhiddenCols Is Nothing Then
Set unhiddenCols = cell
Else
Set unhiddenCols = Union(unhiddenCols, cell)
End If
Else
If hiddenCols Is Nothing Then
Set hiddenCols = cell
Else
Set hiddenCols = Union(hiddenCols, cell)
End If
End If
Next
'Hide and unhide the cells
Application.ScreenUpdating = False 'not really needed here but given as example
If Not unhiddenCols Is Nothing Then
unhiddenCols.EntireColumn.Hidden = False
End If
If Not hiddenCols Is Nothing Then
hiddenCols.EntireColumn.Hidden = True
End If
Application.ScreenUpdating = True
End Sub
You can use a for each loop to loop through all the Worksheets, and check the worksheet name if it should be skipped. Then apply your code onto the sheet selected.
Something like:
Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> "Skip Sheet" Then
Dim the_selection As String
Dim month_in_review As String
the_selection = ws.Range("A1")
Dim Rep As Integer
For Rep = 2 To 379
the_column = GetColumnLetter_ByInteger(Rep)
month_in_review = ws.Range(the_column & "1")
If the_selection = month_in_review Then
ws.Range(the_column & ":" & the_column).EntireColumn.Hidden = False
Else
ws.Range(the_column & ":" & the_column).EntireColumn.Hidden = True
End If
Next Rep
End If
Next ws
End If
End Sub
I wasn't entirely sure what you wished to achieve, so i put ws in the place of Sheet1.
This example will show/hide the columns in all the other sheets if the first cell of the column match/differ with the cell A1 of the sheet where this code is placed:
Private Sub Worksheet_Change(ByVal Target As Range)
' exit if not cell A1
If Target.row <> 1 Or Target.column <> 1 Then Exit Sub
Dim sheet As Worksheet
Dim the_selection As String
Dim month_in_review As String
Dim column As Integer
the_selection = Target.Value
' iterate all the sheets
For Each sheet In ThisWorkbook.Worksheets
' skip this sheet
If Not sheet Is Me Then
' iterate the columns
For column = 2 To 379
' get the first cell of the column
month_in_review = sheet.Cells(1, column).Value
' hide or show the column if it's a match or not
sheet.Columns(column).Hidden = month_in_review <> the_selection
Next
End If
Next
End Sub

Excel VBA Performance - 1 million rows - Delete rows containing a value, in less than 1 min

I am trying to find a way to filter large data and remove rows in a worksheet, in less than one minute
The goal:
Find all records containing specific text in column 1, and delete the entire row
Keep all cell formatting (colors, font, borders, column widths) and formulas as they are
.
Test Data:
:
.
How the code works:
It starts by turning all Excel features Off
If the workbook is not empty and the text value to be removed exists in column 1
Copies the used range of column 1 to an array
Iterates over every value in array backwards
When it finds a match:
Appends the cell address to a tmp string in the format "A11,A275,A3900,..."
If the tmp variable length is close to 255 characters
Deletes rows using .Range("A11,A275,A3900,...").EntireRow.Delete Shift:=xlUp
Resets tmp to empty and moves on to the next set of rows
At the end, it turns all Excel features back On
.
The main issue is the Delete operation, and total duration time should be under one minute. Any code-based solution is acceptable as long as it performs under 1 minute.
This narrows the scope to very few acceptable answers. The answers already provided are also very short and easy to implement. One performs the operation in about 30 seconds, so there is at least one answer that provides an acceptable solution, and other may find it useful as well
.
My main initial function:
Sub DeleteRowsWithValuesStrings()
Const MAX_SZ As Byte = 240
Dim i As Long, j As Long, t As Double, ws As Worksheet
Dim memArr As Variant, max As Long, tmp As String
Set ws = Worksheets(1)
max = GetMaxCell(ws.UsedRange).Row
FastWB True: t = Timer
With ws
If max > 1 Then
If IndexOfValInRowOrCol("Test String", , ws.UsedRange) > 0 Then
memArr = .Range(.Cells(1, 1), .Cells(max, 1)).Value2
For i = max To 1 Step -1
If memArr(i, 1) = "Test String" Then
tmp = tmp & "A" & i & ","
If Len(tmp) > MAX_SZ Then
.Range(Left(tmp, Len(tmp) - 1)).EntireRow.Delete Shift:=xlUp
tmp = vbNullString
End If
End If
Next
If Len(tmp) > 0 Then
.Range(Left(tmp, Len(tmp) - 1)).EntireRow.Delete Shift:=xlUp
End If
.Calculate
End If
End If
End With
FastWB False: InputBox "Duration: ", "Duration", Timer - t
End Sub
Helper functions (turn Excel features off and on):
Public Sub FastWB(Optional ByVal opt As Boolean = True)
With Application
.Calculation = IIf(opt, xlCalculationManual, xlCalculationAutomatic)
.DisplayAlerts = Not opt
.DisplayStatusBar = Not opt
.EnableAnimations = Not opt
.EnableEvents = Not opt
.ScreenUpdating = Not opt
End With
FastWS , opt
End Sub
Public Sub FastWS(Optional ByVal ws As Worksheet = Nothing, _
Optional ByVal opt As Boolean = True)
If ws Is Nothing Then
For Each ws In Application.ActiveWorkbook.Sheets
EnableWS ws, opt
Next
Else
EnableWS ws, opt
End If
End Sub
Private Sub EnableWS(ByVal ws As Worksheet, ByVal opt As Boolean)
With ws
.DisplayPageBreaks = False
.EnableCalculation = Not opt
.EnableFormatConditionsCalculation = Not opt
.EnablePivotTable = Not opt
End With
End Sub
Finds last cell with data (thanks #ZygD - now I tested it in several scenarios):
Public Function GetMaxCell(Optional ByRef rng As Range = Nothing) As Range
'Returns the last cell containing a value, or A1 if Worksheet is empty
Const NONEMPTY As String = "*"
Dim lRow As Range, lCol As Range
If rng Is Nothing Then Set rng = Application.ActiveWorkbook.ActiveSheet.UsedRange
If WorksheetFunction.CountA(rng) = 0 Then
Set GetMaxCell = rng.Parent.Cells(1, 1)
Else
With rng
Set lRow = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByRows)
If Not lRow Is Nothing Then
Set lCol = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByColumns)
Set GetMaxCell = .Parent.Cells(lRow.Row, lCol.Column)
End If
End With
End If
End Function
Returns the index of a match in the array, or 0 if a match is not found:
Public Function IndexOfValInRowOrCol( _
ByVal searchVal As String, _
Optional ByRef ws As Worksheet = Nothing, _
Optional ByRef rng As Range = Nothing, _
Optional ByRef vertical As Boolean = True, _
Optional ByRef rowOrColNum As Long = 1 _
) As Long
'Returns position in Row or Column, or 0 if no matches found
Dim usedRng As Range, result As Variant, searchRow As Long, searchCol As Long
result = CVErr(9999) '- generate custom error
Set usedRng = GetUsedRng(ws, rng)
If Not usedRng Is Nothing Then
If rowOrColNum < 1 Then rowOrColNum = 1
With Application
If vertical Then
result = .Match(searchVal, rng.Columns(rowOrColNum), 0)
Else
result = .Match(searchVal, rng.Rows(rowOrColNum), 0)
End If
End With
End If
If IsError(result) Then IndexOfValInRowOrCol = 0 Else IndexOfValInRowOrCol = result
End Function
.
Update:
Tested 6 solutions (3 tests each): Excel Hero's solution is the fastest so far (removes formulas)
.
Here are the results, fastest to the slowest:
.
Test 1. Total of 100,000 records, 10,000 to be deleted:
1. ExcelHero() - 1.5 seconds
2. DeleteRowsWithValuesNewSheet() - 2.4 seconds
3. DeleteRowsWithValuesStrings() - 2.45 minutes
4. DeleteRowsWithValuesArray() - 2.45 minutes
5. QuickAndEasy() - 3.25 minutes
6. DeleteRowsWithValuesUnion() - Stopped after 5 minutes
.
Test 2. Total of 1 million records, 100,000 to be deleted:
1. ExcelHero() - 16 seconds (average)
2. DeleteRowsWithValuesNewSheet() - 33 seconds (average)
3. DeleteRowsWithValuesStrings() - 4 hrs 38 min (16701.375 sec)
4. DeleteRowsWithValuesArray() - 4 hrs 37 min (16626.3051757813 sec)
5. QuickAndEasy() - 5 hrs 40 min (20434.2104492188 sec)
6. DeleteRowsWithValuesUnion() - N/A
.
Notes:
ExcelHero method: easy to implement, reliable, extremely fast, but removes formulas
NewSheet method: easy to implement, reliable, and meets the target
Strings method: more effort to implement, reliable, but doesn't meet requirement
Array method: similar to Strings, but ReDims an array (faster version of Union)
QuickAndEasy: easy to implement (short, reliable and elegant), but doesn't meet requirement
Range Union: implementation complexity similar to 2 and 3, but too slow
I also made the test data more realistic by introducing unusual values:
empty cells, ranges, rows, and columns
special characters, like =[`~!##$%^&*()_-+{}[]\|;:'",.<>/?, separate and multiple combinations
blank spaces, tabs, empty formulas, border, font, and other cell formatting
large and small numbers with decimals (=12.9999999999999 + 0.00000000000000001)
hyperlinks, conditional formatting rules
empty formatting inside and outside data ranges
anything else that might cause data issues
I'm providing the first answer as a reference
Others may find it useful, if there are no other options available
Fastest way to achieve the result is not to use the Delete operation
Out of 1 million records it removes 100,000 rows in an average of 33 seconds
.
Sub DeleteRowsWithValuesNewSheet() '100K records 10K to delete
'Test 1: 2.40234375 sec
'Test 2: 2.41796875 sec
'Test 3: 2.40234375 sec
'1M records 100K to delete
'Test 1: 32.9140625 sec
'Test 2: 33.1484375 sec
'Test 3: 32.90625 sec
Dim oldWs As Worksheet, newWs As Worksheet, rowHeights() As Long
Dim wsName As String, t As Double, oldUsedRng As Range
FastWB True: t = Timer
Set oldWs = Worksheets(1)
wsName = oldWs.Name
Set oldUsedRng = oldWs.Range("A1", GetMaxCell(oldWs.UsedRange))
If oldUsedRng.Rows.Count > 1 Then 'If sheet is not empty
Set newWs = Sheets.Add(After:=oldWs) 'Add new sheet
With oldUsedRng
.AutoFilter Field:=1, Criteria1:="<>Test String"
.Copy 'Copy visible data
End With
With newWs.Cells
.PasteSpecial xlPasteColumnWidths
.PasteSpecial xlPasteAll 'Paste data on new sheet
.Cells(1, 1).Select 'Deselect paste area
.Cells(1, 1).Copy 'Clear Clipboard
End With
oldWs.Delete 'Delete old sheet
newWs.Name = wsName
End If
FastWB False: InputBox "Duration: ", "Duration", Timer - t
End Sub
.
At high level:
It creates a new worksheet, and keeps a reference to the initial sheet
AutoFilters column 1 on the searched text: .AutoFilter Field:=1, Criteria1:="<>Test String"
Copies all (visible) data from initial sheet
Pastes column widths, formats, and data to the new sheet
Deletes initial sheet
Renames the new sheet to the old sheet name
It uses the same helper functions posted in the question
The 99% of the duration is used by the AutoFilter
.
There are a couple limitations I found so far, the first can be addressed:
If there are any hidden rows on the initial sheet, it unhides them
A separate function is needed to hide them back
Depending on implementation, it might significantly increase duration
VBA related:
It changes the Code Name of the sheet; other VBA referring to Sheet1 will be broken (if any)
It deletes all VBA code associated with the initial sheet (if any)
.
A few notes about using large files like this:
The binary format (.xlsb) reduce file size dramatically (from 137 Mb to 43 Mb)
Unmanaged Conditional Formatting rules can cause exponential performance issues
The same for Comments, and Data validation
Reading file or data from network is much slower than working with a locall file
A significant gain in speed can be achieved if the source data do not contain formulas, or if the scenario would allow (or want) the formulas to be converted into hard values during the conditional row deletions.
With the above as a caveat, my solution uses the AdvancedFilter of the range object. It's about twice as fast as DeleteRowsWithValuesNewSheet().
Public Sub ExcelHero()
Dim t#, crit As Range, data As Range, ws As Worksheet
Dim r&, fc As Range, lc As Range, fr1 As Range, fr2 As Range
FastWB True
t = Timer
Set fc = ActiveSheet.UsedRange.Item(1)
Set lc = GetMaxCell
Set data = ActiveSheet.Range(fc, lc)
Set ws = Sheets.Add
With data
Set fr1 = data.Worksheet.Range(fc, fc.Offset(, lc.Column))
Set fr2 = ws.Range(ws.Cells(fc.Row, fc.Column), ws.Cells(fc.Row, lc.Column))
With fr2
fr1.Copy
.PasteSpecial xlPasteColumnWidths: .PasteSpecial xlPasteAll
.Item(1).Select
End With
Set crit = .Resize(2, 1).Offset(, lc.Column + 1)
crit = [{"Column 1";"<>Test String"}]
.AdvancedFilter xlFilterCopy, crit, fr2
.Worksheet.Delete
End With
FastWB False
r = ws.UsedRange.Rows.Count
Debug.Print "Rows: " & r & ", Duration: " & Timer - t & " seconds"
End Sub
On my elderly Dell Inspiron 1564 (Win 7 Office 2007) this:
Sub QuickAndEasy()
Dim rng As Range
Set rng = Range("AA2:AA1000001")
Range("AB1") = Now
Application.ScreenUpdating = False
With rng
.Formula = "=If(A2=""Test String"",0/0,A2)"
.Cells.SpecialCells(xlCellTypeFormulas, xlErrors).EntireRow.Delete
.Clear
End With
Application.ScreenUpdating = True
Range("AC1") = Now
End Sub
took about 10 seconds to run. I am assuming that column AA is available.
EDIT#1:
Please note that this code does not set Calculation to Manual. Performance will improve if the Calculation mode is set to Manual after the "helper" column is allowed to calculate.
I know I'm incredibly late with my answer here however future visitors may find it very useful.
Please Note: My approach requires an index column for the rows to end up in the original order, however if you do not mind the rows being in a different order then an index column isn't needed and the additional line of code can be removed.
My approach: My approach was to simply select all the rows in the selected range (column), sort them in ascending order using Range.Sort and then collecting the first and last index of "Test String" within the selected range (column). I then create a range from the first and last indices and use Range.EntrieRow.Delete to remove all the rows which contain "Test String".
Pros:
- It is blazing fast.
- It doesn't remove formatting, formulas, charts, pictures or anything like the method which copies to a new sheet.
Cons:
- A decent size of code to implement however it is all straight-forward.
Test Range Generation Sub:
Sub DevelopTest()
Dim index As Long
FastWB True
ActiveSheet.UsedRange.Clear
For index = 1 To 1000000 '1 million test
ActiveSheet.Cells(index, 1).Value = index
If (index Mod 10) = 0 Then
ActiveSheet.Cells(index, 2).Value = "Test String"
Else
ActiveSheet.Cells(index, 2).Value = "Blah Blah Blah"
End If
Next index
Application.StatusBar = ""
FastWB False
End Sub
Filter And Delete Rows Sub:
Sub DeleteRowFast()
Dim curWorksheet As Worksheet 'Current worksheet vairable
Dim rangeSelection As Range 'Selected range
Dim startBadVals As Long 'Start of the unwanted values
Dim endBadVals As Long 'End of the unwanted values
Dim strtTime As Double 'Timer variable
Dim lastRow As Long 'Last Row variable
Dim lastColumn As Long 'Last column variable
Dim indexCell As Range 'Index range start
Dim sortRange As Range 'The range which the sort is applied to
Dim currRow As Range 'Current Row index for the for loop
Dim cell As Range 'Current cell for use in the for loop
On Error GoTo Err
Set rangeSelection = Application.InputBox("Select the (N=) range to be checked", "Get Range", Type:=8) 'Get the desired range from the user
Err.Clear
M1 = MsgBox("This is recommended for large files (50,000 or more entries)", vbYesNo, "Enable Fast Workbook?") 'Prompt the user with an option to enable Fast Workbook, roughly 150% performace gains... Recommended for incredibly large files
Select Case M1
Case vbYes
FastWB True 'Enable fast workbook
Case vbNo
FastWB False 'Disable fast workbook
End Select
strtTime = Timer 'Begin the timer
Set curWorksheet = ActiveSheet
lastRow = CLng(rangeSelection.SpecialCells(xlCellTypeLastCell).Row)
lastColumn = curWorksheet.Cells(1, 16384).End(xlToLeft).Column
Set indexCell = curWorksheet.Cells(1, 1)
On Error Resume Next
If rangeSelection.Rows.Count > 1 Then 'Check if there is anything to do
lastVisRow = rangeSelection.Rows.Count
Set sortRange = curWorksheet.Range(indexCell, curWorksheet.Cells(curWorksheet.Rows(lastRow).Row, 16384).End(xlToLeft)) 'Set the sort range
sortRange.Sort Key1:=rangeSelection.Cells(1, 1), Order1:=xlAscending, Header:=xlNo 'Sort by values, lowest to highest
startBadVals = rangeSelection.Find(What:="Test String", LookAt:=xlWhole, MatchCase:=False).Row
endBadVals = rangeSelection.Find(What:="Test String", LookAt:=xlWhole, SearchDirection:=xlPrevious, MatchCase:=False).Row
curWorksheet.Range(curWorksheet.Rows(startBadVals), curWorksheet.Rows(endBadVals)).EntireRow.Delete 'Delete uneeded rows, deleteing in continuous range blocks is quick than seperated or individual deletions.
sortRange.Sort Key1:=indexCell, Order1:=xlAscending, Header:=xlNo 'Sort by index instead of values, lowest to highest
End If
Application.StatusBar = "" 'Reset the status bar
FastWB False 'Disable fast workbook
MsgBox CStr(Round(Timer - strtTime, 2)) & "s" 'Display duration of task
Err:
Exit Sub
End Sub
THIS CODE USES FastWB, FastWS AND EnableWS BY Paul Bica!
Times at 100K entries (10k to be removed, FastWB True):
1. 0.2 seconds.
2. 0.2 seconds.
3. 0.21 seconds.
Avg. 0.2 seconds.
Times at 1 million entries (100k to be removed, FastWB True):
1. 2.3 seconds.
2. 2.32 seconds.
3. 2.3 seconds.
Avg. 2.31 seconds.
Running on: Windows 10, iMac i3 11,2 (From 2010)
EDIT
This code was originally designed with the purpose of filtering out numeric values outside of a numeric range and has been adapted to filter out "Test String" so some of the code may be redundant.
Your use of arrays in calculating the used range and row count may effect the performance. Here's another approach which in testing proves efficient across 1m+ rows of data - between 25-30 seconds. It doesn't use filters so will delete rows even if hidden. Deleting a whole row won't effect formatting or column widths of the other remaining rows.
First, check if the ActiveSheet has "Test String". Since you're only interested in Column 1 I used this:
TCount = Application.WorksheetFunction.CountIf(sht.Columns(1), "Test String")
If TCount > 0 Then
Instead of using your GetMaxCell() function I simply used Cells.SpecialCells(xlCellTypeLastCell).Row to get the last row:
EndRow = sht.Cells.SpecialCells(xlCellTypeLastCell).Row
Then loop through the rows of data:
While r <= EndRow
To test if the cell in Column 1 is equal to "Test String":
If sht.Cells(r, 1).Text) = "Test String" Then
To delete the row:
Rows(r).Delete Shift:=xlUp
Putting it all together full code below. I've set ActiveSheet to a variable Sht and added turned of ScreenUpdating to improve efficiency. Since it's a lot of data I make sure to clear variables at the end.
Sub RowDeleter()
Dim sht As Worksheet
Dim r As Long
Dim EndRow As Long
Dim TCount As Long
Dim s As Date
Dim e As Date
Application.ScreenUpdating = True
r = 2 'Initialise row number
s = Now 'Start Time
Set sht = ActiveSheet
EndRow = sht.Cells.SpecialCells(xlCellTypeLastCell).Row
'Check if "Test String" is found in Column 1
TCount = Application.WorksheetFunction.CountIf(sht.Columns(1), "Test String")
If TCount > 0 Then
'loop through to the End row
While r <= EndRow
If InStr(sht.Cells(r, 1).Text, "Test String") > 0 Then
sht.Rows(r).Delete Shift:=xlUp
r = r - 1
End If
r = r + 1
Wend
End If
e = Now 'End Time
D = (Hour(e) * 360 + Minute(e) * 60 + Second(e)) - (Hour(s) * 360 + Minute(s) * 60 + Second(s))
Application.ScreenUpdating = True
DurationTime = TimeSerial(0, 0, D)
MsgBox Format(DurationTime, "hh:mm:ss")
End Sub

IOException Was Unhandled Error Help for Newbie

You'll have to excuse me for a repeat question but I just can't seem to understand the responses others have been posting. My coding skills are very limited (barely a year in vb 2006 and vs 2010)
I know that the sr has opened too many of the same file but I cant figure out a fix for it. Explaining it to me in simple concepts would be very helpful. Once again sorry for the newby-ness.
Project Explanation: Create a bowling league stats keeper. cmdRegisterBowler adds bowler name to a dat file (bowlers.dat). cmdEnterScores will write name and stats to games.dat file.
Private Sub cmdRegisterBowler_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdRegisterBowler.Click
'BOWLER REGISTRATION
Dim Person As String
Dim Team As String
txtName.Text.ToUpper()
txtTeam.Text.ToUpper()
Person = txtName.Text
Team = txtTeam.Text
If Person <> "" And Team <> "" Then
If IsInFile(Person & " " & Team) = True Then
MessageBox.Show(Person & " is already in the file.", "Error")
txtName.Clear()
txtTeam.Clear()
txtName.Focus()
Else
'swb = stream writer bowlers file
Dim swb As IO.StreamWriter = IO.File.AppendText("bowlers.dat")
swb.WriteLine(Person & " " & Team)
swb.Close()
MessageBox.Show(Person & " has been added to the file.", "Information Added")
txtName.Clear()
txtTeam.Clear()
txtName.Focus()
End If
Else
MessageBox.Show("You must enter a name.", "Information Incomplete")
End If
End Sub
Function IsInFile(ByVal person As String) As String
If IO.File.Exists("bowlers.dat") Then
Dim sr As IO.StreamReader = IO.File.OpenText("bowlers.dat")
Dim individual As String
Do Until sr.EndOfStream
individual = sr.ReadLine
If individual = person Then
Return True
sr.Close()
End If
Loop
sr.Close()
End If
Return False
End Function
Private Sub cmdEnterScores_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdEnterScores.Click
'SCORE ENTRY
Dim Person As String
Dim Team As String
Dim Score1 As Double
Dim Score2 As Double
Dim Score3 As Double
Dim Average As Double
Dim Display As String = "{0,-10} {1,10} {2,20} {3,10} {4,10} {5,10} {6,10}"
Dim Total As Double
Person = txtName2.Text
Team = txtTeam2.Text
Score1 = Val(txtFirstGame.Text)
Score2 = Val(txtSecondGame.Text)
Score3 = Val(txtThirdGame.Text)
Total = Score1 + Score2 + Score3
Average = Total / 3
Dim swb As IO.StreamWriter = IO.File.AppendText("bowlers.dat")
'Checks for blank entries
If Person <> "" Then
'Checks to see if person is registered
If IsNotInFile(Person) Then
Dim Response As Integer
' Displays a message box with the yes and no options to register bowler.
Response = MsgBox(Prompt:="The bowler you have entered is currently not registered. Would you like to do so?", Buttons:=vbYesNo)
'Yes button was selected. Bowler is then registered to bowlers.dat
If Response = vbYes Then
''swb = stream writer bowlers file
swb.WriteLine(Person & " " & Team)
swb.Close()
MessageBox.Show(Person & " has been added to the file.", "Information Added")
'The no button was selected. Focus is set to bowler registration group box
Else : Response = vbNo
txtName2.Clear()
txtTeam2.Clear()
txtFirstGame.Clear()
txtSecondGame.Clear()
txtThirdGame.Clear()
txtName.Focus()
End If
'If no then clears score entry group box and sets focus back to bowler registr
txtName.Clear()
txtName.Focus()
Else
'Write scores to games.dat file
'swg = stream writer games file
Dim swg As IO.StreamWriter = IO.File.AppendText("games.dat")
swg.WriteLine(Name & " " & Team & " " & Score1 & " " & Score2 & " " & Score3)
swg.Close()
MessageBox.Show(Person & "'s stats added to the file.", "Information Added")
txtName.Clear()
txtName.Focus()
Dim Display2 As String = "{0,-10} {1,10} {2,20} {3,10} {4,10} {5,10} {6,10}"
lstDisplay.Items.Add(String.Format(Display2, Person, Team, Score1, Score2, Score3, Total, Average))
End If
Else
MessageBox.Show("You must enter a name.", "Information Incomplete")
End If
End Sub
Function IsNotInFile(ByVal person As String) As String
If IO.File.Exists("bowlers.dat") Then
Dim sr As IO.StreamReader = IO.File.OpenText("bowlers.dat")
Dim individual As String
Do Until sr.EndOfStream
individual = sr.ReadLine
If individual <> person Then
Return True
sr.Close()
End If
Loop
sr.Close()
End If
Return False
End Function
Heres all my code. Thanks for any help in advance.
I haven't used Visual Basic in quite a while, but I believe the solution is simple.
Basically, your interactions with the System.IO library are currently very optimistic. You should always wrap IO interactions within a TRY...CATCH...END TRY block.
Check it out here: MSDN Example of StreamReader.ReadLine
To illustrate more clearly my meaning, I've altered one of your functions here:
Function IsInFile(ByVal person As String) As String
Try
If IO.File.Exists("bowlers.dat") Then
Dim sr As IO.StreamReader = IO.File.OpenText("bowlers.dat")
Dim individual As String
Do Until sr.EndOfStream
individual = sr.ReadLine
If individual = person Then
Return True
sr.Close()
End If
Loop
sr.Close()
End If
Return False
Catch ie as IOException
MessageBox.Show("Failed to search for " & person & " in file.", "Error")
Return false
End Try
End Function
I'd apply similar logic to surround all your interactions with Files within your code. I'd also recommend against using MessageBox to report errors -- it's okay for small projects, but for anything larger you're going to want to look up using a logging framework. Or build a debug console into your application that you can selective turn on or off via configuration.

Excel Sort By Numbers Macro

I use a macro to delete rows which doesnt containing numbers for my report.
This macro find critical path numbers and split them. In a1 column it delete the numbers which doesnt in the list.
This macro works fine. Beside that i want to sort a1 column by critical path number orders.
In this link I added what i want and my report file. There is a critical path text at the bottom in report file. When i click Düzenle macro delete rows but not sort by critical path number orders.
Thanks for your helps!
I do not like performing complex changes and deleting rows at the same time. If anything goes wrong, you have to restore the worksheet. I have introduced a new worksheet "Critical Path" and have copied to it everything required from worksheet "Revit KBK Sonuç" in the desired sequence.
I have described what I am doing and why within the macro. I hope it is all clear but ask if necessary.
Option Explicit
Sub ertert()
' I avoid literals within the code if I think those literals may change
' over time and/or if I think a name would make the code clearer.
Const ColLast As Long = 10
Const ColShtHdrLast As Long = 2
Const TableHdr1 As String = "Total Pressure Loss Calculations by Sections"
Dim ColCrnt As Long
Dim Section() As String
Dim CriticalPath As String
Dim InxSect As Long
Dim Rng As Range
Dim RowDestNext As Long
Dim RowSrcLast As Long
Dim RowTableHdr1 As Long
Dim wshtDest As Worksheet
Dim wshtSrc As Worksheet
Set wshtSrc = Worksheets("Revit KBK Sonuç")
Set wshtDest = Worksheets("Critical Path")
With wshtDest
.Cells.EntireRow.Delete
End With
' I only work on the ActiveWorksheet if the user is to select the
' target worksheet in this way. Code is easier to understand if
' With statements are used.
With wshtSrc
' Copy column widths
For ColCrnt = 1 To ColLast
wshtDest.Columns(ColCrnt).ColumnWidth = .Columns(ColCrnt).ColumnWidth
Next
' I avoid stringing commands together. The resultant code may be
' marginally faster but it takes longer to write and much longer
' to decipher when you return to the macro in 12 months.
' Extract critial path string and convert to array of Section numbers
RowSrcLast = .Cells(Rows.Count, "A").End(xlUp).Row
CriticalPath = .Cells(RowSrcLast, "A").Value
' Extract text before trailing total pressure loss
CriticalPath = Split(CriticalPath, ";")(0)
' Discard introductory text and trim spaces
CriticalPath = Trim(Split(CriticalPath, ":")(1))
Section = Split(CriticalPath, "-")
Set Rng = .Cells.Find(What:=TableHdr1)
If Rng Is Nothing Then
Call MsgBox("I am unable to find the row containing """ & _
TableHdr1 & """", vbOKOnly)
Exit Sub
End If
RowTableHdr1 = Rng.Row
' Copy header section of worksheet without buttons
.Range(.Cells(1, 1), .Cells(RowTableHdr1 - 1, ColShtHdrLast)).Copy _
Destination:=wshtDest.Cells(1, 1)
' Copy table header
.Range(.Cells(RowTableHdr1, 1), .Cells(RowTableHdr1 + 1, ColLast)).Copy _
Destination:=wshtDest.Cells(RowTableHdr1, 1)
RowDestNext = RowTableHdr1 + 2
' Copy rows for each section in critical path to destination worksheet
For InxSect = 0 To UBound(Section)
Set Rng = .Columns("A:A").Find(What:=Section(InxSect), LookAt:=xlWhole)
If Rng Is Nothing Then
Call MsgBox("I am unable to find the row(s) for Section" & _
Section(InxSect), vbOKOnly)
Else
Set Rng = Rng.MergeArea ' Expand to include all rows for section
' Copy all rows for section
Rng.EntireRow.Copy Destination:=wshtDest.Cells(RowDestNext, 1)
' Step output row number
RowDestNext = RowDestNext + Rng.Rows.Count
End If
Next
' Copy critical path row
.Rows(RowSrcLast).EntireRow.Copy Destination:=wshtDest.Cells(RowDestNext, 1)
RowDestNext = RowDestNext + 1
End With
' Add border at bottom of output table
With wshtDest
With .Range(.Cells(RowDestNext, 1), _
.Cells(RowDestNext, ColLast)).Borders(xlEdgeTop)
.LineStyle = xlContinuous
.Weight = xlMedium
.ColorIndex = 16
End With
End With
End Sub
New version of macro in response to request
Because the sections have different numbers of rows, no in situ sort is possible.
Version 1 solved this problem by copying required rows to a different worksheet. Version 2 solves this problem by copying them to a workarea below the original table but within the same worksheet. That is, a new table is built beneath the old.
Once the new table is complete, the old table is deleted to move the new table into the correct position.
Sub ertert()
Const ColLast As Long = 10
Const ColShtHdrLast As Long = 2
Const TableHdr1 As String = "Total Pressure Loss Calculations by Sections"
Dim ColCrnt As Long
Dim Section() As String
Dim CriticalPath As String
Dim InxSect As Long
Dim Rng As Range
Dim RowDestNext As Long
Dim RowDestStart As Long
Dim RowSrcLast As Long
Dim RowTableHdr1 As Long
Dim wsht As Worksheet
Set wsht = ActiveSheet
With wsht
' Extract critial path string and convert to array of Section numbers
RowSrcLast = .Cells(Rows.Count, "A").End(xlUp).Row
CriticalPath = .Cells(RowSrcLast, "A").Value
' Extract text before trailing total pressure loss
CriticalPath = Split(CriticalPath, ";")(0)
' Discard introductory text and trim spaces
CriticalPath = Trim(Split(CriticalPath, ":")(1))
Section = Split(CriticalPath, "-")
Set Rng = .Cells.Find(What:=TableHdr1)
If Rng Is Nothing Then
Call MsgBox("I am unable to find the row containing """ & _
TableHdr1 & """", vbOKOnly)
Exit Sub
End If
RowTableHdr1 = Rng.Row
' Because there is no fixed number of rows per section no in-situ sort is
' practical. Instead copy required rows in required section to destination
' area below existing area.
RowDestStart = RowSrcLast + 2
RowDestNext = RowDestStart
' Copy rows for each section in critical path to destination area
For InxSect = 0 To UBound(Section)
Set Rng = .Columns("A:A").Find(What:=Section(InxSect), LookAt:=xlWhole)
If Rng Is Nothing Then
Call MsgBox("I am unable to find the row(s) for Section" & _
Section(InxSect), vbOKOnly)
Else
Set Rng = Rng.MergeArea ' Expand to include all rows for section
' Copy all rows for section
Rng.EntireRow.Copy Destination:=.Cells(RowDestNext, 1)
' Step output row number
RowDestNext = RowDestNext + Rng.Rows.Count
End If
Next
' Copy critical path row
.Rows(RowSrcLast).EntireRow.Copy Destination:=.Cells(RowDestNext, 1)
RowDestNext = RowDestNext + 1
' Add border at bottom of output table
With .Range(.Cells(RowDestNext, 1), _
.Cells(RowDestNext, ColLast)).Borders(xlEdgeTop)
.LineStyle = xlContinuous
.Weight = xlMedium
.ColorIndex = 16
End With
' Now have new table on rows RowDestStart to RowDestNext-1.
' Delete rows RowTableHdr1+2 to RowDestStart-1 (old table) to
' move new table into desired position.
.Rows(RowTableHdr1 + 2 & ":" & RowDestStart - 1).EntireRow.Delete
End With
End Sub

Resources