Application • 56:03
If you have custom controls or custom content drawn with QuickDraw in your Carbon application, this session is for you. The Carbon HIView and other HIToolbox features will allow you to easily support fully composited windows and improve the performance and responsiveness of your application. We show you how to implement a wide variety of HIViews to handle drawing, user interaction, accessibility, and lots more.
Speakers: David McLeod, Curt Rothert
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning everyone, welcome to session 433. Please welcome software engineer David McLeod. Good morning. Thank you for coming out. I'm glad to see there's people here interested in making custom HIViews. That means you're getting our message that HIView is the way to go forward and you're ready to do it with us.
[Transcript missing]
You make them special by doing whatever things you do on the inside that we don't do. And you present that specialty through your user interface. So almost everyone here has some kind of custom user interface, whether you're drawing directly into a window or you're using a custom control, you're using some other view system.
Like Power Plant, for example. I'm trying to find a nice way of saying that. So people are going to be moving to HIViews. And you might have some custom content already. As you go forward, you're going to need to have HIView. HIView is the technology we're going to use to move forward as we do things like resolution independence, as we add new features to the toolbox. And you need to be there with us to move forward.
Basically, today I'm going to go over how to make a custom HIView. It's going to be a primer on how to make HIViews. So we're going to learn how to subclass an HIVobject, how to create a custom HIView, and just how to implement the basic behaviors that make a view. First, I'm going to give you a little bit of background.
The whole HIV toolbox on the inside is object oriented. And HIViews are a subclass of the main object, the HI object. All of the APIs associated with HIView and HIObjects are object-oriented. They use C interfaces and Carbon events to do the communication where the HIViews or HIObjects are the classes and the Carbon Events are the methods or messages.
And with those messages, we put in Carbon Event parameters. And that's how you pass parameters to and from your subclasses. So here it is. HIObject is really our base class. Everything's based off an HIObject. And like I said, there's some messages that are associated with the class. There's also some properties.
From that base class, we have subclasses-- HIViews, windows, menus. There's a few other ones I don't have up there, like toolbars, et cetera. But that's just to show that, you know, HIobjects are the base class that we use to inherit from to produce different objects within the toolbox. We're going to focus in on HIViews. So, HIViews are the subclasses-- HIViews, like I said, with all of our classes, messages are the Carbon events, and there's a few properties associated with the subclass.
We use HIView to subclass to make all of the views that you associate with the system, so like pop-up buttons, radio buttons, check boxes, all of that. And this is where you would subclass to make your custom HIView. Notably, you can also subclass an existing subclass of an HIView.
So in this example, I would subclass a push button and just override some of the extra behaviors, make my own custom kind of push button that inherits from the existing push button. But I'm just going to focus on subclassing HIView directly. So a custom HIView. is a subclass of HIObject where you handle a few messages associated with construction.
And it's also a subclass of HIView where you handle a few messages that actually produce the behaviors of your HIView. And that's all that makes up a custom HIView. Understanding these concepts is sort of key in understanding how to subclass and make your own custom HIView. So next I'm going to get into just how to go about doing that subclassing.
First thing you're going to need to do is register your subclass. And that means handling those events That are messages for the HI object associated with doing construction, doing destruction when we're finished with the object, and doing initialization. After that, you're ready to instantiate. That's all there is to it. So here's an example of how to register your subclass using the HIObjectRegisterSubclass API.
The first parameter that's really important is the class ID of your subclass. And that's a reverse domain string that you send to HIToolbox to tell it, To tell it how to identify your subclass. And throughout the process, that is how you identify your subclass to HIToolbox. That's how we know what we're talking about.
Next, you want to tell it the base class ID from which you're going to be subclassing. In today's examples where I'm subclassing an HIView, I'll be passing in the base class ID of an HIView. You can also use this to subclass with the pop-up button's class ID or directly from an HI object. Notably, if you're subclassing directly from an HI object, you can leave this blank because that's the base subclass.
And the last one I'm going to draw attention to is the construct proc. This is the event handler that's going to be handling the events, the construction events, the kEvent, hIObject, construct, destruct, and initialize events. Here's an example in code of using it. So it's just like any other Carbon event handler that you've used before. You set up a list of Carbon events that you're interested in. It's just a set of class event pairs. In this case, I'm interested in the KEvent class HIObject class of events, and I'm interested in KEvent HIObject construct, initialize, and destruct.
Next, you call hiobjectregister subclass, and this is where the reverse domain name class ID identifier comes into play. It identifies uniquely my subclass by this ID. And you can see here in this example that I'm subclassing from HIView using the public K HIView class ID. And I specify which handler is going to be receiving these three events that I'm interested in. It's going to receive them and handle them, dispatch them out appropriately.
So here's an example of the meat of what that event handler would look like. I handle those three events that I've registered for. When I get the construct event, I do something to handle construction. Similarly, with initialization and destruction, I do something to handle those. So I want to get into exactly what you would do in those three cases where you're handling those events.
First, construction. It's very notable. It's called directly by the instantiation mechanism of HIObject. It's not from an event handler stack. And this is really important because under normal circumstances with Carbon event handlers, you can call next event handler and let some of the default behavior happen. But there's no event handler stack in place, so you just can't do that. Really bad things will happen.
When you get the construction event, that's the time when your subclass does its allocation or instantiates maybe a C++ class. Any of the allocation that's going to occur happens here. It's not time to do initialization yet because things might not be set up sufficiently. You do that later on.
And the really-- I think this is the only tricky concept in the whole subclassing mechanism. The keventparam HIObjectInstance parameter During the construction handling, on the way in, what you get is a base HIObjectRef. That's what's been constructed so far by the HIObject subclassing mechanism. That's the ObjectRef that you're going to have for the rest of the life of the instantiation of your subclass.
During the construction handling, on the way in, what you get is a base HIObjectRef. That's what's been constructed so far by the HIObject subclassing mechanism. That's the ObjectRef that you're going to have for the rest of the life of the instantiation of your subclass. So on the way in, it's that HI object ref.
On the way out, you want to set the parameter to whatever the instance data is that you've allocated. And that's important because from now on, whenever your subclass is called, that instance data is going to be passed in as the user data to your event handler. And that's how you can get the reference to what your instance is in your subclass.
So here's an example of handling a k-event HI object construction event. Just a little reminder, don't call Next Event Handler. Bad stuff. Bad stuff's going to happen. I do some allocation. I have a little data structure. It's not really important what's in it. I call it my view data. The only important thing to note is that I have a spot in there to remember my HI object ref that I get on the way in from construction.
I get that HIObjectRef from KEVENT_PARAM HIObjectInstance, and I store it in my allocation space, the space that I've allocated. And on the way out, you can see that I've stored that allocated instance data back in and overwritten the KEVENT_PARAM HIObjectInstance. From now on, whenever my event handler gets called, I'm going to get that instance data, and I can use it to implement my subclass.
I like to talk about destruction first, but I just kind of reordered a bit. The destruction is going to happen way at the end. So before the destruction happens, you're going to do initialization. When you get a K event, HIobject initialized event, that means that construction happened successfully. The allocation is okay. Everything is ready to go. It's time to set things up.
When the initialization event comes to you, all of the initialization parameters are set as Carbon Event parameters in the incoming event. So you can just extract those, if there are any, and do whatever you want with them. Store them into your instance data or set up things or do whatever you want.
This phase is optional. Not everybody's going to have instance initialization, but actually I think it would be pretty common. You don't have to do it. Nothing bad will happen. I think I forgot to mention with a construction event, it's absolutely required. If you don't do it, no construction is going to happen and the rest of the presentation is not going to happen. So you definitely have to handle construction.
So here's an example of handling the initialization event. First of all, you want to call next event handler. That's important because it gives your superclasses a chance to initialize. If you're familiar with C++, that's just like passing on your constructor up to the other superclass constructor. So you've got to give your superclass a chance to initialize itself as well. You check the return value from the call next event handler and make sure that your superclass initialized successfully, because if it didn't, you certainly aren't really going to need to.
And then it's time to handle your own initialization. Like I said, you just use the incoming event, you extract parameters from it using GetEventParameter, and do whatever you want with them. And this is also a time when you can modify the initialization event and set things like your feature bits and that sort of thing. Example of it here.
Lastly, after everything's done and everything's being torn down, you'll get a destruction event. Again, this is not being called from the normal event stack, so don't call NextEventHandler. Even if we did do that and we allowed you to tell your parents to destruct themselves before you did, that would be a very bad thing. It's like calling delete parent in your C++ destruct method. It's a bad idea. This is not optional. You have to handle your destruction.
To get rid of any allocated data. If you don't handle this event, Destruction will fail. And when you do destruction, that's your time to deallocate anything you allocated during the construction phase. If you allocated some memory or if you instantiated a C++ subclass, it's time for you to either free the memory or delete the C++ subclass.
Again, emphasizing don't call Next Event Handler. I say it lots in here. It's going to be in the sample code that you see. It's going to be in Curt's demo. You just don't want to do it. It's also in the header docs. Just don't call Next Event Handler when you're doing construction and destruction.
And as you can see, this is just my time to clean up my allocated data. One important note here is that you will get a destruct event if initialization was unsuccessful. So you have to be able to handle destroying a partially initialized or an uninitialized subclass. So if construction went okay but initialization didn't, you'll still get a destruct event, so you have that opportunity to deallocate.
That's pretty much it. So after you install that event handler, you're ready to instantiate. So you have to register the subclass using that API I mentioned previously. And to do that, you create an initialization event. To actually instantiate, you create an initialization event, stuff any parameters in that you want to end up being passed to your subclass instantiation, and then call hi object create using that initialization event that you just created.
Here's an example of that. I create an event. It's a kEvent class HIObject, kEvent HIObject initialize event. It's a real standard way of making an event. I made up a fake value here to stuff into the event. The initialization event has a parameter. It's just a float I push in there.
And I call hiObjectCreate. And you'll note here that hiObjectCreate takes the reverse domain class ID that I registered my class with. That's how you tell Toolbox, hey, that's the thing I registered. That's how I want you to instantiate right now. So that's how you get your subclass instance to be the one you want.
I'd show you a demo. There's not much to demo yet. You've just made an instance that doesn't do anything. It's not--that's just how to make--that's just how to make a subclass of an HI object. It's not in anything. It doesn't draw. It doesn't have any behavior or anything like that.
So I'm not going to show you anything. To give you something to show, you probably want to implement the simple view behavior. Simple view behaviors are drawing, hit testing, clicking, keyboard handling, and definitely accessibility. These are the simple basic behaviors that you're going to want in almost every HIView.
And really, they just all map onto either directly onto a Carbon event or onto a class of Carbon events. Really straightforward. So say you wanted to add some drawing to your HIView subclass. You want to handle the K event control draw event. Really important concept with HIView drawing is getting this event, that's the only place you ever draw. You don't draw willy-nilly. You invalidate your views, and then when the appropriate time comes, the view system will ask you to draw. And that's how we achieve the compositing and the efficient drawing.
So like I said up here, validation is your friend. You want to invalidate and then let the toolbox tell you when a good time to draw is. When you get the draw event, inside the event there's a CGContextRef, so you can extract that as a parameter of the event.
And the nice thing about the CGContextRef, it's all set up for you. It's all clipped. It's transformed properly. It might be scaled if we're in the future sometime and we're doing resolution independence. You don't have to worry about anything like the Z order and you definitely don't have to erase behind like you did with QuickDraw because painting is all happening in a nice composited stack.
This concept also gives you a context that is top left. Sorry, this list is left for me up here. It's all top left. You might not be used to that if you've been making your own context in an event handler so far with a user pane or that kind of thing. So the context is already top left for you. And then you just go ahead and do whatever kind of drawing you want in there with Quartz and get your cool rendering effects.
Adding a draw event handler is very easy. You just extend the -- this is the class event list that I had before. I've just extended it by putting a kEvent class control, kEvent control draw event onto it. And I register the handler the same as I did before. And then you change your handler so that it handles the KFN class control, KFN control draw. And then you do your drawing there. Here's the meat of what a draw handler would look like. So basically, extract the CG context ref out of the event using GetEventParameter.
I have the reference to my HIView that gets passed every time the event handler is called, so I use that to get my view ref and I get the bounds of it, and I just do some drawing. This simple drawing here does a translucent red rectangle, I think. That's it.
You know, you'd have a nice simple HIView that draws a rectangle. You wouldn't actually see it though. I stuck this in here. This is an important step. If you were to just instantiate now, you wouldn't see anything because that HIView has no parent. It's just off in ether somewhere.
You need to add it to a window or add it as a subview of a view in a window. So you want to either add it to the content view of a window or some kind of parent view. And you also need to make it visible. It's made initially invisible for efficiency.
If you create it and then after you create it you want to set a bunch of things, you don't want something to happen to it and make it redraw over and over while you're setting it up. So it's created initially invisible. You set it up, you know, set the control value or whatever you're going to do on it. And then you make it visible. And only after you make it visible are you going to see it.
This is really one of the big hurdles that people hit when they first make an HIView. It's like, I made it. It's not showing up. Where is it? You have to take these steps. They're very important. It's very common that they're forgotten. It's one of the questions we answer or you guys help us answer on the Carbon Dev List often. So I'd like to ask Curt to come up and just go through some of the things I showed to instantiate a simple HIView subclass. Curt? Thank you, David.
So if we could switch to the demo machine, please. So basically what we're going to look at here is just the HIView test application. Now this is just a sample code. It's available in developer examples Carbon. But I want to walk through just making a very simple subclass of HIView that does just a simple drawing.
So let's go about looking how we do this. We're going to define HITestViewRegister. And during application initialization, this function will be called because we need to register this subclass with the HIObject subclassing system. And then we need to specify that we're going to handle these HI object specific events. So, construct, initialize, and destruct. Now, as David mentioned, construct and destruct are required. If you don't specify these, registration will actually fail. And then, HI object initialization is optional, but as David also mentioned, that's probably where you'll handle most of your initialization.
So you can see how the HIObject specific events and the control, which are the view of specific events, are kind of globbed together. This is all in the event list that we're going to pass into the HIObject register subclass API. And for now, we're just going to be looking at the KEventControl draw.
Next, we register the subclass. As David mentioned, we pass in our class ID, which we defined as "HIV" Next, we register the subclass. As David mentioned, we pass in our class ID, which we defined as "HIV" We give it its construct practice. This is going to be our event handler. We're basically telling the event system that these are the events that we're interested in with this event list. And when one of these events happens, to call our callback, which is the view handler.
And basically, this whole section is in a one-shot section. And what we're doing there is we define a static class ref. And so if you call this particular function, if the application were to call this particular function more than once, the registration would happen multiple times. So basically, we just turn that one shot.
Then, after it's registered and the application wants to instantiate an instance of this class, Our handler is then called. Basically, as David mentioned, we need to handle, deconstruct, and destruct events. and as you know, it's don't call, call next event handler here. And then we'll also be handling initialized as well.
So when the instance is to be constructed-- oh, and I should also mention that we're going to be handling the draw. So after the instance is constructed and then embedded into a view hierarchy, we'll also get this draw event as well. And since we're doing this as a C interface-based method of doing this, first all of the data is going to be allocated in the construct event. And then we'll just operate on that data as well later on. Here, and this is where it is.
So when we construct the instance, we go ahead and malloc our data. Now, as David mentioned, it doesn't really matter for the purposes of this example what that data is, other than that we're going to be grabbing the HI object instance and stuffing it into our data. And then finally, before returning, we stuff a void pointer argument into the event. And this is how we'll reference or grab our data out of the events as always. our draw and tracking and other types of event handlers are called.
After construction has successfully happened, we'll get called to initialize. And so in our event handler, we'll get the KEventHIObjectInitializeEvent, which we then call this function for. And whoever's called us has set up some parameters in the event. And so we grab those parameters out, and we stuff them into our data store. And basically, for this example, we're just going to be drawing a rectangle, and we want to know the RGB values. So somebody stuffed RGB values into the event. We're grabbing them out here and stuffing them into our instance data.
And then after we use this instance, it's going to be drawing, the user's going to be tracking, all this stuff is going to happen. When we need to tear it down, we get an HIObject destruct event. And in that case, we just call our function to destruct it, and we just free our data store for this particular object.
Once it's correctly instantiated, you can embed it into your view hierarchy and make it visible. And at that point, the draw message is going to get sent to your instance. And so here, we're doing some very simple operation. We're going to set up to draw a particular rectangle. And this is all basically designed to have different colors, whether it's selected or whether we're tracking or anything at that particular point. then we just draw that rectangle.
So let's actually see this in action. It's very simple. So what we did in our main function was we registered this subclass. We created a construct event and then called hiObjectCreate, the instantiated object. We embedded that into this window. And the subclassing system tells us to draw. And that's where we draw with the context that it's given us. That's basically it for this particular example. Thank you, Curt. If we can switch back to the slides.
Well, that's just a simple HIView. All of our examples today are simple. You know, we could have written some demo to do some fancy stuff, but all the focus would have been on how we did the fancy drawing, how we did the fancy tracking. What we chose to do instead is use the simple test HIView stuff that's available as sample code right now. You probably even have it on your machine if you have a machine here. It's in Developer Examples something something. If you search for "HITestView," you'll find it.
So Curt went through that, but handling events like that and registering them, you're doing the same thing over and over. It's just, you know, we put some events in a list, and then you've got to handle the event and dispatch it and take the parameters out, and you're just going to be doing the same things over and over.
Especially for event, for HIobject subclassing, all those events are always the same. You're always going to be doing the very same thing, so it's just a boilerplate. And that's kind of boring. You're just going to be, like I said, doing the same things over and over. We don't do that on the inside of HIToolbox. We actually have a C++ framework, because this is no fun.
And that's why we made HIFramework. So HIFramework is a lightweight C++ framework for doing the subclassing of HI objects. and making custom HIViews. And it's really just a subset of what we use internally to make our own Custom HIViews like, well they're not custom for you as they're system HIViews like the push button for example. We use a very similar framework internally.
T-Object wraps the HIObject class. Something notable about that is T-Object doesn't exist in the public HI framework. We're going to be revving that next week. And it's just something we added in, and I'll show you why in a minute. T-View wraps HIView. T-Object wraps the HIObject class. Something notable about that is T-Object doesn't exist in the public HI framework. We're going to be revving that next week. And it's just something we added in, and I'll show you why in a minute. T-View wraps HIView.
And basically, all you have to do is override a few methods, a few base methods, and you will be able to subclass very quickly. So there's a register class method, which I'll show. There's a static construct method, which you'll have to make. And that's so that you can specifically make your subclass instantiation statically.
And you also have to override getKind. It's a pure virtual method so that we can identify what kind it is. And then after that, you only have to handle what is interesting to you. You don't have to worry about the Carbon events, you just go override certain methods, like a draw method, or some kind of accessibility method, or keyboard handling, or whatever.
So here's an example of overriding tObjectRegister class. You can see here this only happens once, so there's a static that's stored. This is a static route-- this is a static-- So that you don't re-register it over and over and over. You only need to register with it once. Again, you see that reverse domain name class ID that I talked about before. And you have to specify what the static construct method is for your subclass. In this case, it's almost always going to be called construct, and it's a static method in your C++ subclass.
Here's an example of overriding the Draw Event Handler. You can see that there's a couple of parameters coming in. Those have been conveniently extracted from you from the Carbon Events. Here you get the nicely clipped, transformed CG context. This is the exact same drawing as happened in my previous sample code I put up here. There's a bounds accesser because I have a I clearly have a reference to my tview instance. I just get its bounds and I draw it. So Curt's going to come back up and he's going to show you that same class, re-implemented using HIFramework.
Curt? Thank you. So basically, yeah, we're just going to re-implement this using the HI framework, which is so much easier. I mean, you probably saw some of the demos that we did in our sessions yesterday. And we always use the HI framework just because it's quick, right? So here, we define our class ID, which is--this is the ID that indicates or identifies our class to the HIObject subclassing system. Then we also implement the GitKind as he mentioned. It's a pure virtual method that we have to override and that's so the system can identify what kind of instance you are.
And then register class. Now, if you remember when we called hiobject register subclass, there was this huge function. We had to add the whole parameter list. And then we called hiobject register subclass. This is just the same concept, but much simpler. Like I mentioned, we had the one shot. This is now all it is.
We specify the class ID, which we defined at the top of this file. And then we specify a construct proc. Now, this is important because when the application needs to instantiate your object, it needs to call something to do that instantiation, and that's this construct proc, which will get called.
So when someone calls HIObject, construct, or create using your class ID and it needs to instantiate your object, this-- This handler is going to get called. And this is a class method. So it's depending on your method. So you need to create an instance of your class. And this is really important. This is where you actually instantiate your object by calling new on your view.
After that, once you embed it into your view hierarchy and make it visible, your draw method is called. This is the same thing that we did in the previous example using C, but we don't have to do all the extraction from the Carbon event in order to get the context and the region that we should be drawing into. It's all passed into us, and then we go ahead and we do our switching, and we can change the color based on active state or whatever at that particular time.
And as David mentioned, drawing, regardless of whether you're doing it in HIFrame or C++ or in C, should only be done in your draw handler. And if you need to force drawing in some other place, like you're tracking the control, you wouldn't draw at those particular points. You would invalidate.
So the next time through the event system where it's going to draw, it will call this function to do all your drawing. So only draw here. That's it. So if we want to go ahead and run that. We can see that this is our old C-based one, and this is our C++ based one using HIFramework. It was a lot less code, really easy setup, and boom, it's there. Thanks, Dave. DAVID MOLEN: Thank you, Curt.
So hopefully that shows some of the simplicity of the HI Framework. I really highly recommend using it. Even if you make one first in C and then try one using the HI Framework afterwards, it will really help you understand the concepts. And once you understand the concepts, you'll just love them and you'll really want to use them.
So let's go over a bit more of the basic behavior that's available to override, like click handling. Click handling has a few events associated with it. First, you want to hit Test. That determines if a point is within a part or within your whole view. Depends how many parts. Maybe if you only have one part, it might be your whole view.
This is not time to handle click. You get asked for hit testing just to see if a point is within your view. It could be used for other things. It's not time to do any click handling yet. One really important and nice point about doing k-event control hit tests, if you contrast it to doing hit testing with, say, a control definition, is that the hit testing can have multiple parts. This doesn't just say hit or not hit.
And that means that the default tracking can track individual parts of your control easily, your custom HIView easily. And it was a little harder to do with You can still do custom tracking if you want. You just handle the Cave-In Control Track event and do your custom tracking there and validate to do redrawing, whatever you want. Another important part of doing click handling is if the value changes or the highlight changes, you probably want to handle that.
You'll get a message that says that they have changed and you will probably want to invalidate so that you appear different when you're being clicked or when you're being tracked or if your highlight changes. And eventually, when someone does do a mouse-up and they do click on your view, you'll get a Cave-In Control Hit event. That means you've been clicked. Then it's time to handle a click.
HI Framework Click Handling. All it does is take those Carbon events, take out a few Carbon event parameters, and to do the Click Handling, all you have to do then is override a few methods. If you're interested in overriding the hit testing, you do the hit testing. Tracking is available. You can do the highlight change or value change overriding to do whatever you want when those change, as well as Control Hit.
Here's an example of overriding a hit test in TView. Tried to make a little more interesting code snippet. This one here, it goes and it gets the bounds of my view, divides it into four, and it tells you which one of the four quadrants gets clicked and returns that part. Everything else is handled by HIFramework. It puts the part back into-- it does a set event parameter, and it goes back into the view system just fine.
Here's an example of overriding Control Hit. What do you do when someone clicks on your control? If it's a simple control, generally you'll just want to set your value to whatever the incoming part was. Say I had that four quadrant view I just mentioned. My value would be maybe one, two, three, four, depending on which quadrant was hit.
Interestingly here, you always want to return event not handled error. And that's so that you can let the rest of the system handle the event. And that does things like sending out the command ID. So if you have a command ID associated with your view or some other client of your custom view is using it and associates a command ID for it and is expecting to receive it, you have to return event not handled error so that the system knows that it's got to keep on processing it and do that for you.
You can also override the change handlers. They're generally very simple. If your value changes, you want to invalidate so that you draw later to reflect how your value has changed. Similarly with the highlight change, you want to invalidate so later on when it's the appropriate time to draw, you can draw a highlight differently. That's really quite common. There's actually a couple of auto-invalidation convenience bits that you can set on a view in the HI framework.
To tell it that you automatically want to invalidate when the value changes or when the highlight changes. Similarly, there's also a few bits for automatic invalidation when your activation changes, enable state changes, or if someone changes your title. Next kind of basic behavior, keyboard handling, someone starts typing.
This is all done through the KEVENT class text input class of Carbon events, and most specifically the KEVENT text input Unicode for key event. You'll get a Unicode value that indicates which key has been pressed. Previously you might have been doing some raw event handling and doing a Keydown or RawKeydown or whatever. I don't even remember anymore, but this is what I use.
To use that in an HI framework, you just got to override the text input method. One important note here is that you probably want to deal with special keys. So if someone presses Command period, you might want to pass that off so the cancel happens properly. Or if someone presses Enter or Return, you might want to pass it off so that the default button gets handled properly.
Here's an example of overriding text input. It's quite simple. It's a little bit different than the other ones because you're actually getting a whole class of events here. So you have to extract the event kind out of the incoming event. Make sure it's a KEvent text input Unicode for KEvent that you extract, that's coming in so that you can act on it.
And then you just get the parameter out of it. This example here looks a little bit different than GetEventParameter because it's using the tcarbon event convenience functions. So you get the keventparam_text_input_send_text out of the event and then you do whatever with it. And remembering to handle those special keys if you need to.
And the last basic behavior you want your control to have, your custom view to have, is to be accessible. It allows an external application like VoiceOver to control your app. And it really is as important as the other ways of manipulating your view because someone that can't use a mouse or can't use a keyboard, this is the way they're going to be using your application through this accessibility layer.
It's all implemented like Carbon events, just like everything else. And there are a few events, and they look a little daunting at first, because Guy likes to write really long event names. But other than that, it's just setting a few attributes, like what the role is and that sort of thing. And it's just generally describing your view so that it can be presented to a user that's not using a keyboard or not being able to visually perceive it.
A guy covered this yesterday morning in an excellent session that hopefully you can review somehow through the... I don't know how you guys get the sessions after they've already run. I think there's DVDs or whatever coming. I saw a video camera on him. So if you can get at that content, it's really, really good. And it's really quite key to have the basic behavior of being an accessible view.
So like I said, there's a class of events, K of N class accessibility, K of N accessible, get child at point, get all attribute names, get named attributes. Those are all just describing what your view is. And it's just really taking some CF types and pushing them into a CFDictionary, a CFArray. And KVN, Accessible Perform Named Action. That's how the external application can tell your view to do its thing.
All of those map directly on-- look, it's just like a one-to-one mapping-- directly on to some methods that we have in tObject. The names are very similar. You'll be able to find them easily. This is where earlier on I mentioned that we added tObject to hiframework. And the reason we added a tObject class is because the accessibility doesn't just apply to HIViews. It applies more generally to hIobjects. So we put them in tObject because accessibility applies to windows and menus and toolbars and whatever other subclasses of hIobject. And that's the layer that we have to provide it at.
So here's an example of overriding one of the accessibility methods, getAccessibleAttributeNames. There's a lot of dependency on your parent
[Transcript missing]
You can allow your parent a chance to fill in some of the accessibility information for you. And what you do is you call Next Event Handler to let your parent event fill in as much as it can. After that comes back successfully, that's your turn to fill in some parameters to describe the value attribute or the role attribute. This is the time that your view gets to describe itself.
And overall, that's a basic Custom HIView. You just have those five basic behaviors. Some of them, you know, you might not have mouse handling. You might just be displaying a bar graph or something. You might not have keyboard handling because there's no text input. But you're probably going to have drawing, and I'd highly recommend having accessibility.
You probably want to add a few more things to it just to make it fancy. Before I get into that, I'd just like to go over some key areas so that you can make a good custom HIView. So I'm going to cover opaque region, what that means as far as drawing performance goes, as well as doing some limited drawing.
Opaque region, I'll show in a minute, lets you describe what parts of your view are completely opaque. And limited drawing lets the view system tell you to only draw parts of your view so you don't have to draw as much, maybe not do as many calculations, and in so doing be a little bit more efficient. And actually there's a feature bit, KHIView does not draw. If your view is just some kind of containment view and it doesn't draw at all, if we have this bit, we can be ultimately efficient as far as drawing goes and just never, ever tell you to draw.
So the opaque region. That's your chance to describe to the view subsystem and tell it which part of you is completely opaque. That means you can't see any of the pixels behind you. And since none of the pixels behind you can be seen, we shouldn't bother drawing them. So that lets everything behind you be more efficient, it lets everything be more efficient, and it lets your whole application be more efficient.
It could be some subpart of your HIView. In the example I mentioned earlier where I had some quadrants with 1, 2, 3, 4, maybe the highlight changed from quadrant 2 to quadrant 3, and I just have to redraw quadrant 2 and quadrant 3 because 1 and 4 are still the same. It could be your whole view, like in the square we were drawing, or if you're drawing a bar graph or something.
And the performance gain, like I said, is that nobody behind you has to draw if you're obscuring them. None of the obscured content has to draw. I think I mixed in something when I said the quadrants there. I'll cover that in a second with the limited drawing. Sorry.
So to feedback about what your opaque region is, you've got the KEventControlGetRegion event. And I can do the compare and contrast. You just override the getRegion method in HIFramework. And watch for the request for the opaque part. All you have to do is report that region that's fully opaque.
Here's an example of doing it. You see the incoming part. It's already been extracted out for you by HIView. And you look for the opaque meta part. In this case, it's just a full rectangle of the whole view. That means anything that's behind the view doesn't have to be drawn. Anything that doesn't have to draw makes your application more efficient.
Limited drawing. Caveman Control Draw on the way in has a parameter which is a region that describes which is the dirty area that needs to be redrawn. So you don't have to redraw everything, you just have to draw that dirty area. Now is when my quadrant example applies. If the highlight changes from one quadrant to another, you don't have to draw all of them, just the two that changed.
So you just extract out the region using the k-event-param region handle. And you just limit your drawing to that. Nice thing is, TViewDraw already does that for you. It takes that limit region out, and it passes it in. So here's an example of using the incoming limit region.
I tried to write some sample code, but you run out of space pretty fast when the font is that big. So basically, you would just cycle through your parts, check to see if that limit region intersects the parts, and you only draw the ones that intersect. In that quadrant example, I would just go through them and check.
The important caveat here is that if all the machinery of checking to see if this intersects and that intersects takes too long, you might just want to draw entirely and not do this. It might be more efficient just to draw everything and the clip will take care of it and not worry about doing this machinery.
Next thing you'll probably want to do is add some kind of animation. Everybody wants animation in their views. They always look nice that way. Because everybody loves pulsing push buttons, right? So the way you do that, you don't install any sort of idle loops or do anything like that. All you have to do is install a CarbonEvent timer that calls a timer function in your view subclass and does an invalidation of some sort to tell your view when it's appropriate time to draw, draw in that newly animated state.
It's important to remember to remove that timer when you destroy your view because there will be a timer in place that's maybe got a reference to your view and if it destroys it, the timer doesn't stop firing and it will try and reference a view that's destroyed. So you want to keep track of that Carbon Event Timer and when your view gets destroyed, you want to remove that Event Timer.
And just to be efficient, you don't ever want to animate when your view is invisible. What's the point? Here's a full animation sample. Well, most of it. You can see here that I installed an event loop timer during the initialization phase of my HIView instantiation. I installed an event loop timer that draws 30 times a second. You don't want to do this too often if the user's not going to be able to perceive it.
And that event loop timer, I tell it what the static callback is, and that's my event handler, and also pass an instance so it can identify which of the view subclass instantiations is actually being animated. Later on, when the event handler gets called, it's going to be past that instance so that it knows.
So here's the, this does the actual animation. That's a static timer, static call that gets called. This is a static entry point into your class, so you have to extract your instantiation and then access it. So that's what the casting of my view does there with the user data.
And then you do whatever changed in your animation. You know, you advanced one frame or you moved something in your implementation, however you're doing your animation. And then you invalidate the view. So that just calls an API underneath an HIView framework called HIViewSetNeedsDisplay. And it lets the view system know that at the appropriate time in the event loop cycle, when it's time to draw, that view needs to be redrawn.
It's also very easy to add any other additional behaviors that you want, like drag and drop. It's just overriding some Carbon events, and there's facilities for using that in the HI framework. Focus handling, scroll to here, other parts and bounds, size reporting, and size constraints. Size constraints are what you use to describe what the size of your view is when you're using it in a toolbar. All of that is just a matter of handling some Carbon events, returning the appropriate parameters, and they're all wrapped up in the HI framework, and you can just override the appropriate method to get these additional behaviors.
Lastly, you probably want to instantiate from A nib, one of these custom HIViews. You don't want to make it programmatically every time. It's really easy to do that. The only important part is that you have to register your view before you actually do the instantiation of your nib.
So you just use the HIView widget in IB. In the widget panel, the orange box says HIView in it. Drag it into your window. You bring up the info panel and the attributes pane of that. You just set the class ID to that string that we've been using over and over when we registered our HIView subclass. That's the unique identifier that tells HIToolbox which subclass to instantiate.
You just go set that in there. And when the Nib is loaded, all the construction routines are called. And it's just like you created programmatically. The really important part there is that you must register before you instantiate or else nothing's going to happen. It's not a recognizable subclass to HIToolbox until you register.
So I've shown you, custom HIViews are really easy to implement. It's just some HIobject subclassing. And it all uses Carbon events, which you're familiar with. You just install Carbon event handlers. And if you use HIFramework, it makes it very simple. I can pop out a custom view really, really fast, making the sample code or making any custom pieces for myself.
Much easier than doing really, really custom UI and drawing into the window. Or it's even easier than doing a custom control definition. It's extremely easy to make a subclass using HIFramework and HIView subclassing. So what I recommend you do is go make a custom HIView right now. Go download the HIFramework sample code and just make one independently of your application.
Make something fun for yourself just so you can get a feel for it. You might want to go through a HITestView sample code so you can compare the C APIs to the C++ ones. But I guarantee you won't want to do that boilerplate stuff over and over and over. If you don't have to, you can just override a few methods.
And once you see that, you're going to see how easy it is to take different pieces of your application's UI, make them into custom HIViews, and make your whole application use the HIView and compositing and do all the new whizbang features that you need to do to come with us into the future, bring your application into the future and not get left behind.
I heard one comment yesterday. Someone said, well, I'm not going to use HIView because Apple comes out with these new technologies and I get worried about it. And I'm going to wait. It's time to stop waiting. We introduced this so you could do it in Jaguar. In Panther, all of our views are HIView.
Like, you're using HIView already. Every single system UI widget is an HIView. Everything is HIView. Even the closed boxes in the windows. It's not like we're saying we're going to do it. We've done it and you have to come with us. And it's time to come now by making a custom HIView.
A little bit more information. You can hit the documents in the reference library. There's upgrading to the Mac OS X tool--HI Toolbox. It's got some great information there to bring you forward with us. Introducing HIView goes into detail some of the concepts I went over today. TechNote 2074 is really good if you're used to the Control Manager and you want to get used to some of these new HIView APIs that we have.
And I definitely recommend going over the HIView sample code that's available up there. Go over HIFramework. Check out some of the sample subclasses that the engineers have written and that are available to you. It will really enlighten you about what you need to do next. You can send Xavier an email directly if you want to. There's his email address.