1

Trying to create a budgeting spreadsheet where expenditure is read in from a csv file, I get to allocate it to categories (via a user form) and for this bit, incoming funds get a separate userform where I can put a short description in a text box which then gets recorded next to the value in the output sheet.

Used this site loads to work out how to do it all - but stuck here.

macro works well, but drops out at the user form code with "Wrong number of arguments or invalid property assignment" and highlights the indescript(x) bit from the expend.Caption line.

Original sub is

Public x, xmax As Integer
Public incmng(1 To 100) As Variant
Public incdescript(1 To 100), inctot As String

' add in the incoming payments with user entered short description
    inctot = ""
    For x = 1 To xmax
        indes.Show
        inctot = incdescript(x) & "; " & inctot
        Cells(r - 2, 7) = inctot
    Next x

the user form (named indes) code is

Private Sub UserForm_Initialize()
    ' Load the expenditure details
    expend.Caption = incdescript(x) & ": " & incmng(x)
    incans.Value = "change me :-)"
End Sub

Private Sub OKButton_Click()
    ' Allow user to put in a short description
    incdescript(x) = incmng(x) & " " & incans.Text
Unload Me
End Sub
Private Sub CancelButton_Click()
    Unload Me
    End
End Sub

It's almost as if it can't transport the values for "x" between the subs, as in the former it is showing as value = 1 in watch, in the userform code it shows as unable to compile.

thanks for any advice!

Community
  • 1
  • 1
ALF
  • 11
  • 2
  • 2
    First step: put `Option Explicit` at the top of each module (including the Form code module) and then fix the errors that are shown. Once you have done that, you will have some cleaner code for which you will probably have more questions. – AJD Oct 06 '18 at 00:28
  • 1
    As it stands, your question above is too broad because there is so much wrong that needs to be fixed. Review "Scope" of variables (a full tutorial in itself) and how loops work (why using `.show` inside the loop is a bad idea). And how your declaration of variables is not really doing what it should (Hint: `x` is declared as a `Variant` not an `Integer` and you should not use `Integer` any more). This code needs a complete re-write and that goes beyond the scope of what Stack Overflow is intended for (specific answers and questions, not cod re-writing service). – AJD Oct 06 '18 at 00:32
  • hi - option explicit is actually in all the sections (I've only copied in parts to try and keep it to the bits I think are relevant). How is x declared as a Variant? Integer I get is not the advantage it was, but this is only dealing with a 100 or so lines so its habit rather than a r . The loops work fine elsewhere - just this one falls over. I'm not looking for a re-write, but there must be something specific here? – ALF Oct 06 '18 at 00:52
  • So dediced to simplify and use a "pass" variable, so build the info I want to put in the caption outside the user form, send it in, display, capture the user input and then rebuild it all back in the module. – ALF Oct 06 '18 at 01:23

2 Answers2

2

Even if this did work, you don't want to use variables like that. They should be given the narrowest possible scope to avoid the possibility that you forget (or someone else maintaining your code forgets) that they are declared globally.

As mentioned in the comments, declarations separated by commas like this...

Public x, xmax As Integer

...are only strongly typed for the ones that have an As {Type} after them. In this case, xmax is an Integer, and x is implicitly a Variant because no type has been declared.

UserForms are classes in VBA, and as classes they can be extended with public methods and properties. The problem is that you are using the default instance of the form instead of creating new instances within your loop. When you call Unload from inside the form, you do just that, and all of the information is simply lost.

The solution is to explicitly create instances of your forms. That way, you can either configure them with custom intializers or properties, and pass information back and forth from the caller easily. The form code would look something like this:

'indes
Option Explicit

Private incdescript As String
Private incmg As String
Private userEntry

Public Sub LoadValues(descript As String, incoming As String)
    expend.Caption = descript & ": " & incoming
    incdescript = descript
    incmg = incoming
End Sub

Public Property Get UserDescription() As String
    UserDescription = incdescript = incmg & " " & incans.Text
End Property

Private Sub OKButton_Click()
    Me.Hide
End Sub

A couple things to note - there is nothing in this form that is dependent on global variables. All of the configuration is handled by passing parameters to the LoadValues procedure, and the "return value" is exposed through a custom UserDescription property.

Also, notice that the OK button does not Unload the form. That's important because you're going to use that to pass information back to the caller.

The calling code would look more like this:

Dim x As Integer, xmax As Integer
Dim incmng(1 To 100) As Variant
Dim incdescript(1 To 100) As String, inctot As String

' add in the incoming payments with user entered short description
inctot = ""
Dim paymentDialog As indes
For x = 1 To xmax
    Set paymentDialog = New indes
    paymentDialog.LoadValues incdescript(x), incmng(x)
    paymentDialog.Show vbModal
    inctot = paymentDialog.UserDescription & "; " & inctot
    Unload paymentDialog
    Cells(r - 2, 7) = inctot
Next x

You create a new instance of the form, pass the values the form needs to it before you show it. When the form calls Me.Hide, control passes back to the calling procedure, and then you ask the form for what the user input was. Then the calling procedure unloads it.

Note that this is only a rough sketch of what I'd consider "best practices" for handling a UserForm. For a much more detailed description, I'd take a look at this answer by @MathieuGuindon and read his blog post here.

Comintern
  • 21,855
  • 5
  • 33
  • 80
  • Firstly- thanks for all the detailed feedback. I had no idea that you need to declare with As like that. makes sense but had picked it up somewhere as a way to save space ref multiple lines. Although the more I think it does start to come back! I read the User form links and can see and agree they should be presentation only - as doing this (first time using them) has meant lots of jumping around. – ALF Oct 06 '18 at 16:25
  • In terms of global variables - dammit I thought I'd learnt something useful with that! Although given this is a small personal use only and will only be me, and ever me, that uses it then perhaps not an issue worth correcting, but take the point over globalising them. – ALF Oct 06 '18 at 16:30
  • @ALF The main take-away is using the default instance of the form. That is a *huge* source of hard to track down errors (and using explicit instances is usually only a couple extra lines of code). – Comintern Oct 06 '18 at 16:34
0

So decided to simplify and use a "pass" variable, building the info I want to put in the user form's caption outside the user form, send it in using pass, display, capture the user's input using the same variable (could be different but no need) and then build my strings back in the core module.

' add in the incoming payments with user entered short description
    Cells(r - 2, 5) = inctot
    For m = 1 To mmax
        pass = incdescript(m) & ": " & Incmng(m)
        indes.Show
        Inctotdes = Incmng(m) & " " & pass & "; " & Inctotdes
        Cells(r - 2, 7) = Inctotdes
    Next m

Oh and I realised x was a pre-defined variant. not that changing that made a difference anyway. I never normally used it (for that reason I now recall - it being the best part of 10 years since I used VBA!) but my preferred letter for counts was already in use.

Private Sub UserForm_Initialize()
    ' Load the expenditure details
    expend.Caption = pass
    incans.Value = "change me :-)"
End Sub

Private Sub OKButton_Click()
    ' Allow user to put in a short description
    pass = incans.Text
Unload Me
End Sub
Private Sub CancelButton_Click()
    Unload Me
    End
End Sub

Solution works, but as to why passing the variable arrays didn't work I'm still stuck. I use the same strategy on other user forms of creating captions from arrays in public memory. Although the difference is the userform code doesn't manipulate them as much and just passes an answer back as I've modified this to do. I guess that's where it failed.

ALF
  • 11
  • 2
  • 1
    This may make the code "work", but it's generally [not good practice](http://wiki.c2.com/?GlobalVariablesAreBad). – Comintern Oct 06 '18 at 01:54
  • What do you mean by "x is a pre-defined variable"? Previously in your code? – ashleedawg Oct 06 '18 at 03:20
  • 2
    Reading over this thread,I see that you're in that slippery place where you have some good VBA knowledge... but are also missing some important key concepts -- and are satisfied when you make a change and the code "starts working again". By no means do I mean this offensively (we've all had to pass through that stage) but hopefully sooner than later, you realize that you really ought to be following other's advice even if it seems like the longer or more complicated way of doing things. I'd suggest you start with @Comintern's answer, and if there's anything you don't understand, ask 'em! – ashleedawg Oct 06 '18 at 03:29
  • ref using x, is that not already an excel/vba function hence not something I can then overwrite? (and why VBA editor shows it in a different font?) – ALF Oct 06 '18 at 16:31
  • am working through the suggested code - trying to understand it really. had seen some of the structure in terms of calling user forms, but my main effort was to get it working so it at least did something rather than nothing. Amateur I know. thanks again for the patience and time. – ALF Oct 06 '18 at 16:33