VBA - use a sub or function - vba

I have written a code for a command button in VBA that uses a (column) range as input and has a (column) range as output. I want to use the same code for other command buttons that refer to other columns. I do not want to repeat the entire code, as only the reference to the columns changes.
I cannot figure out how to define this code as a function or sub that I can 'call' in the code for other command buttons which execute the code on columns B, C, D, etc.
This is the code. It removes duplicates and adds the string "rename" to each element of the list:
Private sub rename_column_A_Click()
'copy values of sheet1 column A to active sheet
Range("A1:A30").Value = Worksheets("sheet1").Range("A1:A30").Value
'remove duplicates, keeping first value as column header
Columns("A:A").Select
ActiveSheet.Range("$A$2:$A$30").RemoveDuplicates Columns:=Array(1), _
Header:=xlNo
Range("A" & 2).Select
'add string to each element of list
For i = 2 To 30
If Not Range("A" & i).Value = "" Then
Range("A" & i).Value = "rename " & Range("A" & i).Value
End If
Next i
End Sub

Like John Coleman suggests, you can have your Sub take a Range parameter:
Private Sub rename_column_A_Click()
ProcessRange "A"
End Sub
Private Sub rename_column_B_Click()
ProcessRange "B"
End Sub
Sub ProcessRange(ColAddress As String)
'copy values of sheet1 column A to active sheet
Range(ColAddress & "1:" & ColAddress & "30").Value = Worksheets("sheet1").Range(ColAddress & "1:" & ColAddress & "30").Value
'remove duplicates, keeping first value as column header
ActiveSheet.Range("$" & ColAddress & ":$" & ColAddress & "$30").RemoveDuplicates Columns:=Array(1), Header:=xlNo
'add string to each element of list
For i = 2 To 30
If Not Range(ColAddress & i).Value = "" Then
Range(ColAddress & i).Value = "rename " & Range(ColAddress & i).Value
End If
Next i
End Sub
I removed the two Select lines of code. I don't think you need them.

Related

I am getting this error message. Run Time error '1004' Method 'Range' of object'_Global' Failed

I am trying to copy text values only from column H and move them to E. I want to automate it so that everytime a text value comes to H from sheet1, it directly goes to E instead of H. Leaving H empty in that cell.
Sheets("102Tk").Select
Dim row As Long
For row = 17 To 1000
' Check if "textvalue" appears in the value anywhere.
If WorksheetFunction.IsText(Range("H" & i)) Then
' Copy the value and then blank the source.
Range("E" & i).value = Range("H" & i).value
Range("H" & i).value = ""
End If
Next
End Sub
Should i be row?
Option Explicit
Sub n()
Sheets("102Tk").Select
Dim row As Long
For row = 17 To 1000
' Check if "save" appears in the value anywhere.
If Not IsNumeric(Range("H" & row)) Then
' Copy the value and then blank the source.
Range("E" & row).Value = Range("H" & row).Value
Range("H" & row).Value = ""
End If
Next
End Sub
Which avoiding using row as a variable name and a few other tidies could be:
Option Explicit
Public Sub SetValues()
Dim currentRow As Long
With ThisWorkbook.Worksheets("102Tk") 'taking note of the comments and using worksheet collection to avoid Chart sheets
For currentRow = 17 To 1000
' Check if "save" appears in the value anywhere.
If Not IsNumeric(.Range("H" & currentRow)) Then
' Copy the value and then blank the source.
.Range("E" & currentRow) = .Range("H" & currentRow)
.Range("H" & currentRow) = vbNullString
End If
Next currentRow
End With
End Sub

Excel VBA macro. How to write an absolute cell reference into a cell

I have a workbook with an index as the first sheet. Each subsequent sheet is a dive-log. As I am one of many using this workbook, it needs to be as 'automatic' as possible (because people are lazy)...
Each log has a macro button for 'New Dive'. The macro creates a new sheet, names it with the new sheet number (the dive number) and clears relevant data ready for filling in. Currently the index sheet needs to be filled manually, but it's being ignored.
I have the macro close, but it's the last step that has me stumped. I've tried Activecell.FormulaR1C1 and Cells(r,c) =... gets close, but no piece of pie. I'm also VERY new at this.
Here's my code
Sub Add_links()
'
' Add_links Macro
' Adds links to the index sheet so it 'fills itself in'...
' Each dive is on the row 9 more than the dive number (bear in mind each log is 50 higher than the previous)
Dim Divenumber As Double
Dim Rownumber As Double
Range("I7").Select: Divenumber = ActiveCell.FormulaR1C1
' Make Linenumber the same as Divenumber.
' Do a loop of reducing the Linenumber by 50 until it's in the range 1 to 50.
' Add 9 to that and it becomes the row number of the index sheet
Rownumber = Divenumber
Do
Rownumber = Rownumber - 50
Loop While Rownumber > 50
Rownumber = Rownumber + 9
Worksheets("Dive Index").Activate
Range("A" & Rownumber).Select
ActiveSheet.Hyperlinks.Add Anchor:=Selection, Address:="", SubAddress:= _
"'Dive " & Divenumber & "'!A1"
'Project number (in cell F4)
Range("B" & Rownumber).Select
ActiveCell.FormulaR1C1 = "='Dive " & Divenumber & "'!F4"
'Task(in cell C7)
Range("C" & Rownumber & ":G" & Rownumber).Select
ActiveCell.FormulaR1C1 = "='Dive " & Divenumber & "'!C7"
'Start date (in cell C21)
Range("H" & Rownumber).Select
ActiveCell.FormulaR1C1 = "='Dive " & Divenumber & Chr(39) & "!$C$21"
'Start time (in cell E21)
Range("I" & Rownumber).Select
ActiveCell.FormulaR1C1 = "='Dive " & Divenumber & Chr(39) & "!$E$21"
'End date (in cell F21)
Range("J" & Rownumber & ":K" & Rownumber).Select
ActiveCell.FormulaR1C1 = "='Dive " & Divenumber & Chr(39) & "!$F$21"
'End time (in cell G21)
Range("L" & Rownumber).Select
ActiveCell.FormulaR1C1 = "='Dive " & Divenumber & Chr(39) & "!$G$21"
Sheets("Dive " & Divenumber).Select
Range("A23").Select
End Sub
This one gets me the closest..
ActiveCell.FormulaR1C1 = "='Dive " & Divenumber & "'!F4"
but adds a couple of unwanted ' to the cell.. Looks like this...
='Dive 53'!'F4' (should be ='Dive 53'!F4)
This approach avoids using hyperlinks (which I find hard to maintain) and instead uses the BeforeDoubleClick event of the index sheet to provide equivalent functionality.
This code goes into the code module for the Dive Index worksheet, so that it picks up the double-click event on the index:
Option Explicit
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
'// This function is called whenever the sheet is double-clicked
'// It checks the value of the target (selected) cell, to see if it is a reference
'// to a dive sheet. If so, it activates that sheet
'// Get hold of the contents of the target cell
Dim sTarget As String: sTarget = Target.Cells(1, 1).Value
'// Check if it refers to a dive sheet
If Left(sTarget, 5) = "Dive " Then
Dim wsTarget As Worksheet
On Error Resume Next
'// Try to find the worksheet referred to by the target cell
Set wsTarget = Worksheets(sTarget)
On Error GoTo 0
'// Check that a target sheet was found
If wsTarget Is Nothing Then
Exit Sub
End If
'// Activate the sheet
wsTarget.Activate
'// Cancel the default action for double-click
Cancel = True
End If
End Sub
This is the code for your function Add_Links(). I am not sure how you invoke it, but it works effectively the same as your example, except I have used some simpler techniques.
Option Explicit
Sub Add_links()
'
' Add_links Macro
' Adds links to the index sheet so it 'fills itself in'...
' Each dive is on the row 9 more than the dive number (bear in mind each log is 50 higher than the previous)
Dim Divenumber As Double
Dim Rownumber As Double
Dim wsDive As Worksheet
Set wsDive = ActiveSheet
'// Dive number is in cell I7 of the sheet
Divenumber = wsDive.Range("I7").Value
'// Make sure the sheet name corresponds to the dive number
Dim sDiveName As String
sDiveName = "Dive " & Divenumber
wsDive.Name = sDiveName
'// Calculate the row number for the index entry
'-- -- Rownumber = Divenumber Mod 50 + 9
'// Use below if dive numbers in sheet run from 1 to 50, not 0 to 49
Rownumber = (Divenumber - 1) Mod 50 + 10
'// Get a reference to the index sheet
Dim wsIndex As Worksheet: Set wsIndex = Worksheets("Dive Index")
'// Get a reference to column A in the index entry row
Dim rLink As Range: Set rLink = wsIndex.Range("A" & Rownumber)
'// Put the Dive name into column A
rLink.Value = sDiveName
'// Reference data from the dive sheet into the index sheet ================
'// Project number (in cell F4)
rLink.Offset(0, 1).Formula = ReferenceToCell(wsDive.Range("F4")) '// Index Column B
'// Task(in cell C7)
rLink.Offset(0, 2).Formula = ReferenceToCell(wsDive.Range("C7")) '// Index Column C
'// Start date (in cell C21)
rLink.Offset(0, 7).Formula = ReferenceToCell(wsDive.Range("C21")) '// Index Column H
'// Start time (in cell E21)
rLink.Offset(0, 8).Formula = ReferenceToCell(wsDive.Range("E21")) '// Index Column I
'// End date (in cell F21)
rLink.Offset(0, 9).Formula = ReferenceToCell(wsDive.Range("F21")) '// Index Column J
'// End time (in cell G21)
rLink.Offset(0, 11).Formula = ReferenceToCell(wsDive.Range("G21")) '// Index Column L
End Sub
Private Function ReferenceToCell(rCell As Range) As String
'// This function returns a formula that references the value of the given cell
ReferenceToCell = "='" & rCell.Parent.Name & "'!" & rCell.Cells(1, 1).Address
End Function

VBA Excel replace line breaks in a cell

I would need to replace the line breaks in a cell, with a line break and the content of a cell in the same column of the active cell.
The code would be something like this:
For i = LBound(arColumns) To UBound(arColumns)
'ActiveColumn = arColumns(i)
Set rng = Range(arColumns(i))
For Each Cell In rng.Cells
If Cell.row > 4 And Cell.row < r Then
colnum=cell.column
Cell.value = "{Something}" & Cells(3, colnum).value & _
", text here{/something}" & Cell.value 'First line in the cell
cell.replace what:=vbCrLf, replacement:=vbCrLf & "{Something}" & _
Cells(3, colnum).value & ", text here{/something}" 'First try
Cell.value = Application.WorksheetFunction.Substitute(CStr(Cell.value), vbCrLf, vbCrLf & _
"{maxlen}{/maxlen}{notes}" & ", No Max length{/notes}") 'Second try
End If
Next
Next
I've tried to replace the values of the line breaks with the two methods, replace and substitute. None of them have been working or I am doing something wrong with this block of code.
The array arColumns have the range of columns that I want to work, for example: B:C,E:E,M:O,Z:AB...
along with the vbLf fix you've already been told, you could refactor your code as follows:
Option Explicit
Sub main()
Dim arColumns As Variant
Dim cell As Range
Dim r As Long, i As Long
arColumns = Array("B:C", "E:E", "M:O", "Z:AB")
r = 10 '<--| just to have a value to test with
For i = LBound(arColumns) To UBound(arColumns)
For Each cell In Intersect(Range(arColumns(i)), Rows("4:" & r)).SpecialCells(xlCellTypeConstants) '<--| loop through current columnns group not empty cells from row 4 to 'r'
cell.Replace what:=vbLf, replacement:=vbLf & "{Something}" & Cells(3, cell.Column).Value & ", text here{/something}" 'First try
Next
Next
End Sub

Copy Non Blank Cells From Range to Range

I wonder if you can help me with this:
Ranges B11:B251 & C11:C251 may or may not have some values.
I want to be able to copy non blank cells from cell ranges M11:M251 & N11:N251 to B11:B251 & C11:C251, so if there are any values in M&N ranges they should overwrite values in the same rows in B&C but if there are blank values in M&N ranges they should not be copied and leave the values already present (or not) in B&C.
Was I clear? ;-)
Thanks for any replies!
Sub Main()
Dim i As Long
For i = 11 To 251
If Not IsEmpty(Range("M" & i)) Then _
Range("B" & i) = Range("M" & i)
If Not IsEmpty(Range("N" & i)) Then _
Range("C" & i) = Range("N" & i)
Next i
End Sub
this code will only copy non empty values from M&N columns to B&C
This piece of code should do the trick:
Sub CopyRangeToRange()
Dim CpyFrom As Range
Dim Cell As Range
Set CpyFrom = ActiveSheet.Range("M11:N251")
For Each Cell In CpyFrom
If Cell.Value <> vbNullString Then
Cell.Offset(0, -11).Value = Cell.Value
End If
Next Cell
End Sub

Excel - VBA : loop as long as content of a cell equals specific value

I am trying to modify my code to allow more factors to be taken into account when running a loop. Here is what I have so far, it's a loop running for i = 2 to 605 (because between 2 and 605, my G column's value is always the same "Makati City").
For this loop, I have some actions defined and it's working well. Here is the relevant code showing what my loop is doing :
For i = 2 To lRowBldg
Range("B" & i).Activate
'try to find a match btwn active cell and one of the elements from parsed address
For Each cell In elementsListRange.Cells
If Match(ActiveCell.Value, cell.Value) Then
Range("K" & i).Value = Range("K" & i).Value + 13
Else
Range("K" & i).Value = Range("K" & i).Value + 0
End If
If Match(ActiveCell.Offset(0, 4).Value, cell.Value) Then
Range("K" & i).Value = Range("K" & i).Value + 8
Else
Range("K" & i).Value = Range("K" & i).Value + 0
End If
Next
Next i
But as I plan to make some modifications to this file, I need to rethink my code. So far, I had only this file for one city so basically I could loop from the first to the last value and it was ok. Now, I plan to add more cities : for example, from 2 to 605 (Makati City), from 606 to 900 (blabla City), from 901 to ... and so on.
What I try to do is something like this :
"Loop for as long as the value of the cell in G column is equal to XXXXX (could be Makati City, could be blabla City, whatever)"
And if I mention Makati City, it will loop for i = 2 to 605, if it's blabla City, then it will loop for i = 606 to 900, and so on.
Do you have any idea about how to do this in such a way that wouldn't be too resource consuming as my file could end up being very long ?
Thanks so much in advance !
Use a while?
Here is some Idea....
Sub Test()
dim rng as range
rng = worksheet.find
While rng.Value2 LIKE "Makhati City"
'your logic here
rng.offset(row+1,col+0 or like this)
Wend
End Sub
you could try this, using columns L & N, and avoiding a loop entirely:
put all the code in a standard module then modify the code in setColumnKValues to search for differenct cioy names in column G.
ASSUMPTIONS:
That the differencet cities will be grouped together
that you can use a couple of extra columns (in this case L & N) as intermediates
that you will call setFormulasColumnK in the order of the cities on the sheet
that, based on the code in your question, you want to add a number to column K if the condition is met.
How does it work:
first, in column N we put marker values showing where the city changes
then, in column L we put in a formula, if it matches the city passed in, then L=K+13
finally, we copy paste the new values in column K, and clear columns L & N
Private oLastRange As Range
Private iFirstCell As Integer
Private iLastCell As Integer
Private lLastRow As Long
Sub setFormulasColumnK(ByVal sCity As String)
Dim sFormula As String
Dim oRange As Range
lLastRow = Cells(Rows.Count, Range("G1").Column).End(xlUp).Row
Range("N4:N" & lLastRow).Formula = "=IF(G4<>G5,NA(),"""")"
If Not Range("G:G").Find(sCity) Is Nothing Then
iFirstCell = Range("G:G").Find(sCity).Row
Else
Exit Sub
End If
Set oRange = Range("N" & iFirstCell)
iLastCell = Range("N" & iFirstCell & ":N" & lLastRow).Find("#N/A", oRange).Row
Range("L" & iFirstCell & ":L" & iLastCell).Formula = "=IF(TRIM(G:G)=""" & sCity & """,K:K+13,0)"
Set oLastRange = Range("L" & iLastCell)
End Sub
Sub setColumnKValues()
Set oLastRange = Nothing
Call setFormulasColumnK("Makati City")
'MsgBox oLastRange.Address
Call setFormulasColumnK("London")
'MsgBox oLastRange.Address
Call setFormulasColumnK("Birmingham")
'MsgBox oLastRange.Address
Call setFormulasColumnK("Moscow")
'MsgBox oLastRange.Address
Call setFormulasColumnK("Luxembourg")
'MsgBox oLastRange.Address
Call setFormulasColumnK("Paris")
'MsgBox oLastRange.Address
Range("L4" & ":L" & lLastRow).Copy
Range("K4" & ":K" & lLastRow).PasteSpecial xlPasteValues
Range("N4:N" & lLastRow).Clear
Range("L4" & ":L" & lLastRow).Clear
End Sub