I have a working macro which changes a linked sheet in a cell according to month. Say from April to March.
From
='C:\Data\Name\[Time.xlsx]2021-04!A1"
to
='C:\Data\Name\[Time.xlsx]2021-05!A1".
This works as long as the user remembers to add a new sheet at the beginning of each month, which is not always done in time. As a result i get a "sheet cannot be found -> choose one from below" Prompt". How can I avoid this selector and add a "table not found" string in the cell instead and move on to the next operation in the loop?
Thanks!
EDIT:
Code Added upon request:
Sub Month()
Set rngB = Range("B2:B6")
strColB = "Range("B1")
iRowB = 1
strMonth = InputBox ("Insert Month as integer","Month")
strMonth = Trim(strMonth)
For Each cellB In rngB
cellB.Formula = "='C:Data\[" & strColB & "Time.xlsx]2021-" & strMonth & "'!B" & iRowB
iRowB = iRowB + 1
Next cellB
End Sub
Here's an easy function to test if a sheet exists prior to accessing its cells
Function SheetExists(ByVal SheetName As String, ByRef InWorkbook As Workbook) As Boolean
On Error Resume Next
SheetExists = Not InWorkbook.Sheets(SheetName) Is Nothing
On Error GoTo 0
End Function
Here's how you would use it.
Sub test()
MsgBox SheetExists("2021-04", Application.Workbooks("Time.xlsx"))
End Sub
Another example:
Sub test()
If SheetExists("2021-04", Application.Workbooks("Time.xlsx")) Then
'do stuff
Else
[a1] = "table not found"
End If
End Sub
Edit:
After the code was added to the original post. Here is an example of how to implement this function with that code:
Sub Month()
Set rngB = Range("B2:B6")
strColB = Range("B1").Text
iRowB = 1
strMonth = InputBox("Insert Month as integer", "Month")
strMonth = Trim(strMonth)
For Each cellB In rngB
If SheetExists("2021-" & strMonth, Application.Workbooks("Time.xlsx")) Then
cellB.Formula = "='C:Data\[" & strColB & "Time.xlsx]2021-" & strMonth & "'!B" & iRowB
Else
cellB.Formula = "table not found"
End If
iRowB = iRowB + 1
Next cellB
End Sub
Related
I am using the below code from this link
Need help
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
'If (Target.Row > 3 And Target.Row < 155) Then Cells(Target.Row, "AT") = Now()
Const sRng As String = "A5:AQ155" ' change as required
Dim sOld As String
Dim sNew As String
Dim sCmt As String
Dim iLen As Long
Dim bHasComment As Boolean
With Target(1)
If Intersect(.Cells, Me.Range(sRng)) Is Nothing Then
Application.EnableEvents = True
Exit Sub
End If
sNew = .Text
sOld = .Text
.Value = sNew
Application.EnableEvents = True
sCmt = "Edit: " & Format$(Now, "dd Mmm YYYY hh:nn:ss") & " by " & Application.UserName & Chr(10) & "Previous Text :- " & sOld
If Target(1).Comment Is Nothing Then
.AddComment
Else
iLen = Len(.Comment.Shape.TextFrame.Characters.Text)
End If
With .Comment.Shape.TextFrame
.AutoSize = True
.Characters(Start:=iLen + 1).Insert IIf(iLen, vbLf, "") & sCmt
End With
End With
End Sub
Sub Hide_Comments_in_Workbook_Completely()
'This macro hides the comments and comment indicators - users wont know there is a comment within the excel workbook
Application.DisplayCommentIndicator = xlNoIndicator
End Sub
The above code works fine only one problem I am facing.
It creates a history of 9 changes in 9 lines in comments if the changes exceeded or Total Character (with space) in comments is 268 more then the previous line is automatically erased.
Can anyone help me to overcome the above problem?
I want no bindings of changes or character input or line limits.
Thanks in advance and appreciate any help.
Characters is limited in the length of text you can address. You could instead delete and re-add the comment with the new text added.
This worked for me in testing:
Private Sub Worksheet_Change(ByVal Target As Range)
Const sRng As String = "A5:AQ155" ' change as required: must be a contiguous range
Dim c As Range, rng As Range, oldVals, newVals, newValsTgt, usr
Dim col As Long, rw As Long, txt As String, s As String
If Target.Areas.Count > 1 Then Exit Sub 'only handling single Area changes
Set rng = Application.Intersect(Target, Me.Range(sRng))
If rng Is Nothing Then Exit Sub
On Error GoTo haveError
Application.EnableEvents = False 'don't re-trigger event
newValsTgt = ToArray(Target) 'get current Target values
newVals = ToArray(rng) 'get current values for range of interest
Application.Undo 'restore previous values
oldVals = ToArray(rng) 'get pre-update values for range of interest
Target.Value = newValsTgt 'restore the Target range values
Application.EnableEvents = True
usr = Application.UserName
For rw = 1 To UBound(newVals, 1) 'loop over the new values
For col = 1 To UBound(newVals, 2)
If newVals(rw, col) <> oldVals(rw, col) Then 'was the content changed?
Set c = rng.Cells(rw, col)
s = "Edit: " & Format$(Now, "dd Mmm YYYY hh:nn:ss") & _
" by " & usr & Chr(10) & _
"Previous Text :- " & oldVals(rw, col)
If c.Comment Is Nothing Then
c.AddComment s
Else
txt = c.Comment.Text
c.Comment.Delete
c.AddComment s & vbLf & txt
End If
c.Comment.Shape.TextFrame.AutoSize = True
End If
Next col
Next rw
Exit Sub 'normal exit
haveError:
Debug.Print "Error: " & Err.Description
Application.EnableEvents = True 'ensure events are back on
End Sub
'returns the value of both single cells and ranges as an array...
Function ToArray(rng As Range)
Dim rv
If rng.CountLarge = 1 Then
ReDim rv(1 To 1, 1 To 1)
rv(1, 1) = rng.Value
ToArray = rv
Else
ToArray = rng.Value
End If
End Function
I have encountered a few issues with some code in VBA. I am trying to have the changes made to a cells on an excel sheet show up in comments on the cell the change was made to and I wish for these changes to be stored in a list so I can view them all later. I have tried lots of different pieces of code I have found to try and implement it into the code but none have worked.
Any ideas on how to get this to work?
Worksheet
The below code is what I am currently using
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Adding As Boolean, Finding As Boolean, Subtracting As Boolean
Dim f As Range, v
Select Case Target.Address(True, True)
Case "$A$4": Adding = True
Case "$C$4": Subtracting = True
Case "$E$4": Finding = True
Case Else: Exit Sub
End Select
v = Trim(Target.Value)
If Len(v) = 0 Then Exit Sub
Set f = Me.Range("C8").Resize(1000, 1).Find(v, lookat:=xlWhole)
If Adding Then
If f Is Nothing Then
'not found: add as new row
Set f = Me.Cells(Rows.Count, 3).End(xlUp).Offset(1, 0)
f.Value = v
End If
f.Offset(0, 1).Value = f.Offset(0, 1).Value + 1
doDate f.Offset(0, 2)
Target.Value = ""
ElseIf Subtracting Then
If f Is Nothing Then
MsgBox v & " not found for subtraction!"
Else
f.Offset(0, 1).Value = f.Offset(0, 1).Value - 1
doDate f.Offset(0, 3)
Target.Value = ""
End If
Else 'finding
If Not f Is Nothing Then
f.EntireRow.Select
Target.Value = ""
Else
MsgBox v & " not found."
End If
End If
If Adding Or Subtracting Then Target.Select
End Sub
Sub doDate(c As Range)
With c
.NumberFormat = "m/d/yyyy h:mm AM/PM"
.Value = Now
End With
End Sub
I have implemented a few formulas on the worksheet but don't see any reason why it would matter in this situation since they only track quantity of items with the same unique identifier.
I also tried some code that added comments to the cells as they were changed that worked but always returned the previous cell value as blank. It is not actually added into the current code though.
Option Explicit
Public preValue As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Count > 1 Then Exit Sub
Target.ClearComments
Target.AddComment.Text Text:="Previous Value was " & preValue & Chr(10) & "Revised " & Format(Date, "mm-dd-yyyy") & Chr(10) & "By " & Environ("UserName")
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Count > 1 Then Exit Sub
If Target = "" Then
preValue = "a blank"
Else: preValue = Target.Value
End If
End Sub
By and large, the code below should do what you want. I marveled at your use of A4 and C4 to express addition and subtraction. As it is, whatever you change in those two cells, apart from clearing them, will result in a quantity of 1 being added or subtracted. I would have expected that a quantity must be entered there which is processed. If the quantity is fixed at 1 the system appears too elaborate.
Anyway, here's the code. I guess you'll be able to modify it to better suit your needs.
Private Sub Worksheet_Change(ByVal Target As Range)
' 038
Dim LookUp As Variant ' subject
Dim Action As Variant ' add = 1, subtract = -1, find = 2
Dim Fnd As Range ' Result of Find method
Dim Txt As String ' comment text
With Target
If (.Row <> 4) Or (.CountLarge > 1) Then Exit Sub
LookUp = Cells(4, "E").Value
On Error Resume Next
Action = Array(0, 1, 0, -1, 0, 2)(.Column)
End With
If Action And (LookUp <> "") Then
' C8 to end of column C
With Range(Cells(8, "C"), Cells(Rows.Count, "C").End(xlUp))
Set Fnd = .Find(LookUp, .Cells(.Cells.Count), xlValues, xlWhole, xlByRows)
End With
End If
If Fnd Is Nothing Then
Select Case Action
Case -1
MsgBox """" & LookUp & """ not found.", vbInformation, "Can't subtract"
Action = -2
Case 2
MsgBox """" & LookUp & """ not found.", vbInformation, "No record"
Action = -2
Case Else
Set Fnd = Cells(Rows.Count, "C").End(xlUp).Offset(1)
Fnd.Value = LookUp
End Select
End If
With Fnd
If Abs(Action) <> 2 Then
With .Offset(0, 1)
If .Comment Is Nothing Then
.AddComment
Else
Txt = Chr(10)
End If
Txt = "Previous Qty = " & .Value & Chr(10) & _
"Revised " & Format(Date, "mm-dd-yyyy") & Chr(10) & _
"by " & Environ("UserName") & Txt
.Comment.Text Txt, 1, False
.Value = Val(.Value) + Action
With .Offset(0, 2)
.NumberFormat = "m/d/yyyy h:mm AM/PM"
.Value = Now
End With
End With
ElseIf Action = 2 Then
.EntireRow.Select
End If
End With
If Action <> 2 Then Target.Select
End Sub
I have tried this code which works fine for a cell that only contain number:
Sub IncreaseCellValue()
'Add 1 to the existing cell value
Range("A1").Value = Range("A1") + 1
End Sub
How can I do something similar if the cell has text and a number. For example, I have "Apple 1" and I want to "increase" the cell text to "Apple 2" and next time I run the macro I want "Apple 3".
Here's another way you could solve this problem:
Sub IncreaseCellValue()
Dim value As Variant
'Add 1 to the existing cell value
If IsNumeric(Range("A1").value) Then
Range("A1").value = Range("A1") + 1
Else
value = Split(Range("A1").value, " ")
Range("A1").value = value(0) & " " & (CInt(value(1)) + 1)
End If
End Sub
It will cover the 2 cases you presented in your question but not every scenario you could throw at it.
Try using the following function
Sub IncreaseCellValue()
'Add 1 to the existing cell value
Range("A1").Value = Replace(Range("A1").Value2, CleanString(Range("A1")), vbNullString) & CInt(CleanString(Range("A1").Value2)) + 1
End Sub
Function CleanString(strIn As String) As String
Dim objRegex
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Global = True
.Pattern = "[^\d]+"
CleanString = .Replace(strIn, vbNullString)
End With
End Function
please check:
Option Explicit
Sub IncreaseCellValue()
'Add 1 to the existing cell value
Dim rg As Range
Set rg = Cells(Rows.Count, "A").End(xlUp)
Range("A1" & ":" & rg.Address).AutoFill Destination:=Range("A1" & ":" & rg.Offset(1, 0).Address), Type:=xlFillDefault
End Sub
Or you may try something like this...
Function GetNumber(ByVal rng As Range) As Long
Dim i As Long
For i = Len(rng.Value) To 1 Step -1
If IsNumeric(Mid(rng.Value, i, 1)) Then
GetNumber = GetNumber & Mid(rng.Value, i, 1)
Else
Exit For
End If
Next i
End Function
Sub IncrementNumber()
Dim num As Long
num = GetNumber(Range("A1"))
Range("A1").Value = Replace(Range("A1").Value, num, num + 1)
End Sub
Why is my first iteration in Sub throughCols that is intended to move one row down each time jumping four rows?
Option Explicit
Dim txt As String
Dim i As Long
Dim strTest As String
Dim strArray() As String
Dim lCaseOn As Boolean
Dim firstRow As Long, startIt As Long
Dim thisCell As Range
Dim lastRow As Long
Dim resetAddress As Range
Sub throughCols()
' Dim thisCell As Range
' get start and end of column data
' NB sheet name is hard coded twice
Call dataRange
startIt = firstRow + 1
For i = 1 To 8 Step 1
' after testing use startIt To lastRow Step 1
' by using activeCell I dont have to pass range through to the sub
Sheets("test").Range("B" & i).Select
MsgBox "this is itteration " & i & " which will output to " & ActiveCell.Offset(0, 2).Address
Call arrayManip
Call cleanTxt(txt)
Next i
End Sub
Sub arrayManip()
' clear out all data
Erase strArray
txt = ""
'set default case
lCaseOn = False
' string into an array using a " " separator
strTest = WorksheetFunction.Proper(ActiveCell.Value)
strTest = Replace(strTest, "-", " - ")
strTest = Replace(strTest, "‘", " ‘ ")
strArray = Split(strTest, " ")
' itterate through array looking to make text formats
For i = LBound(strArray) To UBound(strArray)
If strArray(i) = "-" Then
lCaseOn = True
GoTo NextIteration
End If
If strArray(i) = "‘" Then
lCaseOn = True
GoTo NextIteration
End If
If lCaseOn Then
strArray(i) = LCase(strArray(i))
lCaseOn = False
NextIteration:
End If
Next
End Sub
Function cleanTxt(txt)
' loop through the array to build up a text string
For i = LBound(strArray) To UBound(strArray)
txt = txt & strArray(i) & " "
Next i
' remove the space
txt = Trim(Replace(txt, " - ", "-"))
txt = Trim(Replace(txt, " ‘ ", "‘"))
' MsgBox "active cell is " & activeCell.Address
ActiveCell.Offset(0, 2).Select: ActiveCell.Value = txt
' MsgBox "final output would be " & txt & " to " & activeCell.Address
' this is a thumb suck to attempt to reset the active cell to the itteration address that started it
ActiveCell.Offset(0, -2).Select
MsgBox "next itteration should start with active cell set as " & ActiveCell.Address
End Function
Sub dataRange()
With Sheets("test").Columns("B")
If WorksheetFunction.CountA(.Cells) = 0 Then '<--| if no data whatever
MsgBox "Sorry: no data"
Else
With .SpecialCells(xlCellTypeConstants) '<--| reference its cells with constant (i.e, not derived from formulas) values)
firstRow = .Areas(1).Row
lastRow = .Areas(.Areas.Count).Cells(.Areas(.Areas.Count).Rows.Count).Row
End With
' MsgBox "the first row is " & firstRow
' MsgBox "last row is " & lastRow
End If
End With
End Sub
You are declaring your i variable at module scope, which makes it accessible everywhere within the module; it's modified when you call arrayManip and the value changes.
If you declare a local ind variable inside this routine it won't happen, because the variable will only be accessible to the scope it's declared in. Try the code below:
Sub throughCols()
' Dim thisCell As Range
Dim ind As Long '<-- DECLARE local variable
' get start and end of column data
' NB sheet name is hard coded twice
Call dataRange
startIt = firstRow + 1
' ===== loop on ind and not i (changes when you call arrayManip) ====
For ind = 1 To 8 ' Step 1 <-- actually not needed, that's the default increment value
' after testing use startIt To lastRow Step 1
' by using activeCell I dont have to pass range through to the sub
Sheets("test").Range("B" & ind).Select
MsgBox "this is itteration " & ind & " which will output to " & ActiveCell.Offset(0, 2).Address
Call arrayManip
Call cleanTxt(txt)
Next ind
End Sub
My spreadsheet is used to manage tasks. I add new tasks by running a small macro, and currently the numbering is simply 1, 2, 3, 4..., generated by the following code:
Cells(ActiveSheet.Rows(9).Row, 1).Value = Cells(ActiveSheet.Rows(10).Row, 1).Value + 1
I would like, using VBA, to evolve this by adding a prefix to the number that represents the Year the task was initiated. Furthermore, the numbering should re-start at 1 for the first entry of each year. I.e
15-1, 15-2, 15-3, 15-4....16-1, 16-2, 16-3...
Any ideas for a simple code that could achieve this?
Here is a very basic example. Amend it to suit your needs. You can also create a procedure and pass the row number to which you want the auto numbering to happen as shown at the end of this post.
Sub Sample()
Dim rng As Range
Dim prev As Range
Dim rw As Long
rw = 9 '<~~ Change this to the relevant row
Set rng = ThisWorkbook.Sheets("Sheet1").Cells(rw, 1)
On Error Resume Next
Set prev = rng.Offset(-1)
On Error GoTo 0
'~~> Check if there is one row above
If Not prev Is Nothing Then
'~~> Match the year
If Left(rng.Offset(-1), 2) <> Format(Date, "yy") Then
'~~> Restart numbering
rng.Value = Format(Date, "yy") & "-" & 1
Else
'~~> Increment numbering. Split will extract the number
rng.Value = Format(Date, "yy") & "-" & Val(Split(rng.Value, "-")(1)) + 1
End If
Else
'~~> Restart numbering
rng.Value = Format(Date, "yy") & "-" & 1
End If
End Sub
Screenshot
Edit:
Using it as a procedure where you can pass arguments.
Sub Sample()
Dim r As Long
r = 9 '<~~ Chnage this to the relevant row
AllocateID r
End Sub
Sub AllocateID(rw As Long)
Dim rng As Range
Dim prev As Range
Set rng = Cells(rw, 1)
On Error Resume Next
Set prev = rng.Offset(-1)
On Error GoTo 0
'~~> Check if there is one row above
If Not prev Is Nothing Then
'~~> Match the year
If Left(rng.Offset(-1), 2) <> Format(Date, "yy") Then
'~~> Restart numbering
rng.Value = Format(Date, "yy") & "-" & 1
Else
'~~> Increment numbering. Split will extract the number
rng.Value = Format(Date, "yy") & "-" & Val(Split(rng.Value, "-")(1)) + 1
End If
Else
'~~> Restart numbering
rng.Value = Format(Date, "yy") & "-" & 1
End If
End Sub
How about this:
Sub Test()
Dim i As Integer
Dim startYear As Integer
priorYear = 2014
With ActiveSheet
For i = 1 To 100
.Cells(i, 1) = CStr(priorYear + WorksheetFunction.RoundUp(i / 12, 0) & "-" & ((i + 1) Mod 12))
Next i
End With
End Sub
sure just do that:
Cells(ActiveSheet.Rows(9).Row, 1).Value = format(date,"yy") & "-" & _
Cells(ActiveSheet.Rows(10).Row, 1).Value + 1