Why does my Excel VBA code not fully execute on first attempt? - excel

To start, I am using Excel 2016 on Windows 10.
I have a spreadsheet in Excel that utilizes two macros to either insert data based on a userform or delete all data from the spreadsheet when someone wants to clear it. The focus of this question is the program that clears the data (seen below), though I will be discussing the other one as well.
Sub ClearData()
ToDelete = MsgBox("Are you sure you want to delete all data?", vbYesNo, "Are you sure?")
if ToDelete = vbYes Then
ActiveSheet.Unprotect "Password"
Do While Range("A2").Value <> ""
Range("A2:J2").Delete Shift:=xlUp
Loop
ActiveSheet.Protect "Password"
End If
End Sub
The code is pretty straight forward. It asks the user to confirm they want to delete all the data, and if they do, it unlocks the sheet, continuously deletes the second row until there are no more rows, and then it locks the sheet back.
Problem: So let's say I have 10 rows of data in my spreadsheet and hit the button that leads to the ClearData() sub. In this case, the first row deletes, and then the macro stops entirely, leaving 9 rows of data. Now if I hit the ClearData() button again, the macro executes flawlessly and deletes the remaining 9 rows.
I thought maybe the issue was with the locking and unlocking the spreadsheet portion of the code. So I manually unlocked the sheet and clicked the button, and the same bug occurred.
Another similar issue that occasionally occurs with the "insert data" macro is that it will only add data to one cell (not even a full row, just the first cell). Then, if I run the program again and enter the same exact information into the userform, all of it fills in correctly. In both of these cases, the macro runs through a few lines of code until it executes the first line of code that actually updates the spreadsheet and then completely exits the code without executing any further lines. Also note that no error messages occur, either.
One thing to note is that this problem appears to be intermittent. I just tried it again, and the data is filling out and clearing correctly. There is one thing I can do that will cause the "insert data" macro to have the problem. If I change the code and save it again, even if the code I changed was irrelevant (e.g. adding a space to a comment), then the insert data error will occur on the next execution.

Not an actual answer to your question, but a suggestion to help debuging it.
Add a trap to detect the error condition, and break into debug mode, so you can examine the state of things.
Sub ClearData()
Dim ToDelete As VbMsgBoxResult
Dim RowCount As Long
ToDelete = MsgBox("Are you sure you want to delete all data?", vbYesNo, "Are you sure?")
If ToDelete = vbYes Then
ActiveSheet.Unprotect "Password"
Do While Range("A2").Value <> ""
Range("A2:J2").Delete Shift:=xlUp
RowCount = RowCount + 1
Loop
If RowCount = 1 Then Stop
ActiveSheet.Protect "Password"
End If
End Sub
It the loop ends after one deletion, the code will break on the Stop command. You can then examine the state of your sheet and the value of cell A2, which may (or may not!) shed some light.
Again not an answer, but an alternative method:
This assumes two things
- There is no additional data you want to keep below the first blank row in column A
- There is no additional data you want to keep to the right of column J
If either of these assumptions are wrong, the code can easily be adjusted to suit
Sub ClearData()
Dim rng As Range
If MsgBox("Are you sure you want to delete all data?", vbYesNo, "Are you sure?") <> vbYes Then Exit Sub
With ActiveSheet
Set rng = .Range(.Cells(.Rows.Count, 1).End(xlUp), .Cells(2, 1))
' account for possibility there are no data rows
If rng.Row = 1 Then Exit Sub
.Unprotect "Password"
rng.EntireRow.Delete
.Protect "Password"
End With
End Sub

Related

AUTOFILTERS in VBA

I thought I was finally done with my workbook but alas there arose an issue when we put it into practice (much to my dismay).
In short;
I was using autofilter (not in vba) menus to filter the items in my warehouse, and the shelves they were on.
When I filtered out everything but the shelves i wanted to add inventory to, the values I added (through a VBA programmed button that basically copies everything in the "add to stock" (AKA "C4:C1000" row and adds it into the "currently in stock" row (AKA "D1:d1000")) got added to the wrong row.
My solution was to use the autofilters to find the correct shelf, write the amount added to the stock, and then press the button.
The VBA code of the button would (in my new plan) then do exactly as before, only this time it would first remove the filters, execute as before, and then re-apply filters.
I cannot - for the life of me - figure out how to turn the autofilters back on with VBA code though.
I have searched far and wide on the net, but the closest I can find to what I want is the following code:
Activesheet.range("a4").autofilter
That does nothing but stop my code from completing its execution mid-way.
Please help!
The full code for one of the pages is as follows:
Sub AddtoInnTotalandclear()
'The macro is used to move all amounts plottet into "INN" colum. Amounts are moved into "TOTAL AMOUNT IN STORAGE" while clearing the "INN" colum simultaneously
'Removes flickering from the screen (part 1 of 2)
Application.ScreenUpdating = False
'removes the protection on the worksheet (NB! you have to have the current password in the code line for this to work)
ActiveSheet.Unprotect Password:="kirk"
'Copies the values from the "inn" colum
Range("c4:c1000").Copy
'Adds the copied values to the values already in the "Total Amount" colum
Range("d4:d1000").PasteSpecial Operation:=xlPasteSpecialOperationAdd
'Clears the "inn" colum
Range("c4:c1000").ClearContents
'Disable marchiing atnsa around copied range
Application.CutCopyMode = False
Range("d4:d1000").Select
Selection.Locked = True
'Allows autofilter usage despite the document being locked
'&
'Re-Activates the password protection
With ActiveSheet
.Protect Password:="kirk", AllowFiltering:=True
.EnableSelection = xlNoRestrictions
End With
'Determines where you end up when you are finished
Worksheets("in").Range("c4").Select
'Removes flickering from the screen (part 2 of 2)
Application.ScreenUpdating = True
End Sub
---- I want to remove/disable autofilters when I press the button that activates this VBA code, then re-activate the autofilters once the entire procedure is done...
I need an EXCEL JEDI to give me some sound code advice here.. Please :)
Here is a demo example. Say we have data in cols A through D with the headers in row#1. If you run:
Sub qwerty()
Dim s As Worksheet
Set s = ActiveSheet
s.AutoFilterMode = False
s.Range("A:D").AutoFilter
End Sub
You will end up with filtering on cols A through D, but with no criteria on any of those columns applied.
EDIT#1:
If your header row is row #3 and we are filtering cols A through D, then:
Sub qwerty2()
Dim s As Worksheet, N As Long
Set s = ActiveSheet
N = Cells(Rows.Count, "A").End(xlUp).Row
s.AutoFilterMode = False
s.Range("A3:D" & N).AutoFilter
End Sub

Deleting cell value if "Quit" button selected

I have 1 userform for login called "LoginForm" and 3 additional userforms "AMForm", "FMForm" and "HRMForm" that open up if the user's details are correct. There are 3 spreadsheets "AMChoices", "FMChoices" and "HRMChoices" where the contents from the 3 additional userforms are recorded into the relevant spreadsheet i.e. FMForm into FMChoices.
To specify the user, their UserID appears in the relevant spreadsheet if their credentials are accepted. For example, if is userform "AMForm" their UserID is entered into the next available cell in column B in "AMChoices" (starting at B3). As there are multiple users logging in, it enters to the next empty row.
The code I have works perfectly. However, on each userform "AMForm", "FMForm" and "HRMForm" there is a "quit" button. So I want it to delete the recently entered UserID.
How can I code this? I have entered the code I use to enter the UserID into the spreadsheet from the LoginForm. Please let me know :)
Private Sub btnAMLogout_Click()
If MsgBox("Are you sure you want to quit? Press Yes to proceed and No to cancel.", vbYesNo) = vbYes Then
Unload Me
End If
End Sub
If aCell.Offset(, 4) = "SBUB10" Then
AMForm.Show
With Worksheets("AMChoices")
LastRow = .Range("B" & .Rows.CountLarge).End(xlUp).Row + 1
If LastRow < 3 Then LastRow = 3
.Cells(LastRow, "b") = WorksheetFunction.Proper(ID)
End With
The snippet of code you've shown writes the ID into the worksheet. To remove it, you need to find it and blank out the cell it is in. This action can be performed either before theUnload Me call or after it. The code you have needs some modifications:
Private Sub btnAMLogout_Click()
If MsgBox("Are you sure ...", vbYesNo) = vbYes Then
Unload Me
' Let's say the new code goes here. The last ID (whichever it is) will be removed.
With Worksheets("AMChoices")
LastRow = .Range("B" & .Rows.CountLarge).End(xlUp).Row ' no need for: + 1
If LastRow < 3 Then Exit Sub ' no need for: LastRow = 3
.Cells(LastRow, "b") = "" ' no need for: WorksheetFunction.Proper(ID)
End With
End If
End Sub
Now, since you've mentioned multiple users, it might be a bit more complicated because you need to find the cell with the ID in question (it might not be the last one!) and then delete it. The "deleting" could be just emptying the cell (as above) or deleting the whole row of that cell, or something else--I do not know your situation well enough to say which it is.
Anyway, to look for the last occurrence of that ID in column "B:B" you need to test the cells in the column to have that ID, starting from the last cell and going up, one cell at a time, something like
For i = LastRow to 3 Step -1
If .Cells(i, "B") = WorksheetFunction.Proper(ID) Then
.Rows(i).Delete
Exit For
End If
Next
I sure hope this helps.

Get visible row and column number

I have a simple macro at my Excel worksheet, which does some simple calculation.
What happens is that for a better user experience I want the macro to run and when it is finished to return to the cell that the user was before he activated the macro, so that the user won't see any change on the screen.
Ok, so that's not to hard:
Dim activeRow As Integer
Dim activeCol As Integer
Sub myCode
activeRow = ActiveCell.Row
activeCol = ActiveCell.Column
' code...
Cells(activeRow, activeCol).Select
End Sub
The problem with the code above is that the line and columns that were visible before the macro runs are not the same on the end, because the macro may have scrolled the worksheet to the left, right, up or down.
For example, you can see the cell E26 on your display either if the first line visible at your worksheet is 15 or 16.
So the question here is how can I know the line and column number that are visible at the user display?
I was wondering that would be something like:
Dim topLine As Integer
Dim bottomLine As Integer
topLine = ActiveWorksheet.GetVisibleGrid.TopLine
bottomLine = ActiveWorksheet.GetVisibleGrid.BottomLine
re: "... for a better user experience I want the macro to run and when it is finished to return to the cell that the user was before he activated the macro, so that the user won't see any change on the screen."
The easiest way to do this is never use .Select or .Activate. There are less than an isolated handful of circumstances where using .Select is preferable to direct cell and worksheet reference(s) and only half again of those are actually necessary.
See How to avoid using Select in Excel VBA macros for methods on getting away from relying on .Select and .Activate to accomplish your goals.
Get started converted your code to eliminate .Select and .Activate. If you run into problems, post your working code here or at Code Review (Excel). If it is more than a page or so, post sections of it.
Here is one approach. What I have done is to save the active cell. freeze panes there, then return to that cell at the end and unfreeze panes.
Dim cell As Range
'save current cell and freeze panes here
Set cell = ActiveCell
ActiveWindow.FreezePanes = True
'go somewhere way off screen
Range("A1").Select
'now go back and remove freeze panes
cell.Select
ActiveWindow.FreezePanes = False

Excel VBA - Loop to filter table for each value in column and paste in according worksheet

I am trying to filter a table on the first worksheet ("Data") for each of the items that appear in a table on the second worksheet ("Hosts"), and then paste the filtered results in separate worksheets, each named after the corresponding item on the table.
My understanding of VBA is very basic and I have tried to put together a collage of codes from other users, but it doesn't seem to work properly for me:
The first loop creates worksheets based on the items on the "Hosts" table, but for some reason it adds an extra sheet before the ones I need and calls it "Sheet1"
The second loop simply doesn't work
Are two loops really necessary, or is it possible to combine the two?
This is the code I have so far:
Sub test()
Dim AllData As Worksheet
Dim HostList As Worksheet
Dim DataRange As Range
Dim FilterColumn As Long
Set AllData = ThisWorkbook.Worksheets("Data")
Set HostList = ThisWorkbook.Worksheets("Hosts")
Set DataRange = AllData.Range(Range("A1"), Range("A1").SpecialCells(xlLastCell))
Dim HostValues As Range
For Each HostValues In HostList.ListObjects("Table1").Range
With ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
On Error Resume Next
ActiveSheet.Name = HostValues.Value
If Err.Number = 1004 Then
Debug.Print HostValues.Value & "already used as a sheet name"
End If
On Error GoTo 0
End With
Next HostValues
For Each HostValues In HostList.ListObjects("Table1").Range
AllData.Activate
FilterColumn = 18
DataRange.AutoFilter Field:=FilterColumn, Criteria1:=HostValues
DataRange.SpecialCells(xlCellTypeVisible).Copy
Sheets(HostValues.Text).Range("A1").PasteSpecial xlPasteValues
Selection.Sort Key1:=Range("V:V"), Order1:=xlAscending, Header:=xlGuess
AllData.Activate
Cells.AutoFilter
Next HostValues
End Sub
Some kind soul out there please help me!
There's quite a bit to do here, but I'll give it a shot.
The first loop creates worksheets based on the items on the "Hosts" table, but for some reason it adds an extra sheet before the ones I need and calls it "Sheet1"
My guess here is that Hosts contains a duplicate entry or something is causing the sheet rename section to fail. I would check the debug window for that. Or change
Debug.Print HostValues.Value & "already used as a sheet name"
to
msgBox HostValues.Value & "already used as a sheet name"
That will make a popup happen, should make it easier to see when the error happens. Something else you can try, comment out the two OnError statements with a ' single quote. Then when an error is raised you can hit debug and work through what the program is upset with.
The second loop simply doesn't work
I'm not sure on this one. When you use a For Each many times changing the collection it is operating on will give you some sort of problem. You've asked the computer to do something for every cell in this column, and then you change the values of the column. That's just a guess.
Are two loops really necessary, or is it possible to combine the two?
You can combine the two, after creating the sheet for the Host you can move its data over to it.
Notes
The approach with filtering may be giving you undue complexity try writing a loop without the filters and checking if the Host has a sheet, if it does move the data. If it does not create it and move the data.
You do not need the With/End With block at all.
On Error Resume Next is dangerous. It has its uses, take a look at this for more information or handling errors.
Godspeed.

How to select clear table contents without destroying the table?

I have a vba function in excel 2010 that I built using help from people on here. This function copies the contents of a table/form, sorts them, and sends them to the appropriate tables.
Now after running this function I want the original table to be cleared. I can achieve this with the following code, assuming ACell has been defined as the first cell in the table.
ACell.ListObject.Range.ClearContents works fine, the only problem is it deletes the table as well as the data values.
Is there any way around this? I would rather not have to set the table up every time I enter some data.
How about:
ACell.ListObject.DataBodyRange.Rows.Delete
That will keep your table structure and headings, but clear all the data and rows.
EDIT: I'm going to just modify a section of my answer from your previous post, as it does mostly what you want. This leaves just one row:
With loSource
.Range.AutoFilter
.DataBodyRange.Offset(1).Resize(.DataBodyRange.Rows.Count - 1, .DataBodyRange.Columns.Count).Rows.Delete
.DataBodyRange.Rows(1).Specialcells(xlCellTypeConstants).ClearContents
End With
If you want to leave all the rows intact with their formulas and whatnot, just do:
With loSource
.Range.AutoFilter
.DataBodyRange.Specialcells(xlCellTypeConstants).ClearContents
End With
Which is close to what #Readify suggested, except it won't clear formulas.
Try just clearing the data (not the entire table including headers):
ACell.ListObject.DataBodyRange.ClearContents
I reworked Doug Glancy's solution to avoid rows deletion, which can lead to #Ref issue in formulae.
Sub ListReset(lst As ListObject)
'clears a listObject while leaving row 1 empty, with formulae
With lst
If .ShowAutoFilter Then .AutoFilter.ShowAllData
On Error Resume Next
With .DataBodyRange
.Offset(1).Rows.Clear
.Rows(1).SpecialCells(xlCellTypeConstants).ClearContents
End With
On Error GoTo 0
.Resize .Range.Rows("1:2")
End With
End Sub
There is a condition that most of these solutions do not address. I revised Patrick Honorez's solution to handle it. I felt I had to share this because I was pulling my hair out when the original function was occasionally clearing more data that I expected.
The situation happens when the table only has one column and the .SpecialCells(xlCellTypeConstants).ClearContents attempts to clear the contents of the top row. In this situation, only one cell is selected (the top row of the table that only has one column) and the SpecialCells command applies to the entire sheet instead of the selected range. What was happening to me was other cells on the sheet that were outside of my table were also getting cleared.
I did some digging and found this advice from Mathieu Guindon:
Range SpecialCells ClearContents clears whole sheet
Range({any single cell}).SpecialCells({whatever}) seems to work off the entire sheet.
Range({more than one cell}).SpecialCells({whatever}) seems to work off the specified cells.
If the list/table only has one column (in row 1), this revision will check to see if the cell has a formula and if not, it will only clear the contents of that one cell.
Public Sub ClearList(lst As ListObject)
'Clears a listObject while leaving 1 empty row + formula
' https://stackoverflow.com/a/53856079/1898524
'
'With special help from this post to handle a single column table.
' Range({any single cell}).SpecialCells({whatever}) seems to work off the entire sheet.
' Range({more than one cell}).SpecialCells({whatever}) seems to work off the specified cells.
' https://stackoverflow.com/questions/40537537/range-specialcells-clearcontents-clears-whole-sheet-instead
On Error Resume Next
With lst
'.Range.Worksheet.Activate ' Enable this if you are debugging
If .ShowAutoFilter Then .AutoFilter.ShowAllData
If .DataBodyRange.Rows.Count = 1 Then Exit Sub ' Table is already clear
.DataBodyRange.Offset(1).Rows.Clear
If .DataBodyRange.Columns.Count > 1 Then ' Check to see if SpecialCells is going to evaluate just one cell.
.DataBodyRange.Rows(1).SpecialCells(xlCellTypeConstants).ClearContents
ElseIf Not .Range.HasFormula Then
' Only one cell in range and it does not contain a formula.
.DataBodyRange.Rows(1).ClearContents
End If
.Resize .Range.Rows("1:2")
.HeaderRowRange.Offset(1).Select
' Reset used range on the sheet
Dim X
X = .Range.Worksheet.UsedRange.Rows.Count 'see J-Walkenbach tip 73
End With
End Sub
A final step I included is a tip that is attributed to John Walkenbach, sometimes noted as J-Walkenbach tip 73 Automatically Resetting The Last Cell
I use this code to remove my data but leave the formulas in the top row. It also removes all rows except for the top row and scrolls the page up to the top.
Sub CleanTheTable()
Application.ScreenUpdating = False
Sheets("Data").Select
ActiveSheet.ListObjects("TestTable").HeaderRowRange.Select
'Remove the filters if one exists.
If ActiveSheet.FilterMode Then
Selection.AutoFilter
End If
'Clear all lines but the first one in the table leaving formulas for the next go round.
With Worksheets("Data").ListObjects("TestTable")
.Range.AutoFilter
On Error Resume Next
.DataBodyRange.Offset(1).Resize(.DataBodyRange.Rows.Count - 1, .DataBodyRange.Columns.Count).Rows.Delete
.DataBodyRange.Rows(1).SpecialCells(xlCellTypeConstants).ClearContents
ActiveWindow.SmallScroll Down:=-10000
End With
Application.ScreenUpdating = True
End Sub
I usually use something very simple if you just want to clear table contents.
Sub Clear_table()
Range("Table1").ClearContents
End Sub
Obviously if you have a workbook with multiple pages you might want to change the code to accommodate that.
Sub Clear_table()
Worksheets("Sheet1").Range("Table1").ClearContents
End Sub
If you want to delete the entire table except your headers, and your formula, you can try this:
Sub DeteteTableExceptFormula()
Dim tb As ListObject
Set tb = activeworksheet.ListObjects("MyTable")
tb.DataBodyRange.Delete
End Sub

Resources