Consulting

Page 1 of 2 1 2 LastLast
Results 1 to 20 of 25

Thread: Solved: Part 1, Close, Do Not Save Active Document With Userform

  1. #1

    Solved: Part 1, Close, Do Not Save Active Document With Userform

    Greetings all,

    Big fan of VBA Express

    Intro: Gov't worker in water quality trying to create a semi-automated process for review requests using a Word form with data captured in Access (for my database) but distributed to Excel (for those who ack!Access). The form will be filled out by various districts personnel in the agency located throughout the state and sent back via email. I'm not a VBA expert or programmer by profession. Rank amateur, green-as-grass. The form has potential for elegance (TY VBA!), but in my hands, I will be delighted to get the thing into production.

    I've been "borrowing" code from several sites including Greg Maxey's but there are a few problems I can't seen to comprehend the issue to find a solution. So here I am. I have several questions all related to this project, but will start with this one.

    OP is Windows XP with MSOffice 2007. The Word document is a macro-enabled template. The template has several form controls. The last one is a text form field that when the user moves out of the form field a userform automatically pops up giving the user several choices:

    Save
    Send as attachment
    Cancel(return back to form)
    Close and do not save

    It is the last choice I have been banging my head on for a while. What I want to do is: close the userform, close the active document without saving.

    I've tested some code separately and can get the userform to close OR get the active document to close without saving but NOT within the same procedure. It hangs up. I get the general run-time error '4198' command failed. I did get another error message once indicating that the word document could not close because a dialog box was still open. Makes sense...

    Here is the code:

    [vba]
    Private Sub cmdStop_Click()
    If MsgBox(prompt:="Do you really want to close?", Buttons:=vbOKCancel) = vbOK Then
    Unload frmSaveAndSend
    Application.DisplayAlerts = False
    ActiveDocument.Close SaveChanges:=wdDoNotSaveChanges
    End If
    End Sub
    [/vba]

    When debugging it highlights ActiveDocument.Close SaveChanges:=wdDoNotSaveChanges

    I also tried calling a procedure before closing the document with no issues.

    Thanks to all for your patience (as I'm afraid this is only the beginning).

    Regards!

  2. #2
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    Hmm... is the cmdStop_Click called from within frmSaveAndSend, because that's not necessarily going to work.

    Think about what you're trying to do...

    1. You've got code in a macro-enabled document.
    2. You've got a user form in that macro-enabled document, which is trying both to unload itself, and close the document that holds the code.

    It's like you're trying to do one of those cartoon things where the cartoon character climbs down a rope, gets to the end of the rope, needs more rope... and so cuts the rope above him.

    Sometimes VBA will save you from yourself by holding things in memory when, technically, they shouldn't be held in memory (using a macro-enabled document to close itself). But I suspect you've reached the limits of VBA saving you from yourself when you're trying to do this where you're trying to do it.

    Can you post the macro-enabled document? I suspect you need to do something like the following in a regular module... and that this is the main routine which invokes and shows the userform (also, make sure to remove the Unload statement from within the form code):

    [VBA]
    Sub Main
    Dim f as frmSaveAndSend

    Set f = New frmSaveAndSend
    f.Show
    'then the form does whatever it wants to do... and when control is passed back to the calling macro, unload the form
    Unload f
    'now mark your document as saved (you *did* save it elsewhere? Or do you never want to save it?)
    ActiveDocument.Saved = True
    'and close it
    ActiveDocument.Close
    End Sub
    [/VBA]
    _______________________________________________
    Please don't cross-post without providing links to your cross-posts. We answer questions for free. Please don't waste the time of the people helping you.
    For cross-posting etiquette, please read: http://www.excelguru.ca/content.php?184

    - Frosty

  3. #3
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    Sorry, I see it's a macro enabled template... which is better, but the same kind of stuff still applies. The only difference is that, in normal behavior, a macro-enabled document is unloaded from memory when it is closed. A macro-enabled TEMPLATE is unloaded from memory when the last open document based on that template is closed.

    So if you opened 3 documents based on your macro template, then you would hold it open until the 3rd document was closed.

  4. #4
    Last response first: Yes, it is a macro enabled template. I'm now wondering if it should be distributed as a macro enabled document? I originally thought a template for distribution purposes.

    There is a procedure that removes the orignal template and attaches the normal.dotm simply to break the relationship and remove the macros from the saved document. It works, but it is in the beta phase with no decision on need or how to implement. I really don't want the user to mistake the saved document as the template. This forces the user to go back to the template as the template connects to a database table in order to fill some of the form fields. And I didn't want the saved document to maintain connection with the database where the input values could be arbitrarily changed by a different user.

    It's like you're trying to do one of those cartoon things where the cartoon character climbs down a rope, gets to the end of the rope, needs more rope... and so cuts the rope above him.

    Sometimes VBA will save you from yourself by holding things in memory when, technically, they shouldn't be held in memory (using a macro-enabled document to close itself). But I suspect you've reached the limits of VBA saving you from yourself when you're trying to do this where you're trying to do it.
    Makes sense now that you have pointed out the obvious. In all my searches, I think it is assumed the reader understands this, but it's never been really all that clear when to use the main routine and when it can be put in the userform. I had erroneously assumed that once the userform is activated then all routines had to be in the userform until it was closed...lots to learn...now I know why I'm having other problems. I've read about creating a boolean routine but couldn't quite figure how that worked within the userform. (duh!)

    Here is the code for the document:

    When a new document is created, it calls up an intro userform that explains how to fill in the form fields. Proceeds through a database query that fills a drop-down list then finally ends up where the user decides if they want to save it, email it, return to the document or trash it without saving--the frmSaveAndSend

    [vba]
    Option Explicit

    Private Sub HowToUse()
    Dim oFrm As frmIntroduction
    Set oFrm = frmIntroduction
    oFrm.Show
    Unload oFrm
    Set oFrm = Nothing
    End Sub

    Private Sub Document_New()
    Call HowToUse
    On Error GoTo Document_Open_Err
    Dim cnn As New ADODB.Connection
    Dim rst As New ADODB.Recordset
    cnn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
    "Data Source=NamedDataSouce.mdb"
    rst.Open "SELECT DISTINCT TOP 25 [Field] FROM tblNamedTable ORDER BY [Field];", _
    cnn, adOpenStatic
    rst.MoveFirst
    With ActiveDocument.FormFields("Field").DropDown.ListEntries
    .Clear
    Do
    .Add rst![Field]
    rst.MoveNext
    Loop Until rst.EOF
    End With
    Document_Open_Exit:
    On Error Resume Next
    rst.Close
    cnn.Close
    Set rst = Nothing
    Set cnn = Nothing
    Exit Sub
    Document_Open_Err:
    MsgBox Err.Number & vbCrLf & Err.Description, vbCritical, "Error!"
    Resume Document_Open_Exit
    End Sub

    Sub CascadeList()
    More database stuff
    End Sub

    Sub CascadeList1()
    More database stuff
    End Sub

    Sub PermitFill()
    More database stuff
    End Sub

    Sub OpenCalendar()
    frmCalendar.Show
    End Sub

    Sub OpenSaveAndSend()
    frmSaveAndSend.Show
    End Sub
    [/vba]

    Thanks Frosty! Your comments bring clarity to this befuddled mind.

    Candice

  5. #5
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    Candice,

    Couple of comments on the above.

    1. I think I'd stick with a macro-template rather than a macro-document. That way you have a central location for your code. The last thing you want is users doing a "Save As" of an existing saved macro document, and then spending time why they are having errors connecting to your updated database... only to find out that they are using some older version of the code.

    2. As I said before-- a macro template is held in memory until the last document "attached" to it (as in, the :AttachedTemplate: property) is closed. There are exceptions to this. You've listed one-- if you changed the .AttachedTemplate property, that is, to the macro-template, a "release" -- and it should essentially disappear. But this process has always been a little wonky, and may or may not work (generally, the "inelegant" ways of dismissing documents don't allow Word to do its typical "garbage collection" (a concept in which the application re-claims unused portions of memory/RAM it had previously said it needed).

    3. With #2 in mind, then, there is a third option -- the Global Addin concept. This is a macro-template which would be located in the Startup directory (whether the default, or a custom path you set in the Word Options > File Locations area). You would need to modify your code to use this as an Addin, and that also raises the issue of how you deploy this addin to multiple people. In general, a file is "in use" whenever *someone* is accessing it. A macro template on the network could be held "in use" even when all of your end-users insist they don't have it open (or a document based on it open). There are a lot of ways to approach this issue, but I'm just trying to give a surface view of the "distribution" issue.

    In general, there are multiple problems to solve here: your development process (making it do what you want it to do, and not break) and your deployment process (getting it to the end-users) and your update process (putting development and re-deployment together).

    * - With a macro-enabled document-- you have deployment issues, because you've got multiple versions of the document everywhere.
    * - With a macro-enabled template on which the documents are based-- you may have re-deployment issues if each end-user isn't using their own copy of the template, but rather a central copy of the template. You can have a central copy of the template on the network, but you want to make sure it's a read-only version, so no one can "lock" it from being updated by you (which is only solved, generally, by making everyone restart Word).
    * - With a global adding, you have the same issues as a macro-enabled template, in terms of deployment, but less issues in development when wanting to do things like "close the document without saving changes" -- because your macros don't get released from memory until Word actually closes (Application.Quit is a lot different from Document.Close -- but not to a macro-template, because that *is* the application, as far as its concerned).

    4. Code placement, when to put in a userform and when not. The reason people don't say "this is the way to do it" is because there are differing opinions, even by knowledgeable programmers. I am in the camp of preferring to have my userforms *only* have code related to the display/capture of information from the end-user. All code related to "doing" stuff to a resultant document (or an existing document) I leave outside of a userform. But this is only one approach.

    5. Approaches to accessing a userform:
    couple of different ways to do this. I prefer to create a new instance of a userform via this
    Dim f As frmMyUserForm
    Set f = New frmMyUserForm
    f.Show

    However, you can just as easily do...
    frmMyUserForm.Show

    Both ways will show the userform-- the difference is that my way will show a "clean" version of the userform (that's the "New" keyword), rather than something which might be floating around in memory from a previous use of it.

    6. The immediate solution...
    I think all you have to do with the code you posted is the following-- remove the Unload statement (and perhaps replace with a Me.Hide) from the code with frmSaveAndSend.

    And then replace your existing routine with this:
    [VBA]

    Sub OpenSaveAndSend()
    frmSaveAndSend.Show
    On Error Resume Next
    Unload frmSaveAndSend
    On Error GoTo 0
    ActiveDocument.Saved = True
    ActiveDocument.Close
    End Sub
    [/VBA]

    However, if you need to deal with options on whether to actually perform the close without saving changes (I'm old school, and prefer it to be two lines of code, rather than a parameter to the close event), then you'll need to check any controls on your form (did they check a box to indicate wanting to close without saving changes?) to see whether you want to do that or not.

    In some cases, you may also get a prompt about saving the actual macro-template (depending on how things are coded). If you need to make sure the end-user doesn't get that prompt, you can also try using a ThisDocument.Saved = True somewhere before the .Close method. However, be careful having that code in place while developing-- it could result in you losing changes to your actual macro-template.

    Hope this helps!

    Jason aka Frosty

  6. #6
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    There are a few other implicit questions in your post which I didn't answer-- creating a "boolean routine within your userform" -- you may need to restate your question. But I *think* the quick answer to this is

    A Subroutine (Sub OpenSaveAndSend) *does* stuff.
    A Function *does* stuff, but also *returns* stuff, a la...
    [VBA]
    Function fAmISaved() As Boolean
    fAmISaved = ThisDocument.Saved
    End Function
    [/VBA]
    That will return "True" if the document/template containing that macro is currently saved, or "False" if it isn't.

    However, sometimes you might not be able to call functions/subroutines from other places in the document. This is related to Public/Private keywords, as well as concepts called "Scope" and "Visibility." That may give you enough to go on, in terms of searching on those terms and how they might be hindering what you're trying to do.

  7. #7
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    Also-- you don't need to remove the attached template of a macro enabled template in order to have a document based on it not have macros. "Children" of macro templates do not have code in them, unless something wonky is going on. And since you are using the Document_New event and having your code work (as opposed to Document_Open), I suspect this would be true for you.

  8. #8

    Smile

    Thanks Frosty,

    I'll comb through your comments in greater detail as I can tomorrow and provide feedback.

    You've hit the tip of the iceberg. Distribution is a key issue.

    I had given consideration of having the template reside only on a networked drive on one of our servers, but haven't yet delved into how to secure it from dabblers or how to make is accessible to more than one user at a time...WHEW! I'm also concerned about the database and the macros. Not that it would cause any catastrophe, but it require more maintenance time than it is worth should there be more than one versioned database table flittering about. {shudder over the thought}

    Personally I would prefer that the template and its associates reside globally rather than locally for QA/QC purposes. Easier to maintain and cleanup one rather than many. How to secure it is beyond my scope and focus currently since I'm still pondering simple functionality. Heh!

    As to your last comment, I thought the same. Again, I assumed that the children of the template would not have the macros because of the Document_New event. I will have to look at this afresh. It does begin to spin a bit after a while.

    Regards,
    Candice

  9. #9
    Still working on this case...had a minor setback due to work obligations.

    In response to the "children" documents inheriting the macro when saved: No they do not. However, what does happen is the macro populates the drop-down fields based on decisions (choices) made when the macro is running. So each document, once saved, will have a list of items in the drop-down fields that are no longer "linked" to the database and therefore, do not provide any logic if the user changes the original choice. The user can still choose a different item in the list but it doesn't affect the outcome of the succeeding drop-down list (as it should if the macro was inherited).

    This makes for confusion for the user because it appears the database relationships still exist.

    Since I'm changing the protection of the document from a form that can be filled to a "read-only" once saved. I shouldn't really need to change the template attached to the document. I'll see how that works.

  10. #10
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    You might want to utilize both Document_Open and Document_New events then, and then run that validation routine both when creating a new document and when opening an existing document. As long as the document is still attached to the parent template, you still have access to the code (and thus, a relationship to the database).

    But if you have offline usage scenarios, the typical way to handle this is to not only have your parent template be available when offline, but have a "data dump" process when Word is launched, via a global addin, and then utilize that local copy of the data for your purposes.

    General overview and broad strokes, obviously-- but there are a lot of options, it really depends on how you want it to work and in what scenarios, at the end of the day.

  11. #11
    Frosty,
    The code you gave me works, but I don't understand why it works.

    I've had to borrow a laptop with Windows 7 and am now getting all types of compile errors, etc. What had "worked" now doesn't in some areas and I'm at a loss as to why it does or does not work. Since my agency will eventually migrate from XP to 7.0, I've got to get the fundamentals down pat.

    Thanks again! I am truly grateful! Problem solved. Haven't given up--just need to take a step back again and figure out the fundmentals. I'm always humbled by the knowledge shared here.

    I've got VBA for Dummies (meh!) and Walkenbach's book for Excel 2007 VBA Programming. Any suggestions on other resources?

  12. #12
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    Nothing you've displayed would indicate why there are compile errors if you're using the same code template between versions. Word is pretty good at being backwards compatible, in terms of compilation.

    However, if you've simply imported the code from a .dot in to a .dotm, then you're probably going to want to look at the Tools > References in your original project (you can open a .dot in Word 2010), and verify those same references exist in your new template (the ADODB objects would require a special reference, not included in a new macro template).

    I don't have any suggestions on books, honestly. I haven't used one for a long time, but I imagine they all cover the basics. The advanced stuff, I suspect the internet will be better than any book.

    In terms of general approaches:
    1. Make sure you always use Option Explicit in every module (you can turn this on by having "Require Variable Declaration" checked on in Tools > Options > Editor within VBA.
    2. Explore and make use of the Immediate Window, the Locals Window and the Watch window. Those are each good ways of figuring out why code works (much better than trying to put message boxes in your code to display values of variables you're curious about)
    3. Avoid using public and private variables until you're really familiar with the concept of scope. Dimming variables in each and every sub routine, as well as passing parameters will seem like it takes you longer-- but it will make you a better programmer. And it will also make it easier to figure out what's going on in your code (especially the Locals window).

    And... come back. There's a lot to learn, and I think these kinds of forums are good places to do it.

    Best!
    Jason aka Frosty

  13. #13
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    Oh, and in terms of the more recent versions of Word... the default security on macro projects will generally disable all of your macros. You'll want to explore the Trusted Locations and Macro Security areas when you're developing to make sure your code will actually be accessible.

  14. #14
    Quote Originally Posted by Frosty
    Nothing you've displayed would indicate why there are compile errors if you're using the same code template between versions. Word is pretty good at being backwards compatible, in terms of compilation.

    Jason aka Frosty
    Yeah, it perplexed me as well so I took it as a sign to call it a day.

    After posting I had other unrelated issues more to do with working on a borrowed pc than anything else. Couldn't duplicate the errors this morning so I'll just chalk it up to the server gods and move on. Today all is well. I had changed the securities, so can only wildly guess what was going on. It went wonky.

    On another note, accessing the userform using your suggestion:

    Dim f as frmMyUserForm
    Set f = New frmMyUserform
    f. show

    somehow negates the cancel command:

    [VBA]

    Private Sub cmdCancel_Click()
    Unload
    frmSaveAndSend
    Set frmSaveAndSend = Nothing
    End Sub
    [/VBA]

    removing "New" in the code brings it back into action.

    So, I'm wondering if there is something in the properties in the command buttons of my userform that I need to change. Love the elegance of "clean code" and would like to follow best practices.

    Regards,
    Candice

    P.S. I'll be a frequent visitor, as I would really like to see this project survive and take flight. Oh, and I've some crude Excel stuff that works but would like the little darlings go into production as well.

  15. #15

    Problem continues

    Back to the drawing board:
    I'm still getting the following error:

    Run-time error '4198': Command failed for the same userform as above for the same command and the same line is highlighted when debugging. The active document will not close.

    The code in the user form:

    [vba]

    Option Explicit


    Private Sub cmdCancel_Click()
    Unload
    frmSaveAndSend
    Set frmSaveAndSend = Nothing
    End Sub


    Private Sub CmdStop_Click()
    If MsgBox(prompt:="Do you really
    want to close?", Buttons:=vbOKCancel) = vbOK
    Then
    Application.DisplayAlerts =
    False
    ActiveDocument.Close
    SaveChanges:=wdDoNotSaveChanges
    End If
    End
    Sub


    Private Sub cmdSave_Click()
    Dim aStory As
    Range
    Dim aField As Field
    With
    Dialogs(wdDialogFileSaveAs)
    If .Show = 0 Then Exit
    Sub
    End With
    For Each aStory In
    ActiveDocument.StoryRanges
    For Each aField In
    aStory.Fields

    aField.Update
    Next aField
    Next
    aStory
    ActiveDocument.Save
    End Sub


    Private Sub cmdSend_Click()

    ActiveDocument.SendMail
    End Sub
    [/vba]

    The subroutine:

    [vba]

    Sub OpenSaveAndSend()
    Dim s As
    frmSaveAndSend
    Set s =
    frmSaveAndSend
    s.Show
    On Error
    Resume Next
    Unload frmSaveAndSend
    On
    Error GoTo 0
    ActiveDocument.Saved =
    True
    ActiveDocument.Close
    End Sub
    [/vba]

    Note: I tried unloading the document using "unload s" but that didn't work.

    Sorry to keep repeating this. I seem to running in a perpetual circle.

    Thanks,
    Candice

  16. #16
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    You've got strange line breaks in your code... but since it wouldn't compile at all, I'm assuming that's a vagary of the board.

    Couple of concepts here...

    UserForms are a class/object. What this means is that when you create them, you are creating an "instance" of that class. You can have multiple instances, much like you can have multiple string variables (as long as they are different names).

    So when you dimension ("dim") a class, all you are doing is allocating memory space. You are not actually creating the object. This is what the "New" keyword does.

    [VBA]
    Sub OpenSaveAndSend
    'allocates a block of memory for your userform
    Dim f As frmSaveAndSend

    'Creates a new/fresh instance of that userform (this also triggers the Initialize "event" of the form)
    Set f = New frmSaveAndSend

    'display the form (also triggers the "activate" event of the form)
    f.Show
    'at this point, execution of this routine is halted... you will not come back to
    'this line of code until the form is no longer being displayed

    'this will unload the form from memory
    Unload f

    'this marks your document as saved
    ActiveDocument.Saved = True
    'this closes the document
    ActiveDocument.Close
    End Sub
    [/VBA]
    Now... within your form, don't use the Unload command. Just use the Me.Hide command. This serves to hide the form and allow your OpenSaveAndSend subroutine to continue processing (which will take care of the unloading of the form). Your cancel routine should just look like this:
    [VBA]
    Private Sub cmdCancel_Click()
    Me.Hide
    End Sub
    [/VBA]

    If you unload the form from within itself... then you will also continue the execution of the OpenSaveAndSend subroutine... but there is no form to unload, unless VBA first loads it.

    There's an additional piece of information here, which is VBA treats UserForms a little bit differently than other classes -- it allows you to refer directly to the name of the form, rather than requiring you to always create a variable referring to the class. To me, this is facilitating "lazy" coding (others will disagree), but mostly what it does is facilitate a lack of understanding of what is going on behind the scenes, in much the way that not using Option Explicit still allows you to make working code, but it can be a bear to debug...

    So, I would recommend not unloading your form from within your form. Use Me.Hide instead, to allow the calling code to continue processing.
    Set explicit instances of your form from the calling routine (for you, OpenSaveAndSend).
    Don't ever refer to the form by name (even though VBA allows you to do this)
    Take out your error trapping, so you can understand why an error would be caused.
    Identify what you actually want to have happen, and when. If you are using your cmdStop to close the active document, but then also have your calling code closing the activedocument... you are going to be closing two documents (or generating an error, if you only have one document open).

  17. #17
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    Additional notes:
    Even though VBA will allow the following code, I don't think it helps you learn what is happening.
    [VBA]
    Sub Demo
    frmSaveAndSend.Show
    End Sub
    [/VBA]
    And then within the userform, you have code which does this
    [VBA]
    Private Sub cmdCancel_Click()
    Unload frmSaveAndSend
    End Sub
    [/VBA]
    What is *technically* happening up there would be fine, since there are no references to frmSaveAndSend after unloading it.

    But as soon as you do something like this....
    [VBA]
    Sub Demo
    frmSaveAndSend.Show
    Unload frmSaveAndSend
    [/VBA]
    You will see that VBA will have to do all of the following events of the form...
    1. If the form is not already loaded in memory (which you can check by checking UserForms.Count, and then checking the .Name property of each one), it will Load the form, which causes the Initialize event.
    2. The .Show command then triggers the Activate event of the form
    3. Code execution in the calling routine is paused (assuming the form is being shown modally-- another caveat to the above info), until the form is hidden somehow (whether by Unloading the form from within itself, or hiding the form from view)
    4. Assuming you unloaded the form... it's now no longer in memory, but you have a line of code which says "Unload frmSaveAndSend" ... so what does VBA do? It has to re-initialize the form solely for the purpose of unloading it. Which is what it does.

    You can see this behavior by creating a form which has a single button on it, called cmdOK, and the the code of the form should have the following:
    [VBA]
    Option Explicit
    Private Sub UserForm_Initialize
    Debug.Print "Initialize the form"
    End Sub
    Private Sub UserForm_Activate
    Debug.Print "Activate the form"
    End Sub
    Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Debug.Print "closing the form"
    End Sub
    Private Sub cmdOK_Click
    Debug.Print "Clicked OK"
    Unload frmMyForm
    End Sub
    [/VBA]
    Then have your calling routine like this:
    [VBA]
    Sub Demo
    frmMyForm.Show
    Unload frmMyForm
    End Sub
    [/VBA]
    Then step through all of the code...and watch what happens... if you want to add & UserForms.Count to the debug.print commands, you'll see how things are getting a little screwy and confusing.

    I think you'll be able to see that using Me.Hide will be a more effective way of troubleshooting issues... unless you have a need to set your .ShowModal property of the userform to False. That would be a different conversation, as you have to handle non-modal forms differently (you actually need to handle unloading the form from within the form itself). But Modeless userforms are a lot less common, in practice, so I would cross that bridge when you come to it. It will be easier to "break the rules" when you have a deeper understanding of them in the first place

  18. #18
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    In fact, just to continue a little bit of the lesson (and refresh my own understanding), you have the following "primary" events in a userform during the process of showing and closing the form:
    Initialize = when the form is loaded into memory
    Activate = when the form is actually displayed
    QueryClose = the form has been instructed to be unloaded
    Terminate = the form is about to be Unloaded

    So there are a couple of different ways to handle the normal...

    UserForm1.Show
    Unload UserForm1

    is technically all you need in your calling routine, as long as your "cancel" button simply uses Me.Hide.

    You only need
    UserForm1.Show in your calling routine, if you're going to put Unload UserForm1 in the "cancel" button of your userform...HOWEVER, assuming you use Me.Hide in the userform during the cancel button, you will avoid triggering the QueryClose event if you instead use this:

    UserForm1.Show
    Set UserForm1 = Nothing

    BUT... avoiding the QueryClose event is not a good thing, because it is actually triggered by hitting the Red X on the userform and when you use Unload (although the CloseMode parameter will be different) -- so you can effectively disable the Red X, but you can't make it go away. For me, you should always try to design userforms so that they work the same way as built in dialogs, which means:
    1. The Cancel Button is triggered by hitting the ESC key (that means you set the .Cancel property to True and the .Caption button to "Cancel" for the command button you've designated as your Cancel button). I typically name this control cmdCancel
    2. You put Me.Hide in the cmdCancel_Click event.
    3. You have the QueryClose event call the cmdCancel_Click routine if the CloseMode = 0 (i.e., when the Red X is pressed)

    This gives you a uniform approach to handling your form after the end-user has dismissed... and then you always have the same situation to deal with in your calling code: the userform has been hidden but is still in memory.

    In addition, you can continue to use the userform (and the values therein) while your calling routine is running (so you can continue to check the vales chosen by the user) until you actually choose to unload the form.

    If you refer to your forms specifically by the name of the user form, you can't know for sure whether the form has been unloaded or not... because it will always trigger a new instance of the form rather than an error.

    So my standard "best practice" for form code is the following code in a module
    [VBA]
    Sub DemoHandlingAUserForm
    Dim f as frmMyUserForm

    'initialize the form
    Set f = New frmMyUserForm
    'show the form
    f.Show

    'check my custom public property of the form, to see if the user cancelled in some way
    If f.blCancel = True Then
    GoTo l_exit
    End If

    'check values chosen in the form...
    With f
    If .chkMyCheckBox.Value = True Then
    'do something, because the user clicked this checkbox, etc etc
    End If
    End With

    l_exit:
    Unload f
    End Sub
    [/VBA]
    And then, in the form, I have *at least* the following events set up, assuming the cmdCancel button has the .Cancel property set to True.
    [VBA]
    Option Explicit
    Public blCancel As Boolean
    Private Sub cmdCancel_Click
    blCancel = True
    Me.Hide
    End Sub
    Private Sub UserForm_QueryClose (Cancel As Integer, CloseMode As Integer)
    If CloseMode = 0 Then
    cmdCancel_Click
    End If
    End Sub
    Private Sub UserForm_Initialize
    'anything I need to initialize here
    End Sub
    [/VBA]
    Hope this helps your understanding. Don't forget to use Debug.Print "this event here" in various events, and show your Immediate Window to help yourself learn about the various events. UserForms can be really tricky- and trying to figure out what is firing when can cause a lot of headaches.

    If you don't follow the above, then you easily create the scenario where the end-user clicks the red X, the form is unloaded... but then you start checking values of the form in your later code... which causes a new instance of the form to be created with default values. So instead of an error, you get an operation which might be the exact opposite of what the end-user expected.

    There are other ways to approach this, but this is my preferred practice.

  19. #19
    Thanks again! I'll take this home tonight and try again. Quickly: the line breaks (?) no clue as the code is not displayed like this in the original code nor can find anything underlying to force a line break. Please note that I did a quick copy/paste to get this posted before trotting off.

    Again, completely perplexed as to why what worked initially is now buggy. I usually backup a working copy and when all goes up in flames simply trash the beta, open the working template, test, rename and move forward again.

    Really I appreciate this; a tad concerned as I considered the issue to be rather simple and more of an indication of developer ignorance that when once pointed out the fundamentals of code, it could be corrected easy enough. (I have much more complicated issues yet to discuss with this project). One step at a time....

    Have a good evening and will report back in the morn.

    Candice

  20. #20
    VBAX Master
    Joined
    Feb 2011
    Posts
    1,480
    Location
    Actually, almost all problems are simple... when you already know the answer (or at least one of the possible answers)

    Your main problem with this is that you didn't know any of the answers, and so had aggregated a number of different answers/approaches, which left things confusing.

    I'm only posting what has worked for me and my philosophical approach to coding (separating "data" from "doings" -- which, when applied to form design, makes me take a lot of code out of dialogs which "does stuff" preferring to leave only code in a userform which relates to the gathering of information from the end-user).

    But there are valid approaches to having all of your code in a userform... and very very little outside of it. I just don't happen to organize my code that way. I'm actually hoping someone else will weigh in with a contrasting opinion -- because that opinion may feel more intuitive to you, and thus easier to learn.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •