Adding a ListRow into a table of a protected worksheet - excel

I want to add data to last row in each table in each worksheet when the worksheet is protected.
I have this code in ThisWorkbook to protect the worksheets
Private Sub Workbook_Open()
Dim wSheet As Worksheet
For Each wSheet In Worksheets
wSheet.Protect Password:="Secret", _
UserInterFaceOnly:=True
Next wSheet
End Sub
and the following code to add the data. It throws
Error 1004 "Application-defined or Object-defined error"
at the Set newrow1 = tbl.ListRows.Add when the worksheet is protected.
Sub AddDataToTable()
Application.ScreenUpdating = False
Dim MyValue As String
Dim sh As Worksheet
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim ws3 As Worksheet
Dim ws4 As Worksheet
Dim ws5 As Worksheet
Set ws1 = Sheets("Setting")
Set ws2 = Sheets("R_Buy")
Set ws3 = Sheets("R_Sell")
Set ws4 = Sheets("S_Buy")
Set ws5 = Sheets("S_Sell")
Dim tbl As ListObject
Dim tb2 As ListObject
Dim tb3 As ListObject
Dim tb4 As ListObject
Dim tb5 As ListObject
Set tbl = ws1.ListObjects("T_Setting")
Set tb2 = ws2.ListObjects("T_R_Buy")
Set tb3 = ws3.ListObjects("T_R_Sell")
Set tb4 = ws4.ListObjects("T_S_Buy")
Set tb5 = ws5.ListObjects("T_S_Sell")
Dim newrow1 As ListRow
Dim newrow2 As ListRow
Dim newrow3 As ListRow
Dim newrow4 As ListRow
Dim newrow5 As ListRow
MyValue = InputBox("Add To Table, this cannot be undone")
'check if user clicked Cancel button and, if appropriate, execute statements
If StrPtr(MyValue) = 0 Then
'display message box confirming that user clicked Cancel button
MsgBox "You clicked the Cancel button"
'check if user entered no input and, if appropriate, execute statements
ElseIf MyValue = "" Then
'display message box confirming that user entered no input
MsgBox "There is no Text Input"
Else
Set newrow1 = tbl.ListRows.Add
With newrow1
.Range(1) = MyValue
End With
Set newrow2 = tb2.ListRows.Add
With newrow2
.Range(1) = MyValue
End With
Set newrow3 = tb3.ListRows.Add
With newrow3
.Range(1) = MyValue
End With
Set newrow4 = tb4.ListRows.Add
With newrow4
.Range(1) = MyValue
End With
Set newrow5 = tb5.ListRows.Add
With newrow5
.Range(1) = MyValue
End With
End If
Application.ScreenUpdating = True
End Sub

That's an issue with Excel that it doesn't allow to edit tables in UserInterFaceOnly:=True mode. Unfortunately, the only workaround I've found is to unprotect before any table methods are applied and then reprotect after:
.Unprotect Password:=SHEET_PW 'unprotect sheet
'edit table
.Protect Password:=SHEET_PW, UserInterFaceOnly:=True 'reprotect
Additionally I suggest the following improvement to shorten your code:
Use arrays Dim tbl(1 To 5) instead of multiple variables tbl1, tbl2, tbl3, …
Or better use an array to list your worksheet names only.
Use more descriptive variable names (makes your life easier to maintain and read the code)
If your table names are always T_ followed by the worksheet name you can easily generate them out of your worksheet name.
Use a constant for your worksheet password SHEET_PW to have it stored in only one place (easier to change, prevents typos).
Use loops to do repetitive things.
So we end up with:
Option Explicit
Const SHEET_PW As String = "Secret" 'global password for protecting worksheets
Public Sub AddDataToTableImproved()
Dim AddValue As String
AddValue = InputBox("Add To Table, this cannot be undone")
If StrPtr(AddValue) = 0 Then 'cancel button
MsgBox "You clicked the Cancel button"
Exit Sub
ElseIf AddValue = "" Then 'no input
MsgBox "There is no Text Input"
Exit Sub
End If
Dim NewRow As ListRow
Dim SheetNameList() As Variant
SheetNameList = Array("Setting", "R_Buy", "R_Sell", "S_Buy", "S_Sell")
Dim SheetName As Variant
For Each SheetName In SheetNameList
With ThisWorkbook.Worksheets(SheetName)
.Unprotect Password:=SHEET_PW 'unprotect sheet
Set NewRow = .ListObjects("T_" & SheetName).ListRows.Add
NewRow.Range(1) = AddValue
.Protect Password:=SHEET_PW, UserInterFaceOnly:=True 'reprotect it
End With
Next SheetName
End Sub

A bit late to help the original OP but hopefully this will help other readers.
There is indeed an issue with the ListObject functionality when the worksheet is protected even if the UserInterFaceOnly flag is set to True.
However, we can still use the Range and Application functionality and we can actually work around most of the use cases with the exception of 2 edge cases:
We want to insert immediately after the header row AND the sheet is protected AND the headers are off (.ShowHeaders is False) - I don't think there is any solution to this but to be honest I wonder why would one have the headers off. Not to mention it's a really rare case to meet all 3 criterias.
The table has no rows AND the sheet is protected AND the headers are off. In this case the special 'insert' row cannot easily be turned into a 'listrow' but it can be done with a few column and row inserts - not worth the trouble though as this is potentially rare in real life use.
Here is the code that I came up with:
Option Explicit
Option Private Module
Private Const MODULE_NAME As String = "LibExcelListObjects"
'*******************************************************************************
'Adds rows to a ListObject and returns the corresponding added Range
'Parameters:
' - tbl: the table to add rows to
' - [rowsToAdd]: the number of rows to add. Default is 1
' - [startRow]: the row index from where to start adding. Default is 0 in
' which case the rows would be appended at the end of the table
' - [doEntireSheetRow]:
' * TRUE - adds entire rows including left and right of the target table
' * FALSE - adds rows only below the table bounds shifting down (default)
'Raises error:
' - 5: if 'rowsToAdd' is less than 1
' - 9: if 'startRow' is invalid
' - 91: if 'tbl' is not set
' - 1004: if adding rows failed due to worksheet being protected while the
' UserInterfaceOnly flag is set to False
'*******************************************************************************
Public Function AddListRows(ByVal tbl As ListObject _
, Optional ByVal rowsToAdd As Long = 1 _
, Optional ByVal startRow As Long = 0 _
, Optional ByVal doEntireSheetRow As Boolean = False _
) As Range
Const fullMethodName As String = MODULE_NAME & ".AddListRows"
Dim isSuccess As Boolean
'
If tbl Is Nothing Then
Err.Raise 91, fullMethodName, "Table object not set"
ElseIf startRow < 0 Or startRow > tbl.ListRows.Count + 1 Then
Err.Raise 9, fullMethodName, "Invalid start row index"
ElseIf rowsToAdd < 1 Then
Err.Raise 5, fullMethodName, "Invalid number of rows to add"
End If
If startRow = 0 Then startRow = tbl.ListRows.Count + 1
'
If startRow = tbl.ListRows.Count + 1 Then
isSuccess = AppendListRows(tbl, rowsToAdd, doEntireSheetRow)
Else
isSuccess = InsertListRows(tbl, rowsToAdd, startRow, doEntireSheetRow)
End If
If Not isSuccess Then
If tbl.Parent.ProtectContents And Not tbl.Parent.ProtectionMode Then
Err.Raise 1004, fullMethodName, "Parent sheet is macro protected"
Else
Err.Raise 5, fullMethodName, "Cannot append rows"
End If
End If
Set AddListRows = tbl.ListRows(startRow).Range.Resize(RowSize:=rowsToAdd)
End Function
'*******************************************************************************
'Utility for 'AddListRows' method
'Inserts rows into a ListObject. Does not append!
'*******************************************************************************
Private Function InsertListRows(ByVal tbl As ListObject _
, ByVal rowsToInsert As Long _
, ByVal startRow As Long _
, ByVal doEntireSheetRow As Boolean) As Boolean
Dim rngInsert As Range
Dim fOrigin As XlInsertFormatOrigin: fOrigin = xlFormatFromLeftOrAbove
Dim needsHeaders As Boolean
'
If startRow = 1 Then
If Not tbl.ShowHeaders Then
If tbl.Parent.ProtectContents Then
Exit Function 'Not sure possible without headers
Else
needsHeaders = True
End If
End If
fOrigin = xlFormatFromRightOrBelow
End If
'
Set rngInsert = tbl.ListRows(startRow).Range.Resize(RowSize:=rowsToInsert)
If doEntireSheetRow Then Set rngInsert = rngInsert.EntireRow
'
On Error Resume Next
If needsHeaders Then tbl.ShowHeaders = True
rngInsert.Insert xlShiftDown, fOrigin
If needsHeaders Then tbl.ShowHeaders = False
InsertListRows = (Err.Number = 0)
On Error GoTo 0
End Function
'*******************************************************************************
'Utility for 'AddListRows' method
'Appends rows to the bottom of a ListObject. Does not insert!
'*******************************************************************************
Private Function AppendListRows(ByVal tbl As ListObject _
, ByVal rowsToAppend As Long _
, ByVal doEntireSheetRow As Boolean) As Boolean
If tbl.ListRows.Count = 0 Then
If Not UpgradeInsertRow(tbl) Then Exit Function
If rowsToAppend = 1 Then
AppendListRows = True
Exit Function
End If
rowsToAppend = rowsToAppend - 1
End If
'
Dim rngToAppend As Range
Dim isProtected As Boolean: isProtected = tbl.Parent.ProtectContents
'
On Error GoTo ErrorHandler
If isProtected And tbl.ShowTotals Then
Set rngToAppend = tbl.TotalsRowRange
ElseIf isProtected Then
Set rngToAppend = AutoExpandOneRow(tbl)
Else
Set rngToAppend = tbl.Range.Rows(tbl.Range.Rows.Count + 1)
End If
'
Set rngToAppend = rngToAppend.Resize(RowSize:=rowsToAppend)
If doEntireSheetRow Then Set rngToAppend = rngToAppend.EntireRow
rngToAppend.Insert xlShiftDown, xlFormatFromLeftOrAbove
'
If isProtected And tbl.ShowTotals Then 'Fix formatting
tbl.ListRows(1).Range.Copy
With tbl.ListRows(tbl.ListRows.Count - rowsToAppend + 1).Range
.Resize(RowSize:=rowsToAppend).PasteSpecial xlPasteFormats
End With
ElseIf isProtected Then 'Delete the autoExpand row
tbl.ListRows(tbl.ListRows.Count).Range.Delete xlShiftUp
Else 'Resize table
tbl.Resize tbl.Range.Resize(tbl.Range.Rows.Count + rowsToAppend)
End If
AppendListRows = True
Exit Function
ErrorHandler:
AppendListRows = False
End Function
'*******************************************************************************
'Utility for 'AppendListRows' method
'Transforms the Insert row into a usable ListRow
'*******************************************************************************
Private Function UpgradeInsertRow(ByVal tbl As ListObject) As Boolean
If tbl.InsertRowRange Is Nothing Then Exit Function
If tbl.Parent.ProtectContents And Not tbl.ShowHeaders Then
Exit Function 'Not implemented - can be done using a few inserts
Else
Dim needsHeaders As Boolean: needsHeaders = Not tbl.ShowHeaders
'
If needsHeaders Then tbl.ShowHeaders = True
tbl.InsertRowRange.Insert xlShiftDown, xlFormatFromLeftOrAbove
If needsHeaders Then tbl.ShowHeaders = False
End If
UpgradeInsertRow = True
End Function
'*******************************************************************************
'Utility for 'AppendListRows' method
'Adds one row via auto expand if the worksheet is protected and totals are off
'*******************************************************************************
Private Function AutoExpandOneRow(ByVal tbl As ListObject) As Range
If Not tbl.Parent.ProtectContents Then Exit Function
If tbl.ShowTotals Then Exit Function
'
Dim ac As AutoCorrect: Set ac = Application.AutoCorrect
Dim isAutoExpand As Boolean: isAutoExpand = ac.AutoExpandListRange
Dim tempRow As Range: Set tempRow = tbl.Range.Rows(tbl.Range.Rows.Count + 1)
'
If Not isAutoExpand Then ac.AutoExpandListRange = True
tempRow.Insert xlShiftDown, xlFormatFromLeftOrAbove
Set AutoExpandOneRow = tempRow.Offset(-1, 0)
Const arbitraryValue As Long = 1 'Must not be Empty/Null/""
AutoExpandOneRow.Value2 = arbitraryValue 'AutoExpand is triggered
If Not isAutoExpand Then ac.AutoExpandListRange = False 'Revert if needed
End Function
Assuming tbl is a variable holding the table, we can use the above like this:
AddListRows tbl 'Adds 1 row at the end
AddListRows tbl, 5 'Adds 5 rows at the end
AddListRows tbl, 3, 2 'Inserts 3 rows at index 2
AddListRows tbl, 1, 3, True 'Insert one row at index 3 but for the whole sheet
As long as the UserInterfaceOnly flag is set to True the above will work except the 2 edge cases I mentioned at the beginning of the answer. Of course, the operation would fail if there is another ListObject immediately below the table we want to insert into but that would fail anyway even if the sheet was unprotected.
One nice advantage is that the AddListRows method above returns the range that was inserted so that it can be used to write data immediately after the rows were added.

Related

VBA Create and Rename Tables

I'm looking to create a table without selecting the first row and creating a table. Then naming the table based on what the sheet name is.
Sub ConvertDataToTables()
' For i = 3 To 5
' Sheets(i).Activate
' Rows(1).EntireRow.Delete
' Next i
For i = 3 To 5
On Error Resume Next
Sheets(i).Select
ActiveSheet.ShowAllData
Cells.AutoFilter
Range("A2").CurrentRegion.Select
If ActiveSheet.ListObjects.Count < 1 Then
ActiveSheet.ListObjects.Add.Name = ActiveSheet.Name
End If
Next i
Table names get place with an underscore with a space and I don't want that. so Sum Day = Sum_Day from my code. I also want to have the selection not choose the top row but everything below.
Convert Table to Excel Table (ListObject)
Option Explicit
Sub ConvertDataToTables()
Const FIRST_CELL As String = "A2"
Const FIRST_INDEX As Long = 3
Const LAST_INDEX As Long = 5
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet, rg As Range, fCell As Range, lo As ListObject
Dim i As Long, NewName As String
For i = FIRST_INDEX To LAST_INDEX
Set ws = wb.Worksheets(i)
If ws.ListObjects.Count = 0 Then
' Remove the auto filter.
If ws.AutoFilterMode Then ws.AutoFilterMode = False
NewName = Replace(Application.Proper(ws.Name), " ", "")
ws.Name = NewName
Set fCell = ws.Range(FIRST_CELL)
With fCell.CurrentRegion
Set rg = fCell.Resize(.Row + .Rows.Count - fCell.Row, _
.Column + .Columns.Count - fCell.Column)
End With
Set lo = ws.ListObjects.Add(xlSrcRange, rg, , xlYes)
lo.Name = NewName
End If
Next i
End Sub
Try the following code. It will replace spaces from the sheet names. Also, it doesn't use Select to rely on the ActiveSheet - for further reading refer to How to avoid using Select in Excel VBA
The code uses intermediate Range variables to define the range for the table. It starts at cell A2 (startCell) and uses the last cell of the CurrentRegion as endCell.
Dim sheetIndex As Long
For sheetIndex = 3 To ThisWorkbook.Worksheets.Count
With ThisWorkbook.Worksheets(sheetIndex)
If .ListObjects.Count = 0 Then
Dim startcell As Range, endCell As Range, tableRange As Range
Set startcell = .Cells(2, 1)
Set endCell = startcell.CurrentRegion.Cells(startcell.CurrentRegion.Cells.Count)
Set tableRange = .Range(startcell, endCell)
Debug.Print tableRange.Address
.ListObjects.Add(xlSrcRange, tableRange).Name = Replace(.Name, " ", "")
End If
End With
Next sheetIndex
Note that you should always use Option Explicit and declare all Variables and you should never use On Error Resume Next except for single statement where you know that they might fail (and you want to do the error handling by your own).

Create new sheets based on dynamic values in certain column

Given a range of values in column B, for example - we only have 2 values from B4 to B5, where 12 is in B4 and 99 is in B5.
For each value(we call it product code) in column B (here they are 12 and 99), I want to:
create a duplicate of the existing sheet "Order", and replace the cell which is named "Symbol"(C2) with the product code (the value in the collection)
name the new sheet with the value (product code) in the cell
Trick: The number of values is dynamic, where it definitely starts with B4, but might end with any value in column B
For the code, I am thinking the logic should be:
##(1) get the range of values in column B starting from B4 (which is dynamic)
##(2) loop through all values in the column, create a sheet for each and change its name to the product
However, I am not sure
(1) how to get the values within a column and maybe store them in a collection to facilitate 2nd step?
(2) maybe I can do something like below for the 2nd step:
Dim SourceSheet As Object
Set SourceSheet = ActiveSheet
SourceSheet.Copy After:=SourceSheet
Dim NewSheet As Object
Set NewSheet = ThisWorkbook.Sheets(SourceSheet.Index + 1)
On Error GoTo ERR_RENAME
NewSheet.Name = "InputName"
On Error GoTo 0
But here we need to do it for each item in the collection we have generated in step 1, and name it according to the item value (product code in the collection).
Any help would be greatly appreciated, thanks in advance.
Add Worksheets
Option Explicit
Sub CreateOrders()
' Define constants.
Const PROC_TITLE As String = "Create Orders"
Const DATA_SHEET_NAME As String = "Sheet1" ' adjust!
Const DATA_FIRST_CELL As String = "B4"
Const SOURCE_SHEET_NAME As String = "Order"
Const DST_CELL As String = "C2"
' Reference the workbook.
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the data range.
Dim ws As Worksheet: Set ws = wb.Sheets(DATA_SHEET_NAME)
If ws.AutoFilterMode Then ws.AutoFilterMode = False
Dim rg As Range, rCount As Long
With ws.Range(DATA_FIRST_CELL)
Dim lCell As Range: Set lCell = .Resize(ws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then
MsgBox "No product IDs found.", vbExclamation, PROC_TITLE
Exit Sub
End If
rCount = lCell.Row - .Row + 1
Set rg = .Resize(rCount)
End With
' Write the values from the data range to an array.
Dim Data() As Variant
If rCount = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
Else
Data = rg.Value
End If
' Write the unique values from the array to a dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim r As Long, rString As String
For r = 1 To rCount
rString = CStr(Data(r, 1))
If Len(rString) > 0 Then ' not blank
dict(rString) = Empty
End If
Next r
If dict.Count = 0 Then
MsgBox "The product ID column is blank.", vbExclamation, PROC_TITLE
Exit Sub
End If
' Reference the source worksheet.
Dim sws As Worksheet: Set sws = wb.Sheets(SOURCE_SHEET_NAME)
' Create orders.
Application.ScreenUpdating = False
Dim dsh As Object, rKey As Variant, oCount As Long, ErrNum As Long
For Each rKey In dict.Keys
' Check if the order exists.
On Error Resume Next ' defer error trapping
Set dsh = wb.Sheets(rKey)
On Error GoTo 0 ' turn off error trapping
' Create order.
If dsh Is Nothing Then ' the order doesn't exist
sws.Copy After:=wb.Sheets(wb.Sheets.Count) ' copy as last sheet
Set dsh = wb.Sheets(wb.Sheets.Count) ' reference the new last sheet
On Error Resume Next ' defer error trapping
dsh.Name = rKey ' rename
ErrNum = Err.Number
On Error GoTo 0 ' turn off error trapping
If ErrNum = 0 Then ' valid sheet name
dsh.Range(DST_CELL).Value = rKey ' write to the cell
oCount = oCount + 1
Else ' invalid sheet name
Application.DisplayAlerts = False ' delete without confirmation
dsh.Delete
Application.DisplayAlerts = True
End If
'Else ' the order exists; do nothing
End If
Set dsh = Nothing ' reset for the next iteration
Next rKey
Application.ScreenUpdating = True
' Inform.
Select Case oCount
Case 0: MsgBox "No new orders.", vbExclamation, PROC_TITLE
Case 1: MsgBox "One new order created.", vbInformation, PROC_TITLE
Case Else: MsgBox oCount & " new orders created.", _
vbInformation, PROC_TITLE
End Select
End Sub

Excel VBA Table Filter issues- Delete items in a table

When applying this code I am running into the issue that the top most filtered Item isn't being counted.
IE: When trying to delete the data within a Table if i have 1 entry TestEmptyTable() Returns False.
If i try to count the header as an entry and mark as >= 2 then it doesn't delete the top most entry. When it is >=1 It attempts to delete the whole sheet- When it is >1 it does nothing for the topmost entry but gets everything else. Referring to this section below when saying '>'
The Entire code is below the first code entry.
Any advise on how to get this Pesky first entry in my filtered tables?
Edit- I have learned the values that are being counted in tbl.Range.SpecialCells are not aligned with what i actually have, trying to fix that.
If tbl.Range.SpecialCells(xlCellTypeVisible).Areas.Count >= 2 Then
tblIsVisible = True
Else
If tbl.Range.SpecialCells(xlCellTypeVisible).Areas.Count < 1 Then
tblIsVisible = False
End If
End If
'In Module6
Function TestEmptyTable()
Dim tbl As ListObject
Dim tblIsVisible As Boolean
Set tbl = ActiveSheet.ListObjects(1)
If tbl.Range.SpecialCells(xlCellTypeVisible).Areas.Count >= 2 Then
tblIsVisible = True
Else
If tbl.Range.SpecialCells(xlCellTypeVisible).Areas.Count < 1 Then
tblIsVisible = False
End If
End If
TestEmptyTable = tblIsVisible
'MsgBox (TestEmptyTable)
End Function
Function DelTable()
Application.DisplayAlerts = False
If TestEmptyTable() = True Then
'MsgBox ("TestEmptyTable = True")
ActiveSheet.ListObjects("Table1").DataBodyRange.Delete
Else
'MsgBox ("TestEmptyTable= False")
End If
Application.DisplayAlerts = True
End Function
'In Module5
Sub DeleteTable()
'
'
'
'
If Module6.TestEmptyTable = True Then
Call Module6.DelTable
End If
End Sub
'in Module1
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=3, Criteria1:="MyFilterString"
MsgBox (Module6.TestEmptyTable)'Still here from trying to test what happens.
Call DeleteTable
I had some problems to understanding what you needed.
I think this code might help you achieved what you need.
Option Explicit
Sub Main()
Dim ol As ListObject: Set ol = ActiveSheet.ListObjects(1)
If isTableEmpty(ol) Then
Debug.Print "table empty"
Exit Sub
Else
Debug.Print "table not empty"
If TableHasFilters(ol) Then
Call TableDeleteFilteredRows(ol)
Else
ol.DataBodyRange.Delete
End If
End If
End Sub
Function isTableEmpty(ol As ListObject) As Boolean
If ol.ListRows.Count = 0 Then isTableEmpty = True
End Function
Function TableHasFilters(ol As ListObject) As Boolean
TableHasFilters = ol.AutoFilter.FilterMode
End Function
Sub TableFilterRestore(ol As ListObject)
If ol.AutoFilter.FilterMode Then ol.AutoFilter.ShowAllData
End Sub
Function TableVisibleRowsCount(ol As ListObject) As Integer
If ol.ListRows.Count > 0 Then
TableVisibleRowsCount = ol.ListColumns(1).DataBodyRange.SpecialCells(xlCellTypeVisible).Count
End If
End Function
Sub TableDeleteFilteredRows(ol As ListObject)
Dim rCell As Range
Dim olRng As Range
Dim olRowHd As Integer
Dim lrIdx As Integer
Dim arr() As Variant
Dim i As Integer: i = 0
' Exit if table has no rows
If ol.ListRows.Count = 0 Then Exit Sub
' Set variables
Set olRng = ol.ListColumns(1).DataBodyRange.SpecialCells(xlCellTypeVisible)
olRowHd = ol.HeaderRowRange.Row
' Count filtered rows
Dim nRows As Integer: nRows = TableVisibleRowsCount(ol)
' Redim array
ReDim arr(1 To nRows)
' Popuplate array with listrow index of visible rows
For Each rCell In olRng
' get listrow index
lrIdx = ol.ListRows(rCell.Row - olRowHd).Index
' Add item to array
i = i + 1
arr(i) = lrIdx
Next rCell
' Clear table filters
Call TableFilterRestore(ol)
' Delete rows
For i = UBound(arr) To LBound(arr) Step -1
ol.ListRows(arr(i)).Delete
Next i
End Sub

Using 'if.....then' loop with a Checkbox in VBA Excel

I am creating a VBA Excel program where I can copy the cell value to another sheet if its corresponding checkbox is checked. I have 278 "number" entries in one column and an corresponding individual "checkboxes" in one column. But when click the checkbox, the corresponding row text is not displayed.Instead it shows only the first 5 column values. For example, If I select 5 checkboxes randomly, it shows 1,2,3,4,5 numbers are displayed in the "sheet 2" columns.
Sub Button21_Click()
Dim chkbx As CheckBox
Dim i As Integer
a = Worksheets("sheet1").Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To a
For Each chkbx In ActiveSheet.CheckBoxes
If chkbx.Value = xlOn Then
Worksheets("sheet1").Cells(i, 1).Copy
Worksheets("sheet2").Activate
b = Worksheets("sheet2").Cells(i, 1).End(xlUp).Row
Worksheets("sheet2").Cells(b + 1, 1).Select
ActiveSheet.Paste
i = i + 1
End If
Next chkbx
Next i
End Sub
This is the code I've used.
Any help would be appreciated.
An Objects Investigation
The Solution
The TopLeftCell Solution, The Solution, is based on the idea of Tim Williams suggested in the comments.
This will be in your sheet code (Sheet1).
Sub Button21_Click()
executeCheckBoxes
End Sub
The rest will be in a standard module (e.g. Module1).
Sub executeCheckBoxes()
Dim src As Worksheet ' Source Worksheet (Object)
Dim tgt As Worksheet ' Target Worksheet (Object)
Dim chkbx As CheckBox ' CheckBox (For Each Control Variable)
Dim srcLR As Long ' Source Last Row
Dim tgtER As Long ' Target Empty Row
Dim i As Long ' Source Row Counter
Set src = ThisWorkbook.Worksheets("Sheet1")
Set tgt = ThisWorkbook.Worksheets("Sheet2")
srcLR = src.Cells(src.Rows.Count, 1).End(xlUp).Row
tgtER = tgt.Cells(tgt.Rows.Count, 1).End(xlUp).Row + 1
For Each chkbx In src.CheckBoxes
If chkbx.Value = xlOn Then
' Cell Version
tgt.Cells(tgtER, 1).Value = _
src.Cells(chkbx.TopLeftCell.Row, 1).Value
' The following 2 ideas are not so good. They are running into trouble
' when adding new checkboxes if not sooner.
' Index Version
' Assuming the index of the checkbox is 1 for row 2, 2 for 3 etc.
' Adjust the "+1" as needed.
' tgt.Cells(tgtER, 1).Value = src.Cells(chkbx.Index + 1, 1).Value
' Name Version
' Assuming the name of the checkbox is "Check Box 1" for row 2,
' "Check Box 2" for 3 etc. Adjust the "+1" as needed.
' tgt.Cells(tgtER, 1).Value = src.Cells(Val(Right(chkbx.Name, _
' Len(chkbx.Name) - Len("Check Box "))) + 1, 1).Value
tgtER = tgtER + 1
Debug.Print chkbx.Name
End If
Next chkbx
End Sub
Extras
The following are codes used to help to create the two inferior solutions.
Sub deleteCB()
deleteCheckBoxes ThisWorkbook.Worksheets("Sheet1")
End Sub
' Deletes all check boxes on a worksheet.
' Note: When you delete all check boxes, the 'counter' is not reset i.e. if you
' e.g. had "Check Box 100" the next check box will be named "Check Box 101".
' But after you save and close the workbook and open it again,
' the first check box name will be "Check Box 1".
Sub deleteCheckBoxes(Sheet As Worksheet)
Sheet.CheckBoxes.Delete
End Sub
' Creates check boxes in a range.
Sub addCheckBoxes()
Const SheetName As String = "Sheet1"
Const chkRange As String = "B2:B279"
Const chkCaption As String = "Chk"
Dim chk As CheckBox, rng As Range, cel As Range, i As Long
i = 1
With ThisWorkbook.Worksheets(SheetName)
Set rng = .Range(chkRange)
For Each cel In rng.Cells
Set chk = .CheckBoxes.Add(cel.Left, cel.Top, cel.Width, cel.Height)
With chk
.Caption = chkCaption & i
End With
i = i + 1
Next
End With
End Sub
Sub showSomeCheckBoxProperties()
Dim chk As CheckBox, rng As Range, cel As Range, i As Long
i = 1
With ThisWorkbook.Worksheets("Sheet1")
For Each chk In .CheckBoxes
With chk
Debug.Print .BottomRightCell.Address, .Caption, _
.Characters.Count, .Enabled, .Index, .Name, .Placement, _
.Text, .TopLeftCell.Address, .Value, .Visible
End With
Next
End With
End Sub
Extras 2
The following is the code based on the YouTube video
Add Button to Worksheet and Assign a Macro to it -Excel Help by XLorate that helped quite a lot in answering this question.
Sub addButtons()
Dim btn As Button, rng As Range, cel As Range, i As Long
i = 1
With ThisWorkbook.Worksheets("Sheet1")
Set rng = .Range("A1:A3")
For Each cel In rng.Cells
Set btn = .Buttons.Add(cel.Left, cel.Top, cel.Width, cel.Height)
With btn
.Caption = "Macro" & i
.OnAction = "Macro" & i
End With
i = i + 1
Next
End With
End Sub
The following are some other more or less helpful codes which I created while investigating objects.
Sub showSomeShapesProperties()
Dim ws As Worksheet, sh As Shape
Set ws = ThisWorkbook.Worksheets("Sheet1")
For Each sh In ws.Shapes
With sh
If sh.Type = 12 Then
Debug.Print .Name, .Type, .ID, .OnAction, .TopLeftCell.Address
End If
If sh.Type = 8 Then
Debug.Print .Name, .Type, .ID, .OnAction, .TopLeftCell.Address
End If
End With
Next
End Sub
Sub showSomeOleObjectProperties()
Dim ws As Worksheet, oo As OLEObject
Set ws = ThisWorkbook.Worksheets("Sheet1")
For Each oo In ws.OLEObjects
With oo
Debug.Print .Name, .OLEType, .AutoLoad, .Enabled, .Index, _
.BottomRightCell.Address
End With
Next
End Sub
Sub addOLECheckBoxes()
Const srcName As String = "Sheet1"
Dim chk As OLEObject, rng As Range, cel As Range, i As Long
With ThisWorkbook.Worksheets(srcName)
Set rng = .Range("A1:A10")
i = 1
For Each cel In rng.Cells
Set chk = .OLEObjects.Add(ClassType:="Forms.CheckBox.1", _
Left:=cel.Left, Top:=cel.Top, Width:=cel.Width, Height:=cel.Height)
With chk
'.Name = "Chk" & i
'.Placement = xlMoveAndSize
End With
i = i + 1
Next cel
End With
End Sub

How to create error message if column contains non-numeric field - VBA

I am trying to write a code to produce an error message if the values of the table are non-numeric. I am not getting any error messages but the code isn't accomplishing the task. Any help? Code below:
Sub Refresh()
'
' Warning Code to check if all values are numeric
'-----------------------------------------------------------------------
Dim sh As Worksheet
Dim i As Integer
Dim bisnumberic As Boolean
bIsNumeric = True
For Each sh In ActiveWorkbook.Sheets
Select Case sh.Name
Case "AltA", "AltB", "AltC1", "AltC2"
Dim lRow As Long
lRow = Cells(Rows.Count, 1).End(xlUp).Row
For i = 3 To lRow
If Not IsNumeric(Cells(i, 1).Value) Then
bisnumber = False
End If
Next i
End Select
Next sh
If bIsNumeric = False Then
'There are non-numeric values in your range
MsgBox "There are non-numeric values in your range. Go check-
yourself and try again."
Else
'-----------------------------------------------------------------------
' Code to summarize data Sheets("AlternativeSummary").Select
Range("B5").Select
ActiveSheet.PivotTables("PivotTable5").PivotCache.Refresh
MsgBox "Complete"
'All values in your range are numeric
End If
End Sub
lRow = Cells(Rows.Count, 1).End(xlUp).Row should have a clearly defined parent worksheet. Remember that you are cycling through a series of worksheets. Same with If Not IsNumeric(Cells(i, 1).Value) Then.
Sub Refresh()
'
' Warning Code to check if all values are numeric
'-----------------------------------------------------------------------
Dim sh As Worksheet, i As Long, bIsNumeric As Boolean, lRow As Long, errng as range
bIsNumeric = True
For Each sh In ActiveWorkbook.Worksheets
Select Case sh.Name
Case "AltA", "AltB", "AltC1", "AltC2"
lRow = sh.Cells(sh.Rows.Count, 1).End(xlUp).Row
For i = 3 To lRow
If Not IsNumeric(sh.Cells(i, 1).Value) Then
bIsNumeric = False
set errng = sh.Cells(i, 1)
Exit For
End If
Next i
End Select
If Not bIsNumeric Then Exit For
Next sh
If Not bIsNumeric Then
'There are non-numeric values in your range
MsgBox "There are non-numeric values in your range. Go check yourself and try again." _
& chr(10) & errng.address(0, 0, external:=true)
Else
'---------------------------------------------------
' Code to summarize data
With Sheets("AlternativeSummary")
.Activate
.Range("B5").Select
.PivotTables("PivotTable5").PivotCache.Refresh
MsgBox "Complete"
'All values in your range are numeric
end with
End If
End Sub
The problem here is in your if statement:
If Not IsNumeric(Cells(i, 1).Value) Then
bisnumber = False
End If
You are changing the bisnumber variable.
Later on, you actually check the bIsNumeric variable to see if something is or is not a number. You would need to change:
If Not IsNumeric(Cells(i, 1).Value) Then
bisnumber = False
End If
to:
If Not IsNumeric(Cells(i, 1).Value) Then
bIsNumeric = False
End If
Good code writing - and welcome to Stack Overflow.
Option Explicit
Sub Refresh()
'
' Warning Code to check if all values are numeric
'-----------------------------------------------------------------------
Dim sh As Worksheet
Dim i As Integer
Dim bIsNumeric As Boolean
bIsNumeric = True
For Each sh In ActiveWorkbook.Sheets
Dim lRow As Long
lRow = sh.Cells(Rows.Count, 1).End(xlUp).Row
For i = 3 To lRow + 1
If Not IsNumeric(sh.Cells(i, 1).Value) Then
bIsNumeric = False
End If
Next i
Next sh
If bIsNumeric = False Then 'There are non-numeric values in your range
MsgBox "There are non-numeric values in your range. Go check-yourself and try again."
Else
'-----------------------------------------------------------------------
' Code to summarize data Sheets("AlternativeSummary").Select
Range("B5").Select
ActiveSheet.PivotTables("PivotTable5").PivotCache.Refresh
MsgBox "Complete"
'All values in your range are numeric
End If
End Sub
This will loop through all the sheets and check for non-numeric fields.

Resources