I'm currently using Target.Address to identify when a cell is double clicked and run a piece of code to take the user to a relevant sheet showing the information contained in that cell.
So for example, if a cell says that 3 systems haven't done some sort of action, if a user clicks on that cell they get taken to what those 3 systems are.
Currently, I'm doing this like so:
If Target.Address = "$B$20" Then
Win2KTrackerIncompleteFilter (strEngUnits(9))
ElseIf Target.Address = "$C$20" Then
Win2KTrackerIncompleteFilter (strEngUnits(0))
ElseIf Target.Address = "$D$20" Then
Win2KTrackerIncompleteFilter (strEngUnits(1))
etc
I've put the majority of the code in one small function, so this seems to be doing the job okay. However, if I were to insert a new row above row 20 (from the above example), all of these references would be pointing to the wrong place. I thought I could handle this by removing the absolute references (the $ sign) but that just breaks the mechanism.
Can anybody advise how I could either a) rewrite the code to make it more efficient and b) protect the code so new rows can be inserted and the code will remember which rows/columns it was pointing to and update itself accordingly.
Select a cell on a spreadsheet and give it a name
For example B3 is now named myCell
and edit your script a little to use the named ranges like this
Dim namedRange As Name
If Target.Address = Me.Range("myCell").Address Then
Win2KTrackerIncompleteFilter (strEngUnits(9))
ElseIf ...
...
End If
So now even if you insert new rows the name will update its .RefersTo property automatically and you will not have to touch the script.
I usually do this using named ranges, along with the Not Intersect(...) Is Nothing technique, which is a bit more robust than simply looking at the Address property.
First, name your cells e.g. as shown here:
If you insert more rows/columns, the names will follow the cells as they get pushed around, so you won't have to change the code each time.
Then, to test for which cells is being clicked, you can use Not Intersect(...) Is Nothing:
If Not Intersect(Target, Range("System1Report")) Is Nothing Then
Win2KTrackerIncompleteFilter strEngUnits(9)
ElseIf Not Intersect(Target, Range("System2Report")) Is Nothing Then
Win2KTrackerIncompleteFilter strEngUnits(0)
ElseIf Not Intersect(Target, Range("System3Report")) Is Nothing Then
Win2KTrackerIncompleteFilter strEngUnits(1)
End If
Why do this instead of just checking if Target.Address = Range("System1Report").Address? In some circumstances (in my case, most of the time), you may want to have named ranges that include many cells. In that case, that named range's address won't match a single cell's address. Hence checking whether they intersect is more robust.
Related
I am trying to highlight the cell of a second entry (and beyond) within a range of cells. I am tyring to implement this feature in my attendence tracking.
For example, in the image below, I have "Peter", "John" and "Tom". If there are only one person who is absent, I do not need to highlight anything, as shown below.
However, if either "Tom" or "John" is the second person to be entered as "Not in" (absent), I want to highlight the cells, as shown below.
Finally, if the last person left is also absent, I would like to highlight it as well, like below image
The values in Column C is not restricted to these phrases ("Absent","Away","Not in").
From another point of view, I want to highlight the second and the later entries within a range of cells. Is there a way to implement such feature without using VBA or macro? Can it be donne using conditional formating?
Please advise. Thank you so much!
You can use the Worksheet_Change event to add a timestamp to a helper column. In my case, I added it to column D:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim myrange As Range
Set myrange = Range("C3:C9")
If Not Application.Intersect(myrange, Range(Target.Address)) Is Nothing And Target.Count = 1 Then
If Target.Value = "" Then
Target.Offset(0, 1).Value = ""
Else
Target.Offset(0, 1).Value = Now()
End If
End If
End Sub
Then use conditional formatting on column C using something like this:
=$D3>=SMALL($D$3:$D$9,2)
This will highlight any rows where the the timestamp is greater than the second smallest timestamp (ignoring blanks).
Here's an example of it in action. For illustration purposes, I put the order I typed things in column C rather than "Absent", "Not Here", etc.
In order to avoid the user typing = before they enter an arithmetic operation in a cell (i.e. 5+2, 8*8, etc) I have this code:
Private Sub Worksheet_Change(ByVal Target As Range)
On Error Resume Next
If Not Intersect(Target, Range("C4:C303")) Is Nothing Then
If Target = "" Then Exit Sub
Application.EnableEvents = False
TempTarget = Target
Target = "=" & TempTarget
If IsError(Target) Then Target = TempTarget
Application.EnableEvents = True
End If
On Error GoTo 0
End Sub
It works perfectly for additions (5+5) and multiplications (9*55), but doesn't work well for divisions and subtractions with small numbers (5-2, 8/9) because Excel treats them as dates. It works well for subtractions and divisions with bigger numbers (that couldn't be dates). I want to have in the cell the arithmetic formula but displaying the result. How can I fix the code so that it always work the way I want?
This isn't as straight-forward to solve as it might appear. If you enter values like 5-2 or 8/9 there's no way to intercept the change taking place to the cell before Excel changes it into a Date format.
However there is a solution, although it may not suit your purpose.
Set the Format of each of the cells you want to apply this to (presumably C4:C303) to "Text" or "#".
Change your code to include the addition of Target.NumberFormat = "General" before setting the value of the cell.
The code to achieve that would look like this:
...
Application.EnableEvents = False
TempTarget = Target
' Add this line to change the format back to General
Target.NumberFormat = "General"
Target = "=" & TempTarget
...
This will then correctly handle cases like 5-2 or 8/9 that would otherwise become dates automatically.
Side Effect
This creates an undesired side effect that if one of the existing calculated cells is edited again later it will now be in "General" format instead of text.
This could be resolved by using the SelectionChange event and by setting the format of the cell to "Text" or "#" again if it's inside the range you are working in, but you'd want to undo that if the user doesn't edit the value of the cell.
It's a bit messy. Excel clearly doesn't lend itself to this type of approach.
I am working on a spreadsheet to copy a users data from the previous day, then delete any numerical values but keep cells with "NA". What I want is for users to not be able to change/delete the cells that still have "NA" in them. I found some code that used OFFSET to move down one cell if a certain cell was selected (based on the row and column) but I haven't been able to figure out how to use the OFFSET to move down one cell if the current cell contains "NA". (https://www.extendoffice.com/documents/excel/3820-excel-lock-cell-without-protecting-sheet.html) This worksheet is already locked with a Quality-set password, so I can't do anything to unlock the spreadsheet, then select the "NA" cells to be locked, then relock the spreadsheet, thus looking for a creative way to keep the cells from being selected or changed. Also, the code would need to run all the time, not just when a macro was selected to run. Any ideas?
If it's possible for the user to open the book without macros enabled, then I'm not sure what you're asking is possible.
If you can assume macros are enabled though, you could use events to either prevent the user selecting the cell (similar to the OFFSET you mention) or you could track changes manually onto a hidden tab in order to note changes and deal with them as you see fit. There are many ways you can achieve the latter, just search "VBA tracking changes to a sheet" etc.
This is how you'd use the OFFSET method:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells(1, 1).Text = "NA" Then
Beep
Cells(Target.Row, Target.Column).Offset(0, 1).Select
End If
End Sub
Keep in mind though, this is a very simplistic method. It won't prevent users selecting multiple cells (a range) and deleting the contents. Nor will it prevent values being pasted to range that includes the 'NA'.
UPDATE:
The following is an improved version that will at least prevent users from selecting multiple cells (for pasting into or deleting) if one of the cells contains "NA".
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim testarea As Range
Set testarea = Intersect(UsedRange, Target.Cells)
If Not (testarea Is Nothing) Then
Application.EnableEvents = False
For Each cell_to_check In Intersect(UsedRange, Target.Cells)
If cell_to_check.Text = "NA" Then
Beep
Cells(cell_to_check.Row, cell_to_check.Column).Offset(0, 1).Select
Do Until Selection.Text <> "NA"
Selection.Offset(0, 1).Select
Loop
Exit For
End If
Next
Application.EnableEvents = True
End If
End Sub
This is still slightly flawed however, as it is still possible to drag-fill cells from other areas over a cell containing "NA".
I run into this issue a fair amount and am curious if someone can tell me why or how I can write this a little cleaner.
Below is my code and it does work.
If Target.Row = rTime.Offset(0, 1).Row Then
If Target.Column = rTime.Offset(0, 1).Column Then
cboStatus.Activate
End If
End If
How come I can’t just write it like this?
If Target = rTime.Offset(0, 1) Then
cboStatus.Activate
End If
If target is already a range then why do I need to specify the individual row and individual column? That second code will not work and I have tried many variations of it. I even tried something like If Target.Range = range(“C4”) Then or If Target.Range = cells(4, 3) Then, but neither of those worked either. I tried many variations of similar stuff. Although, I don’t want to use a specific range like A4, since I wanted to use the rTime like what is in the example, but I was just trying to figure this out.
Nothing seems to work, other than specifying the individual row and column each time. Can someone please explain this to me? Also, is there a better way to write this than what I did in the first example, which does work?
Thanks for anything that relieves my confusion.
The default property of a range object is .Value so when you say If Target = rTime.Offset(0, 1), it will always compare the values in that range rather than the address of those ranges.
One way is already shown by L42. Here is another way using Intersect
If Not Intersect(Target, rtime.Offset(0, 1)) Is Nothing Then cboStatus.Activate
EDIT
When you say Target.Column and Target.Row, you will always get the first column and the first row of the cell in that range, even if Target has multiple cells. To avoid this use the below to ensure that you have the desired Target. Your code will give you unexpected results even if there is a single cell in Target. For example, say the value of cell B1 is equal to any other cell which at the moment is target. So if Cell B1 = "Sid" and Cell F1 = "Sid" and you select cell F1 then you will get the "Hello World" message box.
For xl2003, you can use an additional check
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rtime As Range
Set rtime = Range("A1")
If Target.Cells.Count > 1 Then
MsgBox "you have chosen more than one cell"
Exit Sub
End If
If Not Intersect(Target, rtime.Offset(0, 1)) Is Nothing Then
MsgBox "Hello World"
End If
End Sub
For xl2007+, replace Target.Cells.Count with Target.Cells.CountLarge
For L42
Your method is correct but then you also will have to put the above check to get the correct results.
try this:
Edit1: To cover Chis' concern
If Target.Address = rtime.Offset(0,1).Address(,,,True) then cboStatus.Activate
you cannot compare objects, just the properties? I'm not certain though.
I'm currently trying to write a macro based on sheet change, where the letters in a table column are automatically converted to upper case. So, for example, if I entered "abcde-12345-678" into a cell, it would automatically correct to "ABCDE-12345-678". After doing some digging, I found some code that has worked for some people, but I'm having trouble tweaking it to suit my needs.
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Range("E:E")) Is Nothing Then Exit Sub
Application.EnableEvents = False
Target = UCase(Target)
Application.EnableEvents = True
End Sub
There are two things that I would like to address. The first being, that this code isn't currently working for me. I have it in the correct location according to the author (located in the Sheet1 object). Are there any ideas as to why this isn't working?
The second is that I would like to modify the code to refer to a table column rather than a range. For example, I've tried changing the second line of the above code to the following (the name of my table is ReviewTracker, and the column I'm interested in is Product Number):
If Intersect(Target, Range(ReviewTracker[[#Headers],[Product Number]])) Is Nothing Then Exit Sub
This returned a compile error "Expected: list separator or )". So there is obviously something wrong with it, but hopefully it might help illustrate what it is I'm trying to accomplish.
Thanks in advance for any help on the issue.
-Sean
First. You can have events disabled due to lots of reason. Let's make it sure that events are on which you can do as follows:
go to VBA Editor >> open Immediate Window >> write there: Application.EnableEvents = true >> press Enter
Second. To check if intersection match appropriate column within you ListObject table you need something like this:
If Intersect(Target, Range("ReviewTracker[Product Number]")) is Nothing Then
assuming that ReviewTracker is table name and Product Number is table column. You don't need #Headersas it will refer only to header row.
What UCase does is converting all the characters in a given string into upper case and thus you can apply it to any Range.Value. Worksheet_Change is called every time the value of a cell has changed and thus is a good place to put your code. But the way you are using to refer the table is wrong. The code your posted adapted to your requirements:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Sheet1.ListObjects("Table1").ListColumns(1).Range) Is Nothing Then Exit Sub
Target.Value = UCase(Target.Value)
End Sub
It converts into upper caps any string input in the first column of Table1 in Sheet1. It has to be placed in the Sheet1 object file (in the VBA Project explorer: Sheet1 (Sheet1) inside the Microsoft Excel Object folder). Adapting it to your actual conditions is straightforward.