The following code adds/deletes sheets and inserts a list of all the sheet names while making them hyperlinks:
https://stackoverflow.com/a/48159499/9102830
The following code changes the sheet name depending on several cell names:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Target.Address(0, 0) = "C13" Then
Sh.Name = Sh.Range("B33").Value + "_" + Sh.Range("C13").Value + "_" + Sh.Range("C22").Value + "_N01"
End If
End Sub
The questions are:
Instead of changing when the cell input changes, can this be done automatically?
Could it be a part of the "Sub add_sheet" code? Like, if I add a sheet, it should be named depending on the cells, and since it is copied and the cells are the same, the name will obviously be taken, in which case the name should be "N02" instead of "N01" (instead of the default "(2)" that Excel does).
Finally, when/if the cells are changed, so that the sheet name is not equal to a previous sheet name, could it automatically go back to "N01"?
Thanks in advance!
There are a few ways you can handle that. One way is to combine Worksheet_Activate with Worksheet_Calculate, and throw in a public variable.
When you first open up your worksheet, you will automatically set the variable, and the variable will set with every change as well.
Option Explicit
Public myVar As Variant
Private Sub Worksheet_Activate()
'Set the public variable
myVar = Range("B33").Value
End Sub
Private Sub Worksheet_Calculate()
If myVar <> Range("B33").Value Then
sh.Name = ...
myVar = Range("B33")
End If
End Sub
You can also set the variable when you open the workbook with Workbook_Open as well. Really just depends on your particular needs.
Related
I'm very new at VBA ad I have the following problem.
I want to reference worksheets by codenames (because the tab name can be modify by the user)
I know that is not possible add a new worksheet specifying the codename.
In a running of my macro I create a new Worksheet using:
Worksheets.Add().Name = "aSheet"
st = Worksheets("aSheet").CodeName
now I have the codename in variable st.
In a following run of the macro (in one in which I don't create the new worksheets) I want to access the previous created worksheet by codename i.e. I
want to use code with codename hard coded. I don't want to use
st = Worksheets("aSheet").CodeName
because between the two runs of the macro the user must have changed the tab "aSheet" name.
That seems impossible to me, but I hope to be wrong.
Instead of going to the workbook's Worksheets collection, just refer directly to the worksheet by its codename:
debug.print Sheet5.name
You can also use it's index if you are super into the Worksheets collection:
debug.print Sheets(5).name
You might find it helpful to save the worksheet as a global variable (declared outside the scope of your function or subroutine. The global variable will be available after code execution, but will be reset if the workbook is closed and reopened.
Dim st As Worksheet
Sub addWorksheet()
Set st = ThisWorkbook.Worksheets.Add()
st.Name = "test3"
Debug.Print st.Name, st.CodeName
End Sub
That variable st is a worksheet object and can be referenced in any other subroutine or function after it's set.
...later on
Sub printWSName()
Debug.print st.name
End Sub
Finally you may want to save this value if the workbook closes. In your Workbook Object in the Project pane you can use the Workbook_BeforeClose and Workbook_Open events to save and recapture this value:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Worksheets("savedStuff").Cells(1, 1).Value = st.Name
End Sub
Private Sub Workbook_Open()
Set st = Worksheets(st.Name)
End Sub
I have tried code that I've found here on stackoverflow, and elsewhere but they aren't working as I think they can. I'll list them below. I'm almost certain this is an easy question.
What I'm trying to do: If in any of the cells in the range A2:A100 there is any text or number whatsoever, then make the worksheet tab red. And I will need to do this on over 20 tabs. This must execute upon opening the workbook, and thus not require manually changing a cell or recalculating.
The problems I've had with other code: As far as I can tell they require editing a cell, and then quickly hitting enter again. I tried SHIFT + F9 to recalculate, but this had no effect, as I think this is only for formulas. Code 1 seems to work albeit with having to manually re-enter text, but no matter what color value, I always get a black tab color.
Code I've tried:
Code 1:
Private Sub Worksheet_Change(ByVal Target As Range)
MyVal = Range("A2:A27").Text
With ActiveSheet.Tab
Select Case MyVal
Case ""
.Color = xlColorIndexNone
Case Else
.ColorIndex = 6
End Select
End With
End Sub
Code 2: This is from a stackoverflow question, although I modified the code slightly to fit my needs. Specifically, if in the set range there are no values to leave the tab color alone, and otherwise to change it to color value 6. But I'm sure I've done something wrong, I'm unfamiliar with VBA coding.
Private Sub Worksheet_Calculate()
If Range("A2:A100").Text = "" Then
ActiveWorkbook.ActiveSheet.Tab.Color = xlColorIndexNone
Else
ActiveWorkbook.ActiveSheet.Tab.Color = 6
End If
End Sub
Thanks for your help!
I posted this on superuser first, but perhaps stackoverflow is more appropriate since it is explicitly programming-related.
Only two things will be able to switch the condition in this statement:
If Range("A2:A100").Text = "" Then
You've already identified both of them, changing the contents of the one of the cells in that range on a worksheet, or a formula in one of those cells recalculating to or from a value of "". As far as event triggers go, if the formula result changes, both the WorkSheet_Calculate and Worksheet_Change events will fire. Of the two, Worksheet_Change is the one to respond to, because WorkSheet_Calculate will only fire if any of the cells in A2:A100 contain a formula. Not if they only contain values - your "Code 2" isn't wrong, the event was just never firing.
The simple solution is to set your tab colors when you open the workbook. That way it doesn't matter if you have to activate a cell in that range and change it - that's only way the value you're testing against is going to change.
I'd do something like this (code in ThisWorkbook):
Option Explicit
Private Sub Workbook_Open()
Dim sheet As Worksheet
For Each sheet In Me.Worksheets
SetTabColor sheet
Next sheet
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Not Intersect(Target, Sh.Range("A2:A100")) Is Nothing Then
SetTabColor Sh
End If
End Sub
Private Sub SetTabColor(sheet As Worksheet)
If sheet.Range("A2:A100").Text = vbNullString Then
sheet.Tab.Color = xlColorIndexNone
Else
sheet.Tab.Color = 6
End If
End Sub
EDIT: To test for the presence of specific text, you can do the same thing but need to have the test check every cell in the range you're monitoring.
Private Sub SetTabColor(sheet As Worksheet)
Dim test As Range
For Each test In sheet.Range("A2:A100")
sheet.Tab.Color = xlColorIndexNone
If test.Text = "whatever" Then
sheet.Tab.Color = vbRed
Exit For
End If
Next test
End Sub
Maybe test the len of the trimmed joined string of cells:
Private Sub Worksheet_Calculate()
If Len(Trim(Join(Application.Transpose(Range("A2:A100"))))) = 0 Then
ActiveWorkbook.ActiveSheet.Tab.Color = xlColorIndexNone
Else
ActiveWorkbook.ActiveSheet.Tab.Color = 6
End If
End Sub
This code will fire off every time the sheet calculates though as it is event code, I am not sure if that is what you want? If not then post back and we can drop it into a normal sub for you and make it poll all the sheets to test.
Worksheet_Change function will get called everytime there's change in the target range. You just need to place the code under Worksheet. If you have placed the code in the module or Thisworkbook then it wont work.
Paste the below in Sheet1 of your workbook and check if it works. Of Course you will need to do modification to the below code as I have not written complete code.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim WatchRange As Range
Dim IntersectRange As Range
Set WatchRange = Range("A1:A20")
Set IntersectRange = Intersect(Target, WatchRange)
If IntersectRange Is Nothing Then
''Here undo tab color
Else
ActiveSheet.Tab.ColorIndex = 6
End If
End Sub
bear with me, as I am a complete vba newbie and wrapping my head around what I already have has already taken me much longer than I care to admit.
I have a workbook with one master list "ITEMS" and several (up to 15) sub-tabs that grab information from the ITEMS sheet. I've been able to make this happen using buttons on each sub sheet which call this code:
Private Sub getNELL_Click()
Sheets("ITEMS").Range("A1:K400").AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=Sheets("ITEMS").Range("O1:O2"), CopyToRange:=Range("A1:K1") _
, Unique:=False
End Sub
This code successfully grabs each relevant row into the sheet each time I click the button, where each getX has a different name/criteria range (getRILEY, getELLE etc.)
But what I'm looking to do next is to have these macros run automatically when any cell in the G column of the ITEMS sheet is changed. In plain text, what I need is:
When [Any Cell in Column G] in Sheet("ITEMS") is changed
Run getNELL, getRiley, getELLE (x15 different macros)
here's my file with all the sheet (sic) in it.
EDIT:
and it's done!
moving the macros to a module instead of in each individual sheet, making them public and removing the _Click, along with the following code worked the magic I needed.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Range("G2:G400")
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
getNELL
getMIKA
getRILEY
getJANNA
getWOO
getELLE
getMK
getLAURA
getFLIPSE
getJENN
getCRIS
End If
End Sub
First off, use this link as a resource for triggering an event when cells change. That's usually just in the subroutine declaration.
For the code, change all of your private subs to public subs by replacing "private" with "public". Then in your subroutine list the subroutines to call:
>
Subx
Suby
Subz
end sub
Sorry the answer isn't super detailed as I am typing from my phone. Also, those sub examples should each be on their own line. I can't seem to change that on here.
you have already created filter criteria in ITEM sheet (grey highlighted)
so create one mapping for what sheet needs what criteria range in INDEX sheet
e.g.
SheetName Criteria Mapping
nell O1:O2
mika P1:P2
riley Q1:Q2
janna R1:R2
woo S1:S2
elle O3:O4
mk P3:P4
laura Q3:Q4
flipse R3:R4
jenn S3:S4
cris O5:O6
Add this code in a Module
Public Sub pGet_Data(ByVal SheetName As Worksheet, ByVal CriteriaRng As Range)
ThisWorkbook.Worksheets("ITEMS").Range("A1:K400").AdvancedFilter _
Action:=xlFilterCopy, _
CriteriaRange:=CriteriaRng, _
CopyToRange:=SheetName.Range("A1:K1"), _
Unique:=False
End Sub
And in Thisworkbook Module add given code:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim rngCriteriaRange As Range
Dim rngOneMap As Range
Dim wksSheet As Worksheet
If Sh.Name = "ITEMS" And Target.Column = 7 Then
Set rngCriteriaRange = Sh.Range("W6:X16") '<--you can make it dynamic
For Each rngOneMap In rngCriteriaRange.Rows
Set wksSheet = ThisWorkbook.Worksheets(rngOneMap.Cells(1, 1).Value)
Application.StatusBar = "Updating [" & wksSheet & "] Sheet"
Call pGet_Data(wksSheet, wksSheet.Range(rngOneMap.Cells(1, 2).Value))
Next rngOneMap
End If
MsgBox "Sheets has been updated.", vbOKOnly, "Be Happy..."
ClearMemory:
Set rngCriteriaRange = Nothing
Set rngOneMap = Nothing
Set wksSheet = Nothing
End Sub
I think this will resolve what you are looking for... :)
I would like my Macro to launch whenever a value in a cell containing a formula changes.
i.e. the user is modifying another cell thus changing the value of the cell in question.
I have noticed that using the statement (found herein), only works if the user modifies the cell itself but not if the cell changes automatically - due to a formula as specified above.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A20")) Is Nothing Then ...
Any thoughts??
I tried to follow the answers from this question "automatically execute an Excel macro on a cell change" but it did not work...
Thanks in advance :)
A possible work-around comes from the fact that, to change a value, the user needs to change the selection first. So I would:
1) Declare a global variable called "oldValue" on top of the WS source code module:
Dim oldValue As Variant
2) Register the old value of your formula before the user types anything (let's say it's in Range("A4"), I let you adapt with the others):
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Range("A4")
End Sub
3) Check if the change has affected the formula in the Change event:
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("A4") <> oldValue Then
MsgBox "User action has affected your formula"
End If
End Sub
I've tested with a simple sum, I'm able to write cells that are not involved without any prompt but if I touch one of the cells involved in the sum the MsgBox will show up. I let you adapt for multiple cases, for user adding/removing rows (in that case I suggest to name the ranges containing the formulas you want to track) and the worksheet references.
EDIT I'd like to do it at once, not by going through 2 processes, is it possible? The problem is my macro involves a range containing more than one cell so it will be hard to store old values for 10 cells.
If ranges are next to each other, then instead of using a variable you can use a collection:
Dim oldValues As New Collection
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
For j = 1 To 10
oldValues.Add Range("A" & j).Value
Next j
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
For j = 1 To 10
If Range("A" & j).Value <> oldValues(j) Then
MsgBox "The value of Range(A" & j & ") has changed"
End If
Next j
End Sub
Of course, if ranges are not close to each other, you can just store them anyway in the SelectionChange event like this:
oldValues.Add Range("A1").Value
oldValues.Add Range("B7").Value
'...
and if you done this ONCE, with 10 ranges only, it should be a reasonable solution to your problem.
You said, "I would like my Macro to launch whenever a value in a cell containing a formula changes..."
If having your code run whenever a cell containing a formula is recalculated (which is not exactly what you asked for), one solution might be to create a VBA function that simply returns that value passed to it, plus does whatever else you want to do when the formula is recalculated...
Public Function Hook(ByVal vValue As Variant) As Variant
Hook = vValue
' Add your code here...
End Function
...then "wrap" your formula in a call to this function. For example, if the formula you are interested in is =A1+1, you would change this to =Hook(A1+1), and the Hook function would be called whenever A1+1 is recalculated (for example, when the value in A1 changes). However, it is possible that recalculating A1+1 will yield the same result and still call the Hook function (for example, if the user re-enters the same value in A1).
You can have a go at this:
First, in a Module Code declare a Public Variable.
Public r As Range, myVal '<~~ Place it in Module
Second, initialize your variables in Workbook_Open event.
Private Sub Workbook_Open()
Set r = Sheet1.Range("C2:C3") '<~~ Change to your actual sheet and range
myVal = Application.Transpose(r)
End Sub
Finally, set up your Worksheet_Calculate event.
Private Sub Worksheet_Calculate()
On Error GoTo halt
With Application
.EnableEvents = False
If Join(myVal) <> Join(.Transpose(r)) Then
MsgBox "Something changed in your range"
'~~> You put your cool stuff here
End If
myVal = .Transpose(r)
forward:
.EnableEvents = True
End With
Exit Sub
halt:
MsgBox "Error " & Err.Number & ": " & Err.Description
Resume forward
End Sub
Above will trigger the event when values in C2:C3 changes.
Not really very neat but works in detecting changes in your target range. HTH.
Declaring a module -level variable like Matteo describes is definitely one good way to go.
Brian 's answer is on the right track with regards to keeping all is the code in the same place, but it's missing one critical part : Application.Caller
When used in function that is called by a single cell, Application.Caller will return the Range object of that cell. This way you can store the old value within the function itself when it is called, then once you're done with calculating the new value you can compare it with the old and run more code as required.
Edit: The advantage with Application.Caller is that the solution scales in and of itself, and does not change no matter how the target cells are arranged (I.e. Continuous or not).
Ok so what I am trying to accomplish: I have one userform that asks how many new orders the user needs to process. I set the user input to a variable in this code (not sure if it even does anything).
Private Sub CommandButton1_Click()
UserForm2.Show
OrderNum.Text = NewOrders
'I changed the textbox name to OrderNum
End Sub
Then when UserForm2 pops up, I want to be able to input more data with more specific information about the orders. So if on Userform1 I entered in 3, I want to have to submit new data into UserForm2 3 different times. I tried using a For - Next loop (below) but it doesn't work. I'm not sure how (or if) I can store variables like that between Userforms.
Private Sub CommandButton1_Click()
Dim lRow As Long
Dim ws As Worksheet
Set ws = Worksheets("Core Info")
OrderNum.Text = NewOrders
lRow = ws.Cells(Rows.Count, 2).End(xlUp).Offset(1, 0).Row
For i = 1 To NewOrders
ws.Cells(lRow, 1).Value = TextBox1.Text
ws.Cells(lRow, 3).Value = TextBox2.Text
Next i
UserForm2.Hide
End Sub
The the second userform pops up as it should, but then nothing works after that. Can anyone tell me what I could do to fix this?
Note: I realize that those of the above start with CommandButton1 (default) but they are on different Userforms.
It will be helpful (and is good coding practice) to give your form controls more easily recognizable names, instead of the ambiguous CommandButton1 nomenclature. Revise CommandButton1's name on UserForm1 to Form1_SubmitButton. Then, use this code to handle the form submission.
Sub Form1_SubmitButton_Click()
' a textbox control named OrderNum on UserForm1 has captured the value
'Assign the value from the OrderNum textbox to a named variable in the worksheet
ActiveWorkbook.Names.Add "OrderNum", Me.OrderNum.Text
UserForm2.Show
End Sub
Then, in UserForm2 change the name of your command button to something like Form2_SubmitButton and use this code:
Sub Form2_SubmitButton_Click()
Dim orderNum as Long
orderNum = Replace(ActiveWorkbook.Names("OrderNum").Value,"=",vbNullString)
'the rest of your code goes here.
End Sub
So, what we have done above is to create a Name in the worksheet which contains the value you want to use on the several forms. You can always obtain this value by Replace(ActiveWorkbook.Names("OrderNum").Value,"=",vbNullString)
Alternatively, you could use a public variable in your code module.