|
|
|
|
REALbasic: The Definitive GuideBy Matt Neuburg1st Edition October 1999 1-56592-657-9, Order Number: 6579 686 pages, $29.95 |
Chapter 3 Objects, Classes, and Instances
In this chapter:
Messages and Dot Notation
Object Design Philosophy
Classes and Instances
Anatomy of a Class
An Instance Is Born
Referring to Instances
The Truth About Controls
Control Clones and Control Arrays
Being Careful with Instance References
Destruction of InstancesProgramming is a way of thinking; code is a way of speaking--a language. The subject of this language is what a program is supposed to do; the programmer's task is to describe how the program works. An object is a programming construct which lets the program, and hence the programmer's thought and speech about it, be organized in a natural, powerful way: the program works as if it consisted of autonomous bundles of functionality (the objects). This organization facilitates thought and speech about the program, because the objects the program consists of are somewhat like the bundles of reality, the "things," the "objects," in terms of which we perceive and describe the natural world.
What are the objects of which a program is constructed? In general, that's up to the programmer, who, rather like some divinity creating a small universe, dictates both what objects should populate that universe ("Let there be light; let there be the sun; let there be the earth") and what those objects should do and how they should interact ("Let the sun make light to shine upon the earth"). Such power may seem daunting, almost paralyzing, rather than helpful. Where to begin? If there were an easy answer, there wouldn't be approximately six gazillion books about designing object-oriented programs.
REALbasic both enforces object-oriented programming and helps you with object-oriented design. Before you write a line of code, the construction of your program's universe has already been started for you. The program you are contemplating is to be an application; REALbasic is an application framework, and has stocked your program, beforehand, with objects that mirror the physical elements of an application's interface: windows, buttons, text fields, menu items, and so forth. Such physical elements of the interface are the objects of the "natural world" the user perceives when working with your application, and so are the very objects in terms of which it is most natural to think and speak about the operation of your program ("This button should react to being pressed by opening that window"). REALbasic's built-in objects act as repositories of code, organizing your program into bundles that reflect the structure of its interface; they also come equipped with the functionality needed to implement that interface as the application runs, so the user actually sees and can interact with their physical counterparts on the screen: windows know how to open, buttons know how to be pressed, text fields know how to accept typing, menu items know how to be chosen.
In writing an application with REALbasic, you take advantage of the existing stock of objects to give your program both its basic functionality and its basic organization. You are not, however, entirely freed from having to make intelligent object-oriented design decisions. You will have to construct your program's interface. You will have to decide how to allocate your code among the objects that implement that interface. And if your program's functionality demands additional types of object, you will have to implement them from the ground up.
The purpose of this book is to teach you what you will need to know to do this. Throughout this chapter and the rest of , I'll be describing the REALbasic object model, explaining how you work with objects in general, and how they fit together to provide the architecture of your application as a whole. The subsequent parts of the book detail the functionality of each of REALbasic's built-in objects.
Messages and Dot Notation
As a programming construct, objects divide a program into pieces. The glue that unites those pieces to form a single working program is the ability of the objects to send each other commands and ask each other questions. The objects are autonomous, but they can communicate; this communication takes place by means of messages. In REALbasic code, the sending of a message to an object is expressed by dot notation.
Suppose, for example, that we have an object called MyWindow and we wish to tell it to turn blue. We could do this by sending it a TurnBlue message--assuming, of course, that the MyWindow object can accept a TurnBlue message. If it can, then we might say:
myWindow.turnBlueThis shows the basic syntax whereby messages are sent: the name of the object is followed by a dot, which is followed by the name of the message. This syntax is what is denoted by dot notation.
But how does this syntax fit into the larger syntax of the REALbasic language? The answer is that every message is one of two types, both of which are familiar to us from Chapter 2, The Basic Language: it operates, depending on the particular message, either as a subroutine call or as a variable name. As a matter of nomenclature, it happens that the subroutines that can be called by sending a message to an object are called methods, and the variable-like things whose values can be accessed by sending a message to an object are called properties. But syntactically and conceptually, they present nothing new. Since we already know how to use subroutine calls and variable names in code, we already know how to use messages as well.
To illustrate, suppose a particular message designates a method. That means it operates as a subroutine call. As we know, subroutines can be either procedures or functions, so let's suppose this message is a procedure call. Then, if that procedure takes no parameters, we might say:
myWindow.turnBlueIf the procedure takes one parameter (perhaps an integer telling how blue the window should turn), we might say:
myWindow.turnBlue 32If the subroutine is a function taking no parameters (perhaps returning an integer telling how blue the window is), we might say:
dim yourBlueness as integeryourBlueness = myWindow.howBlue()All the other forms of syntax may also be used, as appropriate to a procedure or a function, taking zero or more parameters.
Now, suppose a particular message designates a property. That means it operates as a variable name. For example, the window might accept a message representing an integer, denoting the maximum blueness this window should allow itself to adopt. Then we might say:
myWindow.maxBlueness = 40or:
dim theMax as integertheMax = myWindow.maxBluenessor:
myWindow.maxBlueness = myWindow.maxBlueness + 10Thus, aside from dot notation itself, no new syntax is required to send a message to an object, beyond what was already described in Chapter 2.
REALbasic also comes with a large number of built-in procedures and functions; for example, in Chapter 2, we already had cause to mention the MsgBox procedure, the Beep procedure, and the Str function. To call these, as we have already seen, you just use their names; no dot notation is required. For the sake of completeness, though, and for a rigorous understanding of their ontological status, you might like to consider such built-in procedures and functions to be methods of a sort of supreme, ultimate, universal object--the global object.
A thing is global if it is available from everywhere without specifying an object that it belongs to; so, for example, since any code whatever can call the Beep procedure without using dot notation, the Beep procedure is global. So, we can pretend there is a universal object, which we might term REALbasicItself, and that when we say:
beepit is really a kind of shorthand for:
REALbasicItself.beep // you can't actually say this!This construct is only a pretense; there actually is no REALbasicItself object, and you can't actually send a message to it as in the second example. But there is a sense in which this pretense is accurate, and helps explain the status of REALbasic's built-in procedures and functions: when you call such a procedure or function, you're actually sending a message to REALbasic itself, and accessing one of its methods.
Object Design Philosophy
Methods and properties correspond to the two primary purposes of objects as programming constructs: encapsulation of functionality, and maintenance of state. These two purposes underlie the design of object-oriented code, and an understanding of them will help you to make sense of REALbasic's built-in objects, to appreciate REALbasic's application architecture, to organize your code intelligently, and to know when to create your own objects.
Encapsulation of Functionality
The idea behind encapsulation of functionality is that each object should be the repository of knowledge about how to do all the things appropriate to itself.
Consider, for example, a shoot-em-up arcade game, where every time the user hits a target, the target explodes, and the score, displayed in a box, increases. Now, we could write such a program without using object orientation at all; it's all just pixels on a screen, after all, and code that controls pixels has the same effect no matter how it's organized. But if we do use object orientation, the very language in which we describe the program, where the nouns are "target" and "score" and the verbs are "explodes" and "increases", suggests that the target and the box containing the score are two different objects, and that it is the target that should contain the code for exploding, and the scorebox that should contain the code for increasing. The target "knows" how to explode; the scorebox "knows" how to increase. In other words, the target object has an Explode method, and the scorebox object has an Increase method.
Messages can be sent to the appropriate objects to instigate the appropriate actions. Instead of exploding the target from elsewhere, we send the target the Explode message, as if to say: explode yourself! Instead of increasing the score from elsewhere, we send the scorebox the Increase message, as if to say: increase yourself! In fact, our use of the conjunction "and" in describing the program's behavior suggests that it should probably be the target which, as it explodes, tells the scorebox to increase.
When code is object-oriented, and when the objects encapsulate their appropriate functionality, the objects become more like objects in the real world. It is encapsulation of functionality that gives objects their autonomy. Only the target needs to know how to explode; all other objects can remain blissfully ignorant of the details, secure in the knowledge that they can just say
target.explodeand all the right things will happen. Moreover, as we develop the program, we can change what an explosion consists of (we can decide to add a sound, for instance) without affecting any code in any other objects; they still just saytarget.explode. Indeed, the target object may be so autonomous as to have virtually no dependency on the rest of the program; everything the target needs in order to function is inside itself. This makes it easier to write and maintain not only this program, but any other programs that may need targets; to implement targets in another program, we have only to copy into that program our target object code from this one.Maintenance of State
The idea behind maintenance of state is that a value needing to be preserved over time should be preserved as part of the object that chiefly operates on it.
Recall that our arcade game is to have a scorebox that knows how to increase itself, and observe that this implies there is a score. Even while the user is busy missing targets, or taking time out to answer the telephone, some object is remembering this score. It makes sense that this object should be the scorebox itself; the jobs of increasing the score, displaying the score, and knowing the score are naturally related. In other words, the scorebox object has a Score property.
Once again, this approach allows other objects to be ignorant of the details, and allows the scorebox to maintain autonomy. When the user hits a target, the score should increase. Let's say that when the user hits a different object (a bad guy), the score should also increase. Both the target object and the bad guy object react to being hit by telling the scorebox to increase itself:
scorebox.increase. Neither has to worry about what this means, or what the score is, or how the user will be shown the score. The scorebox, in its turn, doesn't have to worry about who is telling it to increase the score. It just sits there, remembering the score, and when it's told to increase it, it does so, and it both remembers and displays the new score, which is simply the value of its Score property.To be sure, this example is particularly clear-cut in a way that is sometimes not the case. Interesting questions of philosophy and implementation often arise. That's part of the fun, and the challenge, of designing an object-oriented program.
For example, the target object and the bad guy object don't need to know the score or manipulate it themselves; but what if they did? Should they be permitted to access the scorebox's Score property directly, or should they be required to call one of the scorebox's methods? In other words, is the scorebox not just this value's container, but also its owner and, in some sense, its protector?
Furthermore, it may not always be entirely obvious which is "the object that chiefly operates" upon a certain value. It often seems that direct access to a value needs to be shared among several different objects; should one of these be its container, or its owner and protector, or should the value be spun off into a different object entirely?
Then there is the problem of a value's life-cycle. Clearly, if the scorebox object for some reason goes out of existence, yet we still need access to the score, the scorebox was an inappropriate choice of container for the score. At the other extreme, we could make the score permanently and publicly accessible and part of no object at all[1], but this seems to defeat the purpose of object-orientation, if there is any object that seems naturally to be the score's owner.
These are all practical and philosophical design decisions with which the programmer must constantly grapple.
Summary
Figure 3-1 represents some of the features of objects we've just discussed: their autonomy, their ability to receive and send messages, their encapsulation of functionality, and their maintenance of state.
Figure 3-1. Objects doing their jobs autonomously
![]()
Functionality is expressed by methods. State is expressed by properties. Code that is well organized into objects with appropriate methods and properties has several virtues. It is:
- Legible
- When code in some object says
target.explode, it's fairly obvious what it's meant to do.- Maintainable
- If we decide to modify the code that explodes the target, we know where to find it, and code outside the object remains untouched.
- Expandable
- If we decide the target needs to be able to do something more, such as move to the left, we know where to put the code.
- Portable
- If we write another game that uses targets, we can just export our target object code from this program and import it into the other.
Classes and Instances
An object is an abstraction, a way of thinking and speaking. We come now to the details of how objects are actually implemented in the REALbasic programming environment. They appear in two forms: as classes, and as instances. We begin with an explanation of what a class is.
Consider the notion that every object is of some type. The notion of types of object accords intuitively with our ideas of objects in the real world; these two pencils are two different objects, yet they are both pencils. Such a notion implies immediately that our program may have more than one object of the same type; for if this were not possible, and every object were the only one of its kind, the notion of types of object would be superfluous.
The possibility of our program containing more than one object of the same type has both a ready intuitive appeal and an obvious practical value. Returning to our arcade game example, imagine that the user is to be confronted with numerous targets marching across the screen. They look alike, and they behave alike (they explode when hit). It makes perfect sense that we should be able to say and think that each of these targets is a different object (only the one that is hit will explode), yet they are all targets (any one that is hit will explode). The same is true of the scorebox; there might be two different scores (how many large targets the user has hit, and how many small targets), each of which is increased individually, and each of which is displayed in a separate box. It makes perfect sense that we should be able to maintain two different scores in two different objects, yet we should be able to say and think that both objects are scoreboxes (each contains a score that it knows how to display and increase).
Furthermore, without the ability to implement multiple objects of the same type, objects threaten to become a hindrance to the organization of our code, rather than a benefit. Suppose our arcade game is to show the user twenty targets, each of which can be sent the Explode message. If exploding is to be implemented the same way for every target, and yet that same code must be written separately in each target, imagine what this will mean both for writing the code originally and for changing it later on! The programmer would surely collapse in a state of boredom, frustration, or madness before the program was ever completed. Clearly, in order to do useful object-oriented programming at all, we must have a way to implement object types, and this implementation must include a facility for sharing code, so that all objects of the same type can be sent the same messages and can respond identically to them.
The programming construct that expresses the notion of an object type is called a class. Methods and properties that are to be in common for every object of a certain type are placed in the class which represents that type.
For example, if we have a Target class, then we write one Explode method, in the Target class. Now any and all targets in our program will be able to receive the Explode message and will know what to do in response. If we decide to change the way all targets respond to the Explode message (we decide to add a sound to the explosion), we change the code in just one place, the Explode handler in the Target class.
Similarly, if we have a ScoreBox class, then we write one Increase method, in the ScoreBox class, and we create one Score property, in the ScoreBox class. Now any and all scoreboxes in our program will maintain a score, and will know how to increase it and how to display it.
However, there's an important distinction to be drawn here between the case of a method and the case of a property. For example, every scorebox will have an Increase method (because the ScoreBox class has one), and it will be the same method.[2] On the other hand, every scorebox will have a Score property (because the ScoreBox class has one), and its datatype will be the same (namely, the datatype declared for this property in the ScoreBox class), but its value will be separate, and separately maintained, for each scorebox. In other words, a property is declared in the class, but the individual objects in the program still maintain state individually, so each has its own value for that property.[3] This makes intuitive sense if we consider an object such as a button: clearly any button has a location within its window, so it should have a Top property and a Left property, but we would not want all buttons to be forced to have the same location.
So, a class is an object type, with facilities for sharing code and property declarations among individual objects. Those individual objects, for their part, are called instances; and every object is said to be an instance of some class. In our arcade game, each target that the user sees represents a separate instance of the Target class, and each of the boxes showing a different score represents a separate instance of the ScoreBox class.
Whenever a message is sent, it is sent to some particular instance. Returning to Figure 3-1, we can see why this makes sense. A target is a type of object, and any target object needs to know how to explode; but at this moment we're sending the Explode message to this particular target. A scorebox is a type of object, and any scorebox object needs to maintain a score and to know how to increase it; but at this moment we're sending the Increase message to this particular scorebox so it will increase its particular score.
It may appear at this point that the relationship between classes and instances borders upon the metaphysical. Anyone who knows anything about philosophy will be inclined to suspect that classes and instances are just another way of talking about universals and particulars. Indeed, object-oriented programming seems to fulfill Plato's philosophical program announced in the Euthyphro (6e):[4]
SOCRATES. Now, you recall that I asked you to explain to me, not this or that particular pious thing, but that Form Itself through which all pious things are pious? You did say, I believe, that it was through one Form that impious things are impious and pious things are pious; don't you remember?EUTHYPHRO. Yes, I do.SOCRATES. All right, then; so, explain to me what is this Form Itself, so that by keeping my eyes upon it and using it as a model, I may declare that whatever you or anyone else does that is of this sort, is pious, and that whatever is not, is not.The problems with Plato's characterization are well known: the Form seems to be a "thing" separate from the particular things of the world around us, the notion "through" is crucial but slippery, and Plato seems to equivocate rather glibly between the Form's being responsible for a thing's being such-and-such and our ability to know that a thing is such-and-such; thus, his program is almost certainly doomed to failure as an explanation of how the world works. But he is perfectly accurate about how an object-oriented program works! If an instance is of the Pious type, there really is a separate Pious class that really is responsible for the instance being such as it is.
Fortunately, unlike Plato's Forms and particulars, there is nothing mysterious, metaphysical, or vague about the relationship between classes and instances. And REALbasic makes the relationship especially clear, and the distinction especially sharp, by locating classes and instances firmly in two different worlds (Plato would have been proud!), as follows.
In the IDE, every object you edit is a class. Every window listed in the Project Window, and viewed in a Window Editor, is a class. Every control whose icon is shown in the Tools Window is a class. Any new class you create is a class.
In your code, on the other hand, every object to which you send a message is an instance. In fact, your application, as it runs, consists of nothing but a lot of instances, sending messages to one another. And since REALbasic's built-in objects know how to portray themselves on the screen as interface elements, the interface of the running application may be thought of as being composed of instances. The window that appears as the application runs corresponds to an instance. The button contained by the window corresponds to an instance. The menu item you choose corresponds to an instance.
TIP: The principle just enunciated is so important and so fundamental to one's ability to work in REALbasic with fluidity and understanding, that I call it The Golden Rule of REALbasic. The following mantra serves to summarize the Golden Rule, and repeating it daily (preferably in front of a mirror, though this is not absolutely necessary) is guaranteed to bring enlightenment and to dispel the clouds of confusion: In the IDE, only classes. In the running application, only instances.
Figure 3-2 illustrates the Golden Rule. The programmer is shown editing classes; then the application is built and run, and the running application consists of instances, which can receive messages, and some of which are responsible for interacting with the user.
Figure 3-2. The Golden Rule of REALbasic
![]()
In Chapter 1 it was pointed out, in connection with properties, that editing in the IDE sets up the initial conditions of the application, but then, once the application has actually started running, the code takes over, and has the power to change those properties. A somewhat analogous observation may be made with respect to classes and instances. Editing in the IDE defines the classes for all the objects that will appear in the running application, but it is only as the application starts up, and as the application continues to run, that any instances of those classes are generated, so that messages may be sent to them, so that subroutines may be called, so that the application can actually do anything.
The reader, as programmer, will thus be seriously concerned with how instances are generated (so that there will be something to send a message to) and how they are referred to in code (so that one can specify which object the message is to be sent to). Before we come to this, though, we pause to provide a road-map of the internals of a class, as experienced by the programmer working in the IDE.
Anatomy of a Class
Back in Chapter 1, we learned our way around a Code Editor, and spoke loosely of a Code Editor as belonging to an object. Now we can be more rigorous. As we have just seen, what's edited in the IDE are classes. Thus, a Code Editor provides a view of what constitutes a class. Indeed, it is the best such view, since the other two views, the Properties Window and the Window Editor, show only properties; they are for setting initial property values of individual objects, and chiefly objects belonging to REALbasic's built-in classes, to which the Code Editor provides no access. Only in a Code Editor does the anatomy of a class stand fully revealed.
You might wish to open a Code Editor at this point, to help you follow the discussion; the Window1 Code Editor in a new project would be a perfect choice.[5]
A Code Editor reveals that a class consists of two sorts of thing: handlers and properties. Properties, as we now know, are variable-like entities belonging to an object, which can be accessed through messages. Handlers are containers for subroutines, as explained in Chapter 2. Methods are one kind of handler--the kind that can be called by sending a message to an object. Other handlers in a Code Editor get called in a different way, namely, by REALbasic itself; more will be said about that mechanism later in this section.
Handlers and properties are the only things an object consists of; together, they constitute its members. In the following list, I describe each of the different types of member, how they are classified, where (if anywhere) each type is displayed in the IDE, and how they are created and edited:
- Built-in methods of built-in classes
- REALbasic includes many built-in classes, most of which already have some functionality, available to you through methods that your code can call by sending an appropriate message to an instance of the class. For example, a window knows how to make itself visible or invisible on the screen, and you can make a window instance do this by sending it the Show or Hide message. No trace of this functionality is displayed in the IDE, nor have you any power to edit it. Yet you must know about it in order to send the appropriate messages; for that, consult the REALbasic documentation, or this book.
- Built-in properties of built-in classes
- REALbasic includes many built-in classes, most of which have some properties that your code can access by sending an appropriate message to an instance of the class. For example, a window has a Top property, which you can set to position the window vertically on the screen, and a MouseCursor property, which you can set to change the appearance of the cursor. For some built-in properties, your code can get the value, but cannot set it; these are known as read-only properties. Some built-in properties, but not necessarily all, are listed in the Properties Window, or are made manifest in the Window Editor, so that you can set their initial values in the IDE. For example, the Top property appears in a window's Properties Window; the MouseCursor property does not. To learn the purpose of built-in properties, and to know about those not listed in the Properties Window, consult the REALbasic documentation, or this book.
- Controls
- A window's Code Editor contains a Controls category. The entries here constitute a special case, because only a window class can contain controls. Controls are discussed later in this chapter. The handlers listed in a window's Code Editor as belonging to controls are actually event handlers, which we take up next.
- Event handlers
- Event handlers are listed in a Code Editor's Events category and in its Controls category. Event handler code, if any, is entirely written by the programmer. But the event handlers themselves are created only by REALbasic; the programmer cannot add or remove them. What event handlers appear in a Code Editor depends upon the class being edited. Your code cannot call any event handlers; they are called exclusively by REALbasic itself, when an event of the appropriate type occurs.[6] Event handlers are the primary way in which your code learns what the user is doing, and gets to react. For example, the Action event handler code that you write for a button is called by REALbasic when the user clicks that button. To learn what events trigger what event handlers, consult the REALbasic documentation, or this book.
- Menu event handlers
- Menu event handlers are listed in a Code Editor's Menu Handlers category. Menu event handlers are created by the programmer choosing Edit ➝ New Menu Handler; the programmer also writes their code. In essence, a menu event handler is a specialized kind of event handler. Unlike an ordinary event handler, the programmer can create or delete menu event handlers. But, like an ordinary event handler, a menu event handler cannot be called by the programmer's code; REALbasic calls it in response to the user choosing a menu item.
- Method handlers
- Method handlers are listed in a Code Editor's Methods category. Method handlers are created by the programmer choosing Edit ➝ New Method; the programmer also writes their code. Method handlers may be called only by code the programmer has written. This is done by sending an appropriate message to an instance of the class.
- Programmer-defined properties
- Properties defined by the programmer are listed in a Code Editor's Properties category. They are created by the programmer choosing Edit ➝ New Property. This brings up a dialog in which the property is declared; the declaration is formally identical to a
Dimstatement without theDimkeyword. The dialog is pictured in Figure 3-3. Property declarations can subsequently be edited by double-clicking the property's listing in the Code Editor, which brings up the same dialog. The programmer's code accesses the value of a property by sending an appropriate message to an instance of the class; if the dialog's Visible box is checked, and if an instance of the class lives in a window, the property value for that instance can also be initialized in the Properties Window.[7]
Figure 3-3. The dialog for editing property declarations
![]()
An Instance Is Born
Instances are literally the only things that allow an application to act. Without instances, there will be nothing to send messages to, no methods or event handlers will ever be called, and none of our code will ever run. The objects in the built application's running code are all instances. But the only thing that can be edited in the IDE are classes. So, in the running application, where do instances come from?
Since every instance is an instance of some class, the question amounts to this: how to make an instance of a given class. Another way of putting it is to use the technical verb: we wish to know how to instantiate a given class.
In the following section, this question will at last be answered. There are five different cases.
The Application Class
As your application starts up, the Application class (or a subclass of it, if you've created one) is instantiated once, automatically. This single instance persists throughout the lifetime of the application. You cannot instantiate the Application class (or its subclass) in code.
The Default Window Class
Every application has either one default window class or none. To determine which is the default window class, choose Edit ➝ Project Settings and, in the resulting dialog, use the "Default window" popup; you can specify any existing window class, or "<none>".
If there is a default window class, an initial instance of it is generated automatically as the application starts up, just after the Application class is instantiated. This also instantiates the controls within the window, as we shall see. There is nothing special about this instance; it's just a convenience so your application has something to get started with, if you like. Nor does the default window class behave specially in any other way. Your code can generate further instances of it, and any instance of it can be destroyed, just as with any window class.
Control Classes
Controls are instances contained by windows. Basically, the rule is that you cannot instantiate a control in code, and you don't need to worry about doing so: a window will automatically instantiate all the controls it contains, when it itself is instantiated. The rule is very slightly more complicated than this; as we shall see later in this chapter, once a control has been instantiated by its containing window, the instance can be cloned to make another instance.
MenuItems
A MenuItem instance represents an item in a menu at the top of the screen. A MenuItem is very much like a control, except that it isn't contained in a window. You cannot instantiate it; instead, it is automatically instantiated as your application starts up. MenuItems are discussed in Chapter 6. As with controls, I am deliberately ignoring for the present the matter of cloning MenuItems.
Non-control Classes
If an instance is not contained in a window, is not a MenuItem, is not the Application class instance, and is not the initial default window class instance, it does not exist until your code in the running application explicitly generates it. This is done in one of two ways.
One way is for you to call a function which creates and returns an instance of the class. Quite a number of such functions are provided. For example, the GetFolderItem function takes a file's pathname, expressed as a string, and returns an instance of the FolderItem class which corresponds to that file; so, given a file myFile in a folder myFolder on a disk myDisk, we might say:
dim f as folderItemf = getFolderItem("myDisk:myFolder:myFile")Since GetFolderItem is a function, we must call it in a context where a value of type FolderItem is expected. In this example we have assigned the value to a variable which was previously declared as being of this type.
The second way to make a new instance is with the
Newoperator. This operator must be accompanied by the name of the class of which a new instance is to be made; it instantiates the class and returns the instance just as a function does. Let's suppose your project has a window class, MyOtherWindow. Then you could say:dim w as myOtherWindoww = new myOtherWindowThis causes an instance of the MyOtherWindow class to be generated; indeed, if this class's Visible property is initialized to
true, a MyOtherWindow window will visibly appear in the running application.Like a function, the
Newoperator returns a value, so it must be used in a context where a value of the particular class type is expected. In this example, we have assigned it to a variable that was previously declared as being of this type.[8]In the case of window classes only, there is a third way to make an instance, discussed in the next section.
Referring to Instances
Once your application is running and some instances have been generated, much of the action will be precipitated by your code calling methods. Many of REALbasic's built-in methods, as was pointed out earlier, are global, but many of them belong to some particular class, and the only way you can call these is by sending a message to an instance of the proper class. The same is true for methods that you write. You can write global methods (as will be explained in Chapter 4), but for the most part, in line with the considerations presented in "Object Design Philosophy" earlier in this chapter, you'll write them as method handlers in some class, and the only way you can call such a method is by sending a message to an instance of that class.
Exactly the same thing is true for properties. Many of REALbasic's built-in classes have properties, and you will often need to retrieve the value of such properties, as a way of obtaining information; for example, getting a window's Top and Left properties tells you where it is on the screen. You'll also frequently want to change the value of such properties; for example, changing a window's Top and Left properties is how you move the window. And of course you'll frequently have properties you've created yourself, and though you can make global properties, more often you'll assign the property to some particular class. Then you'll want to get or set the property's value, in code. And the way you get and set a class's property in code is by sending a message to an instance of that class.
Now, we already know the syntax by which your code will send a message to an instance--dot notation. As explained earlier in this chapter, what appears after the dot will be the name of a property or method, and the syntax of the dot notation expression as a whole is the same as that of a variable name or a subroutine call.
But what about what comes before the dot? This is where you tell REALbasic what instance to send the message to. You do this by providing a reference to the desired instance--a name or other expression that specifies the instance in question.
The ability to provide a reference to an instance is thus of crucial importance. In REALbasic's object-oriented programming environment, making things happen is largely a matter of sending messages to instances. If you can't send a message to an instance, you can't make any of its method handlers execute, or get or set any of its properties. And you can't send a message to an instance if you can't say what instance it is. Thus, it is crucial to know how to refer, in code, to any desired instance. It is not too much to say (and I do often say it) that 99% of the art of writing REALbasic code lies in knowing how to obtain a reference to an instance. That's what the rest of this chapter is about, so pay attention!
Maintaining a Name
A very typical way to refer to an instance in code is to use the name of a variable or property to which that instance has been assigned. Once such an assignment has been made, then as long as that variable or property persists, and as long as some other value is not assigned to the variable or property, this name can be used as a reference to the instance.
For example, recall from the previous section that a FolderItem instance was generated by saying:
dim f as folderItemf = getFolderItem("myDisk:myFolder:myFile")After this assignment,
frefers to a FolderItem instance, and can be sent messages appropriate to a FolderItem. For example, a FolderItem has a Delete method, so we could say:f.deleteThis would not delete the variable
f, nor would it delete the FolderItem instance; it would be sent to the FolderItem instance to whichfrefers, which would respond by executing the FolderItem class's Delete method (which happens to mean that it would delete a file).Let's do it again, for good measure. Recall that earlier we generated a window class instance by saying:
dim w as myOtherWindoww = new myOtherWindowAfterwards,
wrefers to the instance just generated, and we can usewto keep referring to this same window instance, sending it messages such as these:w.top = 50w.title = "This is fun!"Variables stop existing when their subroutine finishes executing, though, so if we wish to maintain a reference to an instance for longer than a single subroutine, we may prefer to assign the instance to a property, which can be made available for as long as we like. This is not necessary for every instance, since, as we shall see in the rest of this section, there maybe other ways to get a reference to the instance; but for many instances, it is necessary. An instance with no way to refer to it is useless, and in fact will go out of existence as we will see later in this chapter. It is up to the programmer to see to it that there is some way for an instance, once generated, to be referred to, for as long as it is needed. One way to be perfectly certain of this is to maintain a name.
Be warned: merely declaring a variable or property has nothing to do with instances! It doesn't generate an instance, and it doesn't make a reference to an instance. Thus, in the FolderItem example, just after the
Dimstatement,fis not a reference to a FolderItem at all. The declaration says that the variablefis to be of the FolderItem type, but until you have actually gotten hold of an actual FolderItem instance (as described in the previous section) and assigned this FolderItem instance as a value tof,fdoes not refer to a FolderItem instance and must not be sent any messages. If you were to say this:dim f as folderItemf.delete // oh, no!an exception would be raised, meaning that your application would terminate prematurely. (Unless you are prepared to catch the exception, as described in Chapter 8.) We shall have more to say on this topic later in the chapter.
Functions as References
A number of built-in REALbasic functions return an instance. Also, it is possible for you to write such a function. Since the result of a function is substituted for the function call, the function call itself is a reference to an instance. Under certain circumstances, that will be the only reference you need.
For example, on rare occasions an instance is maintained so briefly that it doesn't require a name at all. Consider the task of deleting a file on disk given its pathname. We could say:
dim f as folderItemf = getFolderItem("myDisk:myFolder:myFile")f.deleteBut if all we wish to do with this file is delete it, there is no need for such elaborate measures; we're not going to refer to this FolderItem ever again, so the name
fis not required later on, and it certainly isn't required as an intermediary just so we can send an instance the Delete message. Rather, we can create the instance and send it the Delete message all in one line; we lose all ability to refer to the instance immediately afterwards, but we don't care:getFolderItem("myDisk:myFolder:myFile").deleteAlso, sometimes it is possible, and easier, to let a function return a reference to an instance than to maintain a name ourselves. For example, let's suppose that our application has three windows showing. And suppose we have a menu item, Toggle Front Two, which brings the second window to the front. How can we know which is the second window? We could maintain a name for each window and keep a list of the windows in order, changing the list each time the user brings a different window to the front. But this would be tedious and a waste of effort, because it just so happens that REALbasic already maintains such a list for us, and we can consult REALbasic's list by calling the Window function.
So, to get a reference to the second window, it is sufficient to speak of
window(1). (The parameter value is1because the Window function is zero-based.) Since the Show method brings a window to the front, the way to bring the second window to the front is simply to say:window(1).showParadoxically, sometimes we are forced to set aside a name for an instance even though we don't need it. For example, the Window function (and the Self function and the global instance name, which we shall come to in a moment) may be enough to ensure that we will be able to obtain a reference to a window without maintaining a name for it. But all the same, we are often compelled, if we wish to instantiate a window class using
New, to assign the result to a name anyway! That's because we cannot say simply:new myOtherWindow // errorThe
Newoperator returns a result, and we must do something with that result. The simplest option is to assign that result to a variable; but this means we must go to all the trouble of declaring that variable beforehand and performing the assignment even though we may have no intention of using this variable ever again:dim w as myOtherWindoww = new myOtherWindow // and that's the last we'll ever hear of wSelf
Code can obtain a reference to the instance that is actually running that code by way of the Self function. It may not be obvious to you what class this will be an instance of, so just follow this rule: in whatever class's Code Editor the Self function appears, the result of Self will be an instance of that class.
Use of the Self function is a very important technique, because the programmer, writing code in a class, may have no idea what instance will actually be executing that code. As a rudimentary example, suppose that, in our arcade game, when a target receives the Explode message, one of the things it ultimately does is to vanish, and the way it does this is by setting its own Visible property to
false. The programmer, writing the Explode method handler in the Target class, therefore wants to set this Target instance's Visible property tofalse. But which instance is that? The programmer has no notion of which target the user will hit, or even of how many targets there may be at the time; how can the message be sent to the correct Target instance? Use of the Self function solves the problem:self.visible = falseObserve that for an event handler of a control in a window, the rule about the Code Editor means that Self returns a reference to the window instance. If you drag a PushButton from the Tools Window into a Window Editor, and make its Action event handler go like this:
msgBox str(self.width)and run the project and press the button, the dialog will display the width of the window, not the button. This obeys the rule, because the button's Action event handler is edited in the window's Code Editor.
On the other hand, this is not necessarily the case for control classes that the programmer has defined. The matter is complex, and is discussed further in Chapter 4.
When sending a message to the instance returned by Self, it is permitted, as a kind of shorthand, to omit
selfaltogether. In this syntax, only the name of the message appears. REALbasic figures out what you mean by searching the namespaces in a definite order:
- If the context is that of a method call, REALbasic looks among its own built-in global methods. (This is so no names you define can accidentally prevent you from accessing a built-in global method.)
- If the context is that of a variable name, REALbasic looks to see whether this is in fact the name of a local variable.
- REALbasic tries to find the name among the members of the class of which an instance would be returned by Self; in other words, it will call Self for you.
- REALbasic looks among any global methods or properties that you've defined (in a module, as explained in Chapter 4).
For example, in a button's Action event handler in a window's Code Editor, saying:
dim title as stringtitle = "Hello"will set the variable
titleto"Hello", but:title = "Hello" // there is no variable "title"is the equivalent of:
self.title = "Hello"and will set the window's titlebar text to "Hello".
This explains the technique outlined in Chapter 2, where the reader was invited to test the use of subroutines by calling a window's method handler from a button's Action event handler by name alone. If, for instance, the button's Action event handler said
myFunction(), it was actually sending a MyFunction message to Self, the window instance.Referring to Controls
A control instance is generated automatically by the instantiation of its containing window, so you are given no chance to capture a reference to it as a name. Therefore, it has a name already--the value of its Name property in the Properties Window. We may speak of this as the control instance's global name. The instance can be referred to in any context, by chaining its global name to a reference to the window, using dot notation:
theWindow.theControl.theMessageSince code within a window's Code Editor can obtain a reference to the window using the Self function, the Self function enables such code to refer to any control within the same window. For example, suppose a window contains two PushButton instances, named PushButton1 and PushButton2, and we wish the first, when pushed, to disable the second (by setting its Enabled property to
false). The Pushbutton1 Action event handler (in the window's Code Editor) can say:self.pushButton2.enabled = falseFurthermore,
selfas a message recipient can be omitted; therefore in actuality it suffices to say:pushButton2.enabled = falseBut to speak of a control in a different window, we need an explicit reference to the window:
isEnabled = w.pushButton2.enabledA completely different way to obtain a reference to a control in a window is through the window's Control function. The parameter is a zero-based index value specifying the desired control (the order of the controls is the order set in the Format ➝ Control Order dialog). To learn the upper bound of this parameter, subtract
1from the window's ControlCount property. For example, the following routine interrogates a window to learn how many of its controls are instances of PushButton (or subclasses thereof):[9]dim i, j, u as integeru = myOtherWindow.controlCount - 1for i = 0 to uif myOtherWindow.control(i) isa pushButton thenj = j + 1endnextmsgbox "MyOtherWindow has " + str(j) + "buttons."Me
Code within a control's event handlers in a window class's Code Editor (that is, code in the handlers listed under the Code Editor's Controls heading) can obtain a reference to the control instance that is actually running that code by way of the Me function (not to be confused with the Self function, which refers, in such code, to the containing window). This technique is particularly useful when a window contains clones of a control (discussed later in this chapter), because the programmer, writing the event handler, may have no way of knowing which clone will actually be executing the code. But it also just makes code clearer.
Here, for example, PushButton1 moves PushButton2 just beneath the bottom edge of itself:
pushButton2.left = me.leftpushButton2.top = me.top + me.height + 2This makes it much more obvious what's going on than for PushButton1 to refer to its own Left property as
pushButton1.left, and so on.It is not permitted to omit
mein the way that one may omitself. Indeed, it would be a mistake to do so, because REALbasic will assume that what's omitted isself, which means the window, not the control. For example, suppose PushButton1 says:pushButton2.top = top + height + 2 // oopsPushButton2 promptly vanishes, because it has just been placed lower than the bottom edge of the window.
Me and Self differ only within the event handlers of controls in a window class's Code Editor. In other contexts, they may be used interchangeably.
The Window Global Instance Name
Instances of windows, and of windows only, can be referred to in another way: to use the window's class name as an instance name. You may do this only for one instance of each window class--obviously, since you could not use the same name to refer to more than one instance. We shall call this name, which is the same as the window's class name, its global instance name.
This seems on the face of it like a strange rule: you're abusing the syntax of the language by speaking of an instance by using the name of a class. However, the device is actually rather a clever one. It is often the case that a window instance persists without your having defined a persistent name to refer to it; the idea here is to enable each of your window classes to have at least one instance to which you can always obtain a reference.
There is a tricky rule associated with using global instance names. The way you instantiate a window class so as to be able to refer to that instance using the global instance name is not to use
New. Instead, you just refer to the instance by its global instance name even though the instance doesn't exist. This doesn't cause an error; instead, the window class will automatically be instantiated, and your reference works as a way of speaking of this new instance. The rule is this: whenever you use a window's global instance name at a time when no instance by that name exists, an instance by that name is generated. This is called implicit instantiation.So, for example, let's say your project has a window class, MyOtherWindow, and let's say that no implicitly generated instance of MyOtherWindow exists. Then you can say:
myOtherWindow.title = "Info"myOtherWindow.left = 20The first line instantiates MyOtherWindow, assigns the new instance the global instance name
myOtherWindow, and sets the value of the new instance's Title property, all in one move. The second line sets the value of the same instance's Left property; it doesn't implicitly generate a new instance of MyOtherWindow, because an instance with the global instance name already exists (the one generated by the first line).Now, on the one hand, it's clear enough why implicit instantiation exists. If it didn't, the global instance name would be useless, because how would you (or REALbasic) know which instance of this window class the global instance name refers to? With implicit instantiation, it is possible to make a clear rule about this, because at every moment there either is or there isn't an instance of the window class to which the global instance name refers. If there is, that's the one. If there isn't, a new instance of the window class is generated, and this new instance becomes the one.
But although implicit instantiation may be explicable, it still tricks many REALbasic beginners--and not-so-beginners. Misuse of the syntax, which is all too easy, will cause a window instance to be created accidentally. Moreover, if there is already an instance of this window class, the new window may be created directly on top of the old one, so that the programmer, running and debugging the application, thinks it is the old one; a extra window has been created, and the programmer doesn't even know it. This is the source of many strange phenomena.
For example, let's suppose that the window class MyOtherWindow contains a button PushButton1. And let's suppose that you say:
dim w as myOtherWindoww = new myOtherWindowmyOtherWindow.title = "hello" // oopsw.pushbutton1.caption = "Press Me"The window appears, but the button's caption does not change. What's gone wrong? Nothing; but what you said isn't what you meant. Here are the consequences of this code. First, an instance of the MyOtherWindow class is created, using
New, and a window appears. Then, a second instance of the MyOtherWindow class is created, using implicit instantiation, so a second window appears, hiding the first; the second window's title is changed to "Hello". Finally, the first window's button's caption is changed to "Press Me"; but you can't see this, because the second window is in the way!A more subtle error is to use the global instance name where a class name was intended. Suppose, for example, you wish to know whether the frontmost window is an instance of the MyOtherWindow class. You can get a reference to the frontmost window as
window(0), so you might be tempted to say:if window(0) = myOtherWindow then // oopsIf there is no instance of MyOtherWindow with the global instance name, this expression will generate one, by implicit instantiation. The problem is that you asked the wrong question; what you wanted to know was:
if window(0) isA myOtherWindow thenNow
myOtherWindowis just a class name, becauseIsAtakes a class name; and no implicit instantiation takes place.[10]A window instantiated by using
Newdoes not receive the global instance name, regardless of what name you use for it locally. Suppose that a subroutine were to create an instance of the MyOtherWindow class in this way:dim myOtherWindow as myOtherWindowmyOtherWindow = new myOtherWindowThe window thus created does not receive the global instance name, because implicit instantiation was not used. So if another subroutine were later to say:
myOtherWindow.pushbutton1.caption = "Press me"this would create a second window, by implicit instantiation, rather than referring to the first.
On the other hand, if your project has a default window class, then when that window is automatically instantiated as the application starts up, that instance is assigned the global instance name. So if your default window class is Window1, and if the automatically created window is still present, then when you say:
window1.top = 100no new window is created; the default window is moved.
The Truth About Controls
The term control is used in two ways in REALbasic. In general, a control is an instance that is contained by a window. The window is responsible for generating the instance, when the window itself is instantiated. You can make a control from any programmer-defined class (except a window class), by dragging the class's listing from the Project Window into the Window Editor.
There is also a built-in Control class, which has the following property: an instance of a subclass of the Control class can exist only as a control (that is, it must be contained by a window). REALbasic supplies many built-in Control subclasses; all of these are represented by icons in the Tools Window. A few icons in the Tools Window also represent built-in classes that are not Control subclasses.[11] You can make a control from any class represented in the Tools Window, by dragging its icon into the Window Editor.
When you make a control by dragging into a Window Editor, what are you really doing? Let's suppose that what you drag is the PushButton icon from the Tools Window. This icon represents a class--the built-in PushButton class. In dragging it into a Window Editor, you are not making a new class, because the PushButton class already exists; but you cannot be generating an instance, either, because what you edit in the IDE are classes. What you're actually doing is editing the window class, telling it to contain an instance of the PushButton class. Let's suppose this is the Window1 Window Editor. You're saying to the Window1 window class: "I want there to be a control that is an instance of the PushButton class. This instance is going to be your responsibility, Window1! You contain it. So, part of your job is to instantiate the PushButton class whenever you yourself are instantiated."
This makes perfect sense, because it captures our intuitive notion of window types. Let's say you're writing an email application, and one of your window types is for composing a new email message, and has a Send button in the upper right. You want the user to be able to work on several new messages at once. Therefore more than one of these email composition windows may have to be open; in object terms, there will have to be more than one instance of this window class. So each of those windows should have a Send button in the upper right, because they are the same type of window.
That is precisely what will happen. You have one window class representing this type of window. You use the Window Editor to help describe it to REALbasic. Part of the way you do this is to drag a PushButton icon from the Tools Window into this window class's Window Editor. This lets REALbasic know that the window is to contain a PushButton instance. You also describe the PushButton instance's initial properties. For example, you dictate that its Caption property should be
"Send". And the position and dimensions of the button's representation in the Window Editor also set the initial values of the instance's Top, Left, Width, and Height properties. Now each time a window of that class is instantiated in your running application, it instantiates a PushButton and initializes its properties. Thus, each window of this type that actually appears when your application runs, will contain a button, at that position, with those dimensions, and with the caption "Send".Nor is it just a matter of how the Send button will look; there's also the matter of how it will behave. If you give the Send button some functionality in its Action event handler in the window's Code Editor, you expect that the Send buttons in all instances of this window type will exhibit this same functionality. And so they do. But why? Not because you've edited the PushButton class; after all, not every PushButton does this. Nor because you've created and edited a PushButton subclass; you haven't. It's because in editing the button's Action event handler within the window's Code Editor, it's the window class's code that you're really editing. You're saying to the window class: "Oh, and one more thing: when the user clicks on that PushButton instance you created, here's the code that should be executed."
This explains the fact noted in Chapter 1, that when you double-click a control in a Window Editor to edit its code, it is the window's Code Editor that opens. It also explains why, in this code, Self refers to the window, as explained earlier in this chapter.
Control Clones and Control Arrays
There is usually no need for you to instantiate any controls in code, because you have already effectively instantiated all the controls you're going to need, by dragging them into a Window Editor. The window will instantiate them for you, when it itself is instantiated. That's what it means for an instance to be a control, and what it means for it to be contained by a window.
Nonetheless, you can instantiate a control in code, provided that an instance of that control exists in that window already (ultimately because you dragged it into the Window Editor). In other words, your code, although it cannot create an instance of a control ex nihilo, can clone an existing control.
This limitation makes a certain amount of sense, not least because of the problem of assigning code to the control's event handlers. For example, you cannot modify a PushButton instance's Action event handler while the application is running; so if a PushButton instantiated by your code is to have an Action event handler, it must copy it from an existing PushButton instance.
The copying of handlers is really what cloning is all about. The values of properties can differ from clone to clone, but their event handlers, as shown in the window's Code Editor, are shared. Thus, cloning is a way of having more than one control with identical functionality. Of course, so is the class-instance relationship. But control cloning lets you assign multiple controls the same functionality without bothering to create a new class to house that functionality.
So far, I've spoken as if the only purpose of cloning was to create controls dynamically, in code. But you don't have to wait until the application runs; you can clone a control manually, in the IDE. We'll see in a moment why you might want to do this.
Some newcomers to REALbasic are disappointed at its inability to create controls dynamically that are not clones. But this limitation is not as serious as it may appear, because no rule says that the existing control, the one that is to be cloned, must be visible. As far as the user is concerned, a button can still be made to appear in a window where none was previously; the truth is that there was one, but it was invisible, or was located outside the boundaries of the window. In fact, a common technique is to avoid cloning controls in code altogether through the use of invisible controls. For example, if you know that you will need up to 10 buttons, you can create 10 buttons in the IDE and make them invisible. If they will all have the same functionality, they can be clones of a single button. When the application runs, if it needs one of these buttons, it just makes it visible.
Control Arrays
There is a second limitation on the cloning of controls. To clone a control, the control must be a member of a control array.
A control array is a construct for referring to a set of control clones numerically. It is not quite the same as an array of controls. An array of controls would be an array whose elements can refer to any control instances of the appropriate type; a control array consists of all the clones of a single control within a single window.
The notation for referring to the various clones within a control array is just like the notation for referring to the elements of any array: you use the array name and an index number, which is zero-based. The array name is the control's global name. For example, there might be three clones of a PushButton instance. The name of the control array is the global name of the original control, which might be
pushButton1. The individual clones might then be calledpushButton1(0),pushButton1(1), andpushButton1(2). These names can be used just like any control's global name. For example:window1.pushButton1(2).caption = "Delete"Control arrays have four great advantages. First, a control array makes it easy for your code to get a reference to a dynamically cloned control. For example, an original button, instantiated automatically by its window, has a global name; it might be
pushButton1(or whatever name you assigned it in the Properties Window). But without control arrays, a clone of this button generated by your code would have no global name in and of itself; it would be up to you to maintain another name that refers to it.The second advantage of a control array is that it lets you refer to controls by index number, which is valuable in contexts where arithmetic most conveniently specifies the desired control. To repeat the example from Chapter 2, suppose we are playing a game involving a four-by-four grid; the squares where pieces can go might be represented by Canvas control instances. These Canvas controls are all going to behave identically, so they can be clones of one another. Thus they constitute a control array, and can be referred to as
myCanvas(1),myCanvas(2), and so forth up to the sixteenth square which is numberedmyCanvas(0). This means that the square vertically beneathmyCanvas(n)is:myCanvas((n+4) mod 16)The game grid is a good example of why you'd clone a control in the IDE. You know how many
myCanvasclones you're going to need, so there is no point generating them dynamically in code; you're creating the clones just to take advantage of the power of control arrays.The third advantage of control arrays is that they are self-initializing. With an ordinary array, your code must declare the array in some persistent storage, and then initialize it. If this were an array of controls, say an array of 16 Canvas controls, this would involve setting the first element to refer to the first Canvas, the second element to refer to the second Canvas, and so forth. With a control array, the global names
myCanvas(1),myCanvas(2), and so on, automatically refer to the clones.The fourth advantage of a control array is the most important: a control in a control array can report its index number within the array. It does this by way of its Index property. Such a thing would be quite impossible with an ordinary array. To see the significance of this, recall that since the items of a control array are clones, their code is shared. Yet that code can distinguish which particular clone is running it, by index number; and so the actual functionality of each control can differ in response to that number. To take a trivial example, consider a button whose Action event handler contains this code:
dim i, u as integeru = me.index + 1for i = 1 to ubeepnextThree clones of this button as a control array would behave differently when pressed: the first would beep once, the second would beep twice, the third would beep three times. Or, to take a much less trivial example, returning to our game grid, a square represented by a Canvas in a control array can identify the square beneath itself because it knows its own index.
On the other hand, control arrays lack the functions of normal arrays. You cannot learn their size with Ubound. Also, you cannot use Insert, Append, or Delete on a control array.
The inability to delete an element of a control array raises a somewhat unpleassant asymmetry: you can clone a control dynamically, but then you can't get rid of the clone dynamically when you're done with it. Perhaps the reason a control clone cannot be destroyed dynamically is that in general a control cannot be destroyed dynamically.[12] In any case, it isn't as if one can't think of workarounds. Instead of a control array which grows dynamically, you might consider using a statically sized control array whose items become visible or invisible as necessary. Or, instead of cloning controls at all, you might consider structuring your interface so that each control appears in a separate window; a window can be closed, and this destroys both the window and its controls.
Cloning in the IDE
To create a control array, select a control in a Window Editor and, in the Properties Window, assign a value to its Index property. Any legal index value will do, but you will almost certainly use
0.If you wish to create further elements of the control array in the IDE, just copy and paste, or duplicate, the original. Now you have cloned the control in the IDE. In the window's Code Editor, the control array is represented by a single Control entry and its event handlers. To make more clones, just repeat.
Each new clone is assigned the lowest array index available at the time it is created. That's why it's best to assign the original element the index
0, so that the next clone will have index1, the next will have index2, and so forth.Cloning in Code
Before you can add elements to a control array in code, the control array must exist. So, in the IDE, you will have had to give a control an Index number, which once again will almost certainly be
0. Now your code can append to the array dynamically: useNewand the name of the control with no index.For example, let's say you've dragged the PushButton icon into a Window Editor, and that you've assigned the button's Index property a value in the Properties Window to make it a control array. And let's say you've left the control array's name the same as that of the original control, which is
pushButton1. If you've setpushButton1's Index property to 0 in the IDE, then in code within that window's Code Editor, you can say this:dim b as pushButtonb = new pushButton1This will generate
pushButton(1), or whatever the next available index may be. WithNew, we are here using an instance name, not a class name.[13] This looks like a violation of the rules ofNew's syntax, and that's exactly what it is. This exceptional syntax is permitted only in the case of a control array, as a way of cloning to generate a new element.Once again, the new element will be assigned the lowest available index value; and again, that's why it's best to assign the original element the index
0. Also, note that the newly generated control will copy the property values of the oldest element of the control array. This has important implications for what will happen at the moment of the clone's instantiation. For example, because the new clone will get its initial Left, Top, Width, and Height property values from the oldest clone, it will be located directly on top of that clone at the moment it comes into existence.Notice that because
Newreturns an instance, we were forced to do something with this returned value; in this case, we chose to assign it to a variable, so we were also forced to declare the variable. In one sense, this is something of a waste, because the variable isn't needed as a reference to the newly generated clone; we already have such a reference, by way of the new clone's index number. Nonetheless, it is often quite convenient to have the new clone referenced by this variable as well, because typically one will want to set the clone's properties the moment it is generated. For example, since the new clone will be located on top of the clone which it copies, you might want to move it, like this:dim b as pushButtonb = new pushButton1b.top = b.top + b.height + 5You cannot clone a control except from code within the same window. The reason is that the
Newoperator doesn't support the required syntax. Suppose that from outside the MyOtherWindow window, you try to say:dim b as pushbuttonb = new myOtherWindow.pushbutton1 // errorb.top = b.top + b.height + 5That doesn't work, because you're not allowed to refer to a control array without an index number from another window; you have to refer to a specific element of the array. So you try:
dim b as pushbuttonb = new myOtherWindow.pushbutton1(0) // this may not do what you thinkb.top = b.top + b.height + 5That doesn't work either. No new button is created, because the
Newoperator sees the namemyOtherWindowand creates a new instance of that class. The rest of the second line merely obtains a reference topushButton1in that new window, so the third line ends up moving the old button in the new window, not (as intended) the new button in the old window. You cannot solve this problem by obtaining a reference to the window as a variable name, such asw, and then sayingnew w.pushbutton1; you'll get an error. (In case anyone cares, I regard this entire situation as a bug in the syntax ofNew.)The inability to use Ubound on a control array means that if you are creating clones dynamically, you may wish to maintain your own count. However, there is another way.[14] It turns out that there is no bounds checking on a control array; thus, there is no penalty for speaking of an array index for which no clone exists. Therefore, you can iterate up the array to find the first index which is a nil pointer. For example:
dim i as integerwhile pushButton1(i) <> nili = i + 1wendAfterwards,
iholds the number of clones in the control array.Being Careful with Instance References
The value of a variable or property has a datatype, and this datatype is either a scalar type or an object type. Scalar types are strings, numbers (integers, singles, doubles), and booleans. Object types are classes. When a variable or property has an object datatype, the name of the datatype is the name of a class, and the variable or property can be used as a reference to an instance of that class.
So far, this chapter has explained how to generate an instance, and how to get a reference to that instance. Now it's time to talk about how to work with references to instances.
References to instances are tricky. Paradoxically, this is for the same reason they are so easy. REALbasic tries to shield you from the truth, which is that references to instances are pointers. The word "pointer" doesn't appear in this connection anywhere in the REALbasic documentation. REALbasic doesn't make you explicitly dereference a pointer in order to gain access to the properties of an instance. Instead, such dereferencing is handled for you implicitly. REALbasic tries to create the illusion that objects are just like scalars.
The purpose of this section is to shatter that illusion and trample its fragments in the dust. My experience is that beginners get into much less trouble when they understand what's really going on. If you remember that references to instances are pointers, and grasp the implications of that one simple fact, you'll manipulate such references successfully.
Initialization Is Not Instantiation
Declaring a scalar variable or property creates a variable whose value is valid. When you say:
dim s as stringa string really does come into existence;
shas a valid string value (the empty string). Similarly, declaring a variable or property as an integer, single, or double assigns that variable or property a valid integer, single, or double value (a form of0); and declaring a variable or property as a boolean assigns it the valid boolean valuefalse.But objects don't work the same way. That's because an object name is merely a pointer; the instance itself has an independent existence. Declaring a variable or property as an object type uses the name of a class, but it doesn't instantiate that class. Getting an instance and assigning it to the variable or property is up to you. Meanwhile, the variable or property has a value which is
nil. The valuenilmeans merely: "I am supposed to point to an instance, but at the moment I don't." The name of a variable or property whose value isnilis a nil pointer.Only an instance can be sent a message; but
nilisn't an instance. If you sendnila message, your application will terminate prematurely.[15] This means that you must be concerned with not using a nil pointer as the recipient of a message. The name of a variable, the moment that variable is declared as having an object datatype, is a nil pointer. When you Dim a variable to an object type, the variable is a nil pointer. If an array has an object datatype, then when that array is declared, all of its elements are nil pointers, and if the array is redimmed to create new elements, the new elements are nil pointers. If a class has a property with an object datatype, then whenever that class is instantiated, that property of the new instance is a nil pointer.There are several ways to avoid sending a message to a nil pointer. One is simply to be careful. However, accidents will happen, so it is useful to have some programming tricks up your sleeve to make it less likely that you will forget what you're doing and accidentally send a message to a nil pointer.
Since you cannot avoid generating nil pointers, one rule of thumb is to keep those nil pointers as short-lived as possible; the moment you make one, you immediately replace its
nilvalue with an instance, if you can. So, for example:dim f as folderItem // f is a nil pointerf = getFolderItem("myDisk:myFolder:myFile") // f is no longer a nil pointeris better than:
dim f as folderItem// ... two hundred lines of code, during which f is a nil pointer ...f = getFolderItem("myDisk:myFolder:myFile")Another device is to test the waters before you dive in, by comparing a value to
nilbefore you use its name as a reference to an instance:dim f as folderItem// ... two hundred lines of code, during which f may or may not have been assigned an instance ...if f <> nil thenf.delete // f is an instance, so it's okay to send it a messageDeclaring an array of objects generates multiple nil pointers at once. For some reason, even beginners who understand how to declare a variable and initialize it with an instance value have a tendency to forget that the same thing must be done for every element of an array. Typically, you will iterate through the array, obtaining an instance to assign to each element.
So, for example, imagine that we wish to create an array of Date instances, and then to assign each instance a value by way of its TotalSeconds property.[16] This is not the way:
dim d(5) as dated(1).totalseconds = 1764098908.0 // game over, thank you for playingBefore we can send a message to an element of the array, that element must refer to an instance. A
Forloop lets us create instances easily:dim d(5) as datedim i as integerfor i = 1 to 5d(i) = new datenextd(1).totalseconds = 1764098908.0 // no problemAssignment Is Not Cloning
Scalar values are copied by assignment. When you say:
dim this, that as stringthis = "Hello"that = thisthis = "Goodbye"then
thatis"Hello"andthisis"Goodbye". The value ofthiswas copied to become the value ofthat; moreover, the two copies have independent existence, so afterwards, changingthisdoes not affectthat.But when an instance value is assigned to a variable or property, what is copied is the pointer. If the first instance value was obtained from a variable or property, the result is that two variables or properties now point to the selfsame instance. So, suppose we say this:
dim w, w2 as myOtherWindoww = new myOtherWindoww2 = ww2.top = 50w.top = 100msgbox str(w2.top)The dialog box displays "100". But how can that be? We just said that
w2's Top property should be50, and we didn't change it; how did it get to be100? The answer is thatwandw2are both just pointers, and after the assignment:w2 = wthey point to the same thing--a third thing, an instance. Saying:
w.top = 100sends a message to that instance. In other words,
wandw2don't have a Top property; they are just names. It's the instance that has a Top property, and in our code there is only one instance. Our code sets that instance's Top property, first to50, then to100. Figure 3-4 schematizes the process: object names are pointers, and the instance has independent existence.
Figure 3-4. Object names are pointers
![]()
I have seen many REALbasic beginners, and indeed, many REALbasic programmers of long standing, absolutely stunned by this behavior. They are used to the intuitive notion that assigning one scalar to another clones the scalar's value, and they expect the same thing to happen with objects. This misunderstanding results in some astonishing code. For example, I often see people do this:
dim o, o2 as myClasso = new myClasso2 = new myClasso2.itsProperty = "test"o = o2This programmer is laboring under three misconceptions. First, he thinks that the last line is copying the value
"test"fromo2.itsPropertytoo.itsProperty. Second, he thinks that after the last line he still has two instances of MyClass. Third, he thinks that he needs the second line so as to provide an instance to copyo2's properties into.All three ideas are wrong. The last line causes
oto stop being a reference to whatever it used to be a reference to, and to point to the very same instance thato2points to. There is now only one MyClass instance. It's true that the second line generated an instance of MyClass, and causedoto point to it; but the last line causedoto point to the same instance aso2instead, so the instance thatooriginally pointed to now has nothing pointing to it, becomes useless, and goes out of existence. That instance was utterly redundant, born only to die unused.So, assignment is not cloning; it does not make a second instance with all the same property values as the first. How, then, do you clone an instance? The answer is, you do it the same way Superman gets into his trousers--one leg at a time. If you want the property values of one instance to be copied into the properties of another instance, you must copy them one by one:
dim o, o2 as myClasso = new myClasso2 = new myClasso2.itsProperty = "test"o2.itsOtherProperty = "test2"// clone o2 into oo.itsProperty = o2.itsPropertyo.itsOtherProperty = o2.itsOtherPropertyTo be sure, if this is a class you have created yourself, and if this is something you're going to want to do frequently, you can abstract the cloning code into a method, and you very probably will. But that method must still copy the property values one at a time:
Sub clone(o as myClass)o.itsProperty = self.itsPropertyo.itsOtherProperty = self.itsOtherPropertyEnd SubParameters Pass Pointers
The situation when values are passed as parameters is completely parallel to the situation when values are passed by assignment. When a string is passed as a parameter to a subroutine, its value is copied, so that the subroutine receives an independent copy:
dim s as strings = "testing"mungeMyString sWhatever MungeMyString may do, one thing is for certain: after the call to MungeMyString, the value of
swill not have changed. That's because in order fors's value to be passed as a parameter, MungeMyString created its own string name and assigned a copy ofs's value to that name. (That is, of course, unless MungeMyString was declared to accept this string parameter ByRef, as explained in Chapter 2. In that case, all bets are off.)But when an instance is passed as a parameter to a subroutine, what's being passed is the pointer to that instance. So the subroutine now has access to the very same instance. For example, suppose we have a subroutine that goes like this:
Sub moveMyWindow(theWindow as window)theWindow.top = 100End SubNow we'll call the subroutine, as follows:
dim w as myOtherWindoww = new myOtherWindoww.top = 50moveMyWindow wAfterwards,
w.topis100; MoveMyWindow does indeed move the window that is handed to it as a parameter.Now, you might say: aha! Objects are passed by reference! And indeed, as I was first writing this chapter, that is exactly what I did say. But no. By default, objects are passed by value, just like scalars. It's just that the value of a variable or property with an object datatype is a pointer. So, the subroutine does indeed receive a copy of the variable or property's value; but that's a copy of the pointer. The copy of the pointer points to the same instance that the original pointer does! Therefore, the subroutine has access to that instance.
So, you may ask, what happens if you do pass an object by reference? I mean, since a subroutine that receives an object as a parameter by value can already change that object's property values, what further ability can it possibly gain by receiving that object by reference? The answer is that the subroutine now has the ability to repoint the pointer. For example:
dim someWindow as window1repoint someWindowsomeWindow.top = 100The last line does not cause our application to terminate prematurely; yet we are sending a message to
someWindow, which as far as this routine is concerned is a nil pointer. How can this be? Repoint runs as follows:Sub repoint(byRef w as window1)w = new window1End SubNow, Repoint received its parameter
wby reference. This means that if Repoint performs an assignment tow, the original variable back in the caller receives the same assignment. Therefore, after Repoint returns, back in the caller, a new Window1 instance has been generated, andsomeWindowis pointing to it.Comparison Tests Identity
Object comparison also differs from scalar comparison. For one thing, concepts like "greater than" and "less than" are largely meaningless when applied to instances; REALbasic won't complain, but the results of the comparison won't be of any use to you. Therefore, the main comparison you're likely to use on an instance is equality comparison with another instance, or with
nil. The purpose of comparison withnilhas already been explained: it's to determine whether a reference is a nil pointer. So it remains only to talk about equality comparison between instances.Equality comparison between two instances asks whether they are the same instance, not whether they have the same "value" (whatever that would seem to mean). Consider, for example, the FolderItem
desktopFolder.child("test"), which denotes a file called test on the desktop. The condition in this line:if desktopFolder.child("test") = desktopFolder.child("test") thenis
false, because two separate instances are formed by the two separate function calls on the two sides of the comparison. It's true that those two instances provide access to the same file on disk, but that's irrelevant.On the other hand, after executing the following:
dim f, g as folderItemf = desktopFolder.child("test")g = fthe variables
fandgpoint to identically the same instance, and so the boolean expression in this line:if f = g thenis
true.Destruction of Instances
We know how instances are born; but how do they die? The short answer is that, in general, you're not supposed to worry about this. REALbasic does its best to shield you from problems of instance destruction. Those problems are intimately related to memory management; and one of REALbasic's great strengths is that it manages memory for you.
The core of REALbasic's memory management system is a principle called garbage collection. This states that REALbasic will see to it that an instance will go out of existence all by itself, and the memory that it used to occupy will be cleared, if there is no way at all for code to refer it.
For example, if a subroutine instantiates a FolderItem, assigning it to a variable declared within that subroutine and not assigning it to any other name, then once the subroutine has finished executing, the FolderItem instance is no longer pointed to by any name at all. Therefore, the instance is useless and it will be destroyed. On the other hand, if a subroutine instantiates a FolderItem, assigning its pointer to a property, and if the instance containing the property lives on after the subroutine has finished executing, the FolderItem instance is still pointed to by the property name, and will persist.[17]
Some instances seem to work differently. Chief among these are window instances. Windows have a life of their own, as it were, because they are fundamental features of the interface. If a window vanished just because your code stopped talking about it, the user would see a lot of unpleasant action on the screen! So, it seems that windows persist in defiance of the garbage-collection rules. However, that isn't really true, because even if your code maintains no name by which to refer to a window, there are ways to retrieve a reference to it--for example, by calling the Window function.
On the other hand, windows are certainly special in this sense: since they don't die automatically, your code has to have a way to kill them. Therefore, windows accept a Close message as a way of telling an instance to die. This has a secondary effect: all the window's controls are also destroyed. Thus, windows are an important device for easy management of instance lifetimes: if an instance is a control in a window class, it is brought into existence automatically when the window is instantiated, and is destroyed automatically when the window is destroyed. Any class that appears in the Project Window or Tools Window, even a non-control class, can be instantiated as a control contained by a window, by dragging it into a Window Editor; this feature of windows, that they automatically handle the lifetimes of their controls to match their own lifetimes, explains why one would wish to do this.
The behavior of references to a window after it has been sent the Close message is somewhat odd. Let's say we have two window classes, Window1 and Window2. And let's say Window1 has a property, W, whose type is Window2. We create a new instance of Window2 and use W to maintain a name for it:
w = new window2This causes Window2 to be instantiated, and a new window appears. Then, we say this:
w.closeif w = nil thenmsgbox "it is nil"elsemsgbox "it is not nil"endThe first line causes the window to vanish, but then the message dialog reads, "it is not nil". That's odd; W has no window to point to, yet it isn't
nil? So perhaps it still points to Window2, which is merely invisible. So, we try this:w.showThis is the oddest part of all: nothing happens. No new window appears, but we don't terminate prematurely either. Evidently, W isn't
nil, but whatever it points to is not a real instance. The really disturbing thing is that we are left with no way to use W to ascertain that its window has closed. A workaround is to assignnilto W ourselves when we close the window.On rare occasions, you may want to try to tweak REALbasic's memory management a little, by encouraging it to collect the garbage sooner and thus clear some more memory. You cannot actually trigger a garbage collection, but you may find that it helps to nillify some references yourself. On the other hand, REALbasic's garbage collection is not perfect, and wanton nillification of a reference to an instance on which depends a sizeable chain of references can lead to a crash. This nillification does not have to be explicit; it can happen implicitly--for example, just by quitting your application. In such situations, you may have to help REALbasic along a bit, by getting it to collect the garbage along the successive nodes of the chain.
For example, at the end of Chapter 4, a Stack class is described, for implementing a stack as a chain of Stack instances. Suppose that a window has a property, St, which is declared as a stack. And suppose that we construct this stack to a length of several thousand Stack instances, like this:
dim i as integerst = new stackfor i = 1 to 1000st.push "hey"st.push "ho"st.push "hey"st.push "nonny"st.push "no"nextIf we now quit the application (or the project running in the IDE), the computer will crash. The problem seems to be that the first Stack instance is nillified while still pointing to a Stack instance which still points to a Stack instance which still points to a Stack instance, and so on for several thousand instances. The solution is simply to empty the stack one item at a time before quitting, like this:
dim s as stringwhile not st.isEmptys = st.pop()wendEach time through the loop, the top item of the stack is unlinked; it is not linked to any other instances, and there are no remaining references to it, so it is destroyed. This goes on until there is just one Stack instance left; when the application quits, REALbasic has no problem destroying that instance. Thus, the loop apparently helps REALbasic to collect the garbage in an orderly fashion.
1. By making it part of a module. Modules are discussed in Chapter 4, Subclasses.
2. I simplify somewhat. In Chapter 4 we see ways to modify this behavior.
3. Some implementations of object classes do also permit a second sort of property declared in a class, whose value is in common for every object of that class. REALbasic's implementation, alas, does not.
5. The selection of a window Code Editor is deliberate. What categories of member appear in a Code Editor depends upon what's being edited. A window's Code Editor shows the widest possible range of categories. Even so, one category, Constants, is absent; but this category, which refers to a global version of the local constants described in Chapter 2, appears only in a module, and a module isn't a class. Modules and global constants are discussed in Chapter 4.
6. Actually, when defining both a class and its subclass, there is a way for the programmer to add an event handler, and to call it (though in an extremely limited context). This is called a New Event, and is discussed in Chapter 4.
7. The purpose of the Private checkbox is explained in Chapter 4.
8. The syntax of
Newmust be modified if the class being instantiated has a constructor; this is explained in Chapter 4.9. For
IsA, which reports whether an instance is of a given class, see Chapter 4.11. A list was given in Chapter 1.
12. But MenuItem clones can be destroyed, so this justification seems a little weak. See Chapter 6.
13. In the
Dimstatement, we might instead have used the instance name,pushButton1. But this is probably to be deprecated.14. I owe this tip to Guillaume Grenier.
15. Actually, it will raise an exception; you can catch this exception to prevent premature termination, as explained in Chapter 8.
16. The Date class is discussed in Chapter 5.
17. The whole thing is rather like the haunting short story, "Do You Love Me?", by Peter Carey. The world has been taken over by the Cartographers, who with their elaborate lists of everything have essentially usurped God's task of upholding all things by the word of his power. As the story ends, whatever and whoever is not loved by someone, is ceasing to exist. In effect, it's a world with garbage collection! See Peter Carey, The Fat Man in History and Other Stories (Faber and Faber, 1980).
Back to: REALbasic: The Definitive Guide
© 2001, O'Reilly & Associates, Inc.