ByVal in VBA Events - vba

I am trying to gain some ground on VBA
I know this is pretty basic but in a Worksheet Event such as Change
Private Sub Worksheet_Change(ByVal Target as Range)
Target.Font.ColorIndex = 5
End Sub
I 've searched what ByVal means in this link
And I cannot get out a meaning out of this explanation in the help
Specifies that an argument is passed in such a way that the called procedure or property cannot change the value of a variable underlying the argument in the calling code.
Witch really confused me...
I also searched for it in various sources and found this different explanation which plunged me deeper into the darkness...it goes as:
If you don’t want the called procedure to modify any variables passed as arguments, you can modify the called procedure’s argument list so that arguments are passed to it by value rather than by reference. To do so, precede the argument with the ByVal keyword. This technique causes the called routine to work with a copy of the passed variable’s data — not the data itself.
What does he mean "that arguments are passed to it by value rather than by reference"?
Can you explain me in a simple way what ByVal does, i constantly use it when i code and
I want to know every bit of code i am writing.

Here is an extremely good website with examples that explains the difference between by value, and by reference:
http://www.physics.nyu.edu/grierlab/idl_html_help/procedures12.html
Let me know if it helps!
If not, I can explain it in more depths, but I think the website adequately covers the topic.
EDIT:
By value means that it is set. You cannot call this value again in another function. By reference means that this value can be called again. So in the example, PRO ADD, A, B A = A + B RETURN END
A is passed by reference, as the user calls the A again, and it is returned. However, B is called by value. In the following example, ADD, A, 4 works. A is defined as A = A + B. So the user calls A again, and adds B to it. However, ADD, 4, A doesn't work since 4 is a value. so 4 = A + B would produce an error. Of course, this can be applied to VBA as well.
Just to add, the reason why this distinction matters is from 2 main standpoints:
Protection: In choosing between the two passing mechanisms, the most important criterion is protecting the variable from unwated changes. When calling ByRef, the procedure can return a value to the calling code through that argument. In other words, it can reference the same variable again. ByVal protects the variable from being changed by the procedure. So for example, if you want the variable to = 4 no matter what, you would use ByVal.
Performance: The passing mechanism can affect performance, but it is usually insignificant. One exception to this is a value type passed ByVal. In this case, Visual Basic copies the entire data contents of the argument. Therefore, for a large value type such as a structure, it can be more efficient to pass it ByRef.
Sorry for the long answer, hope it helps.

Related

How to make Listbox.List keep type information?

If I affect a full array to a ListBox, using ListBox1.List = [{1;2;3;4}] for example, the Listbox's .List items keep their correct type (here numbers), but if I use ListBox1.AddItem 5 or Listbox1.List(5) = 6 to set an individual item the type is automatically changed to String.
Sample Code:
Private Sub UserForm_Initialize()
ListBox1.List = [{1;2;3;4}]
ListBox1.AddItem 5
ListBox1.AddItem
ListBox1.List(5) = 6
End Sub
Later on, when comparing values, I get wrong results because numbers are not equals to text (5 <>"5").
Is there any easy (1) way to ensure the type of the list items is not converted to String?
(1) I know I can explicitly make the conversion to String, but I rather keep my values as numbers instead of "numbers-strored-as-text" in the listbox
I guess that will be impossible when using AddItem. According to https://learn.microsoft.com/en-us/office/vba/api/access.listbox.additem, the first parameter is a string, so everything you pass will be converted to a string.
Probably your best bet is to collect all items in an array and assign the array using ListBox1.List. Or you have to live with numbers stored as string...
Update
I mixed the pages up - link to the Access page was wrong.
Anyhow, Documentation is rather poor. It says "valid object" but doesn't define what that means. At least it is not possible to add a object - that throws a type mismatch.
Also, the documentation states that a Variant is returned, but it seems thatis not true - when I try to get the result, the compiler throws an error.
As a conclusion, I assume that AddItem converts everything to a string (and throws an error if that fails). So I still assume that you have to build up an array and assign it to List if you want to have real numbers.

Proper procedure for declaring global variables in VBA?

I have a macro workbook with a number of worksheets that exist permanently, which are constantly cleared, updated, etc. Since they are referred to in various subroutines, I have made each corresponding worksheet object a pseudo-global variable in the following manner, for example for the "Main" sheet:
Function MAIN() As Worksheet
Set MAIN = ThisWorkbook.Sheets("Main")
End Function
By doing so, I can then refer to each sheet in the other subroutines, for example:
MAIN.Cells.ClearContents
I have also defined some pseudo-global constants which are located in a fixed place on the "Main" sheet in a similar way, for example:
Function NumLines() As Integer
NumLines = MAIN.Range("C3").Value
End Function
In this way, I use "NumLines" just like any variable throughout the code.
I expect that there is a more efficient way to manage globally accessed variables like these and was wondering, what would be a better way to accomplish this?
For reliable sheet reference I would suggest to use Sheet.CodeName Property. Each sheet has its unique CodeName which you could find in the place marked yellow on the picture below.
For quick reference to cell value I would suggest to use Range name. After you select you C3 cell you need to put unique name in the box marked yellow below. All Range names are unique in the workbook.
As a result you can use sheet and cell reference as presented below in each of your subroutines in your project.
Sub Test_Macro()
Debug.Print MAIN.Name '>> result: Sheet1
Debug.Print Range("CellC3").Value '>> result: 100
End Sub
I expect that there is a more efficient way to manage globally accessed variables like these and was wondering, what would be a better way to accomplish this?
When I use global variables in VBA, I do three things.
I always preface global variables with a g_ prefix. It seems often that a global variable in VBA is useful. But I've also spent far too long trying to track down "what variables are global or not?" in other people's code. Keeping a very clear naming convention will save you and whoever looks at your code a TON of hassle in the future.
This is even more important if you are less experienced as a developer. Avoiding globals is hard in VBA, and the less experience you have, the more likely it is you will use globals. For others to help or maintain the code this becomes so important.
If you are going to be using even a small number of global variables, you must use Option Explicit unless you want to cause nightmares in maintaining code. It's hard enough to track down these errors when you wrote code let alone months or years later.
I always create a module which is called "GlobalVariables" or something similar. That module contains all of the global declarations in one location. For larger code bases this can become longer but it has always paid off for me because I know exactly where all my globals are defined. None of the "which file is this variable actually being defined in?" game.
Just an unrelated note, too, in your first example - I would use the code name rather than that function. Each VBA worksheet has a sheet name ("Main" in your case) as well as a codename, which you can set in VBA and remains the same. This prevents users from changing the name of "Main" and breaking code.
You can also refer directly to them similar to how you are using MAIN.Cells. KazJaw has a good example of this.

SSRS custom code and variables life

I have a report that needs to process the data that it get from SQL before show it.
For that, I have a custom code, and a Dictionary where I push all the processed data.
My problem is that if I save the dictionary in a report variable when I export the report to Word that variable seems to be cleaned.
What is the lifecycle of the reports variables? What is the most convenient way of saving an object during the report life.
Thanks!
I have been playing around with custom code for a about 6 weeks so I can answer some parts of the question of variable lifecycle in SSRS 2008 R2.
I have report that uses a Dictionary to store totals, allows me provide some specialist subtotals for financial stuff. I have something you can check (as I can't yet comment on things).
Have you declared the variable as 'shared', this is a custom code specific keyword that doesn't translate into VB.net. It ensures the variable lives to the next page, I tested this to Excel and word seemed to work fine transferring over the variable's data.
There is a trade off however under SSRS "report on demand" engine (on web, but not on BIDS) it holds the variable and doesn't garbage collect until the cache itself is cleared. I wrote some more custom code to indicate when my parameters changed and clears the variable.
Code;
Public Shared Dim Totals As New System.Collections.Generic.Dictionary(Of String, Decimal)
Public Function WipeKeys() as Decimal 'Clear Data from Dictionary (this will clear the cached object as well)
Totals.Clear()
Return 0D
End Function
I will also to recommend overwrite the key where ever possible to ensure reduction of addition loops.
Regards,

Purpose of using sub routines over functions

I've been working with Access for a while now, and although I understand the obvious benefit of a Function over a Sub, been that it can return values as a result, I'm not sure as to why I should use a Sub over a Function. After all unless I'm mistaken; Functions can do everything Subs can do?
Note: I'm fully aware of how to use both Sub's and Function's so not looking for an explanation of how they work.
In terms of performance, this would not be any significant issue here.
The main difference is that user defined function can be used in expression in your code, where as a sub cannot.
This is really a HUGE Mount Everest of a difference here.
This difference is not really limited to Access, but tends to applies to every programing language and system I can think of that supports the creating of user defined functions.
The key advantage of using defined function are MANY but the most basic issue is that such function can be used in EXPRESSIONS.
For example, in an on click setting for a button on a form, you can generally have a single VBA [Event Code] routine attached to that button.
However you can ALSO place an expression in the property sheet like this:
=MyUserFunction()
The above is a handy tip, since then you can highlight 10 controls on a form, and type in the above expression and you just assigned the above function to those 10 buttons. You cannot do the above with a sub.
Another significant difference is you can use a function as a data source (expression) for a text box on a form or report (again you cannot do this with a sub).
Another significant difference is you can utilize these functions in SQL. This is a truly fantastic ability as then you can have code "run" for each row of a query. And this means you can extend the ability and functionally of SQL.
And you can even use this idea to display a VBA variable in a sql query as you simply build a public function that returns the VBA variable and this can be used in a query – you cannot however use VBA variables in a query!
And this extending of SQL opens up endless ideas:
So I can build a public function called ToMorrow()
Public Function Tomorrow() as date
Tomorrow() = date() + 1
End Function.
Now in the query builder, I can go:
Select FirstName, lastName, Tomorrow() as NextDay from tblCustomers
And you can even make custom conversions such as:
Select FirstName, LastName, Celsius([DailyGreenHouseTemp]) from tblGreenHouse.
The above Daily temperature reading could in in Fahrenheit and you simply have to define a public function called Celsius like this:
Public Function Celsius(Temperature As Variant) As Variant
Celsius = (Temperature * 1.8) + 32
End Function
Now while the above function is simple, it could do complex record set processing a complex algorithm to determine the moisture above a flower pot based on temperature and humidity.
So once we define such a public function, then the key concept is such a function can be used not only in VBA code as an expression, but ALSO can be used amazing enough this ability includes SQL.
So even in code, you can go:
If MyCustomfucntion(SomeVar) = lngTestValue then
Again in the above, you cannot use a sub in VBA expressions.
And even more interesting is when using custom XML for ribbons in Access, then if you use a function() expression for the "on action" attribute then you can avoid the need for ribbon call backs. Even better is the ribbon will call those functions() in the current form, not a public code module like you MUST do with ribbon call backs.
I could probably type on for another 10+ pages as to the difference, but I think that would start to be redundant and I don't want to appear condensing in any way here.
So the basic difference between a sub and function in VBA or in fact in most programming languages is quite much the same.
And the benefits of using a function in Access or just about any programing language are also much the same. For example I can define a user defined function in t-sql (scalar) – and again you then are free to use that t-sql function in any of your t-sql code or even quires that you create and use for sql server.
So this is basic and simple difference between a sub and a function, and I dare say those who have written computer code will in just about any programing language will instantly realize the above significant and useful differences between a subroutine and a function.
The main difference is not only the return value, it seems that subs are faster than functions
(at least in .net) because the MSIL code of subs is much shorter when no value is returned. so overall subs are faster when no value is returned.
oh i've just found a great source for it (talks about .net), maybe you would like to read further about it- Functions vs. Subroutines
Yes, a Function is just a Sub that returns a value.
I'm not absolutely sure, however, I think subs are faster than functions because the variables of a subroutine are defined upon creation of the subroutine and are accessed by referencing the memory location. Functions must allocate memory space every time they are accessed.
Subroutines modify the variables in the calling code and functions leave them intact. So a subroutine can provide several modified pieces of information to the calling code (as many changes as there are variables, including arrays) but a function can only provide one answer at a time for the values that are passed to it. Because of this difference, if it is important that a variable in a subroutine does not change its value, one must assign the value to a temporary variable defined within the subroutine itself.
FWIW (my theory ;) -
Lets think of the real world to understand this.
Lets say you want to get something done. There are (at least) 2 ways of doing this.
First way, send out requests for information to helpers and they will return with the information for you. so you remain in control ie all info is flowing back to you and you are deciding what to do next if any. this is more of the centrally controlled environment. this is the essence of 'function' in vba
Second way, divide up work into separate tasks and assign responsibility to your helpers to finish the task for you ie actual work is performed here by helpers in contrast to just gathering info. This is the essence of 'sub' in vba.
So think of what to do if code breaks. with function calls, you concentrate on the central command to look for reason of failure. With sub calls, you have to run into each sub's work and find out what they did wrong.
Of course, you can screw up the purpose and have functions do work and subs just get info but that would just be really confusing when things break! Oh but you cant do that, read this link - http://www.cpearson.com/excel/differen.htm, which states that Excel forbids functions changing cell values and subs being called from cells.
You will note that events are always subs, never functions. However, in MS Access, it can be useful to create functions when you wish to use them as the property of an event:
On Close: = MyCloseFunction()
Subs can return a value ByRef.
I found one other difference, at least on Excel but likely other Office apps. If you want to customize the ribbon by adding a button to launch a VB program, when you choose Macros in the "Choose commands from" dropdown menu, it lists any Subs in your code but not Functions. Note that a Private Sub will also be hidden from the customize ribbon selection, as will a Public Function.
So to summarize, these will be available to add as buttons on the ribbon:
Sub, Public Sub
And these will not be available to add:
Function, Public Function, Private Function, Private Sub

SSIS custom script: loop over columns to concatenate values

I'm trying to create a custom script in SSIS 2008 that will loop over the selected input columns and concatenate them so they can be used to create a SHA1 hash. I'm aware of the available custom components but I'm not able to install them on our system at work.
Whilst the example posed here appears to work fine http://www.sqlservercentral.com/articles/Integration+Services+(SSIS)/69766/ when I've tested this selected only a few and not all columns I get odd results. The script only seems to work if columns selected are in sequential order. Even when they are in order, after so many records or perhaps the next buffer different MD5 hashes are generated despite the rows being exactly the same throughout my test data.
I've tried to adapt the code from the previous link along with these articles but have had no joy thus far.
http://msdn.microsoft.com/en-us/library/ms136020.aspx
http://agilebi.com/jwelch/2007/06/03/xml-transformations-part-2/
As a starting point this works fine to display the column names that I have selected to be used as inputs
Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer)
For Each inputColumn As IDTSInputColumn100 In Me.ComponentMetaData.InputCollection(0).InputColumnCollection
MsgBox(inputColumn.Name)
Next
End Sub
Building on this I try to get the values using the code below:
Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer)
Dim column As IDTSInputColumn100
Dim rowType As Type = Row.GetType()
Dim columnValue As PropertyInfo
Dim testString As String = ""
For Each column In Me.ComponentMetaData.InputCollection(0).InputColumnCollection
columnValue = rowType.GetProperty(column.Name)
testString += columnValue.GetValue(Row, Nothing).ToString()
Next
MsgBox(testString)
End Sub
Unfortunately this does not work and I receive the following error:
I'm sure what I am trying to do is easily achievable though my limited knowledge of VB.net and in particular VB.net in SSIS, I'm struggling. I could define the column names individually as shown here http://timlaqua.com/2012/02/slowly-changing-dimensions-with-md5-hashes-in-ssis/ though I'd like to try out a dynamic method.
Your problem is trying to run ToString() on a NULL value from your database.
Try Convert.ToString(columnValue) instead, it just returns an empty string.
The input columns are not guaranteed to be in the same order each time. So you'll end up getting a different hash any time the metadata in the dataflow changes. I went through the same pain when writing exactly the same script.
Every answer on the net I've found states to build a custom component to be able to do this. No need. I relied on SSIS to generate the indexes to column names when it builds the base classes each time the script component is opened. The caveat is that any time the metadata of the data flow changes, the indexes may change and need to be updated by re-opening and closing the SSIS script component.
You will need to override ProcessInput() to get store a reference to PipelineBuffer, which isn't exposed in ProcessInputRow, where you actually need to use it to access the columns by their index rather than by name.
The list of names and associated indexes are stored in ComponentMetaData.InputCollection[0].InputColumnCollection, which needs to be iterated over and sorted to guarantee same HASH every time.
PS. I posted the answer last year but it vanished, probably because it was in C# rather than VB (kind of irrelevant in SSIS). You can find the code with all ugly details here https://gist.github.com/danieljarolim/e89ff5b41b12383c60c7#file-ssis_sha1-cs