I create a VB program to automatically update a Gantt chart for a group project. But now the team wants to add a new colum. The problem is that adding a new column will change my code and make it unusable. Rows can be added without changing the code, but I would have to update all my code if new colums are added. How can I add a column without chaning my VB code?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim StartDate_Row As Integer
Dim Total_Weeks As Integer
Dim Date_Week_Column As Integer
Dim Number_of_Weeks As Integer
Dim Date_Week_Column_Color As Integer
StartDate_Row = 10
Date_Week_Column = 8
Range("H9:AN25").Interior.Color = xlNone
Do
For Total_Weeks = 1 To 33
If Cells(StartDate_Row, 5).Value = Cells(8, Date_Week_Column).Value Then
Date_Week_Column_Color = Date_Week_Column
For Number_of_Weeks = 1 To Cells(StartDate_Row, 6).Value
If Cells(StartDate_Row, 7).Value = 25 Then
Cells(StartDate_Row, Date_Week_Column_Color).Interior.Color = RGB(204, 255, 299)
End If
If Cells(StartDate_Row, 7).Value = 50 Then
Cells(StartDate_Row, Date_Week_Column_Color).Interior.Color = RGB(153, 255, 204)
End If
If Cells(StartDate_Row, 7).Value = 75 Then
Cells(StartDate_Row, Date_Week_Column_Color).Interior.Color = RGB(102, 255, 178)
End If
If Cells(StartDate_Row, 7).Value = 100 Then
Cells(StartDate_Row, Date_Week_Column_Color).Interior.Color = RGB(50, 200, 100)
End If
If Cells(StartDate_Row, 7).Value = 0 Then
Cells(StartDate_Row, Date_Week_Column_Color).Interior.Color = RGB(149, 179, 215)
End If
Date_Week_Column_Color = Date_Week_Column_Color + 1
Next Number_of_Weeks
End If
Date_Week_Column = Date_Week_Column + 1
Next Total_Weeks
Date_Week_Column = 8
StartDate_Row = StartDate_Row + 1
Loop While (Not IsEmpty(Cells(StartDate_Row, 5)))
End Sub
Tom's suggestion is a possibility but is a lot of bother for the user for every run of your macro.
Possible technique 1
I never refer to columns or rows by numbers for two reasons:
Columns and rows may move as you have discovered.
Someone reading your code must know what column 5 or row 6 means.
It is better to use constants. Fo example:
Const ColXxxx As Long = 5
Const RowYyyy As Long = 8
If Cells(StartDate_Row, ColXxxx).Value = Cells(RowYyyy, Date_Week_Column).Value Then
I do not know what your rows and columns are so I have used ColXxxx and RowYyyy as names. You would replace my names with names that tells the reader what the row and column are.
Code like this takes a little longer to write but (1) it is self documenting and (2) if a column or row moves, you only need to change the Const statement to fix the problem.
Note: I have used data type Long. Data type Integer defines a 16-bit variable which requires special (slow) processing on 32-bit and 64-bit computers.
Possible technique 2
Technique 1 requires the user to tell the programmer that they want to add a column or move a row. If they forget to tell the programmer before runnibg the macro with an amended worksheet, the macro may damage the worksheet beyond repair.
Another technique is to search row 1 for the know column headings and record where there are. Pehaps you have a column heading "Start Date". "Start Date" can be in column 5 for one run of the macro and in column 6 for the next and you code will work just as it should.
If this technique is interesting, I will add example code.
The answer in short is that you must change your code. This becomes a bigger and bigger issue the larger and more complicated your code becomes. Therefore where possible try to think dynamically about the methods you use to specify cell locations.
E.g. You could build into your script a user input which allows the user to specify the column which contains the required information. That way if the user adds more columns to the table, the script will still run correctly (provided the user selects the correct column).
'ask user to select the column containing the data which they would like to utilise
Dim colRng As Range
On Error Resume Next
Set colRng = Application.InputBox("Select a cell from anywhere in the column which contains the programme start dates.", Default:="", Type:=8)
On Error GoTo 0
If colRng Is Nothing Then
'notify user that the process cannot continue as selection was invalid
MsgBox "You must select a cell to enable this process to continue!"
'exit the sub
Exit Sub
End If
'output the column number (as selected by the user)
debug.print colRng.column
Hope that helps!
If you define a named range on your worksheet (using Formulas > Define Name), that name will be updated to point to the same cell(s) even if rows and columns are inserted or deleted.
In your code you can use (for example) MySheet.[MyRangeName].Row to get the row number of the first row of the range named MyRangeName, and so on.
Related
I am trying to create a dynamic named range that I can use for a data validation list. I use these all time but in this case I have information that is housed below the range that cannot be counted in the range. Also, I have a macro that insert rows within this range that do need to be counted.
I normally would use something like this if nothing else was in the column: =OFFSET($A$1,0,0,COUNTA($A:$A),1)
I need to start this one down the page a little ways so I used:
=OFFSET($A$24,0,0,COUNTA($A$24:$A24),1)
Notice I have removed the "$" before the last "24" in the formula hoping it would expand accordingly, but that does not seem to be consistent.
Basically, I need the COUNTA range to only include a range of cells that will always be growing and shrinking.
I'm not bad in VBA and am open to a solution that might include looping through a range of cells and stopping once it reaches a cell that's value equals a certain text string (in the case in would be .Value = "Request 1"). But I am a little apprehensive about feeding a form or ActiveX Control, as this has caused me issues in the past with viewing and printing functionality.
I used a the following code to create a range elsewhere in the workbook that I could then easily use to create a dynamic named range:
Sub UpdateEntities()
Dim i As Long, x As Long
i = 24
x = 1
Sheets("Values").Range("AH:AH").ClearContents
Do While Cells(i, 1).Value <> "REQUEST 1"
Cells(i, 1).Select
If ActiveCell.Value <> "" Then
Sheets("Values").Cells(x, 34).Value = ActiveCell.Value
i = i + 1
x = x + 1
Else
i = i + 1
End If
Loop
End Sub
I have two columns with random times and the times come from two different sources so the columns do not have the same amount of data points. I want to start with the first time in the first column and compare it to each time in the second column. If there is a match in times, I would like to pull relevant data. After a match is found (if there is one) I would like for the code to go to the second cell in the first column and compare it to every value in the second column and so on.
Here is the code I have so far:
Sub TransferInfo()
'Activate the Sub to Convert and Format Dates
Call ConvertDates
'Define Variables
Dim st As Worksheet
Dim ts As Worksheet
Dim lastrow As Long
Dim i As Integer
j = 2
'Find and set the last used row
Set st = ThisWorkbook.Worksheets("Data Table")
lastrow = st.Cells(st.Rows.Count, "B").End(xlUp).Row
Set ts = ThisWorkbook.Worksheets("ShopFloor")
'Cycle through/compare Row J, Column 18 based on each cell in Row I, Column 14
For i = 2 To lastrow
Do Until IsEmpty(ts.Cells(j, 8)) Or IsEmpty(st.Cells(j, 2))
If st.Cells(i, 14).Value = ts.Cells(j, 18).Value Then
st.Cells(i, 15).Value = ts.Cells(j, 2).Value
Exit Do
Else
st.Cells(i, 15).Value = ""
End If
j = j + 1
Loop
j = 2
Next i
End Sub
The other sub that I call at the beginning of this sub simply rounds the times in each column to the nearest 15 minute interval to increase the likelihood of matches between the columns.
My question is: The code does not copy and paste any more information although there are times that match between the two columns. Why would the code that I have not work? Also, with larger data sets I am afraid that this the code may crash Excel and because I have a loop within a loop trying to process a lot of data a lot of times, but I don't know of a more efficient way to accomplish what I am trying to without this code.
If anyone has any insights as to why this code doesn't work I would greatly appreciate any help.
Thanks!
Based on your code, it looks like you just need an INDEX/MATCH formula. Use this in O2 and copy down:
=IFERROR(INDEX(B:B,MATCH(N2,R:R,0)),"")
No need for VBA
I should note that there are related solutions to my question online but I've been unable to implement them into my own situation.
We have an .mdb database of all the products that we make. I've managed to take two criteria (Order type and Box), and print all records containing those two criteria to Excel. What I need in addition to that now is to print 30 boxes in one go as a basis for a bigger template. The labeling of these boxes usually increment (e.g. P1, P2...P30), and I'm struggling to see how I can increment the numeric portion of it to fit it into my code. Ideally, I'd like for the user to input the first and last box numbers in excel to represent the entire range (P1 and P30) and use those two values.
Sub Dan()
Dim order As String
Dim title As String 'initialize title
Dim palette As String 'intialize comment
Dim finalpalette As String
Dim finalrow As Integer 'initialize bottom-most row
Dim i As Integer
Dim Cntr As Integer
Dim LR As Integer
'Clears the contents of the last macro run
With Sheets("ALL.txt")
.Range(.Cells(6, 2), .Cells(725, 8)).ClearContents 'equates to (D2:F26)/ row, column ;Erase Columns for next macro
End With
title = Sheets("Sheet2").Range("B1").Value
palette = Sheets("Sheet2").Range("B2").Value
finalrow = Sheets("Sheet1").Range("A2").End(xlDown).Row
For i = 3 To finalrow
If Cells(i, 1) = title And Cells(i, 2) = palette Then
Cells(i, 5).Copy 'Copy ID
Sheets("ALL.txt").Range("B734").End(xlUp).Offset(1, 0).PasteSpecial
Range(Cells(i, 11), Cells(i, 14)).Copy
Sheets("ALL.txt").Range("C734").End(xlUp).Offset(1, 0).PasteSpecial
Range(Cells(i, 9), Cells(i, 10)).Copy
Sheets("ALL.txt").Range("G734").End(xlUp).Offset(1, 0).PasteSpecial
End If
Next i
End Sub
The variable I'm looking to adjust is 'palette'. I originally used it to match records to one Box (P1). What I need is to able to match records from 30 boxes (P1 to P30) in the loop. The variable 'palette' is just taking the static value of whatever is in cell B2 at the moment. I'm thinking there should be some way to type the first and last box into two cells to establish a range for the macro to iterate, or to write all the box numbers into a column and have 'palette' move down a cell each loop to take in a new Box value.
In an attempt to grab data from a column that has all 30 boxes written into 30 cells, I tried the following line of code
End If
palette = Sheets("Sheet2").Range("B2").Offset(, 1)
Next i
but it does not seem to be grabbing any value. It should be grabbing values from cells B2 to B31.
Here is some code that I changed (still no clue as to why you're breaking this up into 3 parts, seems like excel VBA is an extra step that complicates it).
thisworkbook.worksheets(1).cells(i,5) Use full references when learning VBA
let me know if this works, I don't know enough about your situation to know exactly what you need, other than what I can see you're trying to do.
Sub Dan()
Dim Order As String
Dim Title As String 'initialize title
Dim Palette As String 'intialize comment
Dim Fpalette As String
Dim Frow As Integer 'initialize bottom-most row
Dim i As Integer
Dim Cntr As Integer
Dim LR As Integer
Dim wsALL As Worksheet
'Clears the contents of the last macro run
With Sheets("ALL.txt")
.Range(.Cells(6, 2), .Cells(725, 8)).ClearContents 'equates to (D2:F26)/ row, column ;Erase Columns for next macro
End With
Title = Sheets("Sheet2").Range("B1").Value
Palette = Sheets("Sheet2").Range("B2").Value
Frow = Sheets("Sheet1").Range("A2").End(xlDown).Row
Set wsALL = Sheets("ALL.txt")
i = 2
Do While i < Frow
i = i + 1
If ThisWorkbook.Worksheets("Sheet1").Cells(i, 1) = Title And ThisWorkbook.Worksheets("Sheet1").Cells(i, 2) = Palette Then
Sheets("Sheet1").Cells(i, 5).Copy Destination:=wsALL.Range("B734").End(xlUp).Offset(1, 0)
'wsALL.Range("B734").End(xlUp).Offset(1, 0).PasteSpecial
Sheets("Sheet1").Range(Cells(i, 11), Cells(i, 14)).Copy Destination:=wsALL.Range("C734").End(xlUp).Offset(1, 0)
'wsALL.Range("C734").End(xlUp).Offset(1, 0).PasteSpecial
Sheets("Sheet1").Range(Cells(i, 9), Cells(i, 10)).Copy Destination:=wsALL.Range("G734").End(xlUp).Offset(1, 0)
'wsALL.Range("G734").End(xlUp).Offset(1, 0).PasteSpecial
End If
Loop
End Sub
Ignore the Below, I was going to make this way more complicated than necessary. Looking at your code, be sure to reference using
Hi Joshua,
I'm not sure I completely understand what you're trying to accomplish, adding in more details such as the first macro may help in getting you a specific answer. I think possibly VBA in Excel may not be the best way. A VBA in Access sounds possible solution. But this may be of help to you.
I know you said for an end user, It would be much more complicated on your part but I've had great success using microsoft query to import data, with the correct ODBC driver "Access Database Engine" http://www.microsoft.com/en-us/download/details.aspx?id=13255 it works great now and I use it to get data from flat files then send it to SQL based on a query, but I fought with it to get it to work you will rip your hair out and it wouldn't be portable to an end user
Having a user enter a value into a specific cell could work, i.e. put a value in A1 and VBA can check that value using:
Alpha = Cells(1,1).Value
pStart = Cells(2,1).Value 'A2
pEnd = pStart + 30
In order to prevent any issues with spaces this could be done as:
set pStart = Trim(ActiveCell(2,1).Value)
Or another way is to use data validation and give users a drop down list. https://support.office.com/en-ca/article/Create-or-remove-a-drop-down-list-5a598f31-68f9-4db7-b65e-58bb342132f7
Here is the code if for either way. Notice I've made some edits, most are not essential changes, just how I write VBA. When you use the copy -> paste command it avoids the clipboard if you say .Copy Destination:= Another comment, this would be so easy in Access simply write an SQL statement and use the append feature. You say that you have a macro before this, and after this, I would say make it one (very powerful and nice) SQL statement what is run through a user form.
I need to subtract two columns from a large array and see which ones are positive and of those positive values I need to find the positive values row and append a few things onto that value.
Here is the general concept I'm thinking so far
While < 8000
if (cell(i,1).Value - cell(i,2) > 0)
print in another sheet cell(i,3).value (cell(i,2).Value-cell(i,4)) cell.value(i,4)
for example...
suppose I have something like this
[2 2 hi yo]
[3 2 go mo]
this macro would return "go 1 mo" in another sheet.
Sub Leaves()
Dim i As Integer
Dim g As Integer
Dim Quantity As Integer
Dim Executed As Integer
Dim Leaves As Integer
i = 1
g = 1
Do While i < 8000
Quantity = Worksheets("Sheet1").Cells(i, 3).value
Executed = Worksheets("Sheet1").Cells(i, 5).value
Leaves = Quantity - Executed
If Leaves > 0 Then
Worksheets("Sheet2").Cells(g, 1).value = _
Worksheets("Sheet1").Cells(i, 9).value & _
Worksheets("Sheet2").Cells(i, 2).value & _
Leaves & Worksheets("Sheet2").Cells(i, 3).value
g = g + 1
End If
i = i + 1
Loop
End Sub
The above code gives me a Type mismatch error.
It is helpful if you say what line is throwing an error. Also, one should strive to create a Minimal, complete, and verifiable example. The effort to do so often resolves the question before you need to post it.
The code itself seems fine and it runs for me (on an empty workbook) with no type mismatch. Thus, the problem must be with your assumptions about the spreadsheet.
Either of the lines
Quantity = Worksheets("Sheet1").Cells(i, 3).value
Executed = Worksheets("Sheet1").Cells(i, 5).value
will trigger a type mismatch if the corresponding value can't be converted to an integer. This could happen, for example, if one of the cells contains a string (other than something like "1") or an error value such as #N/A or #Value!.
The line which begins
Worksheets("Sheet2").Cells(g, 1).value = _
will throw a type mismatch if one of the values being concatenated can't be converted to a string. An error value in one of the cells is the most likely culprit. If this is the case and for some reason you actually want to create a string that includes substrings which look like e.g. "#N/A" then you could use the Text property of those cells rather than Value.
I am repeatedly performing an action on multiple columns, and would like to eliminate my redundant code. I am posting the code for the first two columns as I believe that is enough to demonstrate what I am doing, but the code is repeated for a total of 16 columns (Column E - Column T).
OldplayerRosterLocation with the offset is basically a "vba vlookup" for the old player to find where they are on the sheet so the proper row stats are modified as needed. It works, but I would like to reduce the redundant code.
'Below determines what weeks old player has already played.
'First part replaces team win/loss for that week as a value instead of
'formula so second part does not ruin sheet.
If Range("E61") = "1" Then 'Wk#1
Range("E42").Value = Range("E62")
Range("E43").Value = Range("E63")
'Second part clears weekly results for new player each weeks that the
'old player has already played.
Range(OldPlayerRosterLocation).Offset(0, 3).ClearContents
Range(OldPlayerRosterLocation).Offset(1, 3).ClearContents
Range(OldPlayerRosterLocation).Offset(2, 3).ClearContents
End If
If Range("F61") = "1" Then 'WK#2
Range("F42").Value = Range("F62")
Range("F43").Value = Range("F63")
Range(OldPlayerRosterLocation).Offset(0, 4).ClearContents
Range(OldPlayerRosterLocation).Offset(1, 4).ClearContents
Range(OldPlayerRosterLocation).Offset(2, 4).ClearContents
End If
How can I simplify this code?
I'd be sure you could use this for your 16 columns:
Dim c As Range
For Each c In Range("E61:T61")
If c = "1" Then
c.Offset(-19, 0).Value = c.Offset(1, 0).Value
c.Offset(-18, 0).Value = c.Offset(2, 0).Value
For j = 0 To 2
Range(OldPlayerRosterLocation).Offset(j, c.Column - 2).ClearContents
Next
End If
Next
c is a range object (a cell in this case). So we use For Each ... In instead of For ... To. c.Column gives the column number of c. When we subtract 2, we get the number of columns to Offset, where you want to ClearContents.
Use the Resize() function
Range(OldPlayerRosterLocation).Offset(0,3).Resize(3,1).ClearContents
...
Range(OldPlayerRosterLocation).Offset(0,4).Resize(3,1).ClearContents
It takes a single cell and creates a range spanning 3 rows and 1 column. Also commonly used for fast value transfers. For example:
Range("B1").Resize(100,1).Value = Range("A1").Resize(100,1).Value
copies 100 rows from A1 into B1. For you, I proposed the following style changes:
' Old Code
'Range("E42").Value = Range("E62")
'Range("E43").Value = Range("E63")
' New Code
Range("E42").Resize(2,1).Value = Range("E62").Resize(2,1).Value