5

I was under the impression that if a function has been defined with some Optional arguments then you can pass Nothing and it will be treated in the same way as "not passing anything at all".

However, I can't make that work. Here's a code snippet that runs perfectly well if all the "fds..." variables are not empty but throws "Invalid procedure call or argument" if any arguments are Nothing:

Dim fdsSortOrders(2) As Variant
Dim fdsKey1 As Variant
Dim fdsKey2 As Variant
Dim fdsKey3 As Variant

' ...skipped...

Call loFinalReport.DataBodyRange.Sort( _
    Header:=xlYes, _
    Key1:=fdsKey1, Order1:=fdsSortOrders(0), _
    Key2:=fdsKey2, Order2:=fdsSortOrders(1), _
    Key3:=fdsKey3, Order3:=fdsSortOrders(2) _
)

Is there a way to pass an Optional argument or shall I make ugly copy-paste code ("if fdskey2 is nothing and fdskey3 is Nothing Then SortUsingKey1Only)?

UPDATE

This will work since Key2/Key3 and Order2/Order3 are Optional, but I don't want to write three versions of procedure call:

Call loFinalReport.DataBodyRange.Sort( _
    Header:=xlYes, _
    Key1:=fdsKey1, Order1:=fdsSortOrders(0) _
)
Community
  • 1
  • 1
Alexander
  • 313
  • 3
  • 10
  • Clarification: Are `fdsKey1` and `fdsSortOrders(0)` always NOT nothing? Hope you see this soon as I'm going home soon :D – AntiDrondert Feb 13 '18 at 15:00
  • The're two options: either they contain something useful and good (confirmed with sanity checks) or they're Nothing. I can make them be not Nothing but anything else (i.e. vbNullString). – Alexander Feb 13 '18 at 15:02

3 Answers3

2

Here is an example of function header with optional parameters:

Function FunctionName(Optional ByVal X As String = "", Optional ByVal Y As Boolean = True)

You need to ommit parameter passing so default value will kick in.
In your example your default values will be of fdsKey1 and fdsSortOrders(0), so it will sort it by the same key and by the same order.


Could not came up with anything else but this:

Call loFinalReport.DataBodyRange.Sort( _
    Header:=xlYes, _
    Key1:=fdsKey1, Order1:=fdsSortOrders(0), _
    Key2:=IIf(IsNothing(fdsKey2), fdsKey1, fdsKey2), _
    Order2:=IIf(IsNothing(fdsSortOrders(1)), fdsSortOrders(0), fdsSortOrders(1)), _
    Key3:=IIf(IsNothing(fdsKey3), fdsKey1, fdsKey3), _
    Order3:=IIf(IsNothing(fdsSortOrders(2)), fdsSortOrders(0), fdsSortOrders(2)) _
)

Note that at least fdsKey1 and fdsSortOrders(0) should.. well, something.
Alternatively you can make this condition for all 3 Keys and Orders and make default values.
Sorting range by multiple keys with the same value should not affect sorting in any way.

AntiDrondert
  • 1,128
  • 8
  • 21
  • Thanks AntiDrondert; it appears, however, that you have misenderstood me. My question is: how do I pass an "empty" parametre without omitting it? I.e., in your example, how can I write one call, say, `Call FunctionName( myX, myY)`, and if myX is `Nothing` (or whatever) then the default value will be taken by FunctionName?.. – Alexander Feb 13 '18 at 14:20
  • 1
    @Alexander Alright, I think I got your point. Then you will need an entry check in your Sub/Function. Otherwise you can check these variables before the call. This question is **very** related, you should take a look [How do I test if optional arguments are supplied or not?](https://stackoverflow.com/questions/1660772/vb-how-do-i-test-if-optional-arguments-are-supplied-or-not) – AntiDrondert Feb 13 '18 at 14:25
  • Well @AntiDrondert the problem is - .Sort is a built-in Method in `Microsoft.Office.Interop.Excel` :) – Alexander Feb 13 '18 at 14:27
  • Aaaaaah IIF! I've forgotten about it! And just yesterday I was sitting and thinking "Give me back my ternary `? :` operator from C++!" I think that your workaround is very elegant. Still not a solution _per se_, but it appears there's simply none... That Micro$oft... – Alexander Feb 13 '18 at 17:33
2

Running a quick test with an Optional Variant, using the IsMissing function shows that Nothing is not the same as Missing:

Option Explicit

Private Function IsItMissing(Optional vTMP As Variant) As Variant
    IsItMissing = CVErr(xlErrNA)
    On Error GoTo FuncErr
    IsItMissing = IsMissing(vTMP)
FuncErr:
End Function

Public Sub TestValues()
    MsgBox IsItMissing("Nope") 'False
    MsgBox IsItMissing 'True
    MsgBox IsItMissing(Nothing) 'False
End Sub

The problem is that Nothing is a Special Object, rather than actually being 'nothing'. It's like the difference in Excel between a Blank cell and a cell with no data (e.g. ="")

I suspect you would want to chain If statements with Is Nothing to create your sort:

If fdsKey2 Is Nothing Then
    loFinalReport.DataBodyRange.Sort Header:=xlYes, _
        Key1:=fdsKey1, Order1:=fdsSortOrders(0)
ElseIf fsdKey3 Is Nothing Then
    loFinalReport.DataBodyRange.Sort Header:=xlYes, _
        Key1:=fdsKey1, Order1:=fdsSortOrders(0), _
        Key2:=fdsKey2, Order2:=fdsSortOrders(1)
Else
    loFinalReport.DataBodyRange.Sort Header:=xlYes, _
        Key1:=fdsKey1, Order1:=fdsSortOrders(0), _
        Key2:=fdsKey2, Order2:=fdsSortOrders(1), _
        Key3:=fdsKey3, Order3:=fdsSortOrders(2)
End If

{EDIT:Following discussions in comments} Here is a function which uses 2 arrays to work out which, if any, of the Keys are not Nothing:

Private Function SortNothing(Target As Range, Optional Key1 As Variant, Optional Order1 As XlSortOrder = xlAscending, Optional Key2 As Variant, Optional Order2 As XlSortOrder = xlAscending, Optional Key3 As Variant, Optional Order3 As XlSortOrder = xlAscending) As Boolean
    Dim iArr As Integer, aKeys(1 To 3) As Variant, aOrders(1 To 3) As XlSortOrder
    iArr = 0 'This pointer will track how many non-Nothing Keys we have
    SortNothing = False
    On Error GoTo FuncErr
    If Not IsMissing(Key1) Then
        If Not (Key1 Is Nothing) Then
            iArr = iArr + 1
            aKeys(iArr) = Key1
            aOrders(iArr) = Order1
        End If
    End If
    If Not IsMissing(Key2) Then
        If Not (Key2 Is Nothing) Then
            iArr = iArr + 1
            aKeys(iArr) = Key2
            aOrders(iArr) = Order2
        End If
    End If
    If Not IsMissing(Key3) Then
        If Not (Key3 Is Nothing) Then
            iArr = iArr + 1
            aKeys(iArr) = Key3
            aOrders(iArr) = Order3
        End If
    End If

    Select Case iArr
        Case 3:
            Target.Sort Key1:=aKeys(1), Order1:=aOrders(1), Key2:=aKeys(2), Order2:=aOrders(2), Key3:=aKeys(3), Order3:=aOrders(3), Header:=xlYes
        Case 2:
            Target.Sort Key1:=aKeys(1), Order1:=aOrders(1), Key2:=aKeys(2), Order2:=aOrders(2), Header:=xlYes
        Case 1:
            Target.Sort Key1:=aKeys(1), Order1:=aOrders(1), Header:=xlYes
    End Select
    SortNothing = True
FuncErr:
End Function
Chronocidal
  • 6,827
  • 1
  • 12
  • 26
  • 1
    OP explicitly said he didn't want `to write three versions of procedure call`. – AntiDrondert Feb 13 '18 at 14:47
  • Ah - the OP added that in an edit after I had already started writing. Given that `Nothing` is an object, so is not `Missing`, I don't think there is any other way to do it. You *could* write a function to accept the range and arguments, and put the 3-version chain in there so that it looks like 1 line in your main code - but only worth doing if you have several instances of this situation – Chronocidal Feb 13 '18 at 14:57
  • Yes @Chronocidal, just like AntiDrondert said :) This case is simple but what if the function can take ANY of those three arguments (i.e. Key3 and Key1 or Key2 and Key3)?.. Then it'd be a horrible permutation of IFs IFs IFs :) – Alexander Feb 13 '18 at 14:58
  • @Alexander Use Arrays and a pointer? I have added a function into my response, which will fill non-`Nothing` Keys and their orders into an array, and use a pointer to track how many – Chronocidal Feb 13 '18 at 15:14
  • @Chronocidal that's a very nice way to check whether a function has any Optional arguments, but still not particularly relevant to my plight, I'm afraid ;) – Alexander Feb 13 '18 at 17:36
  • 1
    @Alexander But, now you can just say `SortNothing loFinalReport.DataBodyRange, fsdKey1, fdsSortOrders(0), fsdKey2, fdsSortOrders(1), fsdKey3, fdsSortOrders(2)` and it will run the sort and automatically exclude any keys where `fdsKey#` is `Nothing` - I though that that was what you wanted? – Chronocidal Feb 13 '18 at 18:06
  • Wait wait wait @Chronocidal, I must have missed something. You're absolutely right! Thank you for your time spent on analysing and answering my question :) – Alexander Feb 13 '18 at 18:22
0
Private Sub PassingVariables(ByRef strNotOptionalText As String, _
                        Optional ByRef strOptionalText As String = "Random or Null", _
                        Optional ByRef wbMainOptional As Workbook = Nothing)

' Statements
End Sub

Public Sub CallingSub()

' Here I'm passing just the required parameter (strNotOptionalText).
' strOptionalText will be "Random or Null" as default and wbMainOptional will be Nothing as default.
Call PassingVariables("Name")

' Here I'm actually passing personalized variabled and not using the default Optionals.
Call PassingVariables("Name", "My Personlalized Text", ThisWorkbook)
End Sub

Here's an example commented on how to use the code optional parameters. Always remember to put your optional parameters after the required ones.

Alex Martinez
  • 201
  • 1
  • 9