How to check if an output column of a script component is null? - sql

I am trying to check if an output column of my script component is NULL.
I tried to use the Row.Column_IsNull, but when I try to do the following:
If Row.Column_IsNull = True Then
// do something
End If
I get an error " Property Row.Column_IsNull is WriteOnly".

What the problem is
The key error in the above was is WriteOnly. When you are referencing columns in Script Components as Transformation, you can specify whether they are ReadOnly, ReadWrite.
When acting as Source, you don't have that option. It's WriteOnly (logically) and they don't even give you the option of the above dialog. So, when you're in your Source and attempt to access write only properties like the following code demonstrates, it breaks.
Public Overrides Sub CreateNewOutputRows()
Output0Buffer.AddRow()
' this is logically wrong
If Output0Buffer.Column_IsNull Then
End If
End Sub
The resolution is that you need to inspect whatever you are assigning into OutputBuffer0.Column prior to making the assignment (or create a separate boolean flag) to keep track of whether the current value was populated.
What the problem isn't
Keeping this here since I already ran down this rabbit hole
Since _IsNull is boolean, you can skip the explicit test and simply use
If Row.Column_IsNull Then
Originally, I had thought this was the classic C-like language issue of assignment (=) vs equality (==) but as #John Saunders was kind enough to point out, this was VB.
That said, the supplied code should work (it does for me).
Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer)
Dim x As String
If Row.Src_IsNull = True Then
x = "" ' do nothing
End If
End Sub

Related

Subroutine will not compile

I'm using Access VBA, and I keep getting
Compile error: Argument not optional
whenever I try to pass a collection into a function. What is going on?
Private Sub btnTest_Click()
Dim GarbageLanguages As New Collection
GarbageLanguages.Add "VBA"
PrintCollectionCount (GarbageLanguages) '<-- error happens here
End Sub
Public Sub PrintCollectionCount(c As Collection)
Debug.Print c.Count
End Sub
Short Answer
Remove the parentheses from the following line:
PrintCollectionCount (GarbageLanguages)
Long Answer
For better or worse (mostly worse), VBA has both functions and subroutines:
Function - expression that must return a value
Subroutine - statement that cannot return a value
Unfortunately, using each of them requires slightly different syntax. Suprisingly, this is not a valid subroutine call:
Subroutine(arguments)
Instead, you need to use one of these two options:
Call Subroutine(arguments)
Subroutine arguments
It's even more unfortunate that when you use the wrong syntax, all you get is extremely cryptic error messages. Finally, it's also hard to get used to not using parenthesis because single arguments that are primitive types instead of objects actually work fine:
Subroutine(SomeString) ' works
Subroutine(SomeInteger) ' works
Subroutine(SomeObject) ' does not work
Subroutine(SomeString, SomeInteger) ' does not work
Aside from memorizing the awful error messages, you can try to train yourself to look out for whenever a space gets automatically inserted after the subroutine's name. This:
Subroutine(argument)
gets changed to this:
Subroutine (argument) '<-- RED FLAG

Input box getting a compile error in VBA

I am learning how to create input boxes and I keep getting the same error. I have tried two different computers and have received the same error. The error I get is a "Compile Error: Wrong number of arguments or invalid property assignment"
Here is my code:
Option Explicit
Sub InputBox()
Dim ss As Worksheet
Dim Link As String
Set ss = Worksheets("ss")
Link = InputBox("give me some input")
ss.Range("A1").Value = Link
With ss
If Link <> "" Then
MsgBox Link
End If
End With
End Sub
When I run the code, it highlights the word "inputbox"
And help would be greatly appreciated.
Thanks,
G
Three things
1) Call your sub something other than the reserved word InputBox as this may confuse things. *Edit... and this alone will resolve your error. See quote from #Mat's Mug.
2) A̶p̶p̶l̶i̶c̶a̶t̶i̶o̶n̶.̶I̶n̶p̶u̶t̶B̶o̶x̶(̶"̶g̶i̶v̶e̶ ̶m̶e̶ ̶s̶o̶m̶e̶ ̶i̶n̶p̶u̶t̶"̶)̶ Use VBA.Interaction.InputBox("give me some input"). You can do this in addition to the first point. Documentation here.
3) Compare with vbNullString rather than "" . See here. Essentially, you will generally want to do this as vbNullString is, as described in that link, faster to assign and process and it takes less memory.
Sub GetInput()
Dim ss As Worksheet
Dim Link As String
Set ss = Worksheets("ss")
Link = VBA.Interaction.InputBox("give me some input")
ss.Range("A1").Value = Link
' With ss ''commented out as not sure how this was being used. It currently serves no purpose.
If Link <> vbNullString Then
MsgBox Link
End If
' End With
End Sub
EDIT: To quote #Mat's Mug:
[In the OP's code, what is actually being called is] VBA.Interaction.InputBox, but the call is shadowed by the procedure's identifier "InputBox", which is causing the error. Changing it to Application.InputBox "fixes" the problem, but doesn't invoke the same function at all. The solution is to either fully-qualify the call (i.e. VBA.Interaction.InputBox), or to rename the procedure (e.g. Sub DoSomething(), or both.
Sub InputBox()
That procedure is implicitly Public. Presumably being written in a standard module, that makes it globally scoped.
Link = InputBox("give me some input")
This means to invoke the VBA.Interaction.InputBox function, and would normally succeed. Except by naming your procedure InputBox, you've changed how VBA resolves this identifier: it no longer resolves to the global-scope VBA.Interaction.InputBox function; it resolves to your InputBox procedure, because VBAProject1.Module1.InputBox (assuming your VBA project and module name are respectively VBAProject1 and Module1) are always going to have priority over any other function defined in any other referenced type library - including the VBA standard library.
When VBA resolves member calls, it only looks at the identifier. If the parameters mismatch, it's not going to say "hmm ok then, not that one" and continue searching the global scope for more matches with a different signature - instead it blows up and says "I've found the procedure you're looking for, but I don't know what to do with these parameters".
If you change your signature to accept a String parameter, you get a recursive call:
Sub InputBox(ByVal msg As String)
That would compile and run... and soon blow up the call stack, because there's a hard limit on how deep the runtime call stack can go.
So one solution could be to properly qualify the InputBox call, so that the compiler knows exactly where to look for that member:
Link = VBA.Interaction.InputBox("give me some input")
Another solution could be to properly name your procedure so that its name starts with a verb, roughly describes what's going on, and doesn't collide with anything else in global scope:
Sub TestInputBox()
Another solution/work-around could be to use a similar function that happens to be available in the Excel object model, as QHarr suggested:
Link = Application.InputBox("give me some input")
This isn't the function you were calling before though, and that will only work in a VBA host that has an InputBox member on its Application class, whereas the VBA.Interaction.InputBox global function is defined in the VBA standard library and works in any VBA host.
A note about this:
If Link <> "" Then
This condition will be False, regardless of whether the user clicked OK or cancelled the dialog by "X-ing out". The InputBox function returns a null string pointer when it's cancelled, and an actual empty string when it's okayed with, well, an empty string input.
So if an empty string needs to be considered a valid input and you need to be able to tell it apart from a cancelled inputbox, you need to compare the string pointers:
If StrPtr(Link) <> 0 Then
This condition will only be False when the user explicitly cancelled, and will still evaluate to True if the user provided a legit empty string.

How to prevent VBA variables from being shared across Word documents?

I have a VBA template project that runs automatically when a Word document is opened. However, if I open multiple documents, they all share the variables values. How can declare these variables to be only associated with the active window or active document?
I tried declaring them in a Class Module, but that did not help. Switching between opened document I can see that these variables are shared.
Any input is appreciated...
This what I have in my Module:
Option Private Module
Dim CurrentCommand As String
Public Function SetCurrentCommand(command)
CurrentCommand = command
End Function
Public Function GetCurrentCommand()
GetCurrentCommand = CurrentCommand
End Function
More Info: The code/Macro start at AutoExec like this:
Public Sub Main()
Set oAppClass.oApp = Word.Application
If PollingRate <> "" Then Application.OnTime Now + TimeValue(PollingRate), "CaptureUserViewState"
End Sub
And the CaptureUserViewState is a Sub that resides in a different Module and does all teh checks (comparing new values to last recorded ones) and here how this Sub does the check:
If WL_GetterAndSetter.GetLastPageVerticalPercentage <> pageVerticalPercentScrolled Then
'Update the last value variable
WL_GetterAndSetter.SetLastPageVerticalPercentage (pageVerticalPercentScrolled)
'log change
End If
You don't give us much information, but I assume you declared public variables at module level like this:
Public myString As String
Public myDouble As Double
From VBA documentation:
Variables declared using the Public statement are available to all procedures in all modules in all applications unless Option Private Module is in effect; in which case, the variables are public only within the project in which they reside.
The answer is to use Option Private Module.
When used in host applications that allow references across multiple projects, Option Private Module prevents a module’s contents from being referenced outside its project.
[...] If used, the Option Private statement must appear at module level, before any procedures.
EDIT You have now clarified that you declare your variables using Dim at module level. In this case, Option Private Module is irrelevant.
Variables declared with Dim at the module level are available to all procedures within the module.
i.e. regardless of whether you're using Option Private Module or not.
If you're finding that the values are retained between runs, then that must be because you are running a procedure from the same module from the same workbook. You may think you're doing something else, but in reality this is what you're doing.
EDIT
In your class module, instead of Dim CurrentCommand As String try Private CurrentCommand As String. Without more information it's hard to debug your program. I'm just taking random potshots here.
What you need to do is store multiple versions of the variables, one set per document.
So I would suggest that you create a simple class to hold the different values.
You then store them in a collection mapping the data-set with the document name or similar as the key.
In classmodule (MyData), marked as public:
Public data1 as String
Public data2 as Integer
In module with the event-handlers:
Dim c as new Collection 'module global declaration
Sub AddData()
Dim d as new MyData 'Your data set
d.data1 = "Some value"
d.data2 = 42
c.add Value:=d, Key:=ActiveDocument.name
End Sub
Then when you enter the event-handler you retrieve the data and use the specific set for the currently active document.
Sub EventHandler()
Dim d as MyData
set d = c.item(ActiveDocument.name)
'use data
'd.data1...
End Sub
Please not that this code is just on conceptual level. It is not working, You have to apply it to your problem but it should give you some idea on what you need to do. You will need to add alot of error handling, checking if the item is already in the collection and so on, but I hope you understand the concept to continue trying on your own.
The reason for this is because, as I understand the situation from your question, you only have one version of your script running, but multiple documents. Hence the script have to know about all the different documents.
On the other hand, If each document would have their own code/eventhandlers, hence having multiple versions of the script running, then you don't need the solution provided above. Instead you need to be careful what document instance you reference in your script. By always using "ThisDocument" instead of "ActiveDocument" you could achieve isolation if the code is placed in each open document.
However, as I understood it, you only have one version of the script running, separate from the open documents, hence the first solution applies.
Best of luck!
You might want to store the Document Specific details using
The Document.CustomDocumentProperties Property
http://msdn.microsoft.com/en-us/library/office/aa212718(v=office.11).aspx
This returns a
DocumentProperties Collection
Which you can add new Properties to Using
Document.CustomDocumentProperties.Add(PropertyName, LinkToContent, Value, Type)
And then Read From using
Document.CustomDocumentProperties.Item(PropertyName)
A downside, or bonus, here is that the properties will remain stored in the document unless you delete them.
This may be a good thing or a bad thing

Is it possible to declare a public variable in vba and assign a default value?

I want to do this but it won't compile:
Public MyVariable as Integer = 123
What's the best way of achieving this?
.NET has spoiled us :)
Your declaration is not valid for VBA.
Only constants can be given a value upon application load. You declare them like so:
Public Const APOSTROPHE_KEYCODE = 222
Here's a sample declaration from one of my vba projects:
If you're looking for something where you declare a public variable and then want to initialize its value, you need to create a Workbook_Open sub and do your initialization there.
Example:
Private Sub Workbook_Open()
Dim iAnswer As Integer
InitializeListSheetDataColumns_S
HideAllMonths_S
If sheetSetupInfo.Range("D6").Value = "Enter Facility Name" Then
iAnswer = MsgBox("It appears you have not yet set up this workbook. Would you like to do so now?", vbYesNo)
If iAnswer = vbYes Then
sheetSetupInfo.Activate
sheetSetupInfo.Range("D6").Select
Exit Sub
End If
End If
Application.Calculation = xlCalculationAutomatic
sheetGeneralInfo.Activate
Load frmInfoSheet
frmInfoSheet.Show
End Sub
Make sure you declare the sub in the Workbook Object itself:
Just to offer you a different angle -
I find it's not a good idea to maintain public variables between function calls. Any variables you need to use should be stored in Subs and Functions and passed as parameters. Once the code is done running, you shouldn't expect the VBA Project to maintain the values of any variables.
The reason for this is that there is just a huge slew of things that can inadvertently reset the VBA Project while using the workbook. When this happens, any public variables get reset to 0.
If you need a value to be stored outside of your subs and functions, I highly recommend using a hidden worksheet with named ranges for any information that needs to persist.
Sure you know, but if its a constant then const MyVariable as Integer = 123 otherwise your out of luck; the variable must be assigned an initial value elsewhere.
You could:
public property get myIntegerThing() as integer
myIntegerThing= 123
end property
In a Class module then globally create it;
public cMyStuff as new MyStuffClass
So cMyStuff.myIntegerThing is available immediately.
Little-Known Fact: A named range can refer to a value instead of specific cells.
This could be leveraged to act like a "global variable", plus you can refer to the value from VBA and in a worksheet cell, and the assigned value will even persist after closing & re-opening the workbook!
To "declare" the name myVariable and assign it a value of 123:
ThisWorkbook.Names.Add "myVariable", 123
To retrieve the value (for example to display the value in a MsgBox):
MsgBox [myVariable]
Alternatively, you could refer to the name with a string: (identical result as square brackets)
MsgBox Evaluate("myVariable")
To use the value on a worksheet just use it's name in your formula as-is:
=myVariable
In fact, you could even store function expressions: (sort of like in JavaScript)
(Admittedly, I can't actually think of a situation where this would be beneficial - but I don't use them in JS either.)
ThisWorkbook.Names.Add "myDay", "=if(isodd(day(today())),""on day"",""off day"")"
Square brackets are just a shortcut for the Evaluate method. I've heard that using them is considered messy or "hacky", but I've had no issues and their use in Excel is supported by Microsoft.
There is probably also a way use the Range function to refer to these names, but I don't see any advantage so I didn't look very deeply into it.
More info:
Microsoft Office Dev Center: Names.Add method (Excel)
Microsoft Office Dev Center: Application.Evaluate method (Excel)
As told above, To declare global accessible variables you can do it outside functions preceded with the public keyword.
And, since the affectation is NOT PERMITTED outside the procedures, you can, for example, create a sub called InitGlobals that initializes your public variables, then you just call this subroutine at the beginning of your statements
Here is an example of it:
Public Coordinates(3) as Double
Public Heat as double
Public Weight as double
Sub InitGlobals()
Coordinates(1)=10.5
Coordinates(2)=22.54
Coordinates(3)=-100.5
Heat=25.5
Weight=70
End Sub
Sub MyWorkSGoesHere()
Call InitGlobals
'Now you can do your work using your global variables initialized as you wanted them to be.
End Sub
You can define the variable in General Declarations and then initialise it in the first event that fires in your environment.
Alternatively, you could create yourself a class with the relevant properties and initialise them in the Initialise method
This is what I do when I need Initialized Global Constants:
1. Add a module called Globals
2. Add Properties like this into the Globals module:
Property Get PSIStartRow() As Integer
PSIStartRow = Sheets("FOB Prices").Range("F1").Value
End Property
Property Get PSIStartCell() As String
PSIStartCell = "B" & PSIStartRow
End Property
there is one way to properly solve your question. i have the same concern with you for a long time. after searching and learning for a long time, finally i get a solution for this kind of question.
The solution is that no need to declare the variable and no need to set value to the variable, and even no need VBA code. Just need the "named range" in excel itself.
For example, the "A1" cell content is "hello, world". and we define the "A1" cell a name as "hello", that is, the "A1" cell have a name now, it's called "hello".
In VBA code, we just need use this method [hello], then we can get the "A1" value.
Sub test()
msgbox [hello]
end sub
the msgbox will show "Hello, word".
this way, we get a global variable without any declaration or assignment. it can be used in any Sub or Function.
we can define many named range in excel, and in VBA code we just use [] method to get the range value.
in fact, the [hello] is a abbreviation of the function Evaluate["Hell"], but it's more shorter.
It's been quite a while, but this may satisfy you :
Public MyVariable as Integer: MyVariable = 123
It's a bit ugly since you have to retype the variable name, but it's on one line.

Why is it legal to pass "Me" ByRef in VB.NET?

I was shocked just a moment ago to discover that the following is legal (the C# equivalent is definitely not):
Class Assigner
''// Ignore this for now.
Public Field As Integer
''// This part is not so weird... take another instance ByRef,
''// assign it to a different instance -- stupid but whatever. '
Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
''// But... what's this?!?
Sub AssignNew()
''// Passing "Me" ByRef???
Assign(Me, New Assigner)
End Sub
''// This is just for testing.
Function GetField() As Integer
Return Me.Field
End Function
End Class
But what's even stranger just as strange to me is that it doesn't seem to do what I expect:
Dim a As New Assigner With {.Field = 10}
a.AssignNew()
Console.WriteLine(a.GetField())
The above outputs "10," not "0" like I thought it would (though naturally, this expectation was itself infused with a certain kind of horror). So it seems that you can pass Me ByRef, but the behavior is somehow overridden (?) by the compiler to be as if you had passed Me ByVal.
Why is it legal to pass Me ByRef? (Is there some backwards-compatibility explanation?)
Am I correct in saying that the behavior of doing this is overridden by the compiler? If not, what am I missing?
This behavior actually follows pretty directly from the Visual Basic specification.
11.4.3 Instance Expressions
An instance expression is the keyword Me, MyClass, or MyBase. An instance expression, which may only be used within the body of a non-shared method, constructor, or property accessor, is classified as a value.
9.2.5.2 Reference Parameters
If the type of the variable being passed to a reference parameter is not compatible with the reference parameter's type, or if a non-variable is passed as an argument to a reference parameter, a temporary variable may be allocated and passed to the reference parameter. The value being passed in will be copied into this temporary variable before the method is invoked and will be copied back to the original variable (if there is one) when the method returns.
(All emphasis mine)
So, the compiler will create a temporary variable assigned to the value of Me to be passed as the ByRef parameter. Upon return, no copy of the resulting value will take place since Me is not a variable.
It appears the compiler transforms "Me" into a variable which is then passed ByRef. If you compile your code, then open it with Reflector, you can see what's happening:
Class Assigner
''// Methods
Public Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
Public Sub AssignNew()
Dim VB$t_ref$S0 As Assigner = Me
Me.Assign((VB$t_ref$S0), New Assigner)
End Sub
Public Function GetField() As Integer
Return Me.Field
End Function
''// Fields
Public Field As Integer
End Class
So it looks like when you call AssignNew(), you are assigning the new instance to the internally generated variable. The "a" variable doesn't get touched because it's not even a part of the function.
This is just one of the thousands of possible 'almost errors' a programmer can make. MS caught most of them, in fact, sometimes I'm suprised at how many warnings do come up.
they missed this one.
As far as why it doesn't change 'me', it's a darn good thing! When you use 'me', it just passes a copy of the real class you are working with, for safety purposes. If this worked they way you were hoping, we would be talking GIANT side-effect. You're innocently working away with in your class' methods, and them BAM all of a sudden you are in an ENTIRELY different object! That would be awful! If you're going to do that, you might as well just write a piece of spagetti MS-Basic line-numbered code with all globals that get randomly set, and no subs/functions.
The way this works is the same way if you pass arguments in parenthesis. For example this works as expected:
Assign(Reference_I_Want_To_Set, New Assigner)
But this doesn't change anything:
Assign((Reference_I_Want_To_Set), New Assigner)
If you reflect the above type of code as adam101 suggests you will see similar results. While that is huge frustration with the parenthesis, it is a very good thing with Me !!!
what you need to do to make this code work is this:
Class Assigner
''// Ignore this for now.
Private newPropertyValue As Integer
Public Property NewProperty() As Integer
Get
Return newPropertyValue
End Get
Set(ByVal value As Integer)
newPropertyValue = value
End Set
End Property
''// This part is not so weird... take another instance ByRef,
''// assign it to a different instance -- stupid but whatever. '
Shared Sub Assign(ByRef x As Assigner, ByVal y As Assigner)
x = y
End Sub
''// But... what's this?!?
Shared Sub AssignNew(ByRef x As Assigner)
''// Passing "Me" ByRef???
Assign(x, New Assigner)
End Sub
End Class
then use it like
Dim a As New Assigner With {.NewProperty = 10}
Assigner.AssignNew(a)
my understanding is you cannot change the reference of the object while using it, so you need to change it in a shared sub
since Me cannot be the target of an assignment, the code seem to create a copy of it and from that point on, your not using the real object, but a copy of it