source: trunk/source/examples/cocoa/ui-elements/HOWTO.html @ 8523

Last change on this file since 8523 was 8523, checked in by mikel, 13 years ago

added code to add a button to the sample window

File size: 13.1 KB
Line 
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3<html xmlns="http://www.w3.org/1999/xhtml">
4  <head>
5    <title>UI Elements HOWTO</title>
6    <link rel="stylesheet" type="text/css" href="HOWTO_files/stylesheets/styles.css" />
7  </head>
8
9  <body>
10
11    <div class="title">
12      <h1>UI Elements HOWTO</h1>
13    </div>
14
15    <div class="body-text">
16      <p>This HOWTO shows how you can create Cocoa user-interface
17      elements by making lisp calls to instantiate and initialize
18      Objective-C objects.</p>
19
20      <p>Cocoa programmers usually create UI elements using Apple's
21      InterfaceBuilder application, and then load those elements from
22      a <strong>nibfile</strong>, but Cocoa supports creating all the
23      same UI elements by making Objective-C method calls. In fact,
24      that's how it loads nibfiles: by making method calls to
25      instantiate the objects described in them.</p>
26
27      <p>For Lisp programmers, accustomed to working incrementally and
28      interactively, it may sometimes make more sense to create
29      user-interface elements by making method calls interactively,
30      rather than by constructing a complete user interface in
31      InterfaceBuilder. This HOWTO shows how you can use Objective-C
32      method calls to create and display windows and other UI
33      elements.</p>
34
35      <p>For more information about how to load nibfiles from Lisp,
36      see the "nib-loading" example. For a complete discussion of how
37      to construct a Cocoa application using nibfiles created with
38      InterfaceBuilder, see the "currency-converter" example.</p>
39
40    <div class="title">
41      <h2>Creating a Window</h2>
42    </div>
43
44    <p>Every user-interface element under Mac OS X appears either in
45    a window or in a menu. We'll begin by exploring how to create and
46    display windows.</p>
47
48    <p>First, switch to the <code>CCL</code> package, for
49    convenience. Most of Clozure CL's Objective-C utilities are in
50    the <code>CCL</code> package:</p>
51
52    <pre>
53? (in-package :ccl)
54#&lt;Package "CCL"&gt;
55    </pre>
56
57    <p>Creating a Cocoa window follows the common Objective-C pattern
58    of allocating an object and then initializing it with some
59    starting data. To allocate a window, just call
60    the <code>alloc</code> method of the <code>NSWindow</code>
61    class:</p>
62
63    <pre>
64? (setf my-window (#/alloc (@class ns-window)))
65#&lt;NS-WINDOW &lt;NSWindow: 0x13b68580&gt; (#x13B68580)&gt;
66    </pre>
67
68    <p>The above expression creates a new window, but doesn't display
69    it. Before it shows up on the screen we must initialize it with
70    some appropriate values. For that, we'll use the
71    method <code>initWithContentRect:styleMask:backing:defer:</code>.</p>
72
73    <p>As always in Objective-C, the name of the method reveals
74    something about the arguments it expects. The <code>NSRect</code>
75    that we pass for the <code>initWithContentRect:</code> segment of
76    the method name describes the shape of the window. The mask
77    for <code>styleMask:</code> is a sequence of bits that specify
78    which window features are turned on. The <code>backing:</code>
79    argument is a constant of type <code>NSBackingStoreType</code>
80    that specifies how Cocoa will draw the contents of the
81    window. Finally, the <code>defer:</code> argument is a Boolean
82    that determines whether to display the window as soon as it's
83    created.</p>
84
85    <p>Next, we'll create data values to pass in these parameters, so
86    that we can display our new window on the screen. We'll build the
87    proper initialization form up piece-by-piece.</p>
88
89    <p>The first argument, of course, is the window object to be
90    initialized. We pass the window that we created before:</p>
91
92    <pre>
93(#/initWithContentRect:styleMask:backing:defer: my-window ...)
94    </pre>
95
96    <p>The next argument, the <code>NSRect</code>, is a structure
97    that we need only temporarily. Because <code>NSRect</code> values
98    appear so often in Cocoa code, Clozure CL provides a handy way to
99    allocate them values temporarily, disposing of them
100    automatically. The <code>with-ns-rect</code> macro (in
101    the <code>NS</code> package) creates an <code>NSRect</code> value,
102    and then disposes of it when control leaves the scope of the
103    macro; for example:</p>
104
105    <pre>
106(ns:with-ns-rect (r 100 100 400 300)
107   ...)
108    </pre>
109
110    <p>The macro creates a temporary <code>NSRect</code> value,
111    visible to any code in the body of the
112    macro. The <code>NSRect</code> value is automatically dispose at
113    the end of the macro form. We can use this rectangle to initialize
114    the shape of our new window:</p>
115
116    <pre>
117(ns:with-ns-rect (r 100 100 400 300)
118   (#/initWithContentRect:styleMask:backing:defer:
119    my-window
120    r
121    ...))
122    </pre>
123
124    <p>To specify the window features we want, we must combine
125    several flags to form the proper style mask. Cocoa provides named
126    constants for each of the various window features. To create the
127    syle mask that describes a new window, use inclusive-or to
128    combine the named flags into a style mask:</p>
129
130    <pre>
131(logior  #$NSTitledWindowMask
132         #$NSClosableWindowMask 
133         #$NSMiniaturizableWindowMask
134         #$NSResizableWindowMask)
135    </pre>
136
137    <p>You can find definitions for all the window masks in the Apple
138    Developer documentation
139    for <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWindow_Class/Reference/Reference.html#//apple_ref/doc/uid/20000013-89097">NSWindow
140    Constants</a>.</p>
141
142    <p>Passing the window mask as the next argument gives us this
143    expression:</p>
144
145    <pre>
146(ns:with-ns-rect (r 100 100 400 300)
147  (#/initWithContentRect:styleMask:backing:defer:
148   my-window
149   r
150   (logior  #$NSTitledWindowMask
151            #$NSClosableWindowMask 
152            #$NSMiniaturizableWindowMask
153            #$NSResizableWindowMask)
154   ...))
155    </pre>
156
157    <p>Like the style masks, the <code>NSBackingStoreType</code> value
158    is a named constant that describes which drawing strategy Cocoa
159    should use for the contents of the window. The value can
160    be <code>NSBackingStoreRetained</code>, <code>NSBackingStoreNonretained</code>,
161    or <code>NSBackingStoreBuffered</code>. For this example, we'll
162    use <code>NSBackingStoreBuffered</code>:</p>
163
164    <pre>
165(ns:with-ns-rect (r 100 100 400 300)
166  (#/initWithContentRect:styleMask:backing:defer:
167   my-window
168   r
169   (logior  #$NSTitledWindowMask
170            #$NSClosableWindowMask 
171            #$NSMiniaturizableWindowMask
172            #$NSResizableWindowMask)
173   #$NSBackingStoreBuffered
174   ...))
175    </pre>
176
177    <p>Finally, the <code>defer</code> argument is just a Boolean. If
178    we pass a true value, Cocoa will defer displaying the window until
179    we explicitly tell it to. If we pass a False value, it will
180    instead display the window right away. We can pass the Lisp
181    values <code>T</code> or <code>NIL</code>, and the Objective-C
182    bridge automatically converts them for us, but in the spirit of
183    using Objective-C values for Objective-C operations, let's use the
184    Objective-C constants <code>#$YES</code>
185    and <code>#$NO</code>:</p>
186
187    <pre>
188(ns:with-ns-rect (r 100 100 400 300)
189  (#/initWithContentRect:styleMask:backing:defer:
190   my-window
191   r
192   (logior  #$NSTitledWindowMask
193            #$NSClosableWindowMask 
194            #$NSMiniaturizableWindowMask
195            #$NSResizableWindowMask)
196   #$NSBackingStoreBuffered
197   #$NO))
198    </pre>
199
200    <p>There; the expression to initialize our window object is
201    finally complete. We can evaluate it in the Listener to
202    initialize the window:</p>
203
204    <pre>
205(ns:with-ns-rect (r 100 100 400 300)
206  (#/initWithContentRect:styleMask:backing:defer:
207   my-window
208   r
209   (logior  #$NSTitledWindowMask
210            #$NSClosableWindowMask 
211            #$NSMiniaturizableWindowMask
212            #$NSResizableWindowMask)
213   #$NSBackingStoreBuffered
214   #$NO))
215    </pre>
216
217    <p>Then we can call <code>makeKeyAndOrderFront:</code> to display the window:</p>
218
219    <pre>
220(#/makeKeyAndOrderFront: my-window nil)
221    </pre>
222
223    <p>The window, empty, but with the shape and features we
224    specified, appears on the left lower corner of the screen.</p>
225
226    <div class="title">
227      <h2>Adding a Button</h2>
228    </div>
229
230    <p>Once we have a window on the screen, we might like to put
231    something in it. Let's start by adding a button.</p>
232
233    <p>Creating a button object is as simple as creating a window
234    object; we simply allocate one:</p>
235
236    <pre>
237(setf my-button (#/alloc ns:ns-button))
238#&lt;NS-BUTTON &lt;NSButton: 0x13b7bec0&gt; (#x13B7BEC0)&gt;
239    </pre>
240
241    <p>As with the window, most of the interesting work is in
242    configuring the allocated button after it's allocated.</p>
243
244    <p>Instances of NSButton include pushbuttons with either text or
245    image labels (or both), checkboxes, and radio buttons. In order to
246    make a text pushbutton, we need to tell our button to use a
247    button-type of <code>NSMomentaryPushInButton</code>, an image
248    position of <code>NSNoImage</code>, and a border style
249    of <code>NSRoundedBezelStyle</code>. These style options are
250    represented by Cocoa constants.</p>
251   
252    <p>We also need to give the button a frame rectangle that defines
253    its size and position. We can once again
254    use <code>ns:with-ns-rect</code> to specify a temporary rectangle
255    for the purpose of initializing our button:</p>
256
257    <pre>
258(ns:with-ns-rect (frame 10 10 72 32)
259  (#/initWithFrame: my-button frame)
260  (#/setButtonType: my-button #$NSMomentaryPushInButton)
261  (#/setImagePosition: my-button #$NSNoImage)
262  (#/setBezelStyle: my-button #$NSRoundedBezelStyle))
263;Compiler warnings :
264;   Undeclared free variable MY-BUTTON (4 references), in an anonymous lambda form
265NIL
266    </pre>
267
268    <p>Now we just need to add the button to the window:</p>
269
270    <pre>
271(#/addSubview: (#/contentView my-window) my-button)
272    </pre>
273
274    <p>The button appears in the window with the rather uninspired
275    label "Button". Clicking it highlights the button but, since we
276    didn't give it any action to perform, does nothing else.</p>
277
278    <p>We can give the button a more interesting label and, perhaps
279    more importantly, an action to perform, by passing a string and an
280    action to it. First, let's set the button title:</p>
281
282    <pre>
283(let ((label (%make-nsstring "Hello!")))
284  (#/setTitle: my-button label)
285  (#/release label))
286;Compiler warnings :
287;   Undeclared free variable MY-BUTTON, in an anonymous lambda form
288NIL
289    </pre>
290
291    <p>The button changes to display the text "Hello!". Notice that we
292    are careful to save a reference to the label text and release it
293    after changing the button title. The normal memory-management
294    policy in Cocoa is that if we allocate an object (like the
295    NSString "Hello!") we are responsible for releasing it. Unlike
296    Lisp, Cocoa does not automatically garbage-collect all allocated
297    objects by default.</p>
298
299    <p>Giving the button an action is slightly more
300    complicated. Clicking a button causes the button object to send a
301    message to a target object. We haven't given our button a message
302    to send, nor a target object to send it to, so it doesn't do
303    anything. In order to get it do perform some kind of action, we
304    need to give it a target object and a message to send. Then, when
305    we click the button, it will send the message we specify to the
306    target we provide. Naturally, the target object had better be able
307    to respond to the message, or else we'll just see a runtime
308    error.</p>
309
310    <p>Let's define a class that knows how to respond to a greeting
311    message, and then make an object of that class to serve as our
312    button's target.</p>
313
314    <p>We can define a subclass of <code>NSObject</code> to handle
315    our button's message:</p>
316
317    <pre>
318(defclass greeter (ns:ns-object)
319  ()
320  (:metaclass ns:+ns-object))
321#&lt;OBJC:OBJC-CLASS GREETER (#x13BAF810)&gt;
322    </pre>
323
324    <p>We'll need to define a method to execute in response to the
325    button's message. Action methods accept one argument (inaddition
326    to the receiver): a sender. Normally Cocoa passes the button
327    object itself as the sender argument; the method can do anything
328    it likes (oor nothing at all) with the sender.</p>
329
330    <p>Here's a method that displays an alert dialog:</p>
331
332    <pre>
333(objc:defmethod #/greet: ((self greeter) (sender :id))
334  (declare (ignore sender))
335  (let ((title (%make-nsstring "Hello!"))
336        (msg (%make-nsstring "Hello, World!"))
337        (default-button (%make-nsstring "Hi!"))
338        (alt-button (%make-nsstring "Hello!"))
339        (other-button (%make-nsstring "Go Away")))
340    (#_NSRunAlertPanel title msg default-button alt-button other-button)
341    (#/release title)
342    (#/release msg)
343    (#/release default-button)))
344    </pre>
345
346    <p>Now we can create an instance of the Greeter class and use it
347    as the button's target:</p>
348
349    <pre>
350(setf my-greeter (#/init (#/alloc greeter)))
351#&lt;GREETER &lt;Greeter: 0x136c58e0&gt; (#x136C58E0)&gt;
352
353(#/setTarget: my-button my-greeter)
354NIL
355
356(#/setAction: my-button (@SELECTOR "greet:"))
357NIL
358    </pre>
359
360    <p>Now, if you click the button, an Alert panel appears.</p>
361
362    </div>
363
364  </body>
365</html>
366
Note: See TracBrowser for help on using the repository browser.