Application Technologies • 1:08:03
Watch and code along as we build a fully featured custom HIView. We'll develop an HIView starting with basic Carbon Events and progress to advanced features such as Accessibility, Text Input, and Drag-and-Drop.
Speakers: Bryan Prusha, 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, everybody. Welcome to session 116, Building a Custom HIView. I'm Bryan Prusha. I'll be doing some quick overviews of some of the features we're going to be talking about today. And Curt Rothert will be coming up to provide demos and sample code. We're going to be covering a lot of territory today. Something I wanted to mention is this is a hands-on session, so if you don't have your code ready and you'd like to follow along with us, we'll be looking at the Image Browser View sample code.
Also, at this point we're assuming that most of you had experience with Carbon Events. So we're not going to be going in-depth on how to handle those today. If you haven't had a lot of experience and want to review, just go ahead and check out the sample code. There's lots of examples there.
We have a lot of material, so I just want to jump right into it. The first thing you want to do when creating a Custom HIView is subclass from the base HIView. The toolbox is really pervasively object-oriented. Even though we export our APIs through C, we still use an H object subclassing model, which I'm going to discuss. You may be familiar with C++ or Objective-C, other object-oriented languages. We try and do everything those do at compile time. We need to do that at runtime. So to subclass, we use the API HIObjectRegisterSubclass. This is similar in C++ to using the class keyword.
I want to concentrate on the first two parameters, the Class ID and Super Class ID. The Class ID is what the client uses to instantiate one of your objects using haObject create. So we want this to be a unique string. So often we suggest that you use the reverse domain name scheme, something along the lines of com.mycompany.myview. And in this case, we will specifically be subclassing from the HIView class ID. And again, in the C++ case, this is analogous to declaring your subclass.
Next, now that we've registered, we need to pass a set of events that we will be handling. These are Carbon Events. Specifically, any HIObject subclass must handle HIObject Construct and Destruct. I've also offered HIObject Initialize and Control Draw here just for an illustration. Now we pass these events and pass the count of the events. And in C++, this is like declaring your class methods.
Now that we've registered for all these events, we need to handle them in a Carbon Event Handler. This is where the toolbox will send these events to you, we listen, or the view listens, and then does what's appropriate for each event. and his C++. This is just like implementing each of your class methods. So you can see the models are very similar. Now that we've covered the basics, I'd like to hand things off to Curt Rothert for a demonstration.
Bryan, so first of all, I want to thank all of you for coming out to see the session. There's some really cool technology, and I think you'll be excited if you haven't taken advantage of the HIViews already. So the view that we've chosen to implement is just an image browser view, as Bryan mentioned, and this image browser is a collection of images, and it will display one of those images within the bounds of that view. So in order to see what the target is, let's see the completed project. Now as Bryan mentioned, this sample code is already available. You can download it and play with it yourself. But let's just look at what the app will look like.
So as you can see here, the image in the middle of the window is our image browser view. It displays an image, here specifically a desktop image. And it supports such features as tracking areas. As I move the mouse in, you can see these different parts become available. Tracking, hit testing, library size. It also handles such things as keyboard focus. So I can focus the image itself.
And when the image is focused, it handles keyboard input. So I can navigate between the various images. And if full keyboard focus is on, you can bore into the different parts of that view as well. So you can see that, actually this might not be a good image to see that.
You can see that the various parts are highlighted as they have focus. And then I can use the keyboard spacebar key to navigate as well. This view also supports Accessibility, so if you can see... Here are the various parts. This will act as a button that an accessibility application such as VoiceOver can interact with your view as well.
Other features this view will implement are such things as Drag-and-Drop, So you can take an image and drag it onto the view and it will respond to drag-and-drop. And also archiving, which is a new feature that Bryan will be going through. But it allows you to save the persistent state of your view so you can, it's like freeze drying and then flash thawing it. So let's go straight into implementing a project. Now I'm just gonna do this in Xcode starting out with a default template, and then we'll just build from there.
In order to prevent me from making any errors, I'm using a service utility which is called DemoMonkey. So if there's any errors in typing, it's the monkey's fault, not mine. So we first launch Xcode, create a new project, Carbon Application, and we'll call this Image Browser. Placing it on the desktop.
Now this creates the default Carbon template. So what we'll do now is we'll create the image browser interface and implementation files, and we'll use the main.c file as a test harness to make sure that our view is working correctly. So in order to create the files, we go back up and say new file.
Make it a Carbon C file. And we'll call this Image Browser View. We also tell Xcode to create the corresponding header file as well. And here we have our header. So as Bryan mentioned, in order to get started, we have to have a class ID, which is a unique way to reference this particular class that we're implementing. So let's start off with that now. Demo monkey, don't fail me.
So this is just a CFString that's using the reverse DNS naming convention. And then we want to implement an API so the client, in this case, main, will be able to register our class with the HI object subclassing system. So we define an API, which is just ImageBrowserView register. And now let's go to the implementation file and get started there.
So most views, to do anything very interesting, have to have a data structure of some type. In our case, since we're implementing an image browser and we have a collection of images, we'll implement that as an array of images with an index specifying which image is to be shown within that array. So we define our data structure initially here.
You can see that we maintain a reference to the HIView ref, which corresponds to this instance. This is so that we can use HIView APIs such as getting the bounds or HIView set frame, for example. We also have our array of images and the index into the array.
Now to make my file easily navigable, I add some sections so we could get these up in the function pop-up. which you'll see later. So we implement our function. This is our registration API. The Registration API, all we do is we specify what events we're interested in, and this is typical if you've done this before, you register what events you're interested in, and then call the HIObjectRegisterSubclass API. You pass in our class ID, which we defined in the header.
We tell what we're subclassing from, which in this case is a HIView class. There's no options. We specify an event handler, which we haven't yet implemented, and I'll get to that in just a moment. And then we pass in the count and the array of events that we're interested in.
This function will return a class reference, which we store in this static variable here. Now, the reason we do this is because we only want to register once. So if our internal API is called many times, of course, this will be non-null after the first registration, and the registration only happens once. So now let's go implement our Event Handler that deals with those events we've specified.
This is just a standard Carbon Event Handler. If you've been using Carbon Events, this is just how you deal with it, where we deal with the specific class and kind of that event, and we switch off of that. So this first section here is the HIObject class handling, and we deal with such things as construction and destruction here. And to deal with that, I'm going to implement a function that will construct and destruct our instance data.
It's very odd typing standing up. I don't know if you actually have done that before. So here we just deal with construction. And this is just when the HIView system wants you to instantiate an instance of your class. You... You allocate enough memory for your data structure, and one of the parameters in that construction event is the HIObjectInstance parameter. This is the HIView that your instance corresponds to. And so we're storing that within our instance data.
And finally, prior to leaving, we set the HIObject instance data to be of type Avoid Pointer, and this will be a reference to your instance. This is so that subsequent calls to your event handler have a reference to your instance data. And you can see that that's passed in as the user data from the event handler. So we can cast that into our internal data and then use that in subsequent event handlers. So for example, when we deal with the HIObjectDestruct event, we have that data available that we can destroy. Let's go ahead and implement that destruct handler as well.
So this is very simple. We just clean up our internal data structure. If there are any images that are in our internal array, we free that up. And finally, we free up our instance to avoid leaking. So those are the required events to just get up and running.
Let's go to the main.c file as the test harness and get this thing going. So the first thing to do, of course, is to include the header. And then during your initialization of your application, you will want to register the subclasses with the HIObject subclassing system. So we do that here. Perform some housekeeping.
Next, since this is just the default template that Xcode came up with, I want to remove the window creation code from the main file and break that out into a separate function so we can create an instance of our view and embed it in the window. So I'll remove our code that deals with window creation and display.
Clara Prototype, make the code look nicer, and then implement that function. So what this does is it does pull out the default window from the Nib. But then we want to create an instance of the view that we've registered. So we call the API, hiobjectcreate, passing in our class ID, and this will return an instance of that class.
The rest of the code in this function deals with embedding it in the window, setting its bounds, and making it visible. Since we haven't dealt with drawing yet, there's not gonna be anything that's visible when we run this app. But let's go ahead and do that. Build, Run, ah, good thing I ran it. One thing I did forget was to actually call this function. From Maine.
So now you can see that it just looks like the standard application that's created from the template because we've loaded it in the window from the nib. But behind the scenes, what we've done is we've created an instance of this class, embedded it in the window, but we're not dealing with drawing yet. So let's talk about that. Bryan. Thank you, Curt. Back to slides.
Great. We have a view. It doesn't draw, but that can be used for something like a grouping view. But we already have a user pane, so let's do something a little more interesting. So, drawing. Drawing in the modern HIToolbox is based on a compositing model. What compositing means is that we can -- if you give the toolbox enough information, it can really optimize when your view actually needs to draw.
To provide this information, use HIView set needs to display to invalidate your view. So invalidation is very lightweight. You can very quickly say on any particular state change, I will need to draw based on this change. So the toolbox will let the toolbox know, and the next time through the event loop, the toolbox will coalesce all these lightweight invalidations and request that you draw once by sending you a K event control draw message.
I'm sorry, one thing I forgot to mention. You can set kWindowCompositingAttribute onto your window so that it supports compositing. So compositing is a window by window attribute. Now let's go back to Curt for drawing. Okay, thank you. So prior to, before we get into drawing actually, I wanted to talk about initialization because this is important for a client to be able to initialize a view or an object with some information initially. One of the parameters to HI Object Create is initialization Carbon Event.
Now this is important because the client can then set event parameters in that event, which the view can then pull out during initialization. The client, if it wants to support this type of feature, it needs to advertise what types of parameters it will get during initialization. So let's do that in our Views header.
So I have two constants here, and one represents an image array. So this is the view advertising that during initialization, it will attempt to pull out an array of images. This is really just a four-character code. And also we define what type we expect that to be and a description. So we expect it to be a CFArrayRef of CFURLRefs to images that are on disk.
Additionally, this view will attempt to pull out the image URL Carbon Event parameter. Which it will expect to be a CFStringRef, which represents a path to a file on disk. Now the client would plug into this. If I go back to the main file, The client would plug into this by providing a string, creating a variable that's an initialization event. It will create that initialization event.
Set that parameter in the event, and then provide that initialization event as the parameter into hiobject.create. So that's all that we do on the client. Oh, next of course is to release that event so we don't leak. So let's go ahead and implement support for initialization. From our function pop-up, Function Pop-up doesn't seem to be working correctly right now. So let's go ahead and we now indicate that we're interested in the initialization event here. We go back up to our event handler. handle that event, and then we go about implementing the function to deal with initialization.
Since we are subclassing from another class, it's important that we allow the superclass an opportunity to initialize itself. So in this case, the HIView superclass. So we call next event handler to allow that to happen. I go about initializing my instance data. And then I attempt to pull out those initialization parameters. So initially we tried to pull out the URL array. Now in this example, the main.c file hasn't provided that.
So this function will return an error and not touch this variable. We've already initialized it to null. So if it doesn't touch it, we're golden here. If the array is available, we create a mutable copy of that and store it internally. Otherwise, we create an empty array that we'll store images into later.
Next, we attempt to pull out the image URL Carbon Event parameter. And this is defined in the header to be a CFStringRef, and we put it in the image path variable. If that exists, since it does represent a file on disk as a URL, we create a CFURLRef out of it, add it to our internal array, and then release it.
The other thing to note here is that during initialization, we're calling HIViewChangeFeatures, and we're turning on the isOpaque feature. This can be a performance optimization because the view system assumes that your view is non-opaque. So it must draw the views behind your view prior to drawing your view.
If we know our view is going to be opaque, which we do in this particular case, since we're implementing it, we can set this feature and the view system will not draw the views behind you. This can be a performance win because it's just going to be overdrawn anyway. Now that we've done that, let's go back to our handler, or our array of event types that we handle and deal with drawing.
We handle the Draw Carbon Event, and then we implement the function that actually deals with drawing. Now this of course is going to be specific to your view, but I'll just describe what we're doing here. Basically, we pull out the CG context from the Draw Carbon Event, We fill it with black, and then if there are any images that are stored in our internal array, we create a CG image out of it and blast it to that context. If you note here, I'm using some constants here, so I must add that to the top of my file as well. So, hope this doesn't make you dizzy as I navigate around.
Now finally, as Bryan mentioned, the view model, it uses an invalidation method of redrawing. So for example, if I want to draw differently, if the view is inactive, then rather than dealing with the deactivate event and then drawing right then, which is called out of band drawing because it's not being requested by the view system, we want to just invalidate. So just as an example, let's go back and deal with that as well.
So we add support for deactivate and deactivate Carbon Events. Handle those in our Event Handler. And we handle that by calling the API HIView Set Needs Display. This just invalidates and at the best opportunity, the view system will then call our draw handler to draw. Since we want to draw differently based on whether the view is active or inactive, we must deal with the state within the draw handler as well. So if you had some keen eyes, you may have noticed in the draw handler, I had this little section that dealt with the state of the view. If it's active, go ahead and draw it one way. Otherwise, draw it a different way. Let's go ahead and build.
We're drawing the image that was first passed into our view during initialization. So this is all fine and dandy. In the test harness, we were able to create an instance of our view and embed it programmatically, but you can also do this in an interface builder, which makes it a lot easier. So I just wanna briefly talk about doing that as well. First, you can open up the Nib and from this toolbar window, if you go to the Enhanced Controls, there's an item here for HIView.
Up in the Tools menu, there is a Show Inspector window. And one of the fields in this Inspector window is a Class ID field. This is really just the Class ID of your instance, which we have defined. So let's go back to our header file, pull out the contents of that class string, go back to Interface Builder, Plug it in there.
So now when this nib is loaded, it will create an instance of our view embedded in that window automatically. We don't have to write any code to do that. Now also in our main.c file, in our test harness anyway, we are providing an initialization parameter as well. Well, you can do that in Interface Builder too. So first let's go ahead and add a parameter. It takes the name, which really is one of these constants. So I take the four character code that represents this constant.
So what I'm trying to do right now is just mimic exactly what our code was doing. I add the four character constant there. The type we identified to be a CFString. I selected that. You may notice that Interface Builder has a bug on Tiger, that it doesn't update the UI here, but you can tell that it is a CFStringRef here. And the value will be the path to the image. So the image path we have in our main file.
So now since we're doing this all in Interface Builder, all of this code that we wrote to create and initialize this view can just go away. That. Don't need any of this. Nor do we need this. Ah, another cool thing about NIBs, well, another cool thing about views in general is that you can affect the layouts of them.
You can bind the layout of a view to its parent, for instance. You can do this programmatically using the HIView layout APIs, but it's so much easier to do in Interface Builder, I don't know why you wouldn't want to. So if you go back to the Inspector and go to the Layout menu, you can bind this to the parent.
[Transcript missing]
So let's build, run. Now you can see that our view has been added by loading in the nib. We didn't write any code at this point to get this up and running from the test harness. And then it handles such things as live resize automatically. So that's pretty cool. And now we're ready to move on to adding some more functionality, which Bryan will talk about now.
Thank you, Curt. As you can see, our view is really beginning to look like a view now that we draw. But again, we already have an image view, so it's very important not to try and do, again, reinvent the wheel for views that the toolbox already handles. Make sure to use those where necessary. So let's go ahead and start adding some more features so that we can differentiate ourselves.
So now that the user can see the view, they may want to interact with it. The basic way to do this is through mouse tracking. And to begin mouse tracking, we'll talk about hit testing. to handle the K Event Control hit test is to allow the toolbox to ask the question, "What part of your view is under the current point?" Typically the mouse moving over your view. And when I say part here, I mean an actual enumerated part of your view. So let me talk about that a little bit.
Let's look at the scroll bar. The scroll bar has lots of pieces to it. It has a scroll thumb, page up and down parts, scroll arrow parts. So each one of these five pieces are enumerated parts within the scroll view. These could be implemented as actual embedded subviews, but that's a little heavyweight, and we really don't need that kind of support in this case.
So the scroll bar itself knows where each of these parts are, how large they are, how to draw them, and how to react to tracking across them. So we want to do the same thing with the Image Browser View Sample Code. Here you can see in the green ring are our parts, forward and backward part, and delete image part.
Now that we handle hit testing, the very basic tracking is actually kind of half handled for us by the toolbox already. If you handle hit testing, the toolbox can ask the question, "What part is under the mouse?" If somebody clicks on our part and tracks on and off of it, the toolbox will automatically set the highlight state to that part or from that part, depending on whether the mouse is tracked on or off. So all our view needs to do is react to the Control Highlight Changed event, invalidate, and then when we go back through the event loop and our view is requested to draw, we just look at our highlight state and draw our part accordingly.
Then when the mouse comes up, we can receive the K event control hit event, and this will allow you to react to that mouse click after it has occurred. Now, often you might want to handle, do some more custom tracking. So for this, you will want to handle the K event control track event. And this will be sent when the user mouses down and drags across your view.
Something that you may have in existing C-DEFs in your code is Still Down within a Wow Loop. This is something we've talked about for a couple years now, and I just want to reiterate that Still Down is a polling API.
[Transcript missing]
and second, it's completely compatible with the compositing model on the window and will run the run the quickly every time you call it. So any invalidations that you've performed will cause a draw event to be sent to your view.
All right, now that we handled basic mouse tracking, let's go to the next step. For users of the keyboard, whether people who just prefer it or by necessity, talk about keyboard navigation. As you tab through the views in your window, your view will be sent the K event control set focus part event. and it will be, you'll leave the review, one of the enumerated parts of review to set focus or a meta part, the next part of your view, the previous part or no part if the toolbox is telling you to lose focus.
Now, so we can see an example here. Tabbing from an Edit Text field to our view. And then if they were handed the next event part, even tab to the subparts within your view. Now many views, like the Edit Text field, accept focus when the user clicks on it. If you want to handle this in your view, set the Get Focus on Click feature, and your view will automatically be requested to set its focus.
Now that your view is focused, the whole point of focusing is that your view can then receive text events from the keyboard. So you can handle these through the KEvent class Text Input suite of events. And specifically in this case, we'll be talking about KEvent Text Input Unicode for Key Event. You may see in the suite of events, KEventRawKeyDown. This is something you want to try and avoid if at all possible.
and I will be talking about the It gives you the naked raw key event that the user pressed. And it doesn't allow the toolbox to do some work for you on your behalf, like send the event through a Text Input method first, for instance. So make sure to handle the Unicode for key event instead.
So you can use the arrow keys to then, in addition to tabbing through your view, if you can see the updating there. And when the user hits the space bar, this means that you should simulate a click on a part. To do this, receive the spacebar key event and call HIView simulate click on the part that's currently focused. "HIV Simulate Click" is really great because it will actually highlight that part of your view.
and then it will behave as if the toolbox will simulate an actual mouse click on your view on that part. And so your existing mouse tracking code can just handle that event and you don't need to worry about it anymore in your text handler, or text input handler. So in this case, it continues on to click the part and you can see the image was deleted.
The third stage, and something that is really important to reach as many people as you can, as many users, is to support Accessibility. I'm just going to go over this very quickly here. You can handle Accessibility through the K-Event Class Accessibility suite of events. Accessibility is concerned with three main topics: A parent-child hierarchy, Attributes of your view, some information about your view, and then Actions, what does your view actually do? And this is information that can be sent back to VoiceOver for disabled users.
Now, we don't have a lot of time to go into that in this particular session. I hope you were able to see the Carbon Accessibility session yesterday. If not, if you have other questions, visit the Accessibility Lab on Friday from 2:00 to 5:00. We'll have engineers there to help you.
- All right, and with that, I want to pass things off to Curt for a demo on user interaction.
- Great, thanks, Bryan. So this is where the meat of our view creation really is. It's dealing with user input so they can interact with your view and get at some of the features that it implements.
- So if you recall, of course, the draw, you only draw in the draw method or in the draw event handler. So what we wanna do is since we're adding multiple parts, we wanna be able to draw focus around those different parts. So I need to maintain some state in my data structure so I know during drawing which part is to currently be focused. So let's go ahead and add some functionality now.
So I add a current focus part so I can maintain that state. And additionally, I'm going to be adding these three fields which deal with image caching. This isn't strictly necessary for user input, but I'm just going to implement this now because it makes drawing faster and resize faster as well.
Next, I'm adding constants that deal with the different parts now because each of those parts have a size, a margin, and some padding between them. So I add those constants there. And finally, I add an enumeration of what those parts are. So you can see here that we have the image part, which is the image itself. We'll have a back part, a forward part, and a delete image part. Now since we've added some fields to our data structure, we have to make sure to initialize those as well. So back in our initialize function.
We initialized that out. Next, we wanna make sure that we draw these new parts. So we modify our draw function and deal with the different parts and the focus. So I'll add that additional code in here and I'll just describe briefly what I've done. The beginning part of this function is essentially the same.
We're just making sure that there's an image in our array and then we're drawing that using CGImageRef. The additional code that we added in here was just dealing with that image cache for performance reasons. So let's skip that part for now. Move down to this next block here.
This just looks at our internal state and determines what part is currently focused and draws a focus box around that part if necessary. This next block actually deals with drawing those additional parts. So we have a section here that draws the back part, which is a triangle, a forward part, which is also a triangle, and a delete part, which is that X display that you probably saw. So it looks dense, but it's not really that dense. Now let's go add some support for user input. So let's start off with just the hit test handler. So we specify that we're interested in hit testing.
Add a Handler for Hit Testing. Then I go up to my Event Handling section and add a function to deal with that. What this function does is it pulls out the mouse location event parameter from the Carbon Event. It determines what part that mouse point corresponds to. I should note that that mouse point is in View Local Coordinates. Determines whether the part's available. And then finally, it notifies the system what part was hit by setting the control part event parameter to that part. So that's pretty simple. Let's go back and add some support for more user interaction.
As you notice, as we incrementally improve on our view, we're just taking three steps. We basically add an event type to our main array to tell the system which events we're interested in. Then we add a handler for that in our main event handler, and then we just implement a function to do that functionality. So here I will add handlers for tracking, hitting, setting, and getting the focus, and dealing with the highlight changed. Step one. Step two is to add handlers for those different Carbon Events.
And then finally, implement those functions by going up to the Event Handler section of our code. We'll start with the Carbon Events. We'll set the Control Part Parameter. As with all Carbon Events, if you return no error, you're indicating that you've handled the event. In this particular case, we tell the View System that if the user is trying to track the image, don't do anything. We've handled it. That's because we don't actually handle tracking the image. But we do handle tracking the parts. At this point, we'll be returning event not handled for those additional parts, and the default tracking will be handled by the View System. Let's add the next handler.
This is the view hit handler, and this is where we react to a hit on one of our parts. So we pull out what part was hit, make sure it's available, and then deal with that part being hit. So if the back part was hit, we go to the previous image and et cetera. I haven't implemented these functions yet. Those will go in in just a moment as well.
Next, we implement this function which may look kind of long, which is to set the focus. This is when the view system is telling us that one of our parts has been requested to focus. It can be one of the part IDs itself or one of the meta parts such as the next or previous part. So this large switch statement here is really just our view determining what is the next part that should be selected or focused.
Then we set which part has been focused. And then this block down here just deals with invalidation because if the focus has changed, we wanna make sure that we draw differently. We want to remove the focus from one part and enable it over another part as well.
Next is to handle the GetFocus part. This is just the view system wanting to know which part is focused right now. And we just returned the control part event parameter with our internal state. And the last handler we'll do right now is the HighlightChanged. Highlight Change is basically the view system saying, "Hey, your highlights changed." And so the only thing of importance in here is that we're just invalidating.
We're calling the HIViewSetNeedsDisplay variant of the API. This code here is just as an optimization because we don't want to invalidate the whole view. If we don't have to, we just want to invalidate parts of the view. I noticed that we had some utility functions as well, so I'm going to go add those right now and explain what those do. So I add a new section to my code.
and go ahead and add those. Hold on as I page up. So there's a utility to get the part frame. This is just get the box around one of those parts, like the back, or forward, or delete parts. I've added a function to determine whether one of those parts is available.
There's three functions that deal with the image cache, which aren't of importance in this particular area that we're talking about, but we have an invalidate release and converting the image into a bitmap image, but I won't go into that. There's a function here that returns the part that corresponds to a mouse point.
and then we have four functions to deal with going to the previous, next, adding images, deleting images. These are just utilities that our viewer will be using. The previous and next are very simple. They just decrement or increment the index respectively and then invalidate by using the HIView set news display.
So this is how we would deal with input from the mouse. Now, as Bryan mentioned, we also want to add support for keyboard input. So I go back to my registration API. Hope I'm not missing anything. and add support for the Text Input class of events and register for the Unicode for key events. Back up in my Event Handler. will handle that event by calling this function, which we'll define now.
for me as I scroll up. So at this point, we're just getting the Unicode character out of this text input event, and we're dealing with that. So for example, if one of the parts was focused and the user hits the keyboard character, or the space bar, we wanna leverage all of the code that we've already done for tracking, hit testing, et cetera. So we just use this API, HIVU simulate click. This will just simulate a click, and that will highlight the part that is to be clicked, and then send out the hit event.
So now we've added keyboard input. So we have mouse input and keyboard input. Now, this is neat because we've added these parts to navigate between different images, but currently we only have one image that's stored in there, and that was passed in by the initialization event. So let's go ahead and add an API that's specific to our view to add additional images.
Let's go to the header. In this little API section, I'm adding an API to allow the client to add images and passes in the HIView ref, which corresponds to our view, and an array of CFURLRefs, which are paths to images on disk. Let's implement that function. Go back to the implementation file.
Function pop-up, I go down to my API section of the code, and add that API. Again, as I said, the client has an HIView reference to you, but you deal with your image data, or your image browser view data, instance data. So we need a way of getting that data from an HIView ref, and you do that by using this API, hiobjectdynamiccast. Passing in the HIView and the class of that view. This will return your instance data. You can now deal with that as we've been dealing with it in the other handlers. So in this case, we add the images to our internal array and we invalidate.
Let's add some support in the test harness to make sure we can add additional images into that view. If I go back to main. If you recall, initially we were creating the instance on our own by calling hObjectCreate and getting a reference to our view. Well now we're using the interface builder and we're having it load automatically with the window as it's loaded. So we don't at this point have a reference to that view. So I need to make sure I add support to do that as well. So back here I will add a Control ID.
and or an HIView ID, their equivalent, which is a signature and an ID that allows you to uniquely identify a view in a window. So let's make sure that that is added in our nib as well. So I go back to Interface Builder, select my view, and in the Control pane of this window, you can see that there are fields for Signature and ID.
This is, we have to make sure that these are in sync. I am BR, I am BR. Make sure I saved it. Now in our creation function, I will add some support to get the image browser reference, and then some fields here for some images. After the window's loaded, I add some code to create an array.
Stuff those URLs into that array. Get a reference to our view using the HIView find by ID API, passing in that control ID that we specified above so we get a reference to our view. And then we add the images to the view by calling the ImageBrowserViewAddImages API, which we defined. Lastly, of course, release that array so we don't leak.
So now, as I'll show you in just a moment, we do deal with mouse input, keyboard input, and the last step is to add Accessibility support. I'm not really going to go into the specifics of adding Accessibility because there's this great Accessibility lab you can attend which is 1024 and that's going to be on Friday.
But just for completeness, I will add support for that in the review as well. I add support for the Accessibility Suite of events. Back up in our handler. I handle those events. and I create a new section in my code to deal with creating and dealing with that accessibility hierarchy.
Let's go ahead and build and run this. Now you can see that we've added some of these additional features. These new parts show up in the draw handler. It handles hit testing and tracking. Handles Mouse Input, because now I can navigate between the various images that were added by that API we specified. And we also handle Keyboard Input. If I focus the view, you can see that there's this focus border drawn around the view. And as long as full keyboard access is enabled, I can bore down into the various parts and use the keyboard to interact with that.
Since we've also added Accessibility Support, a non-sighted user or an Accessibility application such as VoiceOver can interact with your view as well. So now at this point, we do have a pretty full featured view and there's just some advanced features that we can go through. So I'd like to take it back to Bryan to go through that. Back to the slide.
All right, Curt has shown us a great demo, and we've had a lot of features. For many of your views, maybe subclassing, drawing, and basic user interaction is all you need. But I'd like to go one step further and show you a couple other advanced features that you may want to add. First of which is Drag-and-Drop.
To enable Drag-and-Drop in your view, you need to do two things. Your client, the client of your view, needs to call SetAutomaticControlDragTrackingEnabledFor Window. Now, the reason this is, we need to make sure that the toolbox has drag tracking event handlers, drag tracking handlers installed on your window. Now, the reason we can't do this automatically is because we don't want to interfere with any drag tracking you may already have. So we need you to adopt this explicitly.
And once that's set, you'll call Set Control Drag Tracking Enabled on your view during its initialization. And this tells the toolbox you handle the drag protocol, and it should send you these events. Now the drag protocol consists of four events: Drag Enter, Within, Leave, and Receive. Now these mirror very closely the existing drag manager messages.
Each one of these will pass you a drag graph as a parameter. You can go ahead and interrogate that drag reference with the existing Drag Manager and Pasteboard Manager APIs. The event I want to focus on here is the Drag-Enter Event. This is what you'll receive when the user first drags a drag over your view.
At this point, you'll get the message, you'll look at the drag ref, decide whether you like this drag or not, whether your view can accept it. And you'll fill out the KEVENT_PARAM_CTRL_WOULD_ACCEPT_DROP parameter. Now, this is very important because if you fill this out with false, or let me say this another way, you must fill it out with true and return no error from your event handler to guarantee that you will receive more events.
As a tip, if you're having trouble getting Drag-and-Drop to work, make sure that you're handling this parameter properly. Now, if your view doesn't want to handle the drag, you can just pass a false back and you won't get any more events. You don't need to worry about the drag.
The second feature is HIV Archive, which is a new feature for Tiger. Archiving consists of the ability to take your live objects in memory, so whether your HI objects or multiple core foundation property list types, get the persistent information for those objects, write that out to a binary archive that's provided to you in a CFData. You can write that out to disk or send it across the wire to another application. It doesn't matter. It's just a CFData.
And then later on, you can request that all those objects be pulled back out of the archive and reinstantiated to the state that they had when they were written out. To support archiving in an HI object or your HIV specifically, you need to handle the archiving protocol. This consists of the HI object encode and HI object initialize events. Each of these events is passed an HI archive ref as a parameter, which you will use to write your persistent state into or read it out of.
So in the case where a client has requested you encode all your information into an archive for storage, use the HI archive encode CF type API to write out your persistent state. And you'll write it out by key. The key is a CFString. This is very similar to the key value scheme used in dictionaries, so it should be fairly familiar. Now you want these keys to be unique so that you can pull the information back out by key later on when you're asked to re-instantiate.
There are Boolean and Number Convenience Wrappers for ENCODE CF type. Now, what you want to do is make sure that you call your superclass, so call the next event handler, so it can write out its attributes. In the case of a view, you may write out your specific information, but the HIView-based class will write out things like the title and position and size of your view, so you don't need to worry about those pieces.
Later on, your client will request that you decode yourselves from the archive and reinstantiate to your existing state. In this case, you'll be past the HIObjectInitialize event. This is an event that you may already be handling. When somebody calls HIObjectCreate with your class ID, this message is sent to you, and you'll initialize your view to a default state.
In the case where the HIarchiveRef parameter is present, you want to make sure to pull out all your information from the archive. You can do this again by calling hi-archive-copy-decoded-cftype. And again, there are Boolean and number wrappers for this API. So you pull out all of your state and then call the next event handler to allow your superclass to pull its information out.
Once you handle the protocol, make sure to call hiObject.setArchivingIgnored with false during initialization. This will tell the toolbox that yes, you handled the archiving protocol and you should receive these events. So to recap quickly, I'll just show you here. You can take your existing view, identify its persistent state, write those into the archive by key, and once the client has the archive, they can do whatever they want with it. Later on, they'll pull your information back out You'll request it by key again and reinstantiate your object with the state that it had previously.
With that, I'd like to pass it back to Curt to show you those two features in the demo. So these are the last features that we'll be implementing for this view during this session. So let's get started on that. Now, as Bryan mentioned, in order to handle Drag-and-Drop, the client needs to make sure that dragging is enabled for that window. Now, the code that's available to you actually has the view turning this on for the window, and that's incorrect.
Yeah, the window does not belong to the view. It belongs to the client application. So we've updated the sample code, and we'll make that available to you after the conference. But I'll do it correct here. So back in our main file, after the window is created, We enabled drag tracking for that window there.
Similar to drawing a focus around the different parts, we need to be able to provide user feedback to the user while they're dragging items over your view. So again, since we're only drawing during our draw function, we need to maintain some state associated with whether we're tracking or not currently. So let's go ahead and add some data to our data structure.
Bad Monkey. Since we've now added another field to our data structure, we need to make sure we initialize that correctly. So back in my initialize function.
[Transcript missing]
Next, we want to deal with drawing differently whether we are currently tracking. We added this field to our data structure.
Now, if we are currently tracking a drag, we want to make sure that we draw this highlight around the whole image to provide feedback to the user. So I'll modify the draw function. Forgive me for all the page downing. And I add a section here which will draw this highlight if we're currently tracking. So let's go ahead and implement support for that now.
We'll work in our main event type block. We add support for, as Ryan mentioned, there's four drag events in the suite, but we're only gonna handle the Enter, Leave, and Receive event. Then back up in our event handler. We'll handle those events. And then implement the functions to deal with that.
That's why it's not showing up. So the first function we implement is the Drag-Enter Event Handler. What this does is it gets the pasteboard associated with the drag, determines if there are any URLs on that drag, and if so, adds the Would Accept Drop to the event. So the event system will then send subsequent drag events to that control. If it would accept, we enable our flag and we invalidate. This is so the next time through the draw loop, we'll make sure to draw that highlight around the image.
Next, we implement the Drag-Leave function. Again, we just toggle the flag, whether we're tracking or not, and invalidate. This is so the highlight will go away. And finally, we implement the Receive Event Handler. This will pull out the Pasteboard from the drag and then iterate through that Pasteboard, pulling out the image URLs and add that to our internal data structure. I'm also using some additional utility functions, which I haven't implemented just yet. So I'm gonna go back up to the utility section of my code.
and I'll be working on the next step, which is to add the utilities. The utilities that I've added are, as I mentioned before, to determine whether there is a URL on the drag-paste board. I added a function to grab the Pasteboard from the Drag Event. And then also, or actually the first function I showed you, it was actually to iterate the Pasteboard and pull out the URLs. This is the function that determines whether there is a URL on that Drag. So let's go ahead and build. Run.
Now you can see that it just looks like it did before, but now it supports drag-and-drop. So if I take a bunch of images and I drag it over our view, we will get a Drag-Enter event. We set that we are currently tracking and invalidate. So the next time through the loop, we draw this highlight around the view. And likewise, when we drag out of the view, we don't draw that highlight anymore. And if I drop those images on, we get the Receive event, pull those URLs off of the drag, add it to our internal data structure, and we're good to go.
The last feature that we'll add is HI Archiving. Now, as Bryan mentioned, this requires a key. So what you need to do prior to implementing this is figure out what is the persistent state that I'm interested in. In our case, we have an array and an index that we really care about. And what we'll do is we'll define some keys for that.
In my constants area, I define these new keys. One, it corresponds to the array, which is the URLs archive, or the URLs, and the other corresponds to the index, which is the image index constant. As Bryan mentioned, if we need to notify the system that we handled the archiving protocol, we need to call during initialization, The HIObjectSetArchivingIgnored function passing the false. This says we do understand the archiving protocol and we are interested in receiving these types of events. Next, we add support for archiving by going down to our main array.
and adding an Event Type for the Encode Event. Go back up to my Event Handler. This is the second one of my three main steps for doing this. In the HIObject class handler, I add a handler for the encode event. And then we implement the function that deals with that encoding.
So as Bryan mentioned, since we are subclassing the view, we want to make sure that the superclass has an opportunity to encode itself because it knows things such as the view's position, its size, its title, etc. Next, we pull out the hi-archive event parameter, which is this encoder, and then we encode our persistent state using those keys that we defined.
So it's very simple. We just store out our internal state as these CF types, and we're good to go. There's no corresponding Decode Event, as Bryan mentioned, because normally, well, always when you're decoding, you'll be doing this during initialization because you're creating from an archive. So we handle this in the Initialize Event.
I had another variable for a decoder. And then towards the beginning, before I start extracting event parameters, I just check to see if there is a decoder available. If the decoder exists, then we unarchive from that. and that means that we're being requested to instantiate from an archive. If the decoder is not present, we go about and do our normal initialization processing.
And now we're good to go. So I haven't implemented this in the test harness for the purposes of this demo, since we're running out of time, but you can see in the sample code that's provided to you how there is the ability to actually archive out the window and its contents and then unarchive that as well. So, Bryan. - Great. Thank you, Curt. Back to slides. Slides, please.
Thank you. All right. As Curt, I'm sorry. What we've given you today is a lot of information about how to create a custom HIView from the ground up. And I want to talk about one more thing that might be helpful in your use of HIView. That's HIFramework. HIFramework is some sample code that we provide, which is a series of C++ wrappers around HIToolbox functionality, specifically HIObject and HIView subclassing. So this is a very simple thin wrapper. And so it should be very familiar to you if you're familiar with these APIs. But you may be more familiar with C++ and want to use its subclassing mechanism. And so it'll be much more C++ friendly.
We're going to give you today HIV framework 1.1, which is a new version, and I want to talk about some of the differences. In the original HIV framework, we just had a single Tview for view subclassing. We've added a little bit to the inheritance hierarchy there, a TEventHandler and a TObjectSuperclass. And we specifically wanted to add TObjectSuperclass to support accessibility, because accessibility support can be added to any HIVobject, Windows and menus included, not just views.
Now again, this is available with a sample code, and there's a second target in the project which will allow you to build using the HIFramework version. We've created a second copy of the HIView implementation that performs all the same features, but using the HIFramework sample code rather than the strict C API.
I've given you a lot of information today. What can you do with this information now? So we'd like you to go home, or I'm sorry, not home, go back to your hotel room tonight and just play around with the sample code. Experiment with it. Try implementing some new features.
We didn't do very much in the custom tracking event, so maybe try dragging -- using that to create a drag, track drag, and pull the image out and drag it up to the desktop, something like that. Or implement a new part. Perhaps a part that will allow you to do a view for all the images.
Say, you've got a new image. will be here to go through and do a slide show for all your images, something like that. Once you're familiar with the HIView sample code, go ahead and look at your own code and try to convert some of your existing C-DEFs to HIViews. This will allow you to support compositing in your Windows, which is the first step towards resolution independence.
So if you have some feedback on the things that you've learned today, features that you'd like to see, go ahead and contact us at [email protected]. And for more information, you can go to the ADCdeveloperApple.com website. And for this session specifically, you can see sample code that we've been demonstrating today and references to documentation.
And as always, you can go to the header documentation for each one of the HIToolbox's managers. So we do put in a lot of effort to give you a good overview at the top of the header file and document each one of our APIs as we add them.
So for the rest of the roadmap, there's the Carbon and HI Toolbox Feedback Forum on Thursday. And there's a lab going on all week, so bring in your HIViews and talk to the toolbox engineers and developer technical support folk to get some help. And I also mentioned earlier that there's an Accessibility Lab on Friday for your accessibility questions.