vb tutorial class

Upload: tosurajjoshi2115

Post on 10-Apr-2018

235 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/8/2019 VB Tutorial Class

    1/22

    1

    EDais Advanced classes tutorial

    Advanced classes in VB

    Written by Mike D Sutton Of EDais

    Http://www.mvps.org/EDais/

    [email protected]

    - 31.05.2002 -

    Revision 2:

    - 13.03.2004 -

    Http://www.mvps.org/EDais/

    http://www.mvps.org/EDais/mailto:[email protected]:[email protected]://www.mvps.org/EDais/
  • 8/8/2019 VB Tutorial Class

    2/22

    2

    EDais Advanced classes tutorial

    IInnttrroodduuccttiioonn Introduction

    This tutorial assumes a reasonable knowledge of VB, usage of classes and their

    general structure. Also a basic understanding of OO (Object orientation) terminology

    and implementation would be useful but not essential. Knowledge of GDI

    programming would also be helpful since the document references a number of GDI

    APIs however its again not necessarily essential.

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    3/22

    3

    EDais Advanced classes tutorial

    CChhaa

    pptteerrII An introduction to OO

    In VB (Note; VB.NET includes a full OO model) we only get given a small subset of

    the wonderful world of OO, which can be extremely powerful in creating dynamic

    applications. OO is typically broken down into three sections; Encapsulation,

    Inheritance and Polymorphism, in VB we only get offered Encapsulation. Heres

    a definition of the three terms: (Note; This is in my own words so it may not be a

    text-book definition and doubtlessly would be disputed by OO purists, so feel free

    to consult other documentation on the subject as well as this)

    Encapsulation:

    This is the essence of a class and thus the part that VB supports for us since it

    supports the class structure. You can most likely work out the meaning of the term

    by looking at the word encapsulate The dictionary defines this as Enclose in or

    as in a capsule and this is exactly what a class does. If you think of the class itselfas the Capsule then the data and methods contained within it are hidden from the

    outside world, accessible only through whats known as the Public interface which

    are the public properties, methods and events exposed by the class. In essence the

    internal workings and data stored within a class are known only to itself, the outside

    world only gets access to what is specifically exposed.

    Inheritance:

    This is where the object is abstractedinto a more generalised form then further

    classes a built using the generalised class as a base. These further classes Inherit

    the functionality of the base class and can also include further functionality of their

    own thus extending the base class. These classes are called Derived classes as theyderive their functionality from their parent class.

    An example of this would be the base class Vehicle. Inherited from that would be

    the class Car which would support everything a vehicle does, and extra things a car

    does. The class Ferrari would be inherited from the Car class adding more things

    specific to Ferrari model cars and so on.

    Polymorphism:

    This is where a method from a parent class is re-defined in a class derived from it,

    changing or extending its functionality.

    For example, a basic Vehicle class would be created with very few parameters since it

    would have to cover any number of things further down the inheritance tree, howeverwhen going down to an inherited class, the derived classed would override the

    inherited initialisation method, adding further more specific parameters dependant on

    the object in question.

    Whilst VB only natively supports encapsulation, we can emulate the other OO

    features by programming them, which expands the possibilities of VB and gives us

    greater flexibility in writing large applications. Some of these methods may be

    frowned upon or considered bad practice for some reason of other but Im providing

    this document as an example of how it is possible. Also this is by no means a true

    implementation of OO, nor am I claming it to be before I get any flames from

    programmers of languages with a full support of OO!

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    4/22

    4

    EDais Advanced classes tutorial

    One of the first times I was ever properly introduced to OOP was actually on a course

    a few years ago up at Manchester (UK) University on Java programming of all things.

    The course was split up into two sections, the first teaching Java syntax and one on

    OO principle, the latter of which revolutionised the way I write applications - After

    having worked out how to implement those features in the language that I was

    familiar with! One of the examples they used to describe OO principle is that of a

    Pen with derived classes Red pen, Green pen and so on. (of course this would

    really only be done with a single class with a property colour but thats beside the

    point ;)

    The pen class understands things like Pen down, Pen up, Move pen and so on,

    the classes derived from this now already understand all of these methods, but can

    also add their own functionality of changing the colour thats being drawn.

    In a similar fashion Im going to use the example of a Shape that requires points to

    be drawn. Well then derive classes from that such as Circle or Rectangle that

    take the base class and add functionality to it for drawing specific shapes.

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    5/22

    5

    EDais Advanced classes tutorial

    CChhaapp

    tteerrIIII Creating the abstract base class

    If youve not already, fire up VB, start a new Standard EXE project and add a new

    class to the project naming it clsShape

    Ok, time to think about what is general to a shape. Every shape be it a line, circle,

    triangle etc. will have a number of points associated with it that define where it is and

    how big so we can generalise that into saying that a shape has some points, so

    declare some space for them. Each shape can also have a colour so we can tell them

    apart (You could also add the ability for them to have variable line and/or fill styles

    and colours but for simplicities sake well leave these out for now).

    Before we start coding, I recommend grabbing a useful little utility off my site called

    the Class property generator or PropGen for short, which does the grunt work in

    creating property statements as this can get very tedious especially when youre

    creating a lot of them. Simply type the variable declarations directly into the

    application as you would in VB and it generates the methods for you in real time,allowing you to simply copy and paste the full declares into VB (If you want a

    property to be read-only then simply delete the Property Set/Let statement.)

    Ok, regardless of if youre using PropGen or not, you should now have something

    along the lines of:

    PrivateTypePointAPIXAsLongYAsLongEndTypeDimShapePoints()AsPointAPI

    Dimm_NumPointsAsLongDimm_ShapeColAsLongPublicPropertyGetNumPoints()AsLongNumPoints=m_NumPointsEndPropertyPublicPropertyLet NumPoints(ByValinNewAsLong)m_NumPoints=inNewEndPropertyPublicPropertyGetShapeCol()AsLongShapeCol=m_ShapeColEndPropertyPublicPropertyLet ShapeCol(ByValinNewAsLong)m_ShapeCol=inNewEndProperty

    Whilst the shape has point locations, we can also give it an overall X and Y

    coordinate that offset the entire shape in any direction, allowing us to clone the shape

    and simply move it rather than having to move each point in turn. Rather than using

    the property generator for this, Im going to code it by hand since I want to store the

    position in a single PointAPI type rather than two longs: (You can by all means use

    PropGen to create the shell functions and just change the relevant parts, thats what I

    did for this tutorial)

    Dimm_ShapePosAsPointAPI

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    6/22

    6

    EDais Advanced classes tutorial

    PublicPropertyGetShapePosX()AsLongShapePosX=m_ShapePos.XEndPropertyPublicPropertyLet ShapePosX(ByValinNewAsLong)m_ShapePos.X=inNewEndProperty

    PublicPropertyGetShapePosY()AsLongShapePosY=m_ShapePos.YEndPropertyPublicPropertyLet ShapePosY(ByValinNewAsLong)m_ShapePos.Y=inNewEndProperty

    Any shape will be required to draw itself onto a device context so we must have a

    function called Draw() which accepts a handle to a device context to draw on and

    returns a Boolean to report if the drawing operation succeeded correctly or not. We

    can also create the GDI objects here, select them into the DC and clean up afterwards,

    the shell of the function would look like this:

    PublicFunctionDraw(ByValinDCAsLong)AsBooleanDimhPenAsLong,OldPenAsLongDimhBrushAsLong,OldBrushAsLonghPen=CreatePen(PS_SOLID,2,m_ShapeCol)hBrush=CreateBrush(BS_SOLID,m_ShapeCol,HS_SOLID)OldPen=SelectObject(inDC,hPen)OldBrush=SelectObject(inDC,hBrush)'ReadytodrawCallDeleteObject(SelectObject(inDC,OldBrush))CallDeleteObject(SelectObject(inDC,OldPen))EndFunction

    Youll need the CreatePen(), CreateBrushIndirect(), SelectObject() and

    DeleteObject() API function declarations and also the LOGBRUSH type declaration.

    Ive wrapped the CreateBrushIndirect() function to make is slightly easier to use (You

    dont have to mess around with a logical brush structure every time), heres the

    function:

    PrivateFunctionCreateBrush(ByValinStyleAsLong,_

    ByValinColourAsLong,ByValinHatchAsLong)AsLongDimTempStructAsLogBrushWithTempStruct.lbStyle=inStyle.lbColor=inColour.lbHatch=inHatchEndWithCreateBrush=CreateBrushIndirect(TempStruct)EndFunction

    Youll notice this is declared privately and thus is encapsulation at work since the

    class doesnt expose it yet can still use its functionality.

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    7/22

    7

    EDais Advanced classes tutorial

    Youll also notice that Ive not put any drawing code into the class, instead put a

    comment where the drawing code would go. This is because this class is not

    responsible for handling the drawing since that varies for each shape; well cover

    exactly how thats done in the next chapter.

    Well now need a method to add points to the class since otherwise well never have

    anything to draw. This can be written as an intelligent function, since the

    NumPoints variable is exposed in the public interface the number of points (And thus

    the size of the internal point array) can be set via this. If however we try to add a

    point beyond the current size of the array we can both add a point to the array and

    increment NumPoints. First up, well need to make sure setting NumPoints properly

    re-declares the array and that it cant be set to a silly value:

    PublicPropertyLet NumPoints(ByValinNewAsLong)m_NumPoints=IIf(inNew

  • 8/8/2019 VB Tutorial Class

    8/22

    8

    EDais Advanced classes tutorial

    WithShapePoints(LastPoint).X=inX.Y=inYEndWith

    LastPoint=LastPoint+1AddPoint=LastPoint'ReturnindexEndFunction

    Well need to cope with the case where we have a number of points declared but then

    NumPoints gets set to a lower number, thus making LastPoint refer to a point out of

    the bounds of the array, simply add this line to the end of the NumPoints Property Let

    method:

    If(LastPoint>inNew)ThenLastPoint=inNew

    This allows us to put point data into the class, but well also need to extract it whendrawing so Ive created a subroutine that returns the X and Y coordinates of any

    given point:

    PublicFunctionGetPoint(ByValinIndexAsLong,_ByRefoutXAsLong,ByRefoutYAsLong)AsBooleanIf((inIndexLastPoint)) ThenExitFunctionoutX=ShapePoints(inIndex).XoutY=ShapePoints(inIndex).YGetPoint=TrueEndFunction

    They also perform some basic checking to make sure the requested point isnt out ofbounds, not the fastest method but itll do fine for now.

    As far as the base class goes, this is about all we need. In the next chapter well go

    about creating a class derived from this one and getting the two to communicate.

    Heres a visual representation of what weve just created:

    Youll see Ive segregated the internal information into three sections; Private data &

    methods, Member variables and Public methods. The private data & methods never

    get exposed since these are encapsulated within the class. The rest gets exposed as

    needed through the public interface, which you can see on the right, represented bythe two thick arrows for the properties and methods of the class respectively.

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    9/22

  • 8/8/2019 VB Tutorial Class

    10/22

    10

    EDais Advanced classes tutorial

    EndPropertyPublicPropertyLet ShapeCol(ByValinNewAsLong)BaseClass.ShapeCol=inNewEndProperty

    Notice that we dont expose the NumPoints property (And similarly the AddPoint() orGetPoint() functions) since a rectangle will always have 2 points (Top left and bottom

    right).

    In the shape class there was no creation method since there wasnt really anything we

    could do to initialise the shape, however with a rectangle we can assign a position,

    width and height so add a Create() method now:

    PublicSubCreate(ByValinXAsLong,ByValinYAsLong,_ByValinWidthAsLong,ByValinHeightAsLong)WithBaseClass.ShapePosX=inX

    .ShapePosY=inYCall.AddPoint(0,0)Call.AddPoint(inWidth,inHeight)EndWithEndSub

    Note: This Create method is designed to emulate whats known as a constructor

    function in OO terminology, which simply initialises the object in a single line.

    This simply sets the X and Y coordinates of the shape, and then adds the two points

    (The first point is created at 0, 0 since its offset by the X and Y properties when

    drawn.)

    Now, we come to implement the Draw method and we come across a problem.

    Whilst we can propagate down the inheritance tree through pass-through methods,going the other way is a little more complex. Thankfully though there is a solution

    Events as depicted in the following illustration:

    Events, like methods, are called asynchronously meaning that code execution stops in

    the method that raised the event until all the code in event handler for the event has

    completed. In this respect we can add an event to the base class to let the inherited

    class know that its set up the drawing surface and its ready to go. Using this same

    interface we can pass the result of the drawing operation back down through the event

    to return its success or failure via a parameter passed by reference.

    In case youre at all confused about the difference between passing a parameter byvalue (ByVal) or by reference (ByRef) then Ill explain them here since its important

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    11/22

    11

    EDais Advanced classes tutorial

    to understand how this technique works, if you already understand it then skip the

    next paragraph.

    In VB were hidden from the complexities of memory and pointers since its all dealt

    with for us behind the scenes, however sometimes we need to take a little more

    control to tweak the way things work. When we create a variable what happens is we

    ask VB which in turn asks the operating system to allocate it some memory to store it

    in. VB gets the address to this piece of memory in the form of a pointer to it, which

    is nothing more complicated than just a big number Think of it in the same way as

    you would your house address but in the computers case theres a few billion houses

    on Memory street! Going back to the house analogy again, if someone wanted to

    know where you lived you could either tell them the address or walk them to the door

    and show them the house in person, this effectively the difference between ByRef and

    ByVal. When we pass a parameter by reference we actually pass a pointer to that

    data in memory, telling VB where to go to get to the actual value of the variable.

    When a parameter is passed by value we simply pass the value of the variable and as

    such there is no link back to the original. This is the important difference between thetwo methods A variable passed by reference to a method can be changed by that

    method, one passed by value cannot. By default parameters are passed by reference

    in VB6 (Note: In VB.NET the default was changed to by value) but in most cases it

    makes sense to pass parameters by value unless they explicitly need to be changed

    within the method.

    Add this function declaration to the base class:

    PublicEventDrawNow(ByValinDCAsLong,ByRefoutResultAsBoolean)

    Note that were passing the outResult parameter by reference so were actually

    passing a pointer to the variable rather than the value itself, this is very important as itallows us to data both ways, in this case we can pass the DC into the event and the

    result can be passed back.

    In the Draw() event of the base class, I added a comment to show where the drawing

    code would go. Raise the event there, passing the inDC parameter as the first

    parameter and the function name (Return value) as the second parameter:

    RaiseEventDrawNow(inDC,Draw)

    It may seem a bit odd to pass the function name as the second parameter, but heres

    whats happening:

    Rectangle class gets told to Draw() onto a DC. Rectangle class tells base class to Draw(), passing DC handle as parameter. Base class sets up the drawing DC and raises the DrawNow() event telling

    the inherited class its ready to draw, passing back the DC handle and a

    pointer to its return value.

    Inherited class responds to event and draws onto the DC, passing the resultdown through the event to the base class.

    Base class cleans up DC and quits, returning drawing result to inherited class. Inherited class passes drawing result through to whatever called it in the first

    place.

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    12/22

    12

    EDais Advanced classes tutorial

    Wow, complicated... Dont worry; its not too bad once youve done it a few times.

    Well need to tell the inherited class to respond to the base classes event now, so

    well need to add WithEvents to its declaration:

    DimWithEventsBaseClassAsclsShape

    Now youll find the BaseClass on the Object dropdown with its DrawNow event

    exposed and ready to respond to, so well add the drawing code there. Of course

    youll need the Rectange() API declaration for this so copy that into the top of the

    rectangle class, and add a call to it, passing the offset shape points:

    PrivateSubBaseClass_DrawNow(ByValinDCAsLong,ByRefoutResultAsBoolean)DimBasePts(3)AsLongWithBaseClass'GrabtheshapepointsCall.GetPoint(0,BasePts(0),BasePts(1))Call.GetPoint(1,BasePts(2),BasePts(3))

    outResult=Rectangle(inDC,_BasePts(0)+.ShapePosX,BasePts(1)+.ShapePosY,_BasePts(2)+.ShapePosX,BasePts(3)+.ShapePosY)0EndWithEndSub

    Finally pass the Draw() method through as usual to base class so it can set the DC up

    for us:

    PublicFunctionDraw(ByValinDCAsLong)AsBooleanDraw=BaseClass.Draw(inDC)

    EndFunction

    Thats all there is to it. Lets create a simple project to test this out, add a picture box

    to the form, set its AutoRedraw property to True then paste this code into the form

    and run:

    PrivateSubForm_Load()DimMyClassAsNewclsRectangleForm1.AutoRedraw=TrueWithMyClass

    Call.Create(10,20,30,40).ShapeCol=vbRedCall.Draw(Form1.hDC)EndWithCallForm1.RefreshSetMyClass=NothingEndSub

    Hopefully you could see a red rectangle being drawn, if not then check back through

    the code to make sure youve not missed something and if all else fails, grab my copy

    of the code and see whats different.

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    13/22

    13

    EDais Advanced classes tutorial

    CChhaapptteerrIIVV

    Writing more derived classes

    Rather than going on to more new things, Im going to go over the last chapter again,

    in creating a second shape derived from the base class, this time a circle class. If

    youre satisfied that you understand the principle then just grab my code for the class

    and move onto the next chapter since nothing new will be covered here.

    If youve decided to stick with this for now then add a new class called clsCircle to

    the project and add the BaseClass definition, the initialise/terminate events of the

    class and the standard pass-through properties as we did in the previous chapter:

    DimWithEventsBaseClassAsclsShape'PublicinterfacetomembervariablesPublicPropertyGetShapeCol()AsLongShapeCol=BaseClass.ShapeColEndPropertyPublicPropertyLet ShapeCol(ByValinNewAsLong)BaseClass.ShapeCol=inNewEndPropertyPublicPropertyGetShapePosX()AsLongShapePosX=BaseClass.ShapePosXEndPropertyPublicPropertyLet ShapePosX(ByValinNewAsLong)BaseClass.ShapePosX=inNewEndPropertyPublicPropertyGetShapePosY()AsLongShapePosY=BaseClass.ShapePosYEndPropertyPublicPropertyLet ShapePosY(ByValinNewAsLong)BaseClass.ShapePosY=inNewEndProperty'ClasseventhandlersPrivateSubClass_Initialize()SetBaseClass=NewclsShapeEndSubPrivateSubClass_Terminate()SetBaseClass=NothingEndSub

    Now, there are two ways of creating a circle, the first being a centre point, width and

    height and the second a corner-to-corner method a-la the Rectangle. The latter is the

    method the GDI Ellipse() API uses but I prefer the former since I find it more

    intuitive. As such Ill add methods for both and the centre-out creation method will

    translate its points into corner-to-corner mode:

    PublicSubCreateCtoC(ByValinXaAsLong,ByValinYaAsLong,_ByValinXbAsLong,ByValinYbAsLong)WithBaseClass

    .ShapePosX=inXa.ShapePosY=inYaCall.AddPoint(0,0)

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    14/22

    14

    EDais Advanced classes tutorial

    Call.AddPoint((inXb-inXa),(inYb-inYa))EndWithEndSubPublicSubCreateCOut(ByValinXAsLong,ByValinYAsLong,_ByValinWidthAsLong,ByValinHeightAsLong)

    WithBaseClass.ShapePosX=inX-(inWidth\2).ShapePosY=inY-(inHeight\2)Call.AddPoint(0,0)Call.AddPoint(inWidth,inHeight)EndWithEndSub

    This may look a little strange since theyre re-mapping their coordinates to local

    space but it makes a lot of sense in the long run. Ok, we have the public interface set

    up and the shape created, now all that remains is to draw. Were going to need to

    draw a circle so well need a declare to the Ellipse() API before we can go aboutdrawing so copy and paste that into the top of the circle class now.

    The Draw() function will pass through to the base class in exactly the same way as we

    did in the rectangle class:

    PublicFunctionDraw(ByRefinDCAsLong)AsBooleanDraw=BaseClass.Draw(inDC)EndFunction

    Now finally we need to respond to the base classes DrawNow event where we will do

    the drawing, so select the class from the Object drop-down (Make sure youve

    declared it WithEvents) and youll be put into the event handler since its the onlyevent exposed by the base class. Because the Ellipse() API works corner-to-corner in

    the same way as the Rectangle() API, we can simply copy and paste the code from

    the rectangle class and just change the call from Rectangle() to Ellipse():

    PrivateSubBaseClass_DrawNow(ByValinDCAsLong,ByRefoutResultAsBoolean)DimBasePts(3)AsLongWithBaseClassCall.GetPoint(0,BasePts(0),BasePts(1))Call.GetPoint(1,BasePts(2),BasePts(3))

    outResult=Ellipse(inDC,_BasePts(0)+.ShapePosX,BasePts(1)+.ShapePosY,_BasePts(2)+.ShapePosX,BasePts(3)+.ShapePosY)0EndWithEndSub

    And were done!

    To test it, change the code in the form to:

    PrivateSubForm_Load()DimMyClassAsNewclsCircleForm1.AutoRedraw=True

    WithMyClass

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    15/22

    15

    EDais Advanced classes tutorial

    .ShapeCol=vbGreenCall.CreateCOut(15,20,30,40)Call.Draw(Form1.hDC)EndWithCallForm1.Refresh

    SetMyClass=NothingEndSub

    You should see a green circle being drawn but if you dont then check your code

    against mine and see what differs.

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    16/22

    16

    EDais Advanced classes tutorial

    CChhaapptteerrVV

    The power of a common interface

    At this point I wouldnt blame you for thinking that whilst it cuts down on a bit of

    programming this whole derived class business just seems like a lot of hard work and

    fuss over something trivial. This method of programming has a few more tricks up its

    proverbial sleeves yet to try and win you over which Ill discuss now.

    It may seem a little silly adding the same code in every class since this method is

    supposed to cut down on the amount of coding rather than creating more but it does

    allow us to do some cool things by abstracting the data types. Since all the objects

    weve created have a .Draw() method and their colour can be changed in a generic

    way then it actually doesnt matter which shape object were dealing with, we can

    just tell it to draw and it will work out what it is and what it should be doing. There

    are a couple of ways this can be demonstrated; the first is to coerce the object into a

    variant type:

    PrivateFunctionDrawShape(ByRefinShapeAsVariant)AsBooleanDrawShape=inShape.Draw(Form1.hDC)EndFunction

    No matter which shape class we send this function, it will draw onto the specified DC

    (Or return False if for some reason the drawing failed). If we later create a Polygon

    class that uses the same common interface, we can still send it to this function to tell

    it to draw without changing a simple thing in the underlying application.

    You may or may not find this interesting or particularly useful but the thing Im

    trying to emphasise here is abstraction - were abstracting the problem down from

    how to draw a circle or how to draw a rectangle to simply how to draw a shape.

    This may or may not seem important for the time being since this example mostlikely had no relevance to anything youd ever need to code, but numerous

    programming tasks can be broken down into simpler areas to which this abstraction

    approach fits very well.

    The second way we can take advantage of this new technique of abstraction is by

    using VBs Collection object. Dont worry if youve never used them before, up until

    a week ago neither had I (At least creating and using them usefully in an application),

    theyre very easy though, heres an example:

    PrivateSubForm_Load()DimMyRectAsNewclsRectangle

    DimMyCircAsNewclsCircleDimShapesAsCollectionDimDrawShapesAsLong'CreatetwoshapesCallMyRect.Create(10,10,50,30)CallMyCirc.CreateCOut(35,60,50,30)'Createacollectionof'shapes'andpushtheindividualshapeobjectsintoitSetShapes=NewCollectionCallShapes.Add(MyRect)CallShapes.Add(MyCirc)

    Form1.AutoRedraw=True'Drawalltheshapeobjectsregardlessofwhattheyare

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    17/22

  • 8/8/2019 VB Tutorial Class

    18/22

    18

    EDais Advanced classes tutorial

    CChhaapptteerrVVII Interfaces and wrapped collections

    So far weve seen how to create multiple classes that expose the same functions,

    however because VB is incapable of supporting a true base class we must refer to

    them either by their individual class types or as Variants. The latter approach works

    ok but its somewhat clunky and since Variants can hold different variables types we

    get no intellisense, which is somewhat counterintuitive to work with.

    Whilst VB doesnt allow us to derive our classes from a common base class, it does

    have support for interfaces which we can use to simulate the same kind of behaviour.

    An interface is really just a blueprint for a class in the same way as a class is the

    blueprint for an object, and is used as a contract that any class implementing it must

    agree to adhere to.

    To create the interface our shape objects will derive from, simply add a new class

    module to the VB project and name it IShape Interface names are generally

    prefixed I.The interface itself contains only the public properties and methods we wish to

    expose, so youll want something like this:

    PublicPropertyGetShapeCol()AsLongEndPropertyPublicPropertyLet ShapeCol(ByValinNewAsLong)EndPropertyPublicPropertyGetShapePosX()AsLongEndPropertyPublicPropertyLetShapePosX(ByValinNewAsLong)EndProperty

    PublicPropertyGetShapePosY()AsLongEndPropertyPublicPropertyLet ShapePosY(ByValinNewAsLong)EndPropertyPublicFunctionDraw(ByValinDCAsLong)AsBooleanEndFunction

    For one of our shape classes to implement this interface, we simply add this line at

    the start of the class:

    ImplementsIShape

    If you try running the application at this point, it will error and tell you that your class

    does not expose some of the functionality in the interface so you must always

    implement everything. Rather than copying out all the function headers for each

    method inside the interface, youll find a new entry called IShape in the left

    dropdown menu in the code editor, and selecting the function names from the right

    one will fill in the function stubs for you. Now all thats left is to move the

    functionality from the class itself into these new method stubs:

    PrivateFunctionIShape_Draw(ByValinDCAsLong)AsBoolean

    IShape_Draw=BaseClass.Draw(inDC)EndFunction

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    19/22

    19

    EDais Advanced classes tutorial

    PrivatePropertyGetIShape_ShapeCol()AsLongIShape_ShapeCol=BaseClass.ShapeColEndPropertyPrivatePropertyLetIShape_ShapeCol(ByValinNewAsLong)BaseClass.ShapeCol=inNewEndProperty

    PrivatePropertyGetIShape_ShapePosX()AsLongIShape_ShapePosX=BaseClass.ShapePosXEndPropertyPrivatePropertyLetIShape_ShapePosX(ByValinNewAsLong)BaseClass.ShapePosX=inNewEndPropertyPrivatePropertyGetIShape_ShapePosY()AsLongIShape_ShapePosY=BaseClass.ShapePosYEndPropertyPrivatePropertyLetIShape_ShapePosY(ByValinNewAsLong)BaseClass.ShapePosY=inNew

    EndProperty

    The class itself should now have no properties and no Draw() method, they are now

    only accessible through the interface. Basically weve not really added anything to

    the class itself, just changed things around a little but in exchange we can now refer to

    any class that implements this interface simply as an IShape, regardless of what it

    actually is.

    Perform this modification on the Rectangle and Circle classes, then well modify the

    last example to take the new interface into account.

    Since the ShapeCol property and Draw() methods are now exposed through the

    interface rather than the classes public interface, we cant simply go through the

    collection and access them directly off each object but instead must cast them to atemporary variable declared as an IShape:

    DimTempCastAsIShape...SetTempCast=Shapes(DrawShapes)WithTempCast.ShapeCol=Rnd()*vbWhiteCall.Draw(Form1.hDC)

    EndWithSetTempCast=Nothing

    It may seem like a step in the wrong direction since we now have more code to have

    to deal with, but you may notice that inside the With bock we now get

    intellisense/code-completion on the interfaces methods which is IMO a bigger step

    forward and worth the extra couple of lines.

    One thing still remains a little messy here though, and thats the Collection object

    which could in theory hold anything and again we get no intellisense on it since its

    just a collection of stuff.

    What we really want is a hard-typed collection and for that we need another class that

    wraps a standard Collection object, add a new class module and call it clsShapes.

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    20/22

    20

    EDais Advanced classes tutorial

    If you hit F2 now and have a look at the standard Collection object in the object

    browser, youll see that it has 4 methods; Add, Count, Item and Remove,

    however if you opposite click and choose Show hidden members youll see a 5th,

    _NewEnum, which well get to in a bit.

    Fist off though, well use a Collection as our base class and expose its 4 public

    methods, however rather than using Variants as the contents well use IShapes:

    Dimm_CollectionAsCollectionPublicPropertyGetCount()AsLongCount=m_Collection.CountEndPropertyPublicPropertyGetItem(ByValinIdxAsLong)AsIShapeSetItem=m_Collection.Item(inIdx)EndProperty

    PublicSubAdd(ByRefinNewAsIShape)Callm_Collection.Add(inNew)EndSubPublicSubRemove(ByValinIdxAsLong)Callm_Collection.Remove(inIdx)EndSubPrivateSubClass_Initialize()'InstantiatebaseclassSetm_Collection=NewCollectionEndSubPrivateSubClass_Terminate() 'Terminatebaseclass Setm_Collection=NothingEndSub

    This doesnt expose functionality for a keyed collection, but you can add that later if

    you wish.

    The obscure 5th method we saw in the object browser is whats responsible for

    giving us For ... Each functionality to iterate the objects within the collection, and

    since were only going to be working with objects it makes good sense to add it here

    too:

    PublicFunctionNewEnum()AsIUnknown'Requiredfor"For...Each"SetNewEnum=m_Collection.[_NewEnum]

    EndFunction

    One more thing remains before VB will recognise this as a valid enumerator, and

    thats to set its procedure ID by opening Tools -> Procedure Attributes.

    Select the NewEnum method, expand the Advanced section at the bottom and set

    the procedure ID to -4 which is the special code for an enumerator in VB. Since this

    is a bit of an odd function and only of any use to VB, well also check the Hide this

    member checkbox here to avoid cluttering the public interface.

    At this point the Shapes collection is complete, so we can go back to the original code

    and use it instead of the generic Collection object:

    DimMyRectAsNewclsRectangleDimMyCircAsNewclsCircle

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    21/22

    21

    EDais Advanced classes tutorial

    DimShapesAsNewclsShapesDimShapeAsIShape'CreatetwoshapesCallMyRect.Create(10,10,50,30)CallMyCirc.CreateCOut(35,60,50,30)

    'PushindividualshapesintoshapescollectionCallShapes.Add(MyRect)CallShapes.Add(MyCirc)Form1.AutoRedraw=True'DrawalltheshapeobjectsregardlessofwhattheyareForEachShapeInShapesWithShape.ShapeCol=Rnd()*vbWhiteCall.Draw(Form1.hDC)EndWith

    NextShapeCallForm1.RefreshSetShapes=NothingSetMyRect=NothingSetMyCirc=Nothing

    The majority of the code looks the same, but the shape drawing loop is a lot nicer and

    demonstrates the use of the enumerator.

    Drawing all the shapes in a shapes collection seems like it would be a pretty common

    requirement, and since we now have our own collection object we can add that

    functionality right into it:

    PublicSubDraw(ByValinDCAsLong)DimShapeAsIShapeForEachShapeInm_CollectionCallShape.Draw(inDC)NextShapeEndSub

    Now the drawing code can be shortened down to:

    'SetsomerandomcoloursfortheshapesForEachShapeInShapesShape.ShapeCol=Rnd()*vbWhiteNextShape'DrawallshapesincollectiontoDCCallShapes.Draw(Form1.hDC)

    Since every shape in the collection has a ShapePosX and ShapePosY property that

    can move the origin of the shape, we could also add a very simple method to the class

    that moves all the shapes within it:

    PublicSubOffset(ByValinXAsLong,ByValinYAsLong)DimShapeAsIShape

    Http://www.mvps.org/EDais/

  • 8/8/2019 VB Tutorial Class

    22/22

    22

    EDais Advanced classes tutorial

    ForEachShapeInm_CollectionShape.ShapePosX=Shape.ShapePosX+inXShape.ShapePosY=Shape.ShapePosY+inYNextShapeEndSub

    Now we can call this from the form to draw multiple instances of the shapes:

    DimLoopDrawAsLong...ForLoopDraw=0To9'SetsomerandomcoloursfortheshapesForEachShapeInShapesShape.ShapeCol=Rnd()*vbWhiteNextShape

    'DrawallshapesincollectiontoDCthennudgethemCallShapes.Draw(Form1.hDC)CallShapes.Offset(5,5)NextLoopDraw

    I hope this has helped outline some of the possibilities of the technique, feel free tocontact me if you still have questions about anything covered in the tutorial.

    Mike D Sutton EDais 2004