Calling a function from macro - vba

I am trying to use the function: Cells(Rows.Count, 14).End(xlUp).Row to determine the last row with data. This and similar formulas appear in many spots in my VBA code. My spreadsheet is quite complex and I often need to add columns. So rather than referring to Column 14, I'd like to use a value I can easily change in only one place. I am using a named Column of sorts that I only need to update in one place. Items sold appears in column N. I realize that this code is very simple, but I simplified it down for the sake of asking a question. When I try to run the Test sub, I a compile errror: ByRef argument type mismatch and the variable Item_Sold. The reason I am using a letter is that Range() needs a letter, but Cells().End(xlUp).Row needs a number.
It works if I move: Items_Sold = "N" to the sub from the function, but this means this code would have to go in every sub rather than in just one function.
Function:
Function ColNum(ColumnNumber As String) As Integer
Dim Items_Sold As String
Items_Sold = "N"
ColNum = Range(Replace("#:#", "#", ColumnNumber)).Column
End Function
Macro:
Sub Test()
Dim Total_Items_Sold As Integer
Total_Items_Sold = Cells(Rows.Count, ColNum(Items_Sold)).End(xlUp).Row
End Sub

Use a Global constant.
At the top of your module, outside of any Sub/Function:
Const COL_NUM As Long = 14
Usage:
Sub Test()
Dim Total_Items_Sold As Long
Total_Items_Sold = ActiveSheet.Cells(Rows.Count, COL_NUM).End(xlUp).Row
End Sub

Add a global variable at the beginning of the code by writing
public Items_Sold as String
that will solve your ByRef problem. But still you will have problem with undeclared value of Items_Sold when invoking Test() unless you set the value of Items_Sold somewhere before.

Related

Variable not updating passed ByRef

I am using Option Explicit in all modules, this one has me scratching my head, perhaps it is in the call method that things get lost.
Reading worksheets and combining particular data into a new workbook, single worksheet, the variable colData should increment the column and it will be updated and passed back in as many times as there are worksheets (ex. copy months 1-12 for a given year in cols 1-12, then the next year copy months 1-12 in cols 13-24, etc.)
Call to Function returns a Boolean (this is an error check directly afterwards):
'Attempt to load Total Revenue for the Import Sheet
TotRevLoaded = Application.Run("modGetDataHelpers.loadTotRev", wsImport, rng, colData)
Here are the parts of the Function that matter:
'Sub to load total revenue from selected file across the worksheets
Private Function loadTotRev(ByRef wsImport As Worksheet, ByRef rng As Range, ByRef colData As Long) As Boolean
'matchRow is called above and works
Dim k As Integer
For k = 1 To 12
'sets headers for the sheet
wsData.Cells(1, colData) = strYear
wsData.Cells(2, colData) = k
'Copy Total Revenue in to row 3
wsData.Cells(3, colData) = wsImport.Cells(matchRow, 2 + k).Value
colData = colData + 1
Next k
loadTotRev = True 'Success in loading the total rev on sheet
End Function
colData updates in the function as one would expect 1-13, but when leaving the function and coming back for the next worksheet import it is always 1, so the value of colData is not changing at its reference address.
This one should be easy. The variable is being converted to ByVal and once you leave the scope of the function the modifications are lost.
I thought this might be because of the arg wrap in (), Nope. Change to a sub and without assignment you can drop these.
I thought it might be due to the assignment =, Nope. Changed to Sub.
Because it is getting passed to a function, Nope. Changed to a Sub Public and Private.
Because the caller and the modifier are in different modules, Nope
Working on the solution, I don't want a global variable or to bring all helper functions into the same module scope (module global variable).
Great feedback from experienced VBA users, leading to good information.
1) #TimWilliams: Avoid the use of Application.Run to call Private Functions/Subs if you intend to pass parameters ByRef, it will caste these as ByVal.
http://www.tushar-mehta.com/publish_train/xl_vba_cases/1022_ByRef_Argument_with_the_Application_Run_method.shtml
2) #MathieuGuindon: Avoid the use of Application.Run altogether, making the Functions/Subs Public, or investigate the implementation of a class to encapsulate and protect, if truly required look at Option Private Module
I did in fact hit upon the make Public solution through trial and error, thereby dropping the Application.Run call, but the reasoning came later.

How to pass a column of a selected range

I did a little research and I found my question. The problem is it is written for some language called C that I am not familiar with. Not that I am all that familiar with VBA either but that is what I am working in and doing a very nice job of screwing things up. So in an effort to thwart my abilities to screw things up and instead make things work, how do I pass a sub range or column from a user selected column?
so in my example code I was trying to pass the entire first column of whatever was selected. Lets say user range was B34:D40, I want to pass B34:B40 such that arrange is B34:B40.
Sub initialsub (userrange as range)
Call secondfunction (userrange([],1)
end sub
----------------------------------------------------------------
function secondfunction (arrange as range) as long
some function that counts a variety of things in a single column
secondfunction = 45
end function
One way:
Sub dural()
Dim FirstColumn As Range
Set FirstColumn = Intersect(Selection(1).EntireColumn, Selection)
MsgBox FirstColumn.Address
End Sub
Also:
Sub dural()
MsgBox Selection.Columns(1).Address
End Sub
seems to do the trick.

VBA String Function does not update when I change the text

I'm starting with VBA and I'm very upset to discover I have fallen at the first hurdle in this book I am following.
I should type the following
Function Hello() As String
Hello = "Greetings"
End Function
This all works fine but next I am supposed to change the text and see the function change on the Excel spreadsheet. Unfortunately I can't get this to work. Does anyone know why?
I've saved the document as an Excel Macro-enabled workbook and tried opening and closing.
You seem to have this function in the code-file for thisWorkbook, but it should be in a module. Add a module to your project, place your code there and make the function Public. See also: How to Call VBA Function from Excel Cells?
Normally a Function should be in a Module, not in ThisWorkbook. You store Event handlers in the ThisWorkbook or a Sheet module.
It doesn't recalculate because it doesn't have a Range input, since the function doesn’t have any arguments, it is treated constant output and hence updating the function doesn’t update the cell value.
But if you modify it to accept a Range input, and if there are any changes to the input range, it will recalculate.
declare your function right
Function Hello(str As String) As String
then use something like
cells(1,1).value=str
function does the job for you but first you must call the function by parameter like this
cells(1,1).value=Hello("How are you")
the result will be that in cell 1,1 will "How are you" be written.
but from this on I am not sure what are you trying to accomplish. If you need funct. to write in specific cell all the time you should use something like
Funtion Hello(row as integer, column as integer) as string
cells(row,column).value=inputbox("give me the input")
end function
then to use this you write into code
result=Hello(1,2)
this example works
Function Area(row As Integer, column As Integer) As String
Cells(row, column).Value = InputBox("give something here")
End Function
Sub my()
result = Area(2, 2)
End Sub
run my()
Ok I worked it out, I had written the function into the file "This Workbook" instead of into the Module I created...
I knew it would be something simple! All working now.
Function Hello() As String
Application.Volatile
Hello = "Greetings"
End Function

User-defined Function to change color of a cell

I've seen many users asking questions trying to change the colors of cells using User-defined functions. I was always under the impression that it was not possible to do so. My understanding was that a user-defined function cannot change any properties of a cell except the value of the cell that contains the formula. Subs are what change cells themselves.
However, when playing around with some code to test this, I found that it's not always the case.
Using the simple code:
Function ColorCell(rng As Range)
If rng.Value = 1 Then
ColorCell = False
Else
ColorCell = True
rng.Interior.ColorIndex = 3
End If
End Function
If I enter the function into a cell, I achieve expected results, no cells change colors. However, if I use the Formulas > Insert Function button and navigate to my formula to insert it this way, it does color the targeted cells.
How is this possible, and why did the function behave differently when entered in different ways?
EDIT: this was tested using Excel 2007
use this code...just replace sheet name and try
Sheets("sheet_name").range(j:j).clear
for j=2 to 15
if Sheets("sheet_name").Cells(j, 1).value=1 then
else
Sheets("sheet_name").Cells(j, 1).Interior.ColorIndex = 3
next j
As we all find out sooner or later, in user functions you can't access subs that change things in your spreadsheet directly.
But try this:
Dim ColorMeTarget As Range, ColorMeVal As Long
Public Function ColorMe(ByVal TargetRange As Range, ByVal ColVal As Long)
Set ColorMeTarget = TargetRange
ColorMeVal = ColVal
ColorMe = ColVal
End Function
Public Sub ColorMeSub()
Application.OnTime Now + TimeValue("00:00:05"), "ColorMeSub"
If ColorMeTarget.Interior.Color <> ColorMeVal Then ColorMeTarget.Interior.Color = ColorMeVal
End Sub
If you run the sub first, it will constantly scan the static variables ColorMeTarget and ColorMeVal to see if there is a change. The function ColorMe will set these values. Some additional code is needed in case ColorMeTarget is not yet initialized.
If you get smarter, you could have the function first check to see if there is indeed a change and add the new coloring requests to a stack. Your reoccurring sub can then 'catch up', especially if you have many functions like this.
You can then even have all kinds of additional controls added to your function/macro--EVEN STUFF NOT COVERED BY THE LATEST VERSIONS OF 'CONDITIONAL FORMATING'!!! YAY!!!!
Something to try: In some of my automated macros, I am able to set OnTime through a function but cannot make it work here. It would be cleaner to have the function set the OnTime and not have a reoccuring sub that needs initializing.
I use Worksheet_Change event to detect value change in working range. Example.I want to do something when range A1:A5 has been change. I use below event.
Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range(<Your working range>)) Is Nothing Then 'Define range that you want to do
'Statement here
...
End If
End Sub
When the range value has been change. It will execute your code.
And other way. Use Conditional Formatting.
could also try the non-script method of auto-coloring a cell based on the condition of the cell (aka Conditional Formatting):

Highlight and unhighlight specific ranges when a box is clicked

I am a beginning in visual basic. What I am trying to do is whenever a box is clicked, highlight a specific range. Then, if another box is clicked after that, the previous range will unhighlight, and another range will highlight. Here is my code but it doesn't work right now.
Dim FSelect As Boolean
Dim myRange As Range
Sub Rectangle18_Click()
If FSelect Then
UnhighlightBox (myRange) <---error - runtime error "424" object required
End If
Range("C9:D9").Select
HighlightBox
FSelect = True
Set myRange = Range("C9:D9")
End Sub
Sub Rectangle19_Click()
If FSelect Then
UnhighlightBox (myRange)
End If
Range("C11:D11").Select
HighlightBox
FSelect = True
Set myRange = Range("C11:D11")
End Sub
Sub HighlightBox()
Selection.Interior.ColorIndex = 36
End Sub
Sub UnhighlightBox(cellRng As Range)
cellRng.Interior.ColorIndex = 2
End Sub
When I throw this code into excel it complains about Select. I don't think you can use Select as a variable...
EDIT: Select is a reserved keyword in VB/A, It begins the Select Case block.
Putting the parentheses around the argument when calling the UnhighlightBox procedure is incorrect.
Two possible correct forms:
UnhighlightBox myRange
Call UnhighlightBox(myRange)
I find the first form (without the Call keyword) to be preferable
For the Excel 2003 help:
You are not required to use the Call
keyword when calling a procedure.
However, if you use the Call keyword
to call a procedure that requires
arguments, argumentlist must be
enclosed in parentheses. If you omit
the Call keyword, you also must omit
the parentheses around argumentlist.
Please note that this does not apply to a function which returns a value. A function needs to be called as part of an assignment (e.g. a = f(x)) and the arguments must be enclosed in parentheses
Your use of the FSelect boolean (which initialises to false) should be preventing the problem with calling UnhighlightBox before myRange is ever set.
When using the UnhighlightBox routine you either need to put the Call statement before hand or remove the parenthesis.
For example:
Call UnhighlightBox (myRange)
or
UnhighlightBox myRange
For more info on why to use Call, etc see the either of the following:
what-does-the-call-keyword-do-in-vb6
MSDN