1

I came across this similar issue and read the replies: Modeless form that still pauses code execution

I have been attempting to apply in my own situation the suggestion provided by David Zemens. In my situation, I cannot seem to find an approach that incorporates Mr. Zemen's suggestion without also utilizing a GoTo.

I am wondering if there is a better or more elegant solution.

Here is an outline of what I am doing:

I have a UserForm with a Command Button that begins the code execution that will perform several actions on multiple Excel workbooks. As such, there are a number of blocks of code and the successful completion of one block of code allows for the execution of the subsequent block of code.

At a certain point, depending on the situation, the code might require User input; in other situations, the needed data is obtainable from an Excel. If input is needed from the User, another UserForm is displayed.

The User may need to view several different Excel sheets before entering the input, so the UserForm is modeless. So the code comes to a stop until the User enters the needed input and clicks another Command Button.

It is at this point I am having trouble: how to resume the program flow. Is the only way to 'pick-up where it left-off' is by using a GoTo statement? Or is there some way to organize the modules so there is a single consistent program flow, defined in one spot and not duplicated from the point at which User input might be needed?

Community
  • 1
  • 1
  • It's hard to suggest a better way to organize your modules/code, without first seeing your modules/code :) The other possibility, of course, is providing for a way (on the second user-form) for the program itself to "select" and bring the necessary worksheet(s) in to view (e.g., ComboBox control listing all workbooks/worksheets, etc.). – David Zemens Jul 11 '16 at 20:29
  • I'd also suggest that *ideally* you should do the needed validation *prior* to executing any code -- what conditions require manual intervention? Check for those conditions before the code *does* anything, and prompt the user for input at that point, while terminating run-time. Then, when the user re-runs the program, it should not require add'l user input (the user, having previously rectified the missing information) – David Zemens Jul 11 '16 at 20:31
  • Well, there is a LOT of code already written and what each block of code does didn't seem germane to the overall program flow. Basically, there is a Command Button available from a Sheet. That Click-event calls a main function in a module. That main function contains calls to other functions. If the called function successfully executes then the focus returns to the main function and the next line of code executes, which is a call to the next function. – Michael Schulz Jul 11 '16 at 20:33
  • Provide a minimal, self-contained example which exhibits the problem you're experiencing. I know this is asking you to do more "work", but it's better (from my, and everyone else's persepctive) than asking *us* to re-invent your wheel :) – David Zemens Jul 11 '16 at 20:33
  • You can also omit the blocks of code which aren't germane. At a minimum, need to see your overall stucture of modules/forms, and how the second form is called, it's command button/event procedures, etc. Right now, I have literally no idea what you're *actually* doing, and what you're *actually* doing matters quite a bit... – David Zemens Jul 11 '16 at 20:35
  • Right now it sounds like you have one enormous subroutine, when what you probably need are several smaller subroutines, which call (or can call) one another, or be called from the UF's etc., based on certain conditions. – David Zemens Jul 11 '16 at 20:38
  • (This is also my first submission on this site—getting used to how to post) – Michael Schulz Jul 11 '16 at 20:40
  • No worries. I will not likely be available to respond again for at least a few hours, actually leaving the office now and no plans to be back online before 10 or 11pm eastern time. – David Zemens Jul 11 '16 at 20:43
  • Is there a way of posting an Excel file that contains VBA? – Michael Schulz Jul 11 '16 at 20:46
  • Nope. It's on you to post the relevant code and describe it appropriately. You could link to an XL file that you uploaded to Google Docs, but that would really be a last resort... – David Zemens Jul 11 '16 at 20:48

2 Answers2

2

Here is my take on the problem . Hope I understood the problem correctly.

Assumptions:

  1. There are two user forms.
  2. UserForm1 with a button to start the processing.
  3. UserForm2 with a button to supply intermediate input.
  4. A sub inside a module to start/ launch UserForm1.

VBA Code (for the sub routine)

Sub LaunchUserForm1()
    Dim frm As New UserForm1

    '/ Launch the main userform.
    frm.Show vbModeless
End Sub

VBA Code (for UserForm1)

Private Sub cmdStart_Click()
    Dim i       As Long
    Dim linc    As Long
    Dim bCancel As Boolean
    Dim frm     As UserForm2

    '/ Prints 1 to 5 plus the value returned from UserForm2.

    For i = 1 To 5

        If i = 2 Then
            Set frm = New UserForm2
            '/ Launch supplementary form.
            frm.Show vbModeless

'<< This is just a PoC. If you have large number of inputs, better way will be
' to create another prop such as Waiting(Boolean Type) and then manipulate it as and when User
' supplies valid input. Then validate the same in While loop>>

            '/ Wait till we get the value from UserForm2.
            '/ Or the User Cancels the Form with out any input.               
            Do While linc < 1 And (linc < 1 And bCancel = False)
                linc = frm.Prop1
                bCancel = frm.Cancel
                DoEvents
            Loop

            Set frm = Nothing
        End If

        Debug.Print i + linc
    Next

    MsgBox "User Form1's ops finished."

End Sub

VBA Code (for UserForm2)

Dim m_Cancel        As Boolean
Dim m_prop1         As Long

Public Property Let Prop1(lVal As Long)
    m_prop1 = lVal
End Property

Public Property Get Prop1() As Long
   Prop1 = m_prop1
End Property

Public Property Let Cancel(bVal As Boolean)
    m_Cancel = bVal
End Property

Public Property Get Cancel() As Boolean
    Cancel = m_Cancel
End Property

Private Sub cmdlinc_Click()
    '/Set the Property Value to 10
    Me.Prop1 = 10
    Me.Hide
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    '/ Diasble X button

    Me.Cancel = True
    Me.Hide
    Cancel = True

End Sub
cyboashu
  • 10,196
  • 2
  • 27
  • 46
0

OK so here are my thoughts.

You have a userform frmSelectUpdateSheet which you wish to use in order to allow the user to select the sheet, when the sheet can't be determined programmatically. The problem is that if you do .Show vbModeless (which allows the user to navigate the worksheet/s), then code continues to execute which either leads to errors or otherwise undesired output.

I think it's possible to adapt the method I described in the previous answer. However, that's out of the question here unless you're paying me to reverse engineer all of your code :P

Assuming you have a Worksheet object variable (or a string representing the sheet name, etc.) which needs to be assigned at this point (and that this variable is Public in scope), just use the CommandButton on the form to assign this based on the selected item in the frmSelectUpdateSheet list box.

This is probably a superior approach for a number of reasons (not the least of which is trying to avoid application redesign for this sort of fringe case), such as:

  • This keeps your form vbModal, and does prevent the user from inadvertently tampering with the worksheet during the process, etc.

  • Using this approach, the thread remains with the vbModal displayed frmSelectUpdateSheet, and you rely on the form's event procedures for control of process flow/code execution.

  • It should be easier (and hence, cheaper) to implement; whether you're doing it yourself or outsourcing it.

  • It should be easier (and hence, cheaper) to maintain.

NOW, on closer inspection, it looks like you're already doing this sort of approach with the cmdbtnSelect_Click event handler, which leads me to believe there's a related/follow-up problem:

The sheet names (in listbox) are not sufficient for user to identify the correct worksheet. So if the user needs the ability to "scroll" the sheet (e.g., to review data which does not fit in the window, etc.), then add some spinner buttons or other form controls to allow them to navigate the sheet.

Community
  • 1
  • 1
David Zemens
  • 53,033
  • 11
  • 81
  • 130
  • Let me test my understanding: keep the UserForm in Modal state and add controls to the UserForm that enable navigating through the Workbook. Do I have that right? – Michael Schulz Jul 16 '16 at 04:25
  • Yes use the forms events and controls to manipulate the worksheets – David Zemens Jul 16 '16 at 04:32
  • Okay, let's say for the sake of discussion that I had not yet written all this code—that I am in the planning stage of the code—and I know that at a certain point a modeless UserForm will be displayed to obtain User input. How would one organize the code? What would be the approach? I've played around a bit and can't seem to figure a way to achieve it, even with very simple code. – Michael Schulz Jul 19 '16 at 13:52
  • Something like the previous solution -- showing the modeless form *from within a `While` loop*, or, do not use them within flow of a continuous execution -- use them to collect and assign values to e.g., public or module-level variables, and then run the executable code independently. I have an application that runs almost entirely on modeless forms in PowerPoint, [I think 7 different forms](http://imgur.com/aKtYEUZ) which collect input, send & receive data from server, and a RibbonUI with other buttons/commands which processes the data which was previously collected by the UserForms. – David Zemens Jul 19 '16 at 14:26
  • Let me see if I got my head around this right: If I want to use Modeless UserForms to gather input for a process then the execution of the UserForm code, from beginning to end, is best when completely independent of the execution of the code for whatever process I want to do, and the code for that process—beginning to end—is also independent of launching the UserForm. But if I really want modeless forms _during_ the process then a Do-While loop of some sort is one way to achieve that. Do I finally have a handle on this? – Michael Schulz Jul 19 '16 at 14:52
  • *But if I really want modeless forms during the process then a Do-While loop of some sort is one way to achieve that. Do I finally have a handle on this?* Yes I think so. You need to do this way, because showing the modeless form continues code execution in the calling procedure (i.e., wherever you're doing `form.Show vbModeless`, as soon as that line executes, then the next line will execute, etc. So do this in a loop `While Not form.Visible` or something like that. – David Zemens Jul 19 '16 at 14:55
  • Thank you @DavidZemens . I appreciate your insight and taking the time to look at my code and answer my questions. – Michael Schulz Jul 19 '16 at 15:09