UserForm Is A Class

G

Greg Maxey

I just finished reading a skull splitting discussion about "Magic Forms" and
UserForms being Classes, etc, etc.

One of the arguments put forth is that UserForms are classes and should be
used as such.

Quote: "Take the situation where you need to know in the code that loads
the form whether the user pressed Cancel or OK." The author proposed that
the solution was to extend the form by creating a read only property called
"Cancel." He went on to provide about half the code necessary to do this
and I can't figure out the rest.

I have tried to construct a workable code from samples I have of created of
another property in a class module. It simply isn't working. My question
is how do you do what the author above (Peter Hewett) proposed. I have the
code I attempted with an asterisk to the right of the lines Peter listed in
his article. I get a compile error on the line:

If myFrm.Cancel Then *
"Method or DataMemeber not found"

Thanks

The code
Public Sub MagicFormDiscussion() *
Dim myFrm As oFrm6 *
Set myFrm = New oFrm6 *
Load myFrm
myFrm.Cancel = False
myFrm.Show
*
If myFrm.Cancel Then *
MsgBox "Form was canceled" *
Else
*
MsgBox "Form exited normally" *
End If
*
MsgBox "Form complete" *
Unload myFrm
*
Set myFrm = Nothing *
End Sub
*




The UserForm (Simple form with two command buttons)

Option Explicit
Public mboolCancel As Boolean *
Private Sub CmdCancel_Click() *
mboolCancel = True *
Me.Hide
End Sub *
Private Sub CmdOK_Click() *
mboolCancel = False *
Me.Hide
End Sub *
Private Property Get Cancel() As Boolean
*
Cancel = mboolCancel
*
End Property
*
Public Property Let Cancel(NewValue As Boolean)
mboolCancel = NewValue
End Property
*
 
G

Greg Maxey

Once again I am plagued by Gremlins. For some unfathomable reason the code
I was using didn't like the term mboolCancel. I went back to Peters code
and created a form exactly to his instruction using mboolCancel. No joy.

I then created mirror statements in the form using the terms "mResult" and
"Result." I dimmed out the the exist mboolCancel and Cancel terms and it
worked. I then systematically altered term names and as when I changed
mboolCancel to mCancel the code as follows works as intended:

Public Sub MagicFormDiscussion()
Dim myFrm As oFrm6
Set myFrm = New oFrm6
Load myFrm
myFrm.Show
If myFrm.Cancel Then
MsgBox "Form was canceled"
Else
MsgBox "Form exited normally"
End If
MsgBox "Form complete"
Unload myFrm
Set myFrm = Nothing
End Sub

Option Explicit
Private mCancel As Boolean
Private Sub CmdCancel_Click()
mCancel = True
Me.Hide
End Sub
Private Sub cmdOK_Click()
mCancel = False
Me.Hide
End Sub
Public Property Get Cancel() As Boolean
Cancel = mCancel
End Property

Can anyone offer an explanation of what could have caused this? Thanks.
 
J

Jezebel

Not sure what the problem is here, Greg. I duplicated your code *exactly*,
and it worked exactly as expected. I run MagicFormDiscussion and get two
message boxes: 'Form was canceled' or 'Form exited normally', followed by
'Form complete'.

The point about forms and classes is that a form is a special case of class
object. The form does everything a class object can do (you can have
multiple instances, you can define properties, methods, and events); plus
they have a graphic component. This distinction is more apparent in pure VB:
the form object has Initialize and Terminate events, fired when the 'class'
part is created/destroyed; and it has Load/Unload events, fired when the
graphic part is created/destroyed.

You can see something of this with your code, if you put debug.print
statements in the Initialize, Activate, Deactivate, Uninitialize events of
your form. As you'll see, the 'Load' statement is unnecessary. The form is
initialized by the 'set new' statement; it is shown and activated by the
'show' statement. (Also bear in mind that the behaviour is slightly
different if you step through the code, because the form is
activated/deactivated repeatedly.)
 
G

Greg Maxey

Jezebel,

Yes, the code I pasted in my last message is working exactly as expected.
However, that wasn't always the case. When I cobbled it together (mostly
cut and paste), I kept getting an error on the myFrm.Cancel statement in the
calling macro. I knew something was wrong because when I typed in myFrm.
the term "Cancel" wasn't appearing. I must have had some wires crossed
somewhere.

I was reading about "Magic forms" and forms being a Class. That is what got
me started exploring this topic.

There seems to be two schools of thought with respect to the Load and Unload
statements.

Steve Hudson says:
Unload myFrm is the same as Set myFrm=Nothing
and
Load myFrm is the same as Set myFrm = New oFrm1

Peter Hewett says:
In the code Malcom Smith posted (well this particular sample) he uses the
statement: oForm.Show

Whereas I recommend you actually use:
Load frmT
frmT.Show

The difference is that the statement "oForm.Show" implicitly loads the Form.
Whenever a Form is loaded it's UserForm_Initialize event is called. This can
be important if you need to interact with the Form after it's been
initialised but before you display it.

He goes on to give an example, but I don't see a difference either way.

So some say use:
Set myFrm as New oFrm
Load myFrm
Unload myFrm
Set myFrm = Nothing

Others say:
Set myFrm as New oFrm
'Load myFrm (Leave this out)
Unload myFrm
Set myFrm = Nothing

And Steve says:
Set myFrm as New oFrm
'Load myFrm (Leave this out)
'Unload myFrm (Leave this out)
Set myFrm = Nothing

Just so I understand, you. What say ye?

Thanks




:

--
Greg Maxey/Word MVP
See:
http://gregmaxey.mvps.org/word_tips.htm
For some helpful tips using Word.
Not sure what the problem is here, Greg. I duplicated your code
*exactly*, and it worked exactly as expected. I run
MagicFormDiscussion and get two message boxes: 'Form was canceled' or
'Form exited normally', followed by 'Form complete'.

The point about forms and classes is that a form is a special case of
class object. The form does everything a class object can do (you can
have multiple instances, you can define properties, methods, and
events); plus they have a graphic component.

This distinction is more
 
J

Jezebel

Set pFrm = new oFrm1 <------ Initialize event is fired
pFrm.Show <----- Activate event is fired

These differences don't usually matter much; but they're worth bearing in
mind if you use the form for some purpose other than showing it. For
example, if you put a commondialog control on a form, you can use the
control (to display the dialog) without showing the form itself ---

With new oFrm1 <------ Initialize event is fired
with .CommonDialog1
:
.ShowOpen
:
end with
End with <------ Terminate event is fired

Note that the Activate event is not fired at all.
 
J

Jonathan West

Hi Greg,

The way VBA works, oFrm6 in your example is both a class and the default
instance of the class. I prefer the term "default instance" rather then
"magic form" for two reasons.

1. It is a better description of what is going on.
2. It is the term used by VB programmers as well as VBA, and therefore
googling on it will give you a wider range of hits which describes it.

I've no idea why your computer complained about mboolCancel - it works fine
for me. Maybe you have multiple conflicting definitions of it?

As for the overall issue of whether you should avoid using default
instances, I'm very much a pragmatist. The following line

myForm.Show

is fewer lines of code than

Dim oFrm as myForm
Set oFrm = New myForm
oFrm.Show

and in most cases achieves precisely the same practical effect.

The one time where you need to avoid this is where you might have more than
one copy of a form in use at the same time, and your code needs to know
which instance of the form it is interacting with. Given the way that
UserForms are mostly used in VBA, this isn't all that common, but you need
to be aware of the issue when it does occur.

--
Regards
Jonathan West - Word MVP
www.intelligentdocuments.co.uk
Please reply to the newsgroup
Keep your VBA code safe, sign the ClassicVB petition www.classicvb.org
 
G

Greg Maxey

Jonathan,

Thanks for your reply. I now have a version with mboolCancel working as
expected. I wasn't able to figure out why it wasn't before but after
rewriting the routine using a different name it work. I then went back an
canceled the different name to mboolCancel and it work as well. Glad that
one is behind me.

I am still not clear on the

Dim
Set
Show

Vice
Dim
Set
Load
Show

vice simply

Show

I found some set by Peter Hewet that sounded reasonble. He said that Load
was needed anytime you need to interact with the form between the Form_Init
event and actually showing the form. He even gave an example, but I can't
see where is makes any difference.

If you have time, I would be interested in your views on Load and Unload as
well as any other views you have on three options listed above.

Thanks.
 
G

Greg Maxey

Jonathan,

I took one of my example calling macros and stetted out the Dim, Set, and
Load statements and it appears to work equally as well. It seems that Peter
Hewett's concern over user Load (if you have to interact with the UserForm
"before" between the Set event and the Initialize event) is taken care of
just by using a statement that requires access to the form:

Sub PassData1A()
'Dim myFrm As oFrm1
'Set myFrm = New oFrm1
'Load oFrm1
oFrm1.Frame1.Caption = InputBox("Enter a custom Frame Caption: ", _
"Create Custom Caption", _
"Enter a standard brassiere size e.g., 34D")
oFrm1.Show
MsgBox "You entered size " & UCase(oFrm1.TextBox1.Text) & ". " _
& "This is a valid size.", , "Data Returned"
'Unload oFrm1
Set oFrm1 = Nothing
End Sub
 
J

Jonathan West

Greg Maxey said:
Jonathan,

Thanks for your reply. I now have a version with mboolCancel working as
expected. I wasn't able to figure out why it wasn't before but after
rewriting the routine using a different name it work. I then went back an
canceled the different name to mboolCancel and it work as well. Glad that
one is behind me.

I am still not clear on the

Dim
Set
Show

Vice
Dim
Set
Load
Show

vice simply

Show

I found some set by Peter Hewet that sounded reasonble. He said that Load
was needed anytime you need to interact with the form between the
Form_Init event and actually showing the form. He even gave an example,
but I can't see where is makes any difference.

If you have time, I would be interested in your views on Load and Unload
as well as any other views you have on three options listed above.

They key to understanding this is to realise what is going on in the form
itself. What I suggest you do is add the following event procedures to a
sample userform, and just step through the code seeing which events are
triggered when.

Private Sub UserForm_Activate()
Debug.Print Me.name & " activated"
End Sub

Private Sub UserForm_Deactivate()
Debug.Print Me.name & " deactivated."
End Sub

Private Sub UserForm_Initialize()
Debug.Print Me.name & " initialized."
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Debug.Print Me.name & " queryclosed, mode " & CloseMode
End Sub

Private Sub UserForm_Terminate()
Debug.Print Me.name & " terminated."
End Sub

Once you have these events added, take the various alternative ways of
calling the form and closing the form, step through the code a line at a
time and see what happens in which order.

A bit of experimentation on this will help you understand as much as any
amount of explanation, but I'll help push you in the right direction.

Simply having this

myForm.Show

will result in the Initialize and Activate events occurring one immediately
after the other.

Doing this

Load myForm
myForm.Show

will result in the Initalize event occurring on "Load myForm" and the
Activate event firing on "myForm.Show".

This separation of the two events allows you to pre-load the form with
runtime-specific data before it is displayed, like this

Load myForm
myForm.Caption = "Caption changed at runtime"
myForm.Show

There is a further way of handling this with has a similar effect, which you
have not yet mentioned

With myForm
.Caption = "Caption changed at runtime"
.Show
End With


You can do the same kinds of experiment with Set, like this

Dim oForm as myForm
Set oForm = New myForm
oForm.Caption = "Caption changed at runtime"
oForm.Show

I suggest you just try out various combinations and see what happens. If you
use Me.Hide to close a form, try running the same calling routine a second
time and see what happens. You might be surprised!


--
Regards
Jonathan West - Word MVP
www.intelligentdocuments.co.uk
Please reply to the newsgroup
Keep your VBA code safe, sign the ClassicVB petition www.classicvb.org
 
G

Greg Maxey

Jonathan,

I am looking at the experiments that you proposed now.

I have a simple userform with one command button. That command button's
code is simply:

Me.Hide

I see the behaviour that you point out about Load and Show and wil then
assume the using "Load" is the correct in a situation like your example.

Sub CM2b()
Load myForm
With myForm
.Caption = "Caption changed at runtime"
.Show
End With
End Sub

However, as Tony Jolans mentioned, it appears to work equally as well
without the load statement. Either way, the Caption is changed when the
form is displayed. Is there something that I am missing here?

It appears that Unload oForm or Unload myForm is the only thing that fires
the UserForm_QueryClose event.
I assume that that is significant and therefore Unload is a best practice?

I see from these demonstrations that using just:

myForm.Show

performs equally as well as:

Dim oForm as myForm
Set oFrom = New myForm
myForm.Show

but other than your pragmatism do you see "anything" wrong with explicitly
declaring a UserForm even for the simplest of forms?

I didn't see any surprised from using Me.Hide in any of the experiments.
What sould or could I have expected?

As always Jonathan, thanks for your detailed and helpful replies.
 
J

Jonathan West

Greg Maxey said:
Jonathan,

I am looking at the experiments that you proposed now.

I have a simple userform with one command button. That command button's
code is simply:

Me.Hide

I see the behaviour that you point out about Load and Show and wil then
assume the using "Load" is the correct in a situation like your example.

Sub CM2b()
Load myForm
With myForm
.Caption = "Caption changed at runtime"
.Show
End With
End Sub

However, as Tony Jolans mentioned, it appears to work equally as well
without the load statement. Either way, the Caption is changed when the
form is displayed. Is there something that I am missing here?

"With myForm" will do an implicit Load, firing the Initialize event. So you
don't need "Load myForm". In fact, I can't think of a case where the Load
command is needed to ensure that you can separate the Initialize and
Activate events.

It appears that Unload oForm or Unload myForm is the only thing that fires
the UserForm_QueryClose event.

No. This syntax will also cause the QueryClose and Terminate events to fire

Sub FunWithForms()
Dim oForm as myForm
Set oForm = New myForm
oForm.Show
End Sub

In this case, the Unload is done implictly when then routine ends and the
oForm variable goes out of scope. This triggers the QueryClose and Terminate
events.

Furthermore, clicking the X in the top right corner of the form will trigger
the QueryClose event (with a different value for the Mode parameter)
followed by the Terminate event.
I assume that that is significant and therefore Unload is a best practice?

If you are using the default instance, then you must use Unload to get rid
of the form. The simplest way of doing this is to include "Unload Me"
instead of "Me.Hide" in the event routine for the appropriate commandbutton,
unless you need to access the properties of the form from the calling
routine after you have hidden it.
I see from these demonstrations that using just:

myForm.Show

performs equally as well as:

Dim oForm as myForm
Set oFrom = New myForm
myForm.Show

but other than your pragmatism do you see "anything" wrong with explicitly
declaring a UserForm even for the simplest of forms?

There is nothing at all wrong with it except for the extra lines of code
involved. If you decide that doing things consistently this way is a coding
practice that you want to adopt, then I think that is fine. I choose not to,
but that is partly out of habit. Either way works, provided you understand
what you are doing with it.
I didn't see any surprised from using Me.Hide in any of the experiments.
What sould or could I have expected?

You may notice that if you use Me.Hide to close the form and do not have an
Unload command in the calling routine, when you run the routine again, the
Initialize event does *not* occur. This is because the form has not been
unloaded from from memory, but merely hidden. Showing the form merely
triggers the Activate event.


--
Regards
Jonathan West - Word MVP
www.intelligentdocuments.co.uk
Please reply to the newsgroup
Keep your VBA code safe, sign the ClassicVB petition www.classicvb.org
 
G

Greg Maxey

Jonathan,

You said:
No. This syntax will also cause the QueryClose and Terminate events
to fire
Sub FunWithForms()
Dim oForm as myForm
Set oForm = New myForm
oForm.Show
End Sub

In this case, the Unload is done implictly when then routine ends and
the oForm variable goes out of scope. This triggers the QueryClose
and Terminate events.

I see the Terminate event, but not the QueryClose event.

I have a form "myForm" with the event code you provided earlier. The form
has one command button that runs Me.Hide on click.

If I call with:
Sub CM5()
Dim oForm As myForm
Set oForm = New myForm
oForm.Show
Unload oForm
Set oForm = Nothing
End Sub

The debug.Print shows:
myForm initialized.
myForm activated
myForm queryclosed, mode 1
myForm terminated.

If I call with:
Sub CM6()
Dim oForm As myForm
Set oForm = New myForm
oForm.Show
Set oForm = Nothing
End Sub

Debug.Print shows:
myForm initialized.
myForm activated
myForm terminated.

If I call with:
Sub CM7()
Dim oForm As myForm
Set oForm = New myForm
oForm.Show
End Sub

Debug.Print shows:
myForm initialized.
myForm activated
myForm terminated.

The only thing that triggers the QueryClose event is the Unload oForm in the
first example.

If I change the Me.Hide to Unload Me in the Command Button code then yes the
QeuryClose event is triggered.

I expected the behaviour wrt the form not reinitializing and therefore
wasn't surprised. Thanks for the confirmation.
 
J

Jonathan West

Greg Maxey said:
Jonathan,

You said:

I see the Terminate event, but not the QueryClose event.
If I call with:
Sub CM6()
Dim oForm As myForm
Set oForm = New myForm
oForm.Show
Set oForm = Nothing
End Sub

Debug.Print shows:
myForm initialized.
myForm activated
myForm terminated.

Ah, correct. I had forgotten that QueryClose is not triggered when the form
simply drops out of scope having been hidden. That's not a case I use often.

--
Regards
Jonathan West - Word MVP
www.intelligentdocuments.co.uk
Please reply to the newsgroup
Keep your VBA code safe, sign the ClassicVB petition www.classicvb.org
 
J

Jezebel

"With myForm" will do an implicit Load, firing the Initialize event. So
you don't need "Load myForm". In fact, I can't think of a case where the
Load command is needed to ensure that you can separate the Initialize and
Activate events.

I think the Load statement is a carry-over from VB, where there is a clear
distinction between creating the form as a class object and creating it as a
graphic object. In VB, when you instantiate the form object (eg set x = new
Myform), the 'class' components are created in memory, but the GUI elements
(the form as a graphic object, and its controls) are not. The GUI part is
loaded automatically (and the Load event fired) when you show the form or
refer to any control. The Load method is sometimes useful if you want to
force the GUI components to load -- eg to initialize their values -- without
showing the form or refering to any specific control.

But as you say, it's hard to think of a reason to use it in VBA.
 
G

Greg Maxey

Ok thanks.

I know I am asking a lot of questions in the area right now because while I
am likely to quickly forget for lack of use, I would like to comprehend the
whole mash at least for a brief moment in time.

-
Greg Maxey/Word MVP
See:
http://gregmaxey.mvps.org/word_tips.htm
For some helpful tips using Word.
 
G

Greg Maxey

Jezebel,

Thanks. I think I am in agreement with the majority that Load isn't
necessary.

I do need some help on a another issue related to that question. One of
proponents of Load stated:

When using a Form that's had Properties and/or Methods added an explicit
Load statement can be essential. The reason for is that when you "Show" a
Form it implicitly Loads it, which in turn calls the Forms
UserForm_Initialize event handler. But if you want to interact with your
Form after it's been initialised but before the user can actually see it and
interact with it - then you need separate out the implicit Load. Here's an
example of this:
Public Sub ShowFormTheTechnicallyCorrectWay()
Dim frmT As frmTest
'Instantiate the form
Set frmT = New frmTest
Load frmT
'Set some of the forms custom properties that rely on the
'UserForm_Initialize procedure having been run
frmT.Country = "New Zealand"
'Display the form and wait until the user dismisses it
frmT.Show
MsgBox "Form complete"
Unload frmT
Set frmT = Nothing
End Sub

I wanted to see if I could tell what a difference load made in his example,
but for the life of me, I can't get passed the frmT.Country = "New Zealand"
bit.

I cobbled together a Property Get procedure in the UserForm so that I
actually have a frmT.Country property, but when I run the above code, I get
a "Can't assign to a read only property."

My question is "How so you create property that isn't ready only? If you
are going to mention, Property Get, Let, or Set in your answer, then please
please provide an example as those three shake me to the core.

Thanks.
 
J

Jezebel

Load: I think the message you quote was written by someone thinking in VB
terms. As far as I've ever experienced, the form is loaded automatically as
soon as you do anything that requires it, such as referring to its controls.

Get, Let, and Set: you've got the Get part right -- that's for creating the
Read part of the property. Let and Set are for doing the Write part, Let for
simple variable, Set for objects --


---- IN the form or class ----
Dim mModuleLong as long
Dim mModuleObject as object

Public Property Get MyLong() as long
MyLong = mModuleLong
End Property

Public Property Let MyLong(NewValue as Long)
mModuleLong = NewValue
End Property

Public Property Get MyObject() as object
set MyObject = mModuleObject
End Property

Public Property Set MyObject(NewValue as object)
set mModuleObject = NewValue
End Property

Note 'NewValue' can be any name.


--- IN the calling code ----

Dim pLong as long
Dim pObject as object

form.MyLong = 123
pLong = form.MyLong

set form.MyObject = New Word.Application
set pObject = form.MyObject



You always need the Get function if the property is readable.
You need Let or Set if the property is writable. If the property is a
variant and you want to allow both simple and object data types, then you
need *both* Let and Set.
 
J

Jezebel

Once you get the hang of them, classes are a very quick and convenient
programming technique. A collection of class objects is often simpler, and
always more powerful, than working with arrays or user-defined-types. You
might recall some previous threads where the suggested solutions were
collections of class objects to represent things like selected bookmarks,
spelling errors, or Find results. The advantage of using forms as class
objects is that you can, if you need, display the contents of any one
collection member by calling a method of the class object, instead of having
to set up a separate form and pass values to it.

As you've probably worked out, Word itself is constructed almost entirely of
collections of class objects. By building your own applications in the same
way, they become natural extensions of the existing functionality.
 
G

Greg Maxey

Jezebel,

Thanks for this complete explanation. I have squirrled this away and will
hopefully be able to remember and utilize it in the future.

I have had zero formal training in programming nor have I read any
comprensive A-z books on the subject. My very limited knowledge is a
collection of the the tips and techniques that I have picked up reading
these newsgroups. I like to participate in the VBA group simply because I
enjoy trying to solve problems using logical methods. I am after all a
submariner and logic demands the final number of surfaces always = dives +
1. In answer to your reply to another post, unfortunately "Class" is just
one of a hundred other fuzzy concepts that I am struggling to grasp.

While you can be a bit cantakerous at times (and the unknown guy or gal
anyone could love to hate), you are always here with helpful information.
Thanks for that and thanks again for this information.
 
J

Jezebel

Number of dives plus one? As someone put when the navy and airforce were
fighting for control of the nuclear program and the airforce were trying to
build a nuclear plane: you'll have a plane in the ocean before we have a
submarine in the air.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top