Consulting

Results 1 to 11 of 11

Thread: Effectively using Classes and Property Let/Get; referring to parent or sub class...

  1. #1
    Knowledge Base Approver VBAX Guru GTO's Avatar
    Joined
    Sep 2008
    Posts
    3,368
    Location

    Effectively using Classes and Property Let/Get; referring to parent or sub class...

    Greetings and Salutations,

    Preface:

    While designing a UserForm (hereafter, form), I experienced an issue with the calendar control (shocked,eh?). I noted that when going from an older (2000) App version to a newer App version (2003), the buttons (or at least the font on the buttons, I forget which) became incredibly small. I would note that I think I still was in Word, but I do not see that having any bearing.

    Hence – I decided it was time to tackle something a tad bigger using class module(s) than I had heretofore. I decided to build myself a handy-dandy pseudo ‘calendar control,’ which is of course, something like 52 controls to replicate the Calendar control… Stop giggling.

    Code Goal:

    I would like to improve upon what I have done so far, and hopefully improve my understanding of when Set/Let/Get are advantageous, as well as efficiently referencing child/sub classes and parent classes. I admit that I may be using those terms incorrectly, but hope I am close.

    After this thread, I am hoping to be able to add a couple of/few ‘stock’ classes to a project; a few lines of code to a form, and viola – a pseudo calendar control would be usable. Again, stop giggling; I can do it!

    Reference the pseudo calendar control, or PCal for short – while I have seen several of examples of creating a calendar on a form (there are very nice examples in our KB), I was thinking a bit differently; something that might be easily sizeable, visible = true/false and such. That is not to say that such examples do not exist; as King Solomon mentioned, there is nothing new under the Sun. Regardless, I have not run into any.

    I’m rather happy with what I’ve mustered so far, but at this point, am looking forward to hearing, “Yikes! You should do this in this part, and here’s why…”

    Question:

    Specific to a form, when the Initialize event creates an instance of a class, and we need the created class to be able to refer to the form:
    • Why is not the passed reference able to access all of the properties of the form? For instance (and being the bright lad I am, I’ll bet that this is important), the .Name property is gone?
    • Does the below seem a reasonable method, or is there a better way (see the bottom of ThisPButton_Click in cls_pcalPButton in the attachment)?
    [vba]Private Sub ThisPButton_Click()
    '...statements....
    n = -1
    For Each UserForm In UserForms
    If UserForm.Name = PCalParentForm.Name Then
    n = n + 1
    UserForms(n).PCalDateLong = CDate(cPCal.DateLongPicked)
    Call UserForms(n).AssignDate(CDate(cPCal.DateLongPicked))

    If cPCal.AssignReturnToControl Then
    cPCal.PushDateToCtrl cPCal.Caller, VbLet, cPCal.DateLongPicked
    End If


    '***TESTING***
    UserForms(n).Caption = Space(2) & UCase(Format(CDate(cPCal.DateLongPicked), "dd mmm yyyy"))
    Exit For
    End If
    Next
    End Sub[/vba]

    Due to the time here, I will have to check back tonight, but have been meaning to post (where does the day fly to so quickly?).

    Thank you so much,

    Mark
    Attached Files Attached Files
    Last edited by Aussiebear; 07-08-2011 at 02:35 AM. Reason: Stubborn like his avatar..... Does the green VBA button not appear on your screen Mark?

  2. #2
    Distinguished Lord of VBAX VBAX Grand Master Bob Phillips's Avatar
    Joined
    Apr 2005
    Posts
    25,453
    Location
    Quote Originally Posted by GTO
    Why is not the passed reference able to access all of the properties of the form? For instance (and being the bright lad I am, I’ll bet that this is important), the .Name property is gone?
    I don't understand, why can't you access these properties? Have you tried using the Properties collection?
    ____________________________________________
    Nihil simul inventum est et perfectum

    Abusus non tollit usum

    Last night I dreamed of a small consolation enjoyed only by the blind: Nobody knows the trouble I've not seen!
    James Thurber

  3. #3
    Knowledge Base Approver VBAX Guru GTO's Avatar
    Joined
    Sep 2008
    Posts
    3,368
    Location
    Quote Originally Posted by xld
    I don't understand, why can't you access these properties?
    Hi Bob, I hope your trip was a good one :-)

    Well, I may have worded that poorly. The form's Name is but one property that doesn't seem to show up in intellisense, but I thought it a good example as it actually falls down if used.

    By example - in a new wb, if I create a new userform and insert code:
    [vba]Option Explicit

    Dim cButtons As clsCreatedButtons

    Private Sub UserForm_Initialize()

    Set cButtons = New clsCreatedButtons
    Set cButtons.ReferencedForm = Me

    cButtons.ReferencedFormName = Me.Name
    cButtons.CreateButton
    End Sub[/vba] And in the Class:
    [vba]Option Explicit

    Private CallingForm As MSForms.UserForm
    Private WithEvents MyButton As MSForms.CommandButton
    Private strFormName As String

    Property Set ReferencedForm(UForm As UserForm)
    Set CallingForm = UForm
    End Property
    Property Get ReferencedForm() As UserForm
    Set ReferencedForm = CallingForm
    End Property

    Property Let ReferencedFormName(UFormName As String)
    strFormName = UFormName
    End Property
    Property Get ReferencedFormName() As String
    ReferencedFormName = strFormName
    End Property

    Function CreateButton()
    Set MyButton = ReferencedForm.Controls.Add("Forms.CommandButton.1", "cmdBttn_1", True)
    With MyButton
    .Left = 20
    .Top = 20
    .Width = 64
    .Height = 18
    End With
    End Function

    Private Sub MyButton_Click()
    'With property ReferencedForm and variable CallingForm both declared As UserForm...
    CallingForm.Caption = "Hello"
    '...plunks "Hello" onto the userform; but it's BELOW the titlebar!

    'Also, I can get to the userform's name in a roundabout fashion, via...
    CallingForm.Caption = CallingForm.ActiveControl.Parent.Name
    '... (which of course still ends up below the titlebar), but if I try and use .Name...
    CallingForm.Caption = CallingForm.Name
    '... I get error 438, "Object doesn't support this property or method.
    End Sub[/vba] On the other hand, if I declare Private CallingForm As Object, as well as change it to As Object in the Property Set and Get, then when I step through (having already REM'd CallingForm.Caption = CallingForm.ActiveControl.Parent.Name) "Hello" shows up properly in the form's titlebar and everything is sunshine and flowers with CallingForm.Name.

    I would mention that I have a bit of a grip (albeit loosely) on some events not being available when a given activex control is created in a class, though I would not pretend as to know the exact 'whys'. I was figuring certain properties not being available might be similar, but am confused as to why if I declare as Object, certain things work (like .Name), whereas if As UserForm, it goes Kaboom!

    Similarily (and presuming I set the form's StartUpPosition to 0 during Initialize), then if it is passed to the class As Object, I can change the .Left and .Top properties, and watch it move. But As UserForm causes the same falling down.

    Again confusing is that when As UserForm, setting the the form's Caption results in the string appearing below the titlebar! I hope that I am not missing something really simple and that I worded that a bit better.

    Quote Originally Posted by xld
    Have you tried using the Properties collection?
    Do you mean like ThisWorkbook.VBProject.VBComponents("UserForm1").Properties("Name").Value ?

    Thank you,

    Mark
    Last edited by Aussiebear; 07-08-2011 at 02:37 AM. Reason: ...

  4. #4
    Knowledge Base Approver VBAX Guru GTO's Avatar
    Joined
    Sep 2008
    Posts
    3,368
    Location
    Quote Originally Posted by GTO
    Do you mean like ThisWorkbook.VBProject.VBComponents("UserForm1").Properties("Name").Value ?

    Thank you,

    Mark
    ACK! Obviously not, as the Properties property is not happening while the form is loaded.

  5. #5
    VBAX Master Aflatoon's Avatar
    Joined
    Sep 2009
    Location
    UK
    Posts
    1,720
    Location
    The Userform class/interface is a generic one from which other userforms inherit. You cannot create a new instance of Userform (only Userform1 or whatever you have called your form) and hence it does not have properties such as Name or Top or Left, as they don't make sense. You should pass it as Object.

  6. #6
    Knowledge Base Approver VBAX Guru GTO's Avatar
    Joined
    Sep 2008
    Posts
    3,368
    Location
    Hi Aflatoon,

    I am afraid I have run myself out of time today, but wanted to relay a thank you for your answer. Hopefully tomorrow is "smoother"...

    Have a great day,

    Mark

  7. #7
    Knowledge Base Approver VBAX Guru GTO's Avatar
    Joined
    Sep 2008
    Posts
    3,368
    Location
    Greetings All,

    Okay, while I may not have my head totally wrapped around the why we must pass the form as an object, I believe it is making enough sense to me to proceed.

    At the end of post #1, I asked as to whether using the userform collection would be a good way of getting a value passed back to a variable declared in the form's module. But, I think I have made progress and this would (hopefully) be better.

    In the form's module:
    [vba]Option Explicit

    Dim cButton As clsButton
    Dim lValue As Long

    Property Let ValFromClass(SentVal As Long)
    lValue = SentVal
    End Property
    Property Get ValFromClass() As Long
    ValFromClass = lValue
    End Property

    Private Sub CommandButton1_Click()
    Me.TextBox1.Tag = vbNullString
    Me.TextBox1.Value = vbNullString
    ValFromClass = 0
    End Sub

    Private Sub TextBox1_Change()
    Me.Label1.Caption = Me.TextBox1.Tag
    End Sub

    Private Sub UserForm_Initialize()

    With Me
    .Height = 180
    .Width = 240
    .Caption = vbNullString
    With .Label1
    .BorderStyle = fmBorderStyleSingle
    .Caption = vbNullString
    .Left = 12
    .Top = 6
    .Height = 16
    .Width = 72
    End With
    With .TextBox1
    .Left = .Parent.Label1.Left + .Parent.Label1.Width + 12
    .Top = 6
    .Height = 16
    .Width = 72
    .Value = vbNullString
    End With
    End With

    Set cButton = New clsButton
    Set cButton.ParentForm = Me
    cButton.AddButtons
    cButton.CallerName = Me.TextBox1.Name
    cButton.CustomPropName = "ValFromClass"
    End Sub[/vba]
    In the Class (clsButton):
    [vba]Option Explicit

    Private WithEvents Bttn_1 As MSForms.CommandButton
    Private WithEvents Bttn_2 As MSForms.CommandButton
    Private WithEvents Bttn_3 As MSForms.CommandButton

    Private oForm As Object
    Private strCallerName As String
    Private strCustomPropName As String

    Property Set ParentForm(frm As Object)
    Set oForm = frm
    End Property

    Property Let CallerName(s As String)
    strCallerName = s
    End Property

    Property Get CallerName() As String
    CallerName = strCallerName
    End Property

    Property Let CustomPropName(s As String)
    strCustomPropName = s
    End Property

    Function AddButtons()

    Set Bttn_1 = myForm.Controls.Add("Forms.CommandButton.1", , True)
    With Bttn_1
    .Top = 34
    .Height = 22
    .Left = 6
    .Width = 142
    .Caption = "CallByName to userform control"
    End With
    Set Bttn_2 = myForm.Controls.Add("Forms.CommandButton.1", , True)
    With Bttn_2
    .Top = 34 + 22 + 6
    .Height = 22
    .Left = 6
    .Width = 142
    .Caption = "CallByName to userform variable"
    End With
    Set Bttn_3 = myForm.Controls.Add("Forms.CommandButton.1", , True)
    With Bttn_3
    .Top = 34 + 22 + 6 + 22 + 6
    .Height = 22
    .Left = 6
    .Width = 142
    .Caption = "(return stored val if present)"
    End With
    End Function

    Private Sub Bttn_1_Click()

    '// Method 1: Use Property Get within same module.
    CallByName oForm.Controls(CallerName), "Tag", VbLet, CLng(Date - 1)
    '// method 2: Use the module level variable when wishing to return the current value and
    '// within the same module.
    CallByName oForm.Controls(strCallerName), "Value", VbLet, Format(Date - 1, "dd mmm yyyy")
    End Sub

    Private Sub Bttn_2_Click()
    '// Set the value of a variable within the userform's module, by adding a Property Let
    '// in the userform's module.
    CallByName oForm, strCustomPropName, VbLet, Date + 1
    End Sub

    Private Sub Bttn_3_Click()
    '// Just to demonstrate the return value stored in userform variable, if present)
    If Not StrComp(CDate(CallByName(oForm, strCustomPropName, VbGet)), "12:00:00 AM") = 0 Then
    oForm.Caption = CDate(CallByName(oForm, strCustomPropName, VbGet))
    Else
    oForm.Caption = "No val set"
    End If
    End Sub[/vba]
    Please see the small example workbook attached.

    Presuming (and hoping... ) that I am using CallByName effectively, I have a question towards Let/Get. In the first button's click event, you will see that I first referred to the property (method 1) to return the stored value, whereas in the second CallByName I simply referred directly to the variable.

    While I understand the advantage of using the property (Get) from outside the module, to me, it would seem quicker to refer directly to the variable if we are in a procedure in the same module. Am I being sensible in my conclusions?
    Attached Files Attached Files
    Last edited by Aussiebear; 07-08-2011 at 02:40 AM. Reason: .... GRRRRR.......

  8. #8
    VBAX Master Aflatoon's Avatar
    Joined
    Sep 2009
    Location
    UK
    Posts
    1,720
    Location
    A couple of observations:
    1. Your class refers to myForm when adding controls, not to oForm, which violates encapsulation.
    2. I would generally recommend using properties rather than direct variable access, for much the same reason you created the properties in the first place:
    if you decide to change implementation of a property, you may have to rewrite all your code;
    if your property procedures do any sort of validation, it's a lot easier to just use that same code rather than having to validate the variable values yourself every time the class uses them internally;
    it's an easy guarantee that any internal operations you do would produce the same results as if a consumer of your class did them using the same properties.
    3. For this example, I don't really see why you wouldn't implement the whole thing as a form (which can then be called from anywhere) rather than a class that can only be called from a form anyway.

  9. #9
    Mac Moderator VBAX Guru mikerickson's Avatar
    Joined
    May 2007
    Location
    Davis CA
    Posts
    2,778
    A couple of observations.

    1) A userform has been described as a custom class with attached interface.
    Thus (if it is to inherit properties) a variable that refers to a userform must be declared either As Object or As Userfom1, not as msForms.Userform.

    2) When creating classes that refer to controls on a userform, I find it useful to either have a Parent property, either explicitly set when the custom object is defined or a property like
    [vba]'in class module
    Dim myCheckbox as msForms.Checkbox
    '...
    Property Get ufParent() As Object
    myParent as Object
    Set myParent = myCheckBox
    Do until Typename(myParent.Parent) = myParent.Name
    Set Parent = myCheckbox.Parent
    Loop
    set ufParent = myParent
    End Property[/vba]
    The Do loop is to handle situations where myCheckbox is in a Frame or other control.

  10. #10
    Knowledge Base Approver VBAX Guru GTO's Avatar
    Joined
    Sep 2008
    Posts
    3,368
    Location
    Quote Originally Posted by Aflatoon
    A couple of observations:

    1. Your class refers to myForm when adding controls, not to oForm, which violates encapsulation.
    ACK! Sorry about that Aflatoon, my utter bad. I was going too quick, used intellisense and forgot to change it.

    Quote Originally Posted by Aflatoon
    2. I would generally recommend using properties rather than direct variable access, for much the same reason you created the properties in the first place:
    if you decide to change implementation of a property, you may have to rewrite all your code;
    Errrrr... droool... slobber... Okay, I'm not quite grasping this part at the moment, but I certainly accept it.

    Quote Originally Posted by Aflatoon
    if your property procedures do any sort of validation, it's a lot easier to just use that same code rather than having to validate the variable values yourself every time the class uses them internally;
    it's an easy guarantee that any internal operations you do would produce the same results as if a consumer of your class did them using the same properties.
    Ahh! thank you, and I believe that I am grasping these points (at least loosely ).

    Quote Originally Posted by Aflatoon
    3. For this example, I don't really see why you wouldn't implement the whole thing as a form (which can then be called from anywhere) rather than a class that can only be called from a form anyway.
    I am not quite sure, but I presume you mean the workbook at post #1? As my goal is a pseudo calendar, at least as I imagine it, I would not be calling it except from a userform. I thought a moment about using a second form, but it seemed to me that I would facing the same learning curve as to passing the value from the second form to the first, as I am here, in passing the value from the created controls. As mentioned, CallByName seems like a neat thing, once I figured out exactly what I needed to supply for the arguments. (I am afraid I have had only limited time to study this, and can be a bit thick-headed sometimes...)

    Further - whilst certainly only my subjective opinion, I am at least thus far, not seeing the users being able to move the pseudo-calendar hither and yon as advantageous. I am sure my efforts are very rough thus far, and appreciate your patience in reading what is by no means the 'prettiest' bit of coding.

    Quote Originally Posted by mikerickson
    A couple of observations...
    Howdy Mike!

    I am about done in, but I have copied your code and observations.

    Hopefully I get at least a little time this weekend and a chance to post back on Monday.

    Certainly my continued apprecation for everyone's help, and a great weekend to all :-)

    Mark

  11. #11
    VBAX Master Aflatoon's Avatar
    Joined
    Sep 2009
    Location
    UK
    Posts
    1,720
    Location
    Mark,
    Would it not be easier to create a Calendar form that you can call from say a worksheet too as well as from a form? It makes everything a lot easier - you simply expose the return values as properties of your form so the calling code just creates an instance of the form and shows it (modally, so the code pauses) and then when the calendar is closed (you'd want to hide rather than unload it), the calling code resumes by reading its properties and then unloads it. Roughly:
    Dim frm as CalendarForm
    Set frm = new CalendarForm
    frm.Show
    If Not frm.Cancelled Then me.textbox1.value = frm.DateSelected
    set frm = nothing
    where your form implements a simple Boolean Cancelled property and a DateSelected property for the return value.

Posting Permissions

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