macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Understanding the NSTableView Class
Pages: 1, 2, 3, 4, 5

The Data-Source Process

Some table view widgets allow data to be directly added or remove from the view. These widgets usually requires the tabular data to be converted first to a predetermined form. One such example is the REALbasic ListBox control, which requires its tabular data to be converted first into strings.



Other table view widgets require the entire tabular data encapsulated in a separate object. The widgets are then initialized using the encapsulating object. An example of such a widget is the Java JList class. It requires its data to be encapsulate using a DefaultListModel object. Then, an instance of the JList object is created by passing the DefaultListModel object into the class constructor.

Both aforementioned widgets maintain their tabular data internally, which adds to their resource overhead. The NSTableView class, on the other hand, uses a target-action approach called data-sourcing to manage its tabular data. Each time the table updates its display, it sends its requests for data to a separate object known as a data-source controller. Often, this controller is derived from the NSObject class and uses a collection object (such as an NSArray or NSDictionary) to serve as its internal data buffer.

To function correctly, a data-source controller must support at least two of the basic protocol methods provided by the NSTableView class. The first protocol method:

     - (int)numberOfRowsInTableView:(NSTableView *)aTable;

returns the total number of data items available from the controller to the NSTableView parameter, aTable. If the controller returns an integer value less than 0, aTable will interpreted this as a zero-data indicator.

The second protocol method:

     - (id)tableView:(NSTableView *)aTable objectValueForColumn:(NSTableColumn *)aCol row:(int)aRow;

provides the controller the references to the table cell being updated. The controller then retrieves and returns the data encapsulated in an appropriate NSObject to be displayed in the specified cell.

Figure 3 is a UML sequence diagram illustrating a typical data-source process. In this diagram, aTable is assume to contain only single column. The data-source controller, aDatSrc, uses an NSArray object to serve as its internal data buffer, aBuffer.

Figure 4. The Data-Source Process.
Figure 4. The Data-Source Process

Each time aTable receives a [reloadData] message, it first queries the number of rows to be displayed by sending a [numberOfRowsInTableView:] message to aDatSrc. aDatSrc responds by first sending a count message to aBuffer and then sends the resulting count value back to aTable as an unsigned integer.

Once the number of rows has been established, aTable then starts querying for data to be displayed in each row and column by sending a [tableView:objectValueForTableColumn:row:] message to aDatSrc. The row parameter contains the index of the row that is visible on aTable whereas the objectValueForTableColumn parameter contains a reference to the NSTableColumn subview being updated. aDatSrc then queries aBuffer for the desired data by sending it an objectAtIndex message passing along the row index provided by aTable.

If aTable is a multi-column table, aDatSrc first determines which column subview is being updated by sending an [identifier] message to the objectForTableColumn parameter, aCol. aCol responds by returning its unique identifier, which is usually interpreted as an NSString object.

Notice that both data-source methods pass references to the NSTableView widget through the aTable parameter. This allows the same data-source controller to service multiple NSTableView widgets. To identify which widget is being updated, send a [tag] message to aTable. It then returns an NSNumber containing the unique identifier assigned to the widget through the NSTableView Inspector panel (Figure 3).

Notice as well that both the row index and the total row count are represented as signed integers. This implies that NSTableView is limited to a maximum of 2 147 483 648 rows of data at a time. On the other hand, collection classes such as >NSArray represent their indices and item count as unsigned values. This implies that a collection class can contain up to 2256 possible data items. Nonetheless, these discrepancies in storage size is not a major factor as NSTableView mostly updates those rows that are immediately visible to the user.

The easiest way to create a data-source controller and bind it to the desired NSTableView widget is to use Interface Builder. Navigate to the Classes panel of the application nib window and create a subclass of NSObject. Make sure to rename the subclass appropriately. To bind the controller to the widget, first instantiate the controller by selecting its name from the Classes panel and choose Instantiate from the Classes menu. Then in the Instances panel, double-click to select the NSTableView widget and <CTRL>-drag a connection line from the widget to the instantiated controller (Figure 4). This will display an Inspector panel with the dataSource entry automatically selected. Click on the Connect button to complete the binding.

Other classes such as NSTimer or NSThread can also be subclassed and used as the basis for the data-source controller. Be aware, however, that Interface Builder displays classes mostly from the Application Kit framework. If the controller is based on a class from the Foundation Kit or from a third-party framework, the subclassing and binding process will have to be done manually. To manually bind aDatSrc to aTable, send a [setDataSource] message to the table view. Make sure to perform the binding at least during the awakeFromNib message event of aTable.

      [aTable setDataSource:aDatSrc];

Figure 5. Binding a data-source controller to the table view.
Figure 5. Binding a data-source controller to the table view

Once the controller has been bound to the widget, choose Create Files from the Classes menu and follow the instructions provided by the ensuing dialog. Interface Builder then generates the source and header files, and adds them to the XCode project where the controller internals can be defined.

The number of visible columns on the NSTableView widget determines the collection class suitable for use as the internal buffer of the data-source controller. For instance, if the widget only has a single table column, an NSArray class can serve as the internal buffer. Since both objects uses the same zero-indexing scheme, data transfers between the two is straightforward and synchronized. Furthermore, the data-source protocol methods can be implemented as shown in Listing 1.

Listing 1. The data-source controller for a single-column table view.

aDatSrc.h
       #import <Cocoa/Cocoa.h>
       
       @interface aDatSrc : NSObject
       {
             @private
             NSArray     *aBuffer;
       }
       @end
aDatSrc.m
       #import "aDatSrc.h"
       
       @implementation aDatSrc
       // The data-source protocol methods
       - (int)numberOfRowsInTableView:(NSTableView *)aTable
       {
             return ([aBuffer count]);
       }
       
       - (id)tableView:(NSTableView *)aTable 
             objectValueForColumn:(NSTableColumn *)aCol 
                                       row:(int)aRow
       {
             return ([aBuffer objectAtIndex:aRow]);
       }
       
       // -- additional code to initialize, access, and 
       // -- modify the data buffer
       // -- ...
      @end

On the other hand, if the widget has multiple table columns, an NSDictionary class would be more suitable. Data for each column subview is then stored into an NSArray. Then each NSArray object is stored into the NSDictionary object as a key/value entry using the unique identifier assigned to each NSTableColumn subview as the dictionary key (Figure 5). Listing 2 shows how the data-source protocol methods can be implemented for a multiple-column table view.

Figure 6. Proposed data buffer for a multi-column table display.
Figure 6. Proposed data buffer for a multi-column table display

Listing 2. The data-source controller for a multiple-column table view.

aDatSrc.h
	     #import <Cocoa/Cocoa.h>
      
      @interface aDatSrc : NSObject
      {
           @private
           NSDictionary     *aBuffer;
      }
      @end
aDatSrc.m
	     #import "aDatSrc.h"
      
      @implementation aDatSrc
      // The data-source protocol methods
      - (int)numberOfRowsInTableView:(NSTableView *)aTable
      {
           return ([aBuffer count]);
      }
      
      - (id)tableView:(NSTableView *)aTable 
                objectValueForColumn:(NSTableColumn *)aCol 
                  row:(int)aRow
      {
           id     loc_id, loc_dat;
           NSArray          *loc_col;
           
           loc_id = [aCol identifier];
           if (loc_id != nil)
           {
                loc_col = [aBuffer objectForKey:loc_id];
                if (loc_col != nil)
                {
                     loc_dat = [loc_col objectAtIndex:aRow];
                }
           }
           return (loc_dat);
      }
      
      // -- additional code to initialize, access, and 
      // -- modify the data buffer
      // -- ...
      @end

Notice that both controller implementations declare their internal buffers as a private property. This prevents the buffer from being directly accessed and modified by external objects. As part of a good object-oriented design, buffer access must be done only through the requisite accessor and modifier methods.

Notice as well that the collection classes used to implement the internal buffer is immutable. Mutable collection classes such as NSMutableArray and NSMutableDictionary should be used only when data editing is to be supported. Otherwise, use immutable classes to minimize the resource overhead and memory requirements of the application.

Pages: 1, 2, 3, 4, 5

Next Pagearrow