Dynamic target for intersect method in VBA - excel

Background:
I need help adjusting a code I wrote by copy-pasting earlier. The goal of the function is to add a new row in a specified format whenever a target cell is clicked.
This was achieved via this code [excerpt only]:
Private Sub worksheet_selectionchange(ByVal target As Range)
If Not Intersect(target, Range("G6")) Is Nothing Then
Rows("7:7").Select
Selection.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
Range("B7:F7").Select
With Selection.Font
.ColorIndex = xlAutomatic
.TintAndShade = 0
End With
It also works fine so far. However now I want to add this functionality to the same tab again for another cell range. The problem I have now is, that since the relevant rows are stacked vertically, whenever I add a row via the original function, the ranges defined in the second routine now no longer work.
My Question:
Can I define ranges for the Intersect method dynamically? My idea would be something clunky like having the second method refer to a variable instead of a fixed cell (MyRange) which is changed automatically by the first routine (e.g. MyRange = MyRange + 1). How would I do that? Or are there any other ways to achieve what I want to do?

Your idea is on the right track - defining variables beforehand and not hardcoding values in your code is one of the building blocks of maintainable code. There are many ways to do this, including having Excel search for keywords, etc.
My suggestion for your case is that you look into the Name Manager within Excel, and define a new name which refers to all cells that you would like to be watched by your sub. If you do this, Excel will track those cells the same as it would if you were just typing formulas within cells. ie: if you put in cell C5: "=A5+B5", and you insert a new column to the left of column B, C5 will automatically now read "=A5+C5". In this way, your VBA code won't change, but the values within your defined Name will change.
Without knowing exactly how your sheet is setup, here's an example of how you could actually do this:
Private Sub worksheet_selectionchange(ByVal Target As Range)
Dim MyRange As Range
Dim CurrentRow As Integer
Dim FormattedArea As Range 'This will hold the area of your row which you want formatted
Const LeftColumn = 2 'this holds the left-most column of the area you want formatted...
Const RightColumn = 6 'these hardcoded numbers will need to be changed if you want to format a different number of columns; if you have a dynamic way of determining them that would be best
Set MyRange = Range("Possible_Areas") 'This makes MyRange = your defined, Excel-tracked name; you will need to go to the name manager in Excel and create it, listing all target cells you want included within it
CurrentRow = Target.Row 'This will be used to find where to insert the new row, based on the current selection
If Not Intersect(Target, MyRange) Is Nothing Then
Target.EntireRow.Insert 'Notice that I have removed your "selection" command - you can search this site for reasons on why ".Select" is problematic
Set FormattedArea = Range(Cells(CurrentRow, LeftColumn), Cells(CurrentRow, RightColumn))
With FormattedArea.Font
.ColorIndex = xlAutomatic
.TintAndShade = 0
End With
End If
End Sub

Related

VBA - Change cell background color

I've been building a team vacation calendar in Excel (Office 365 version) and I'm using VBA for the first time to automate some calculations and styling.
I've been stuck on the following:
I want to create a function that changes the background color of a cell.
I have four colors to switch between so I'd rather make four functions, one per color.
That function will then be called within different functions when needed.
I don't want to use ColorIndex, but rather a custom color (I can use RGB or the Long value), but I can't get the ColorIndex to work either.
My assumption is that the problem lies with the range but at this point, who knows :D.
The long values of each color are stored within a self-made Enum "OwnColorLong".
Here are some of my tries, every time the result in my Excel sheet (when running as a formula) is "#Value!".
'Function SetBackgroundToRed(RangeToChange As Range)
' Dim ColorIWant As Long
' ColorIWant = OwnColorLong.Red
' RangeToChange.Interior.Color = ColorIWant
'End Function
'Sub SetColorToRed(RangeToChange As Range)
' RangeToChange.Select
' With Selection.Interior
' .ColorIndex = 3
' End With
'End Sub
'
Function SetBackgroundToRed(RangeToChange As Range)
Dim MyRange As Range
Set MyRange = Worksheets("Vacation Calendar").Range("RangeToChange")
MyRange.Select
With Selection.Interior
.ColorIndex = 3
End With
End Function
I'm still a bit confused about when to use a sub or a function, or when to best use a class module. All code is now placed within one module, I'll be writing a Main sub linked to a button and putting all the code in there except for the functions themselves. If there are better practices, feel free to let me know.
This won't work as a UDF called from a worksheet cell. Except for some edge cases, e.g. this, UDFs called from a cell can't modify other cells on the worksheet.
Functions can perform a calculation that returns either a value or text to the cell that they are entered in. Any environmental changes should be made through the use of a Visual Basic subroutine.
Prefer Sub to Function since this does something and doesn't return anything.
"I have four colors to switch between so I'd rather make four functions, one per color" - better to make one function and pass a color parameter.
Private Sub SetColor(ByVal RangeToChange As Range, ByVal Color As Long)
RangeToChange.Interior.Color = Color
End Sub
Called like
SetColor yourRange, OwnColorLong.Red
Second, Sub or Function? If you need an answer from your method, then Function it is. In your case, you need no answer, so it is Sub.
Sub SetBackgroundToRed(RangeToChange As Range)
With RangeToChange.Interior
.ColorIndex = 3
End With
End Sub
I got this to work eventually!
Dim rng As String
rng = "A1"
Range(rng).Interior.Color = OwnColorLong.Red
The problem was not knowing how to pass a range as a variable, I had to use String apparently, not Range.
Thanks everyone for the help!

Excel: copy cell' conditional formatting colour to all cells with same value

Reference Image
The Aim: The image shows the “key” on the left, where cells will be coloured manually using mouse input.
A (red background)
B (green background)
C (blue background)
On the right, you see the "data" where content-matching cells should be formatted to match their key (as I've already done for representation purpose in the image)
Initial Situation: No cells are coloured or formatted in any way. The Excel spreadsheet has column A with certain values, And rest of the columns (C onwards) have the same/different values in a random manner (some cells even empty). Not all values in "key" area will be found in "Data" area or vice versa. No new data is being added in any area. User will only colour certain Values in "Key" area as per their wish.
Thus all “C” cells in the Data area should be coloured blue when "C" in Key area is coloured blue. Furthermore, if I change the formatting of “C” in the Key to have a purple background, all the “C” cells should switch from blue to purple. Also, if I add more to the Key (say, “D” with a yellow background) then any “D” cells should become yellow; if I remove a Key entry, then matching values in the Data area should revert to default styling.
I'm open to different trigger techniques,like manually running a macros via shortcut etc.
I suspect that if any of this is possible it will require VBA, but I’ve never used it so I’ve no idea where to start if that’s the case. The closest answer to my question was found here but doesn't entirely work for me:
First I though of using the Worksheet.Change event, by putting something like this into the worksheet:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim area As Range, c As Range
If Not Intersect(Target, Range("A:A")) Is Nothing And Target.Count = 1 Then
Set area = Range("B1:F20")
For Each c In area
If c.Value = Target.Value Then c.Interior.Color = Target.Interior.Color
Next c
End If
End Sub
This is just a non-dynamic rough draft, but it "kind of" works.
Problem is that a change of color doesn't trigger the change event, funnily enough, changing the letter to the same letter – so no change at all – does trigger the event, as illustrated below:
We could use the Worksheet_SelectionChange event instead, but then it wouldn't update until next time we selected the cell we want to update.
We could instead force it to update on next selection. Which isn't optimal either:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim reference As Range, refCell As Range, area As Range, areaCell As Range
Set reference = Range("A1:A3") 'Reference range
Set area = Range("B1:F20") 'Search range
For Each refCell In reference 'Loop through the references
For Each areaCell In area 'Loop through search range for each reference
If areaCell.Value = refCell.Value Then areaCell.Interior.Color = refCell.Interior.Color
Next areaCell
Next refCell
End Sub
Obviously, using a button or similar to run the same code would also work, but I'm not entirely sure if we can make it run instantly.
To run it manually, put it as a normal sub in a module. (It still works in the sheet though).
Sub fillColor()
Dim reference As Range, area As Range, areaCell As Range
Application.ScreenUpdating = False
Set reference = Range("A1")
Set area = Range("B1:E25")
For Each areaCell In area
If areaCell.Value = reference.Value Then
areaCell.Interior.Color = reference.Interior.Color
areaCell.Interior.Pattern = reference.Interior.Pattern
End If
Next areaCell
Application.ScreenUpdating = True
End Sub
Still, you might want to add an option on what to to update, so it doesn't update it all.

Looping through cells within multiple tables and changing value if font color =

I have a spreadsheet that has 3-6 tables in it. I need to use a macro that will change a value from say "30" to "'30" if any of the cells within the row are red. This is because red rows are cancelled reservations; we still need the value and I don't want my other formulas to count that number as a valid number (since it's not an actual reservation.) I've perused many articles with options but usually these articles have a range that is set with a specific table name. Since there are multiple tables, the tables change and I don't know how to make it dynamic. Furthermore, I don't want to have to update the macro each time. If I can circumvent naming the particular tables, my code could work on each sheet (sheets change monthly). One of the issues with my code is that the value I need to change is always in column J. My current code, I imagine, would see the whole row as needing to be changed. I currently get a Run-time error 6: Overflow.
Option Explicit
Sub LoopThroughAllTablesInWorksheet()
Dim tbl As ListObject
Dim Cell As Range
Dim OldValue As Integer
For Each tbl In ActiveSheet.ListObjects
For Each Cell In tbl.DataBodyRange
If Cell.Font.Color = RGB(255, 0, 0) Then
OldValue = Cell.Value
Cell.Value = "'" & OldValue
End If
Next Cell
Next tbl
End Sub
I used the following to help me get where I am: http://www.TheSpreadsheetGuru.com/the-code-vault
https://www.thespreadsheetguru.com/blog/2014/6/20/the-vba-guide-to-listobject-excel-tables
I was able to make this work by changing the line "For Each Cell In tbl.DataBodyRange" to "For Each Cell In tbl.ListColumns(10).Range".

How to change a value when a list object is selected

I have a master list of products and stock counts (see image below), but I want for when a product is selected (on another sheet) for the stock count to be subtracted by 1.
Example data set
What is the best way of doing this? (I tired a bit of VBA but didn't get too far)
Edit:
Here is how my mind would picture it working:
How I picture how it would work
Basic VBA Example.
The Worksheet_Change event needs to be on the sheet that contains the dropdown.
Just check to see if the address of the changed cell is the address of your dropdown. You can use a named range or you can just use a static address as I did below.
I gave the list a named range called Items which we can reference.
Please note that on a separate sheet, you would need to use Sheets("SheetName").Range("Items")
I also used a simple For Each loop to check the values in the list - it may be better if it gets large enough to use a dictionary, variant array, or Find.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$H$4" Then
Dim c
For Each c In Range("Items")
If c = Target.Value Then
c.Offset(0, 1).Value = c.Offset(0, 1).Value - 1
Exit For
End If
Next c
End If
End Sub

How to highlight active row in excel and then return to base background color in VBA

How do you highlight an active row in excel in VBA. and then when another row is selected, return that row to base background color, and highlight the new row.
Also how to clear all rows highlighted, using a clear button on the user form.
so there are tow question here, one to high light and unhighlight active rows, and the other to just clear all high lights by pressing a clear button on the form.
I know I can highlight a row using Ret.EntireRow.Interior.ColorIndex = 6 but i cant find code to unhighlight.
Thanks for your help.
You can use your 'clear all' functionality before changing the color of the row of the cell that you navigated to.
Open the VB Editor and right click --> view code on the worksheet that you want the row highlighting to take place.
Paste in this code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Me.Range("A1:XFD1048576").Interior.ColorIndex = 0
Target.EntireRow.Interior.ColorIndex = 6
End Sub
This code operates as follows: whenever a user changes his or her selected cell(s) on the sheet, the code will first clear the existing highlighting away in the entire sheet and then will apply new highlighting to the row of the target cell the user has moved to.
This line of code:
Worksheets("YourSheetName").Range("A1:XFD1048576").Interior.ColorIndex = 0
Will clear the colors from all cells in the worksheet.
You may want to limit the Range("A1:XFD1048576") to the usable range on your workbook as this will increase performance. On my machine I see a very subtle, but still noticeable, delay in the colors when I move the cells (because I am clearing all cells in the sheet instead of just the ones I want). If you do this, you probably wouldn't want to use the .EntireRow attribute, instead you would have to enumerate how far along the workbook you want the row to be highlighted.
Update
Try this code below, which eliminates the need to clear the entire worksheet. I used .ColorIndex=xlNone instead of setting it to 0 which should preserve your table formatting. I tested in Excel 2010 and I formatted some data as a table, it highlights the correct row and unhighlights the other row as well as leaving the table formatting in tact.
Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)
Static rr
If rr <> "" Then
With Rows(rr).Interior
.ColorIndex = xlNone
End With
End If
r = Selection.Row
rr = r
With Rows(r).Interior
.ColorIndex = 6
.Pattern = xlSolid
End With
End Sub
The trick is using Static. This allows the variable to continue to exist after termination of the procedure, so it remembers the last row it highlighted and then performs the un-highlight action accordingly.
The procedure first checks to see that rr is set, if it is not then it moves on, if it is then rr represents the row that was previously highlighted.
This can be done without changing the base background color,
In 2 steps,
Set up a conditional formatting rule that highlights an entire row if a certain formula is true.
In the formula field, enter this formula:
=OR(CELL("row")=CELL("row",A1))
Write a macro that recalculates the selected cell(s) when a new selection is made.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Target.Calculate
End Sub
Hit Alt + F11 to get back to Excel and you'll have the active cell's row highlighted with the format you chose, without changing the base colors of the cells.
For detailed explanation visit,
highlighted the entire row of the active cell.

Resources