• Userforms as VBComponents (WordXP)

    • This topic has 21 replies, 5 voices, and was last updated 20 years ago.
    Author
    Topic
    #418540

    Did I beat you to it?

    Sub test1()
    ‘ Create a new MSForms.UserForm as a VBComponent
    Dim vbc As VBIDE.VBComponent
    Set vbc = Application.VBE.VBProjects(“Project”).VBComponents.Add(vbext_ct_MSForm)
    ‘ Mate the vbComponent to the MSForms object model
    Dim ufrm As MSForms.UserForm
    Set ufrm = vbc.Designer
    ‘ Add controls using MSForms object model
    Dim myCmd As MSForms.Control
    Set myCmd = ufrm.Controls.Add(“Forms.CheckBox.1”)
    End Sub

    Viewing 1 reply thread
    Author
    Replies
    • #942304

      > Did I beat you to it? “I couldn’t find my shoes, let alone get them laced up.”

      Thanks Jefferson, this adds a user form and adds a checkbox (see also my rpely to Hans below), but how do I display the “ufrm” device?

      I can see “UserForm2” etc. being created in the Project Explorer window. I have previously tried iterating through all VBCompoents matching a .Name property, but that still only lets me obtain a VBComponent.

      In Brief, ufrm.Show won’t work for me.

    • #942300

      I had thought it would be simple to generate userforms on the fly. At run-time I know how many forms to generate, and how many checkboxes to load to each form. A couple of nested loops should do the trick – the outer loop creating userforms and the inner loop populating the forms with checkboxes.

      I have a function that adds a checkbox to a userform – providing that userform exists. See Test2 below

      My code to create a new userfom and augment that fails, and I can’t understand why. The “vbc” that I have created appears to me like a userform, but I get a Run-time error ‘438’ on the vbc.Controls.Add(“Forms.CheckBox.1”)

      To test this, start a new document. It will have a project name of “Project”.
      Run test1; this fails on my WordXP system.
      Insert a userform, let it default to “UserForm1”, then run test2; this succeeds on my WordXP system.

      Sub test1()
          ' This code fails to add a checkbox to a userform
          Dim vbc As VBComponent
          Set vbc = Application.VBE.VBProjects("Project").VBComponents.Add(vbext_ct_MSForm)
          Dim myCmd As Control
          Set myCmd = vbc.Controls.Add("Forms.CheckBox.1")
      End Sub

      To see a checkbox being added, Insert a user form in your project, let it default to “userForm1”, then run the Test2 below

      Sub test2()
          ' This code successfully loads a checkbox control to an existing userform
          Call Ftest2(UserForm1)
          UserForm1.Show
      End Sub
      Public Function Ftest2(frm As UserForm)
          frm.Add ("Forms.CheckBox.1")
      End Function

      Perhaps I’ve confused a VB component with a userform, but I think of a userform as a vbcomponent of a project.

      • #942303

        Use

        Set myCmd = vbc.Designer.Controls.Add(“Forms.CheckBox.1”)

        The online help provides some info about the Designer property.

        • #942305

          Thanks, Hans (please see above my reply to Jefferson )

          The .Designer does the trick in letting me create a form and add a control, but how do I go about displaying the darn thing?

          I read the all-too-brief help file which suggested, as did you, using .Designer, but neither line of code (below) works to display the newly-created form.

              vbc.Designer.Show 'The Designer object is the form itself.
              vbc.Show 'The Designer object is the form itself.

          I’m still confused about the difference between a “user form” and a “VB component”. In my mind, a user form is a particular kind of VBCompoonent, so that if I’m referring to a VBComponent that I happen to know IS a user form, I feel I ought to be able to treat that VBComponent AS a user form.

          • #942315

            Based on a couple of examples (here’s one), it looks like you may want to try:

            VBA.UserForms.Add(ufrm.Name).Show

            My approach in Word has been to create a handful of ur-UserForms the old-fashioned way and then customize them on-the-fly by manipulating the visibility, position, etc. of their controls before I .Show them. I’ve been keeping them in a separate template, partly because I understand forms are more corruption-prone than other VBComponents.

            For example, I have one form that starts with a label, followed by 4 textboxes, 4 comboboxes, 8 checkboxes and 6 command buttons (left-aligned), finishing with OK and Cancel in the lower right corner. The .Execute method of the form, supplied with the information on how many controls of each type need to be displayed, rolls down the form moving sngControlsBottom farther and farther south while setting the .Top and visible properties (among others) of the available controls.

            Is there a particular reason you want the entire form-creation process to be on-the-fly?

            • #942360

              > Based on a couple of examples
              Thanks Steve, that does the trick. I now have an option to dissect that code and extract the bits that functions for me.

              > Is there a particular reason you want the entire form-creation process to be on-the-fly?
              Yes and no.

              The application is an administrator’s template which is driven by regular tables in the document portion. 1-column tables hold data for listboxes, 2-column tables hold values for checkboxes. The adminstrator can adjust the tables (doesn’t need me on-site) and generate a revised GUI form, from which end-users will select boiler-plate templates. Neat scheme. I have a working model using a single table to provide a (Boolean logic ) decision table to select a template based on an appropriate pattern of yes/no boxes. It was the immediate success of this model that drove me to suggest the 2-table scheme.

              Like you I started with a base set of (five) forms, each with five listboxes and five command buttons and an associated set of five blank user forms; I was thus equipped to provide for five 1-column tables and five 2-column table options. More than enough for the business.

              I planned to .Visible=False anything I didn’t want.

              Then it struck me that for about the same effort I ought to be able to create listboxes, command buttons and spare UserForms on-the-fly, telling the client that the number of tables (options) was limited only by the size of their monitors and their willingness to scroll right.

              I left the fixed listbox & command buttons in place, and last night embarked on the plan to create GUI forms on-the-fly. And I ran into problems.

              Now, thanks to you, I have two major options:
              1) Continue my original path of thought and devise a scheme that develops AND STORES forms in the global template; the forms would be re-created only when the Adminsitrator feels a change is needed
              2) Implement this (radical?) complete on-the-fly solution.

            • #942453

              You may prefer a 3rd option. You can take an old-fashioned “permanent” UserForm with “permanent” controls and then add additional controls to it on-the-fly. The code is simpler, and makes no use of the Designer property (which I’d never heard of before yesterday — but then, I haven’t created controls on the fly, either, although I just experimented before passing this news on to you).

              So, for example, if you wanted to add an additional on-the-fly textbox (txtBox2) to an existing UserForm (frmMyForm), you could use code like this:

                  Dim frm As New frmMyForm
                  Dim txtBox2 As MSForms.TextBox
                  With frm
                      Set txtBox2 = .Controls.Add("Forms.Textbox.1", "txtBox2", True)
                      With txtBox2
                          .Top = 50
                          .Left = 5
                          .Width = 200
                          'etc.
                      End With
                      .Height = 100
                      '.etc.
                      .Show

              If any Lounger reading this is in a position to comment on “what the pros generally do” (and why), I’d be curious, although I suspect the answer may be, “the pros don’t make much use of UserForms.” (I note that Googling designer “userforms.add” show doesn’t get me very many hits.)

            • #942456

              When I need to add controls to a userform dynamically, I use the approach you describe: I insert a userform, place the “fixed” controls on it, then add controls in code as needed.
              2cents

            • #942474

              > You may prefer a 3rd option.
              Thanks Steve. I’ve been pondering this. I dis-assembled John Walkenbach’s code and have built/am building a set of utility functions that build user forms.

              There’s a function to add a Command control – you supply the height, width, caption etc, and an array of VBA code to handle the click event.
              There’s a function to add a set of Checkboxes – you supply the height, width, array of captions etc, and an array of VBA code to handle the click event.
              There’s a function to add a Listbox – you supply the height, width, array of captions etc, and an array of VBA code to handle the click event.
              There’s a function to add a userform and return the form (object?)
              I believe I can hook all these together and build a top-level form with command buttons that link to the sub-forms.

              I like the idea of a table-driven form generator, especially for administrative clients.

              But more than that, I was wondering whether, by (a) building everything on the fly or ( rebuilding the GUI with each update of source data, I might avoid the problem of corrupted templates.

              From the adminsitrator’s point of view, this has to be a good deal – they populate a document with a series of 1-column and 2-column tables and get a completely customised GUI form system – and they are in complete control of the text content.

              From my point of view I get a library of developer functions.

            • #943565

              Crude, but it gets around the problem of the named UserForm.

              The code that generates the tie-in UserForm stores that name in a Custom property (I should use a Document Variable). In the code below, I retrieve that name, which survives an exit/load of Word, and launch the appropriate form. This is ugly code, because I need to get it working. I don’t like the vbc-loop, but I’m just not familiar enough with these designer objects and the various properties.

                  Dim strFormName As String
                  strFormName = u.Channels.blnChannelGetFrom(ThisDocument, "tempFormName", msoPropertyTypeString)
                  Dim vbc As VBComponent
                  For Each vbc In ThisDocument.VBProject.VBComponents
                      If vbc.Type = vbext_ct_MSForm Then
                          If vbc.Name = strFormName Then
                              VBA.UserForms.Add(strFormName).Show
                          Else
                          End If
                      Else
                      End If
                  Next vbc
              

              Again: This application has two modes (1) Adminstrator mode where the admin officer develops a set of Tables that create a series of GUI forms and (2) User mode where the users employ the GUI forms thus created.

            • #943593

              I’d add an Exit For after the .Show line.

              I assume the reason for the For loop is to make sure the form exists. An alternative would be to use On Error Resume Next and then have a line that refers to ThisDocument.VBProject.VBComponents(strFormName) and then test for Error 9 (although your looping approach is definitely better if there’s a possibility that ThisDocument might have a VBComponent named strFormName that isn’t a UserForm).

              On the other hand, if you were going to go the error-test route, you could probably dispense with any advance testing, put On Error Resume Next right before the .Show line and then react to the Error 424 that the .Show line will trigger if the form doesn’t exist.

              Probably a trivial gain (at most) in runtime efficiency in either case if ThisDocument won’t have many VBComponents.

            • #943597

              > I’d add an Exit For after the .Show line.
              Thanks for these comments. I’ve retreated a bit and am now planning to out-flank.

              So far everything has worked well in test mode, but once (as Administrator) I lock the template, some of my code baulks in User mode (because the template is protected). Stay tuned …..

            • #943643

              Yes, the designer object is inaccessible on protected projects. You can however use my technique on a protected project.

              You could build a class module that holds (simple) event code for each type of control and have an init sub inside the blank userform that activates the controls after you have added them. I would use another class module to catch the set properties of each control setting form name, the controlname and the control’s value using the event code in the class module.

              For your control panel form the same applies I guess, no real need to create it on-the-fly, just add the necesary buttons on the fly.
              The advantage will be that the form’s name will be known (or the name of the object variable you instanciated to hold an instance of the form).

            • #943720

              Ooooh! “Gettin’ complex”. I’ve read this through and think that I understand it. It is doable. This morning I’m wrestling with splitting the task in two, and keeping the Adminsitartor side of code in a locked template (so that any failures can be directly attributable to me, and not the client), and having that code build a fresh user template, rather than building user code and forms inside the administrator’s template.

              I’ve got to get this to the client in some semblance of working order. Last night I trapped the user form name in a global string variable, then encompassed that into the user’s “Run” macro, which, ahem, I built on the fly.

              Generally speaking I dislike programs that write themselves; they seem prone to fail, and go against my early teachings. Nowadays I recognise that Class Module code and similar is not that bad. I think it will have to wait for phase 2, through.

              All in all I had hoped, by now, to have posted a complete skeleton of this code so that you and Steve and Jezza et al. (good old Al!) could see it. That’s still my goal, but it’s taking longer to wrap this up than I had anticipated.

            • #943736

              [indent]


              Ooooh! “Gettin’ complex”.


              [/indent]Well, depends on what you call complex. I prefer having the code where it is needed over having to add code on the fly and thus having to edit code inside strings or wherever it is kept when I need to change the “on-the-fly” code.
              I’m not too keen on this on-the-fly stuff either.

            • #943764

              > Well, depends on what you call complex.

              I agree.

              A great deal has to do with the starting-point of the journey. For historic reasons i was familiar with adding controls & program code on the fly. i was stumped by a first-time obstacle with forms. If I’d been starting from scratch, the Class approach would have made sense, but i found myself with an application 95% complete – but for the forms (name etc). At that stage I have to guess where to spend resources and guessed (perhaps wrongly) that I should Push On. One really never knows.

              My modus operandi, if I can use that kind of language, is to batter away at obstacles and then convert the utility functions into a library of code for “next time”; that’s the time that I step back and survey my fabrication of safety-pins and baling wire and think “I can do better than that”. I always can.

              So, I’ll step back in a week or two, during the period that Client plays with this initial version, and start off afresh, and at that time I’ll revisit this thread and use the Class concept.

              I write all this by way of saying “Thanks, much appreciated”, it really is.

              I’s still like to extract the seemingly-useful skeletons for posting here, for those who follow/pursue.

      • #943485

        Still struggling. I’ve got all the code working, builds lovely nested GUI forms, but fails in trying to assign a fixed name to the last form.

        I generate one form for each user-table – so the number of forms is determined at run-time. I start the job by deleting all userforms, so if my user elects to use five tables, I’ll generate five userforms, and a sixth “UserForm6” to tie them together. I’d like to rename the sixth to have a fixed name, so I can refer to it in a macro.

        I can fudge this by creating my “tie” as the first form, hence always “userForm1”, but am mildly curious as to why the code below fails. My guess is that removing a userform from a project does NOT remove its entry from VB’s internal symbol table.

         Public Const strcName As String = "Runner21"
        Sub test()
            ' first run, this macro will create a userform "Runner21"
            ' second run it will fail with a run-time error 75
            Dim vbc As VBComponent
            For Each vbc In ThisDocument.VBProject.VBComponents
                If vbc.Type = vbext_ct_MSForm Then
                    If vbc.Name = strcName Then
                        ThisDocument.VBProject.VBComponents.Remove VBComponent:=vbc
                    Else
                    End If
                Else
                End If
            Next vbc
        
            Dim tempform
            Set tempform = ThisDocument.VBProject.VBComponents.Add(3)
            tempform.Properties("Width") = 600
            tempform.Properties("Name") = strcName
        End Sub
        
        • #943514

          Of course you don’t need to create all these forms on the fly.

          You can just create one single userform and use different instances of the form to add buttons to it on the fly.

          Like this:

          Option Explicit
          
          Sub ShowForms()
              Dim Form1 As UserForm1
              Dim Form2 As UserForm1
              Set Form1 = New UserForm1
              Set Form2 = New UserForm1
              Dim txtBox2 As MSForms.TextBox
              Dim chkBox As MSForms.CheckBox
              
              With Form1
                  Set txtBox2 = .Controls.Add("Forms.Textbox.1", "txtBox1", True)
                  With txtBox2
                      .Top = 50
                      .Left = 5
                      .Width = 200
                      'etc.
                  End With
                  .Height = 100
                  .Show vbModeless
                  
              End With
              With Form2
                  Set chkBox = .Controls.Add("Forms.CheckBox.1", "chkBox1", True)
                  With chkBox
                      .Top = 50
                      .Left = 5
                      .Width = 200
                      .Caption = "My CheckBox"
                  End With
                  .Height = 100
                  .Show vbModeless
              End With
              
          End Sub
          

          I am not sure how one could add the necesary code to the various instances however, so it might not be a valid solution.

          • #943524

            > Of course you don’t need to create all these forms on the fly.

            Thanks for the feedback. That might be a solution, but I’m none too sure.

            This particular application needs to build a set of user forms, each with its own set of checkboxes, and then a single tie-in form with a set of (independant) listboxes PLUS a command button for each of the check box forms.

            Then the results must be saved (by the administrator) and rolled out as a Global template for the end-users.

            Trouble is – I’ve got the thing working now, all except for the macro that will .Show that tie-in form.

            Hence my quandry over creating it first, with a fixed name “UserForm1”, or trying to invoke it through a string-variable.

            More later …..

        • #943519

          Possibly related to the form-renaming troubles I warned about in this post?

          • #943523

            > Possibly related to the form-renaming troubles I warned about in this post?

            Thanks, Steve;
            Almost certainly related.

            I ran a quick test: deleted all user forms, ran the macro (creating UserForm renamed as “Runner21”), then saved my work, exited & reloaded Word and tried to run the macro. Got the same run-time error.

            I’m sure that there is a way around it, but for now I think I’ll try either(1) creating the tie-in form first and always have it named “UserForm1” or (2) remembering its name and trying to invoke it through a string variable.

            More later …..

    Viewing 1 reply thread
    Reply To: Userforms as VBComponents (WordXP)

    You can use BBCodes to format your content.
    Your account can't use all available BBCodes, they will be stripped before saving.

    Your information: