Only allow sub to run after another has been run VBA - vba

I've created a workbook that imports data from another file and then deletes any rows by finding a header string "Time". However, if the macro that deletes the rows is run again it will remove the header as there's a sub-header called "Time" as well.
OR is there a way to limit the characters Find searches for i.e. say my subheader is Real Time and my char limit is 4, find should only return "real" and therefore ignore that?
What I want to do is disable the Row Delete code unless there has been a new data import and the Row Delete hasn't already been run.
Some psuedo code below
If (DataImport has been run){
If (rowDelete has been run since DataImport){
return;
}
else{
run rowDelete
}
}
Excel VBA
Sub ImportData()
Dim wb1 As Workbook
Dim wb2 As Workbook
Dim sheet As Worksheet
Dim pastestart As Range
Set wb1 = ActiveWorkbook 'Sheets("Data")
Set pastestart = [Data!A1]
FileToOpen = Application.GetOpenFilename _
(Title:="Please choose a data file")
If FileToOpen = False Then
MsgBox "No File Specified.", vbExclamation, "error"
Exit Sub
Else
Set wb2 = Workbooks.Open(Filename:=FileToOpen)
For Each sheet In wb2.Sheets
With sheet.UsedRange
.Copy pastestart
Set pastestart = pastestart.Offset(.Rows.Count)
End With
Next sheet
End If
wb2.Close
End Sub
_____________________________________________________________________
Sub rowDelete()
Dim FindRow As Range
On Error Resume Next
With Sheets("Data")
Set FindRow = Cells.Find(What:="Time", After:=.Cells(1, 1), LookIn:=xlValues, LookAt:= _
xlPart, MatchCase:=False)
End With
On Error GoTo 0
Range("A1", FindRow).EntireRow.Delete
End Sub

You could include a cell on a worksheet which states whether an import has been made since the last row delete; include a line of code at the end of your ImportData procedure which updates this cell.
ThisWorkbook.Sheets(1).Range("A1") = "Imported"
Then at the end of the rowDelete procedure you can update this cell to reflect that the rows have been deleted.
ThisWorkbook.Sheets(1).Range("A1") = "Rows deleted"
Then within the rowDelete procedure you can check if the last procedure to be run was rowDelete. If it was, do not run the procedure.
If ThisWorkbook.Sheets(1).Range("A1") = "Rows deleted" Then
Exit Sub
End If
If the cell shows that the ImportData procedure was run last (because the cell shows "Imported") then the rowDelete procedure can run.

How about using CustomDocumentProperties instead. Makes it more invisible:
ThisWorkbook.CustomDocumentProperties("Imported")
You can make this a yes/no property

YOu could use global variables to identify, will the below work for you?
Make sure you call InitiParams at the begining of the cycle
Dim Option Explicit
'Module Level Variables
Dim boolDataImportComplete as boolean
Dim boolRowDeleteRunComplete as boolean
Sub InitParams()
boolDataImportComplete = False
boolRowDeleteRunComplete = False
End Sub
Sub ImportData()
''' Your Code here
boolDataImportComplete = True
End Sub
Sub RowDelete()
''' YOUR Code here
boolRowDeleteRunComplete = True
End Sub

Related

Excel Add .xlam module inject code into new sheet

I have a module which is creating a sheet. It builds a worksheet with a series of tables.
I'd like to add a feature that uses the OnChange event for a cell to validate that the user entered a decimal. The following code does this If I can just inject into the new worksheet. That's the only thing I can't figure out.
Given 's' is the current Worksheet we've just created is there any way to inject the following code into the sheet code module of 's'?
Private Sub Worksheet_Change(ByVal Target As Range)
Const CELL_ADDRESS = "$R$4:$AQ$500"
If Not Application.Intersect(Target, Range(CELL_ADDRESS)) Is Nothing Then
If Not IsNumeric(Target.Value) Then
MsgBox "Please enter numbers only", vbCritical, "Invalid Entry"
Target.Value = vbNullString
End If
End If
End Sub
EDIT: Showing the chosen solution to the problem (chose Wedge's solution).
(Answer:) We will add a public function to the Addin which we will call from the Template and therefore all sheets created from the template.
Using a template and copying it will allow us to have custom code built into new sheets without having to change security settings.
Calling a public function allows us to make modifications to the sheet without putting the protected password in the sheet's code.
(Public Function Call inside the sheet)
Private Sub Worksheet_Change(ByVal Target As Range)
Dim wb As Workbook
Set wb = ActiveWorkbook
Dim ws As Worksheet
Set ws = wb.ActiveSheet
Application.Run "numberaddin.Validate_Input", wb, ws, Target
End Sub
(Public Function built into Addin which will be called by the sheet when a user modifies data.)
-- All this function does is make sure our cells store only numbers AS NUMBERS and with formatting. Any non-value text becomes a 0 in the cell. This works even if the user copy-pastes the data.
Public Function Validate_Input(wb As Workbook, ws As Worksheet, r As Range)
CELL_ADDRESS = Cells(1, 2).Value ''''we'll use the locked Cell B1 to specify the Validation Range
Dim rCell As Range
Dim eCell As Range
Dim numErr As Boolean
numErr = False
Set rCell = Range(CELL_ADDRESS)
If Not Application.Intersect(rCell, r) Is Nothing Then
ActiveSheet.Protect Password:="pw", UserInterfaceOnly:=True
Application.EnableEvents = False
For Each eCell In rCell.Cells
If Not eCell Is Nothing And eCell.Locked = False And Not Application.Intersect(eCell, r) Is Nothing Then
If IsNumeric(eCell.Value) = False Or IsEmpty(eCell.Value) = True Or eCell.Value <> eCell.Value + "0" Then
If Not IsNumeric(eCell.Value) Then
numErr = True
End If
eCell.Value = Val(eCell.Value)
End If
eCell.Interior.Color = RGB(255, 255, 153)
eCell.NumberFormat = "_(* #,##0_);_(* (#,##0);_(* "" - ""??_);_(#_)"
If eCell.Value > 1000000 Then
eCell.Columns.AutoFit
eCell.ColumnWidth = eCell.ColumnWidth * 1.2
End If
End If
Next eCell
Application.EnableEvents = True
ActiveSheet.Protect Password:="pw", UserInterfaceOnly:=False
End If
If numErr = True Then
MsgBox "Only numbers are allowed here.", vbCritical, "Invalid Entry"
End If
End Function
First of all you must enable the "Trust access to the VBA project object model" setting in the Trust Center.
After that you'll have to write something like this:
Sub AddModule()
Dim Module As VBComponent
Dim ModuleString As String
ModuleString = "Sub Test()" & vbCrLf & _
" Msgbox(""Test"")" & vbCrLf & _
"End Sub"
Set Module = Workbooks(2).VBProject.VBComponents.Add(vbext_ct_StdModule)
Module.CodeModule.AddFromString ModuleString
End Sub
Obviously, you will have change the workbook reference and the ModuleString. Also be careful with the trust change. It is there for a reason.
It's not exactly what you were asking, but I would think that you could just create a hidden "template" sheet with the code you want in it (there is an xlVeryHidden option you can use to keep the template sheet from being unhidden from the UI even). Then instead of creating a new worksheet, you create a copy of that "template sheet", which should copy over the sheet VBA code with it.

Excel VBA, execute Macro on selected cell

My problem is that I need to execute a Macro only on the marked cell.
The Macro needs to do the following:
Selected cell is formated always for example as 20*20*20 always 3 numbers.
It should copy this text add a " = " before the numbers and output it on another column.
The Code I got until now is:
Sub First()
'
' First Makro
'
'
Selection.Copy
Range("G11").Select
ActiveSheet.Paste
Application.CutCopyMode = False
ActiveCell.FormulaR1C1 = "=20*20*20"
Range("G12").Select
End Sub
I have got this code with the record Macro function
Thanks very much
#SiddharthRout exactly but i need to be able to select it by hand because sometimes it's for example E17 sometimes e33 and output always need's to be G Column in the Same Row
Is this what you are trying?
Sub Sample()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ThisWorkbook
'~~> Replace Sheet1 with the relevant sheet name
Set ws = wb.Sheets("Sheet1")
'~~> Check if what the user selected is a valid range
If TypeName(Selection) <> "Range" Then
MsgBox "Select a range first."
Exit Sub
End If
'~~> Check if the user has selected a single cell
If Selection.Cells.Count > 1 Then
MsgBox "Please select a single cell"
Exit Sub
End If
ws.Range("G" & Selection.Row).Formula = "=" & Selection.Value
End Sub

How to loop through all and replace some sheets in an Excel workbook

I'm writing a macro in VBA for Excel. I want it to replace all worksheets except for a few. First there is a loop which deletes the unwanted sheets, and then comes another one which creates new sheets to repace them! On a first run, the macro removes unwanted sheets. However, if it is run again it seems to be unable to delete the sheets it previously created, which causes a name duplicity error.
(The rng variable is supposed to extend across the entire row but I haven't gotten to fixing that yet.)
Hope you guys can provide some insight, much appreciated!
sub Terminator()
Dim Current As Worksheet
Application.DisplayAlerts = False
' Loop through all of the worksheets in the active workbook.
For Each Current In Worksheets
If Not Current.Name = "Data" Then
Worksheets(Current.Name).Delete
End If
Next Current
Application.DisplayAlerts = True
' Define range for loop
Dim rng As Range, cell As Range
Set rng = Sheets("Data").Range("A5:M5")
' Loop through entire row, looking for employees
For Each cell In rng
If cell.Value = "Nummer" Then
' Make new chart for employee
With Charts.Add
.ChartType = xlLineMarkers
.Name = cell.Offset(-1, 1).Value
.HasTitle = True
.ChartTitle.Text = cell.Offset(-1, 1).Value
' Set data (dynamic) and x-axis (static) for new chart
.SetSourceData Source:=Sheets("Data").Range(cell.Offset(-2, 3), cell.Offset(7, 4))
.Axes(xlValue).MajorGridlines.Select
.FullSeriesCollection(1).XValues = "=Data!E4:E12"
' Add trendlines
.FullSeriesCollection(1).Trendlines.Add Type:=xlLinear, Forward _
:=0, Backward:=0, DisplayEquation:=0, DisplayRSquared:=0, Name:= _
"Trend (DDE)"
.FullSeriesCollection(2).Trendlines.Add Type:=xlLinear, Forward _
:=0, Backward:=0, DisplayEquation:=0, DisplayRSquared:=0, Name:= _
"Trend (SDE)"
End With
' Chart is moved to end of all sheets
Sheets(cell.Offset(-1, 1).Value).Move _
after:=Sheets(Sheets.Count)
End If
Next cell
End Sub
No need to define the worksheet with the Worksheets()
Sub Terminator()
Dim Current As Worksheet
Application.DisplayAlerts = False
' Loop through all of the worksheets in the active workbook.
For Each Current In ActiveWorkbook.Worksheets
If Not Current.Name = "Data" Then
Current.Delete
End If
Next Current
Application.DisplayAlerts = True
End sub
The Following code (minor changes worked in my workbook), are you sure you have the names you put in the If in your Workbook ?
Anyway, I think it's better to use Select for multiple possible mathces
Sub Terminator()
Dim Current As Excel.Worksheet
Application.DisplayAlerts = False
' Loop through all of the worksheets in the active workbook.
For Each Current In ActiveWorkbook.Sheets
If Not (Current.Name = "Data") Then
ActiveWorkbook.Worksheets(Current.Name).Delete
End If
Next Current
Application.DisplayAlerts = True
End Sub
Solution to the deletion is supplied by RGA, but in case you want to avoid several AND statements for each sheet that you want to retain, you can utilize a function similar to the isInArray below:
Sub Terminator()
Dim Current As Variant
Application.DisplayAlerts = False
' Loop through all of the worksheets in the active workbook.
For Each Current In ThisWorkbook.Sheets
If Not isInArray(Current.Name, Array("Data")) Then
Current.Delete
End If
Next
Application.DisplayAlerts = True
End Sub
Function isInArray(theValue As String, vArr As Variant) As Boolean
Dim vVal As Variant
isInArray = False
For Each vVal In vArr
If LCase(vVal) = LCase(theValue) Then
isInArray = True
End If
Next
End Function
EDIT:
function that takes a worksheet name as argument, and returns a worksheet object of that name. If the name is allready taken, the existing sheet is deleted and a new one created:
'example of use:
'set newWorksheet = doExist("This new Sheet")
Function doExist(strSheetName) As Worksheet
Dim wb As Workbook: Set wb = ThisWorkbook
Dim wsTest As Worksheet
Dim nWs As Worksheet
Set wsTest = Nothing
On Error Resume Next
'Set wsTest = wb.Worksheets(strSheetName) 'commented out in Edit of Edit
Set wsTest = wb.Sheets(strSheetName) 'as a comment for one of the other threads reveal, the error could be the deletion of Worksheets, which would be a subgroup to Sheets of which graph sheets are no a part
On Error GoTo 0
If Not wsTest Is Nothing Then
Application.DisplayAlerts = False
wsTest.Delete
Application.DisplayAlerts = True
End If
'Set doExist = wb.Worksheets.Add(after:=wb.Sheets(wb.Sheets.Count)) 'Edit of Edit, the later call to Charts.Add does this for you
'doExist.Name = strSheetName 'Edit of Edit, no need to return anything
End Function

how to retain the clipboard data after changing workbooks in vba?

i have a program that copies a range of cells and the needs to paste the contents into a new workbook that is created in code. i can copy the data but somehow the clipboard loses its data whenever i change workbooks to the new one created. i considered copying the cells to an array and then just copying the array to the new workbook but i wont know the size of the array at coding time this varies almost every time the macro runs.
how do i then keep the data on the clipboard while i change the active workbook?
cell = "k7: l" & row
Worksheets(1).Range(cell).Select
Selection.Copy
relpath = ThisWorkbook.Path & "\" & "DispersionList.xls"
If Dir(relpath) <> "" Then
Application.Workbooks.Open (relpath)
Workbooks("DispersionList.xls").Activate
Else
Call createWorkbook
End If
Worksheets(1).Cells(7, 14).Select
Selection.PasteSpecial
End Sub
if i run through the code line by line and check the clipboard it loses its contents at the workbooks.open line
There are a few actions in Excel/VBA that will void the selection/clipboard, e.g. changing any window/display settings. Thus, I suspect there is some event being called when you change the worksheet/workbook.
You can either debug it and while stepping through the code figure out, when the selection is voided and avoid this statement (if possible).
Alternatively, use subStoreClipboard and subRestoreClipboard from below code in your event code. To use the code, insert it in a new module in your worksheet - and also insert a new (hidden) worksheet which is named "ws_Temp" in VBA.
Private mIntCutCopyMode As XlCutCopyMode
Private mRngClipboard As Range
Public Sub subStoreClipboard()
On Error GoTo ErrorHandler
Dim wsActiveSource As Worksheet, wsActiveTarget As Worksheet
Dim strClipboardRange As String
mIntCutCopyMode = Application.CutCopyMode
If Not fctBlnIsExcelClipboard Then Exit Sub
Application.EnableEvents = False
'Paste data as link
Set wsActiveTarget = ActiveSheet
Set wsActiveSource = ThisWorkbook.ActiveSheet
With ws_Temp
.Visible = xlSheetVisible
.Activate
.Cells(3, 1).Select
On Error Resume Next
.Paste Link:=True
If Err.Number Then
Err.Clear
GoTo Finalize
End If
On Error GoTo ErrorHandler
End With
'Extract link from pasted formula and clear range
With Selection
strClipboardRange = Mid(.Cells(1, 1).Formula, 2)
If .Rows.Count > 1 Or .Columns.Count > 1 Then
strClipboardRange = strClipboardRange & ":" & _
Mid(.Cells(.Rows.Count, .Columns.Count).Formula, 2)
End If
Set mRngClipboard = Range(strClipboardRange)
.Clear
End With
Finalize:
wsActiveSource.Activate
wsActiveTarget.Parent.Activate
wsActiveTarget.Activate
ws_Temp.Visible = xlSheetVeryHidden
Application.EnableEvents = True
Exit Sub
ErrorHandler:
Err.Clear
Resume Finalize
End Sub
Public Sub subRestoreClipboard()
Select Case mIntCutCopyMode
Case 0:
Case xlCopy: mRngClipboard.Copy
Case xlCut: mRngClipboard.Cut
End Select
End Sub
Private Function fctBlnIsExcelClipboard() As Boolean
Dim var As Variant
fctBlnIsExcelClipboard = False
'check if clipboard is in use
If mIntCutCopyMode = 0 Then Exit Function
'check if Excel data is in clipboard
For Each var In Application.ClipboardFormats
If var = xlClipboardFormatCSV Then
fctBlnIsExcelClipboard = True
Exit For
End If
Next var
End Function

Excel Macro for creating new worksheets

I am trying to loop through some columns in a row and create new worksheets with the name of the value of the current column/row that I am in.
Sub test()
Range("R5").Select
Do Until IsEmpty(ActiveCell)
Sheets.Add.Name = ActiveCell.Value
ActiveCell.Offset(0, 1).Select
Loop
End Sub
This code creates the first one correctly starting at R5 but then it appears that the macro switches to that worksheet and doesn't complete the task.
The Sheets.Add automatically moves your selection to the newly created sheet (just like if you insert a new sheet by hand). In consequence the Offset is based on cell A1 of the new sheet which now has become your selection - you select an empty cell (as the sheet is empty) and the loop terminates.
Sub test()
Dim MyNames As Range, MyNewSheet As Range
Set MyNames = Range("R5").CurrentRegion ' load contigeous range into variable
For Each MyNewSheet In MyNames.Cells ' loop through cell children of range variable
Sheets.Add.Name = MyNewSheet.Value
Next MyNewSheet
MyNames.Worksheet.Select ' move selection to original sheet
End Sub
This will work better .... you assign the list of names to an object variable of type Range and work this off in a For Each loop. After you finish you put your Selection back to where you came from.
Sheets.Add will automatically make your new sheet the active sheet. Your best bet is to declare variables to your objects (this is always best practice) and reference them. See like I've done below:
Sub test()
Dim wks As Worksheet
Set wks = Sheets("sheet1")
With wks
Dim rng As Range
Set rng = .Range("R5")
Do Until IsEmpty(rng)
Sheets.Add.Name = rng.Value
Set rng = rng.Offset(0, 1)
Loop
End With
End Sub
Error handling should always be used when naming sheets from a list to handle
invalid characters in sheet names
sheet names that are too long
duplicate sheet names
Pls change Sheets("Title") to match the sheet name (or position) of your title sheet
The code below uses a variant array rather than a range for the sheet name for performance reasons, although turning off ScreenUpdating is likely to make the biggest difference to the user
Sub SheetAdd()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim strError As String
Dim vArr()
Dim lngCnt As Long
Dim lngCalc As Long
Set ws1 = Sheets("Title")
vArr = ws1.Range(ws1.[r5], ws1.[r5].End(xltoRight))
If UBound(vArr) = Rows.Count - 5 Then
MsgBox "sheet range for titles appears to be empty"
Exit Sub
End If
With Application
.ScreenUpdating = False
.EnableEvents = False
lngCalc = .Calculation
End With
For lngCnt = 1 To UBound(vArr)
Set ws2 = Sheets.Add
On Error Resume Next
ws2.Name = vArr(lngCnt, 1)
If Err.Number <> 0 Then strError = strError & vArr(lngCnt, 1) & vbNewLine
On Error GoTo 0
Next lngCnt
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = lngCalc
End With
If Len(strError) > 0 Then MsgBox strError, vbCritical, "These potential sheet names were invalid"
End Sub
This is probably the simplest. No error-handling, just a one-time code to create sheets
Sub test()
Workbooks("Book1").Sheets("Sheet1").Range("A1").Activate
Do Until IsEmpty(ActiveCell)
Sheets.Add.Name = ActiveCell.Value
Workbooks("Book1").Sheets("Sheet1").Select
ActiveCell.Offset(0, 1).Select
Loop
End Sub