Carbon • 54:31
The Carbon Event model is a simple, flexible, and efficient model for handling events in Mac OS X. This session provides developers with step-by-step instructions for migrating WaitNextEvent-based applications to the new Carbon Event model.
Speakers: Curt Rothert, Bryan Prusha
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
and the Mac OS X evangelist, Xavier Legros. Hi, everybody. Welcome to session 203, Migrating to Carbon Events. This session is very important for all Carbon developers, not because Carbon Events are very important technology in Mac OS X, but because it's the start of the future. This session is the beginning where we're going to be introducing brand new features for Carbon developers. And for that, for you to take advantage of actually these new features we're going to be bringing, you're going to have to use Carbon Events.
Today we're going to be talking about a lot of things. We're going to help you understand all the different steps for you to move to Carbon Events. And you have to understand that this session is the base for you to move forward. and to talk about the future and to talk about how you're going to be developing and how you're going to be migrating to Carbon Events. I'd like to invite on stage Curt Rothert, who actually has been working on the toolbox and will take you on a step-by-step approach for migrating your application.
Curt? So good afternoon, ladies and gentlemen. It is a pleasure to be here with you to talk about Carbon Events. Carbon Events, in a nutshell, is the event dispatching system in the high-level toolbox. It permeates every part of the toolbox, and it has been designed specifically for performance, extensibility, and modularity.
So, what is this session about? First, I'm going to give you an overview of the Carbon Event model so we can become familiar with it. and be comfortable with using it to migrate your WaitNext Event-based application to Carbon Events. Who is this design for? Well, if you have an application that you want to add new features to, if you're running--existing running on OS 9 or OS X using WaitNext Event, and you want to take advantage of new features or new technologies that are available on OS X, this is for you. It's also if you want to migrate your current event processing over to Carbon Events, and also to be a good Mac OS X citizen in terms of performance and functionality.
So you may be asking yourself, "What benefits will I get by moving to Carbon Events?" Well, importantly, it encourages a modular design in your application. That is, that you can incorporate your event processing with your UI of your application. So if you work with different groups, they can incorporate different modules, and you can put them together with very few, if any, integration problems whatsoever. It's also compatible back to Mac OS 8.6 with the Carbon Lib shared library. It's important because the toolbox can add new features for free. You benefit from standard features that are available in the toolbox, And as new features are added, you get those for free.
It's also helpful because this predictable behavior defined by Apple. Now, traditionally, Apple has provided human interface guidelines that you would have had to react to and put together into your application, which makes your application's event processing part of your app. Well, using Carbon Events, we have the responsibility of taking care of the event processing.
and new features in Mac OS X and in the high-level toolbox are Carbon Event only. There's also a number of performance improvements. You may be asking yourself, what kind of performance improvements? Well, it encourages no polling. Using WaitNextEvent, you're sitting on one entry point, polling, trying to figure out if an event has happened. With Carbon Events, you no longer have to do that. It's a direct dispatching model. So when an event happens, it's dispatched to your event handler, and you do the processing that you need to do.
In this way, there's no dead-end event processing. And what I mean by that is, let's say the user clicks in the menu bar to track down a menu, and then they decide they don't want to select a menu item. So they track off and release. Using WaitNextEvent, you would be getting that mouse down. You'd be asking the toolbox, where was that mouse down, finding that it was in the menu bar, and then telling the toolbox to track that. Well, the user drags off.
They don't select any menu item. And your application was involved with dispatching an event that did nothing. So Carbon Events gets away from that. And there are APIs that are specifically designed for performance. And for a lot more information on that, please go see Session 207, Performance with Carbon Events, which is tomorrow in this hall at 3:30.
So an important thing to know about Carbon Events is you can migrate at your own pace. You can make incremental changes in your application that help move off of WaitNextEvent and go over to Carbon Events. Like I mentioned earlier, it encourages a modular design in your application. So you can add new modules to your application and not have your WaitNext Event code affected by those changes.
At this point, I think it's important to look at the relationship between the look and feel of a Mac OS application. Traditionally, the look is presented by the appearance manager. The UI is drawn by that. And it's important to use the Appearance Manager because it adds a layer of abstraction for the drawing.
If you were always drawing your custom controls every time, like the migration from OS 9 and OS 8 using Platinum over to Aquon 10, it would have been very difficult. So it was facilitated by using the Appearance Manager. Well, similarly, the event processing using WaitNextEvent is limited in the sense that we can't move forward, add new behaviors, add new functionality.
So the toolbox has provided that, just tools for you to add and make your own application, such as different types of controls, push buttons, radio buttons, check boxes. You put all of those into your application, and you're responsible for taking care of what happens with those controls. Well, it's useful to think of Carbon Events as a full framework.
The framework for your house is already built. All you need to do is plug into the functionality that you're interested in benefiting from. So what does this mean for an event-driven app? Well, with WaitNextEvent, as I mentioned earlier, you have to process each event and tell the toolbox what to do.
Now, it's important to understand that WaitNextEvent is based on top of Carbon Events. So any new functionality that you add using Carbon Events won't affect your WaitNextEvent code. In fact, it will bypass-- if you handle certain events, it won't even be returned to WaitNextEvent. So you can take care of some event processing on your own by bypassing that. And with Carbon Events, the toolbox processes each event and then tells you what to do.
So graphically, what's going on here? Is with WaitNextEvent, your application gets an event from the event loop. It figures out what to do with many switch case statements, tries to figure out where should I dispatch this to, dispatches it to your own handlers, and then you tell the toolbox what to do.
With Carbon Events, you define a set of events that you're interested in receiving. You tell the toolbox about that. You get into the event loop, which blocks, and the toolbox will dispatch events. And if it's an event that you're interested in, it will call back into your application's handlers, and you take care of the event.
So because of this, it encourages the event handling of your application to be part of the design. Like I mentioned earlier, you can design all the functionality in a module that deals with the UI. for example, if you wanted to add a new Inspector window, all of the event handling for that window could be in the file or module that deals with the UI of that window. So you can take new modules, plug them in, very few integration problems. And a lot of standard behaviors are provided for free by the toolbox. But those behaviors can be overrided by you to enhance it with your own functionality.
So how are events dispatched in the toolbox? As you can see in this image, an event can go, like, let's say, for example, that the user clicks in a control. If the control does not handle the event, it gets propagated to its parent if it's in an embedded situation. If its parent doesn't handle the event, it gets dispatched and falls down to the window. If the window doesn't handle it, it propagates to the application. At that point, if it's not handled, it gets returned via WaitNextEvent.
So at any point along this, you can install a handler, handle the event, take care of your functionality. If you've handled the event, the propagation stops processing. If you don't handle the event, it drops down to the application and then out via WaitNextEvent, if it makes sense for WaitNextEvent to return that event.
So it's a hierarchical dispatching model. And because of that, we can have standard handling, which comes for free. We have our handlers. The toolbox has its handlers installed on particular targets, and it can take care of actions. You can override those, or if it's not handled, it falls down out through WaitNextEvent. But you can modify the standard behavior because handlers are stackable. Certain handlers are provided by the toolbox, and you can stack your handler on top of that. If you don't handle it, it falls into the toolbox's domain to take care of.
So first, let's go through a primer, figure out what is this Carbon Event. So let's become comfortable with using it. So I want to talk about the data types and the APIs that are specific to Carbon Events. Now, all of these are in the carbontevents.h header. So if you have your laptop, you can pop it out and read it as we go.
So I'll be talking about the event type spec data structure, which indicates in the toolbox what events you're interested in. I'll talk about the Event Ref type, which is the main type of notification, how you are notified that an event has happened. will talk about the Event Target Ref, and this is where you install your handler and where events are sent to. and also an Event Handler Ref, which is a reference to your installed Event Handler.
So the event type spec is a very simple data structure, which includes the event class and the event kind. The event class is the high-level class of the type of event. For example, if an event happened in a control, it's K event class control. Or, no, yes. If it's in a window, it's a K event class window.
And then the event kind is a constant indicating what kind of event that was. So if there was a mouse down, for instance, that would be K event class mouse, K event mouse down. And usually you're interested in more than one type of event. So when you tell the toolbox what events you're interested in, you pass it in as an array of this type.
The Event Ref is an opaque data type. So when you need to get information in or out of an Event Ref, you have to use accessors. and it can contain an arbitrary amount of data in terms of the number of data types, in terms of the size of the data, and in terms of the type of data. Now, this is important because it's extremely extensible. In the future, if we decide we want to add different data to a particular event, we can go ahead and do that.
and the data is referenced by a parameter and a type pair. And all of this is in the Carbonevents.h header. has a listing of what types of parameters are in each particular event and what type of data that is that you can get out. And also, it's a two-way street. Information can come in with an Event Ref, and then you can set information in that event, and when the toolbox gets that, it can act on that as well.
Each UI element has an event target associated with it. So that's in terms of windows, controls, menus, the application, even the user focus, and the event dispatcher itself. This is where you install your event handler. So if you're interested in getting an event in a control, you would install event on that control target. And the events are then sent to that target and then to your handler.
The EventHandlerRef is a reference to your EventHandler. You get this when you call installEventHandler. You don't have to get this return. You can pass it null if you aren't interested. But it's useful if you get into a modal situation or a certain particular mode where you want to add event types to that handler or remove event types from that handler because you want a broader range of events being dispatched to your handler, for instance.
So that's the basic data types to get going on a Carbon Event-based application. But how do I get notifications? Well, you have to define your Carbon Event handler, and I'll describe how that looks next. And then you install your handler on an event target. And then when events get dispatched, they will get--your callback will get called.
So this is what your event handler would look like. When you install on a particular target and an event comes in, it will call your handler with an event handler call ref, which is useful for manual propagation, which I'll talk about later. It will also give you the event ref, what is the type of event that has just occurred. And it will also return to you some user data that you specified when you installed the handler on that target.
and the propagation, whether it's dispatched along the hierarchy, is controlled by your return value. If you handle the event, you'll return no error. And that tells the toolbox that the event has been handled. Stop propagation. If the event not handled error is returned, that will tell the toolbox that the event has not been handled. Continue propagation.
So finally, to install the Event Handler, you call installEventHandler. Now this takes the target that you're interested in,
[Transcript missing]
Now, if you get the EventHandler ref, you can also remove the EventHandler using RemoveEventHandler. talk about all these groovy concepts. I'd like to bring out Bryan Prusha to give a quick demo of getting a quick Carbon Event app running. Bryan? Thank you very much, Curt.
I'd like to just give a small walkthrough of installing a Carbon Event and then handling that Carbon Event elsewhere in the application. So this is pretty much the default code generated by Project Builder for a Carbon Nib-based application. Now, the first thing I'd like to do is show you the Nib.
So this is an Interface Builder document. And so here's our default window. I'm just going to add a couple buttons and show off some of the features of Interface Builder. So you notice the lines appearing show you how to line up your UI according to the Aqua Human Interface guidelines.
So I'm going to make this a quit button, and we'll make this a beat button. Now here, I can show the information, all the attributes on these buttons. And I'm going to add what's called a command ID. So we'll make this the quit command. There are a whole series of default commands here. I'll just choose quit.
So command IDs are things that can be passed around as a Carbon Event that you can intercept later. So if I click this button, this quit command ID will be sent out. I can intercept that and do what I want with it. Now, for the beep button, I'll just give it a beep command.
[Transcript missing]
We also have the beep command installed here. And so this command ID can be sent out from the menus, from buttons, and in fact, it can also be sent out from keyboard events. So if I bring the window back up, and here's the beep control. I'm going to set this to the default button. So when I hit return or enter, the button will be activated, and the command will also be sent out. So let me save that.
will come back to the code here. Now here is just grabbing all the information out of the nib, grabbing the menu bar, the window that we were just dealing with. And then what I want to do special is install this command event handler. So the type of Carbon Event I want to deal with is the kEvent class command and kEvent command process kind.
So here I'll create the universal procedure proc, UPP, for my event handler. And then I want to install that event handler. So I call the aptly named installEventHandler. And I want to install this on the application event target. Like I said, this is a high-level event, not anything like a mouse down.
I don't have to--I don't care whether it's in my control or a window or a menu specifically. In this case, I want to get it no matter where it comes from in the application. So I'll install my event handler. I'll count up the number of events that I'm registering for. And I'll register for these particular notifications.
I'll call runApplicationEventLoop, which drops us down into the HIToolbox's event handling routines. And this is where all the standard handling, things like that, happen for you. And after we're done running the application, we want to dispose of that event handler. So let's look at the event handler itself.
Here I want to look at the HICommand, which is what I registered for. An HICommand is a data structure which contains the command ID that I passed in. So I want to get this particular event parameter. In this case, it's the kventparam direct object. And it's of type HICommand. And I'll just grab that command right out.
and I'm going to switch off the command ID. And in the case where it's beep, which is my private type, which I set up, I want to just call sys beep. And in that case, I want to return no error because I don't want this to propagate anywhere else. Nothing in the toolbox cares about the beep ID. It's only important to me. So I don't want anybody else to know about this.
But I also want to make sure that in every other case, for the default case, I return event not handled error. And this will allow the application to go on processing every other type of command that comes through. In the case of this demo, where I created the quit beep-- I'm sorry, the quit button, I want to make sure that that quit command ID is processed through so the toolbox can intercept it later and quit your application on your behalf in a standard way. So I'll go ahead and run this application. And we'll see how it behaves.
Now, there will be a lot of things that happen for us here because the standard event handlers are all being used. So we'll get window dragging and menu activation. Oh, this is my worst nightmare. Just compile it again. Hmm. Great. Well, we're going to go a little hardcore on you here, so give me just a second. I'm sorry? All right. Let me clean this, make sure everything's all cleaned out here. Build from scratch.
All right, here we go. Whew. Now we have our window. It's dragging around. We get that for free because it's a default behavior. We get the resize. We get menu tracking. This is great. I didn't have to do anything specifically to handle this. What we're here to look at is the command ID.
So here's my beep button, and I'm gonna click, and then the command ID beep will be sent out, and I'll intercept it in my handler and beep. So, just as you would imagine, this would happen. And again, because this is a high-level event, I can hit Enter. It tracks for me automatically, and we get the same behavior. And again from the menu.
If I wanted to, I could quit this application through the default quit menu item. But in this case, I'm going to send this off somewhere else. I want to quit with my button. So that quit command ID is sent off and handled in a standard manner by the IHI toolbox on your behalf. All right, thank you very much. Back to you, Curt.
Thanks, Bryan. As you can see, there was very little additional code that needed to go in in order to make a simple application that actually did something. There wasn't any boilerplate code required using WaitNextEvent in order to track the event and process it. and So now that we understand how to build a basic Carbon application, Carbon Event-based application, let's talk about how to migrate your existing application. So I've listed five steps for migration. The first two steps I think you could do today.
That is, first, replace your tracking loops. If you need to track where the mouse is, for instance, the user clicks in a certain area of your screen, and you need to track where the mouse location is while the mouse is down, traditionally you'd sit in a loop and pull the mouse location. Well, we have API specifically designed to get away from that, and I'll describe that in a moment.
Second, get rid of null event idling with WaitNextEvent. WaitNextEvent, as you know, returns null events if no events have come in, and you've asked for null events, or it'll just return false. And then you can do some particular idling. Well, there's ways to deal with that as well.
And those two things you can do today. Next, get your feet wet with Carbon Events. Design some new features using Carbon Events. will allow you to become familiar with the data types, the event handling, how to install handlers and handle those events. Next. With your existing event processing, migrate that over to using Carbon Events. I'll describe that in a moment as well. And lastly, when you've taken care of everything, get rid of WaitNextEvent altogether. It's been a good friend, but we want to move forward.
Step one, replace your tracking loops. Like I mentioned, traditionally, when the user clicks on the mouse button and you want to track that, you would call, while still down, getMouse. Now what this does is it sits in a tight loop, pulling the hardware to see if-- where the mouse location is.
It's bad because it's sitting in a tight loop, even though maybe nothing has happened. Maybe the user hasn't actually done anything with the mouse in terms of moving it or releasing it or doing anything interesting. It's doubly bad because this requires inter-process communication. We have to ask the Windows server for the state of the mouse. So instead, use the API trackMouseLocation. What this does is it gets in the event loop, and it blocks until the user actually moves the mouse or does something interesting with the mouse.
This frees the CPU up for other processes. You're not starving the processor or starving other applications that need to do things. And also, since it's blocked on the event loop, if you have any timers installed, they will still be called and your application can continue processing even while the mouse is down.
In addition, there's a new mouse tracking region API. This is new in Jaguar. This is as a replacement for the mouse region parameter in WaitNextEvent. The advantages of this are using the mouse region parameter in WaitNextEvent limited you to one mouse region per application. While using the mouse tracking region API, you can have many regions per window that you want to track.
And this allows you to get entered notifications and exited notifications. So if the user enters a particular region of your window, you'll get a notification to do something. This is useful if you need to do cursor switching. If you have a text field that when you drag the cursor over, you want to turn to an I-beam, for example.
And this is also how the window widgets work. So when you drag the mouse over the close, minimize, or maximize widget, we'll highlight them. And this is exactly how it's done. Step 2: Get rid of your Null Event Idle Processing. This is important because you're just spinning. You're sitting on WaitNextEvent waiting for nothing to happen in order for you to go ahead and do something.
So what is the problem? What are you trying to solve with ILEvent processing? Well, perhaps you need to do some processing at periodic times to animate an image, for instance, or blink the cursor. or let's say you have a text editor and you need to do repagination. Well, you don't want to interfere with the user while they're typing, so you want to wait a certain amount of time, repaginate while they're not doing anything, or do some searching or sorting in the background.
What are the problems with using WaitNextEvent? As I mentioned, it wastes CPU cycles. Sometimes you're sitting there and you're not interested in any particular event at this one time. You want to wait for a certain amount of time before something happens. But you're still returning from WaitNextEvent and have to do some calculations. It's also not modular.
If you have a new module that needs some idle processing, you need to incorporate that, include that into your existing code with WaitNextEvent to handle some processing. And it requires internal logic to maintain timers. And what I mean by that is, let's say you have two animations in your document.
One that needs to be animated four times a second and one five times a second. Well, since WaitNextEvent is based on ticks, the easiest solution is to call yourself back 20 times a second, do some calculations to figure out which image needs to be animated that particular time.
Solution? Use event loop timers. It's a very simple API, and it allows you to install one timer per task. In my example, if you had two images that you needed to update, you would just install one timer for one image, a different timer for another image, and each of those would only be updated when they need to be updated. There's also three classes of timers. There's one shot for singular events. That is, let's say you build up a cache, you want to keep it around for a particular amount of time, and then flush it. You'd use a one-shot timer for that. Thank you.
There's periodic timers for recurring events, like an animation, cursor blanking, etc. And new in Jaguar, there's idle timers. Now, this is a type of timer that you can install that will be called when the user is idle, in terms of moving the mouse, keyboard input, etc. There's more information on this in Session 207, which is Performance Carbon Events, which, again, is tomorrow, here in this hall at 3:30.
Like I mentioned, the Event Loop Timer API is very simple. There's Install Event Loop Timer. This takes the event loop that you're interested in installing this timer on, so you can install per preemptive thread. : Hi, everyone. I'm Curt Rothert, and I'm the founder of the Carbon Event Management team. and then the toolbox will never get rid of a timer for you, specifically for if you want to reset the time. So you'll always have to call RemoveEventLoopTimer when you're done using it.
I'm going to talk about idle timers and show how easy it is to get up on them and get off of WaitNextEvent. I'd like to bring Bryan Prusha back up. Thank you again, Curt. All right, for this demo, I have a WaitNextEvent-based app, which I will convert over to event loop timers.
So here we have... here we have our main.cp. And I initialized some things from our NID. This is, again, a NID-based app. And I'm going to be running an animation of the Game of Life. You may have seen it on some screen savers. So I initialize my animation, and then I run that nasty boilerplate event loop code wrapped around WaitNextEvent that everybody has to write. And then when I'm done with the application, I'm going to go ahead and dispose of my animation.
So let's look at that boilerplate event code. Here we have WaitNextEvent. And then when an event comes through, we have to decide, okay, is this a mouse down? Is this a key down? What are we going to do with each one of those different types of events? Oh, if it's a mouse down, is it going to be in the menu bar? Is it going to be in the drag part of the menu? I'm sorry, the drag part of the window? And the go away box, the grow box, all these things. So all this code that you have to handle manually. and it becomes a lot of code.
So here we come back again to WaitNextEvent. And I'm calling this back to every six ticks, which is every ten times a second, to update my animation. Now, as Curt described, we have to wait until something doesn't happen. So when WaitNextEvent returns false, we're going to go ahead and come down to this switch statement, handle our null event, and this is where I evolve the animation.
And so now that we've looked at this, let's go ahead and run the app and see what it does. So here's the nice animation running along, updating every tenth of a second. But what happens when an event does happen and we can't process on all events?
[Transcript missing]
Okay, then.
All right. So if I go to resize my window, again, everything stops. Or if I just go to access the menu, everything grinds to a halt. This isn't necessarily the greatest thing in the world. So I'm going to go ahead and quit this. And I'm going to go over to my animation code. Part of what Carbon Events does for you is it allows you to bring the event handling over to those parts of the application where it really matters. I want to keep my animation code together with the event processing for it.
So here's my initialization routine. And I grabbed some more information from the nib. And what I want to do in this initialization now is install an event loop timer. So here I'm creating the event loop timer UPP. And I want to install that on the current event loop. So in the multi-threaded case, this would be on whatever thread I'm running in. In this case, it's single thread. I'm running on the main event loop.
Now I'm going to fire this off immediately because I don't want to wait for my animation to start. I just want to get right in there and going. And again, like the WaitNextEvent case, I'm going to go ahead and have this call back every ten times a second.
And I'll pass through my UPP, and I'll be notified whenever this timer fires. So here in my timer, all I'm doing is calling Evolve Life just like I was back in the WaitNextEvent case. Now that I install that timer, I can go ahead and remove this WaitNextEvent null idle processing. It's no longer necessary. So I'm gonna go ahead and rebuild.
One other thing that's very important is once you move over to null event processing-- or from null event processing to event loop timers, you want to give your WaitNext event a very long timeout so it's not being needlessly called back. So we'll give it a very long timeout.
So now that I'm running on timers, you'll see there are some basic changes in the application. For one, as soon as I do have events, things don't stop processing. This is great. My animation keeps going. And while I resize, the animation continues, and we can even watch the animation underneath these wonderful translucent menus.
So there we've converted the WaitNext Event idle event processing over to Carbon Event timers. So thank you very much. Back to you, Curt. Thanks, Bryan. So that looked pretty easy, didn't it? You could get off of WaitNextEvent for your event processing and use timers. So those two steps I think you could do today. Get off of tracking the mouse using while still down get mouse. and also using Null Event Processing with WaitNextEvent. Step three.
Do your new development with Carbon Events. It helps you become familiar with Carbon Events concepts, data types, APIs, when your event handlers are called, etc. It won't impact your existing code. Like I mentioned, it encourages modularity. So as you create a new module, you can just plug it in. It won't impact your existing WaitNext Event code.
So do this when you have to add new application features, such as if you're going to do a toolbar, or an inspector window, or even new window definitions. They can be completely Carbon Event-based. Also become familiar with using NIBs. As you saw in Bryan's demo, it was very easy to construct an interface in Interface Builder. And it generates what's called a NIB.
also do this when you want to adopt new toolbox features. Most new features will and are implemented using Carbon Events, such as the Services menu, the mouse tracking regions which I mentioned, even HI Object, which is this cool new philosophy that we're really excited about. It'll be talked more about tomorrow at 9:00 in this hall in the overview of the HI Toolbox architecture. All Carbon Event-based. It's very cool.
So step four, migrate your existing event processing over to using Carbon Events. You don't have to deal with all the event dispatching yourself. Give that responsibility to us.
[Transcript missing]
So what's the problem with handling events the old way? What's wrong with it? Well, it's limited to 16 types of events, and that's simply because WaitNextEvent's event mask is a 16-bit integer.
Since it has the limitation of only returning 16 types of events, it gives you the low-level event. A mouse down has occurred, a keyboard down has occurred. It doesn't have a full, rich sense of events, like actions that have happened. You also see that it's the main bottleneck for idle event processing and mouse tracking, which I already mentioned. It makes your architecture very difficult and hard to maintain.
and you are required to do the proper dispatching. So if we ever want to change behavior, you have to go maintain your code. Change it. Now, an example of that is when the proxy icon was added to the window title bar. If you wanted to take advantage of that, you were required to go into your WaitNextEvent-based code and deal with that accordingly.
We want to give you an extensible solution. So the easiest thing to do is use the standard handlers. You get standard behaviors for free. For example, with the window, you saw in Bryan's first example that you could drag the window around, resize it. All that was provided for free by the toolbox. And now if we include any future technologies, you'll also get that for free. And it allows you to migrate off of WaitNextEvent.
So for a window, if you want to take advantage of standard handlers, you would set the KWindowStandardHandler attribute. Now, this you could do in creation with the API. It takes attributes. There's also an API change window attributes. Or if you're using Interface Builder, there's a checkbox that says Standard Handler. And you click it, and you get all this for free. Resizing, dragging, tracking the widgets.
Very cool. Then you can also customize the behavior. If you're not satisfied with the standard behavior provided by the toolbox, you can go ahead and install handlers for the events that you're interested in. Let's say you want to know when the user has dragged a window close to the edge of the screen, so you could snap the window to the edge of the screen.
Well, you could install a handler to find out when the window has moved, and then change data in that event ref accordingly, and the toolbox will listen to that and snap the window. Or let's say you want to grow the window, and you want to constrain the size so it doesn't just have odd dimensions. You can also do that. Install a handler for that particular event, a bounds-changing event.
[Transcript missing]
I mentioned earlier that you can control the propagation of an event as it's dispatched through the hierarchy. Well, for standard handling, you would set the standard handler attribute and then do nothing. The toolbox takes care of everything for you. Now let's say you want to override some of the standard behavior. Well, you would install a handler on a target for the particular event you're interested in. You'd handle that event, and then you return no error. Processing stops--it doesn't propagate any further.
But let's say you want to modify the standard behavior. You want to get the standard behavior, but you want to do something in addition. Well, you still can do that. If you want to pre-process, you install your handler on a target for that particular event. When your handler is called, you can do something and return event not handled there. The toolbox will continue propagation. or you could post-process it. And you would explicitly continue propagation by calling CallNextEventHandler. Or you could do a combination of both.
Now let's go on to some advanced event processing. So that's kind of the basics. Now, it can also be useful to create your own events. Let's say you have a command handler for the Close event, like for a window. What you've seen is that the user has tracked over the close box of a window, and you want to generate a close command. So you can use the API createEvent and direct that towards the application so all your event handling goes through the same bottleneck, if you will, the same handling.
You can also use Carbon Events for internal communication. Go ahead and define your own event classes, and then you can use-- create event, send the event to a target. Let's say you created a module for an inspector window, and all the event handling's dealt with that, and it defines an interface using event classes. A different part of your application can create an event of that class, send it to the window, and it can react to that. It's also useful for communicating between preemptive threads.
So you've done all of that. And you don't want to stay so long to WaitNextEvent. What are the steps to doing that? So follow all four steps I mentioned earlier. When you're done with that, maximize the sleep, which you saw Bryan do. And what that does is it makes sure that your timers are doing what you would expect them to do, that your application continues to process events the way you would expect. Then, you remove WaitNextEvent and replace it with runApplicationEventLoop. I'll describe that API next. And then finally, you would remove all of the WaitNextEvent artifacts. All of that switch case stuff just should go away.
RunApplicationEventLoop, the replacement for WaitNextEvent, when you call it, it enters the event loop, and then it will block until any events occur. That means that your application is not processing events, it's not doing anything until something interesting happens. So this provides a good user experience to the user, because your application does not consume resources when it's not doing anything.
While it's in this function, it will dispatch events to your handlers as they come in, and it will only return when QuitApplicationEventLoop is called. Now, that can either be called explicitly by you and one of your event handlers or implicitly by the toolbox. For example, it will take care of the H-I command Quit and call this function, so it will return from RunApplicationEventLoop and come out of your code. And so to go through these advanced things and actually show a very cool demo, I'd like to call Bryan back up.
All right, thank you again, Curt. This time I'm going to knock on wood here. All right, I'm going to come back to this animation demo. And what I'm going to do first is go open the nib. So there's some things I want to do there. I want to start using standard event handlers.
I want to kind of get myself off of WaitNextEvent. So here's the main window. And so down here, as Curt mentioned, there's the standard handler checkbox. Now I'm gonna click it. Now that I'm using standard handlers, I'm gonna click live resize, and I'll get live resize in the standard behavior. I can save off that nib.
I'm going to come back to the code. I'll go again to the animation code. And here there is more code to uncomment. So I'm going to install another command event process handler. And so this looks just like it did before. I'm installing this on the application event target. I have my handler. And the same KEVENT class command, KEVENT command process types for the event.
So I'll come down here and look at the handler. And like before, I'll call getEventParameter, get the direct object, which is the command, and I'll switch off the command ID. But I want to handle specifically the khicmdnew, which is outlined in carbontevents.h. And I'm going to initialize my animation here.
and again, return no error, because I want to be the only thing that handles the new command. I'm also going to handle khicmdquit and dispose of all the pieces of my animation. But this I do want to go ahead and continue, so I'm going to return event not handled error.
because I want to have the standard handler call QuitApplicationEventLoop on my behalf. Now I can go back to my main.cp. Because I'm handling the quit command, I no longer have to dispose of my animation here. In fact, because I'm using standard event handlers, I no longer need my boilerplate event loop. Instead, this is going to be replaced by runApplicationEventLoop.
And since I'm no longer using my boilerplate event code, I can simply delete it. certainly makes that file a lot shorter, which is very nice. Good benefit. Okay, now I'm going to go ahead and run this. And we'll see that I can actually remove code, and using the standard event handlers, I don't have... The application still behaves very similarly to what it did before. I can still drag it around, the widgets still update, and now I even get benefits for free, like live resizing.
And again, the menus still track. I can close this window... and then choose New, and it comes back. So I'm now handling all my event processing in my animation code itself. So everything is all localized and modularized. Now, just to really belabor this point, I'm gonna go ahead and add a completely new module to this application.
I have a new module set up right here. I'm just going to drag it into Project Builder, into my sources. I'll add it. And so I have just some code, a header file, and a nib part of my module. I'm going to go back to my main code and include module.h.
and then I'll initialize my module just like I initialized my life animation. Now, in my module, I handle the MODL command ID. So I'm going to go back to my main nib. and add a new menu item. I'll just duplicate the new item here. And I'll say "new module." I'll give it a new command key equivalent.
And then in this menu item, I want to handle the MODL-- and the MODL command. Now, when I choose this menu item, it'll send out this command, be intercepted by the event handler that was installed in my module during the initialization, and it'll create the new module for me. Save that off. Go ahead and rerun.
and now with very little effort, I'm able to add a completely new module to my application. Now, this module also has standard events, so I can go ahead and just drag this around. It's completely separate from this. I didn't have to handle anything specifically. I can resize live just like I could before.
And if I close this and choose New Module from the File menu, it'll pop up and just start going again. So I can dynamically dispose and recreate my module. So here it is, complete new functionality, completely wrapped up. So if there are two groups working on the development of this application, they can work independently and with very little work integrate them together into their app. So thank you very much. Back to Curt.
Thanks, Bryan. So you can see, it was very little effort to take a new module. You don't have to know anything about the module other than the interface to initialize it. Maybe some Carbon Events, event class or two that the new module requires. and the integration was very easy. Plug it into Project Builder, and it just built and worked.
So some tips for using Carbon Events. Sometimes you may not know what events are coming in, going out, what I should be installing a handler on. Well, you can find out what events are going on by setting this environment variable, event trace. Now, there's a number of ways to do this, but on Mac OS X, in Terminal, what you would do is you would type in setEnv event trace 1, and that sets the environment variable, and then you would launch your application from that terminal session.
Better make sure your terminal buffer's pretty high, because there's a lot of spewage that comes out telling you which events are being sent and what's going on. There's also some very valuable information on the web in a Tips and Tricks page for Carbon Events. It's on Apple's developer website.
So I hope I've been able to impress upon you that migration is easy. I mean, it's an incremental process. I don't expect you to do all of it today. There's some that can be done today. But it's useful to do any new development using Carbon Events. In fact, you should be doing all new development with Carbon Events.
Carbon Events are the future of the toolbox. This is the direction that we're going. If you want to take advantage of new features, you'll have to be Carbon Event-based. So it's useful to get up and on there now. And it's a must-have for being good OS X citizens in terms of performance and functionality. Like I mentioned, there's a lot of new functionality that's Carbon Event only.
If you want to take advantage of that, you have to be up on Carbon Events. There's some excellent documentation available. There's a Carbon Porting Guide. There's a book on handling Carbon Events, Carbon Event Manager Reference. There's some documentation on your Jaguar CD. and John Lennon will be joining us for the session.
Five steps of migration. It's pretty straightforward. You saw Bryan go through everything today. Replace the track and loops. Get rid of WaitNextEvent-hidling. Design all of your new features using Carbon Events. Migrate your event processing over to Carbon Events. And finally, get rid of WaitNextEvent and all the cruft associated with that.
and the High-Level Toolbox team is completely monopolizing this room tomorrow, so I'd encourage you to go see all of the sessions that are available. There's an architectural overview, which will discuss new features coming into the toolbox. A HIView session, excellent session. New controls and services to go in more depth of what's going on in the toolbox, new stuff that's available. And I highly recommend seeing Improving Performance with Carbon Events, and that's at 3:30. And that will teach you how to use To Be a Better Citizen for performance.