The tab control provides a convenient way to present information in a multipage format. Figure 14.6 shows two tabs centered horizontally in a window. The content area below a tab is called a pane. Panes are often used to organize settings for a utility program or control panel. When panes are used in combination with tabs, users can see, at a glance, all the categories of settings or features available. Tabs allow users to quickly switch from one pane to another with a single click.
As shown in the figure, you can use other controls, such as push buttons, radio buttons, and text fields, in a tabbed window. The controls can be global—affecting the settings of all panes—or specific to an individual pane. Make it clear through labeling and placement (within or outside of a tab pane’s boundary, for example) whether a control affects one pane or all panes. In the figure shown, the Quit button is outside the pane area, so it’s available regardless of which pane is visible.
When the user clicks a tab, the pane should switch appropriately. For example, when the user clicks the Itinerary tab, the Travel Time pane should disappear and the Itinerary pane should appear, as shown in Figure 14.7. Although it appears to the user as if the pane and the tab are one, you must implement this illusion in your code, because the tab and the pane operate independently. Each tab must be paired with a pane. When the user clicks a tab, your code must switch the tab view, make the appropriate pane visible, and make sure all other panes are hidden. (You’ll see how to do this in Section 14.3.3.)
Besides coordinating tabs with panes, the other tricky aspect of tab controls is adding items to a pane. When you add items, you need to make sure you are actually adding them to the appropriate pane. It’s easy to either add the items to the wrong pane or simply add an item to the window, but not associate the item with a pane. If you do either of these, when you run your application you’ll see misplaced items or items that persist from pane to pane. We’ll show you how to avoid this in the example that follows.
You’ll add tabs to an interface by doing the following:
Add a tab control to a window
Add controls to a user pane
Write a handler for a tab control and panes
Adding a tab control using Interface Builder is fairly straightforward. You’ll start with a window and add a tab data view that has two tabs. You can add more if you’d like, but we’ll stick to two for the purpose of showing you how to do it:
Drag a tab control (shown in Figure 14.8) from the DataViews palette in Interface Builder to a window.
Resize the tab control by dragging its corner until it fits the Aqua guides, as shown in Figure 14.9.
Use the Show Info window to set the number of tabs and tab
orientation. Click the middle of the tab view, then choose
Show Info from the Tools menu. The Tab Info window should open. You’ll
keep the default values: 2
as
the number of tabs, and North as the tab direction.
Assign the tab control an ID and a signature. The ID
must be an 8-bit value; use the application’s creator code as the
signature. You’ll need these when you write a handler to switch
tabs. Choose Control from the pop-up menu. In the Control
ID box, type MTPP
for
the signature and 128
for
the ID.
Double-click the tab labeled One to make the tab and its pane editable. The Info window should now be titled Tab Item Info and you should see a thick line around the pane, as shown in Figure 14.10.
Use the Attributes pane of the Tab Item Info window to name
the tabs. For each tab, type the name in the Label text field,
as shown in the example for the Travel Time tab in Figure 14.10. Type Travel Time
for tab One and Itinerary
for
tab Two.
Assign a User Pane Signature and User Pane ID to each tab
item. The User Pane Signature for each tab item should be the
same as that assigned to the tab—the application’s creator code.
Type MTPP
in the User
Pane Signature text field. The IDs should be sequentially
assigned, using the ID you assigned to the tab (128
)
as the basis. Type 129
in
the User Pane ID field for the Travel Time tab item and 130
for the
Itinerary tab item.
The procedures for adding controls to a user pane are the same as those you used in Chapter 5 to add controls to a window. The only difference is that you must make sure the pane to which you want to add controls is in edit mode and is in front of the other panes.
Before you add a control, double-click the tab associated with the pane to which you want to add a control. Make sure the user pane is editable—the tab you clicked should be active and a thick line should appear around the pane. Then, you can drag controls to the pane as you would to a window.
You need to write a handler that switches the frontmost tab in response to a user click on a tab and then shows the appropriate pane while hiding the others. First, you’ll need to declare the constant that represents the IDs you assigned to the tab control and tab items when you set them up in Interface Builder. First, define constants to represent the tab ID:
#define TAB_ID 128;
Next, set up an array—a tab list—to specify the number
of tabs and the ID of each tab. The first item in the array is
2 because you set up two tab items. Recall you assigned 129
and 130
as
the tab item IDs. The variable lastTabIndex
is
a housekeeping variable you’ll use to keep track of the last
tab chosen by the user. You’ll set the starting value at 1
:
int tabList[] = {2, 129, 130}; int lastTabIndex = 1;
You’ll need to declare the tab handler (MyTabEventHandler
)
and the function (MySwitchItemOfTabControl
)
that switches from one pane to another. Here are the function declarations
for each:
pascal OSStatus MyTabEventHandler (EventHandlerCallRef inHandlerRef, EventRef inEvent, void *inUserData); void MySwitchItemOfTabControl (int index, ControlRef tabControl);
In your main function, you install an event handler for the
tab control. You’ll use the InstallEventHandler
function
we discussed in Chapter 6.
As you recall, this function requires an event target as its first
parameter. The event target is the tab control, but you need to
pass a parameter of type EventTargetRef
.
The Carbon Event Manager has a function, GetControlEventTarget
,
that takes a control as a parameter and returns an EventTargetRef
for
that control. But GetControlEventTarget
takes
a ControlRef
as a parameter.
You know what the ID and signature of the tab control are, so you’ll
need to use those to get the ControlRef
associated
with our tab control’s ID and signature. The Control Manager has
a function, GetControlByID
,
that will get a ControlRef
associated
with a window reference and a ControlID
data
type. A ControlID
is
a structure that contains a signature and an ID—the two items
you assigned to your tab control when you set it up in Interface
Builder.
When you put it all together, the code (which should be added to your main function) looks like Example 14.6.
Example 14-6. Code to Install the Tab Control Event Handler
ControlID controlID; controlID.signature = TAB_SIGNATURE; controlID.id = TAB_ID; GetControlByID (MyWindow, &controlID, &tabControl); eventTypeSpec controlSpec = { InstallEventHandler (GetControlEventTarget (tabControl), NewEventHandlerUPP (MyTabEventHandler), 2, &controlSpec, 0, NULL);
You need to assure that when your application launches, the tab control displays properly. To do so, put the following code in your main function to select the first tab item when the application launches:
MySwitchItemOfTabControl (1,tabControl);
Now, you are ready to add the actual tab handler shown in Example 14.7. The handle switches tabs only if the tab value has changed. If the user clicks the active tab, nothing happens.
Example 14-7. The Tab Event Handler
pascal OSStatus MyTabEventHandler (EventHandlerCallRef inHandlerRef, EventRef inEvent, void *inUserData) { ControlRef tabControl; ControlID controlID; OSStatus result; controlID.signature = TAB_SIGNATURE; controlID.id = TAB_ID; GetControlByID (MyWindow, &controlID, &tabControl); // 1 if (GetControlValue (tabControl) == lastTabIndex) // 2 { result = eventNotHandledErr; } else { // 3 MySwitchItemOfTabControl (GetControlValue (tabControl), tabControl); result = noErr; } return result; }
Here is what the function does:
You need
to get the ControlRef
associated
with the tab control so you can check its value. As you saw in Chapter 6
the Control Manager function GetControlById
will
do just that.
You use GetControlValue
to
get the value of the pane setting and compare it with the last known
value to see if the tab value has changed. If it hasn’t changed,
you pass back eventNotHandledErr
to
give the Carbon Event Manager a chance to let another handler take
care of the event.
Otherwise, the value has changed, so you need to call your
function (MySwitchItemOfTabControl
)
to switch the pane associated with the tab and then redraw the tab
control.
You’ll need to add the function called by your tab handler, as shown in Example 14.8, to switch to the pane associated with the tab.
Example 14-8. A Function to Switch Panes
void MySwitchItemOfTabControl (int currentTabIndex, ControlRef tabControl) { ControlRef userPane; ControlRef selectedPane = NULL; ControlID controlID; UInt16 i; lastTabIndex = currentTabIndex; controlID.signature = TAB_SIGNATURE; for (i = 1; i < tabList[0] + 1; i++) { controlID.id = tabList[i]; GetControlByID (MyWindow, &controlID, &userPane); // 1 if (i == currentTabIndex) // 2 selectedPane = userPane; else SetControlVisibility (userPane,false,true); // 3 } if (selectedPane) SetControlVisibility (selectedPane,true,true); // 4 Draw1Control (tabControl); // 5 }
Here is what the function does:
Call the
Control Manager function GetControlByID
to
get the control reference for the pane associated with a tab control.
If the current tab index has the same value as the pane you’re checking, then set the selected pane to the pane you’re checking.
If the selected pane is not the same as the pane you’re
checking, then call the Control Manager function SetControlVisibility
to
set the visibility for the pane to false
(don’t show
it) and update the display (indicated by true
).
If the selected pane is the same as the pane you’re checking,
call the function SetControlVisibility
to
make the pane visible and update the display.
The Control Manager function Draw1Control
updates
the tab control so the correct tab is active.
Notice that none of the code you’ve added handles controls in the pane. How could you handle them? Let’s say your tabbed window has a pane identical to the Travel Time pane shown earlier, in Figure 14.6. You would use the same window event handler and compute travel time functions you wrote in Chapter 6. You let your tab handler take care of switching the view while your window event handler and compute travel time functions respond to user interaction in the actual pane.
Instead of displaying content in multiple windows, tab controls let your application display the content as a stack of panes in a single window. To use tabs, you must add tab controls to your application and then write a handler that switches from one pane to another when the user clicks a tab. As an exercise, you can modify the Moon Travel Planner application to use multiple panes instead of multiple windows.