Re: NSTableView & different columns
Re: NSTableView & different columns
- Subject: Re: NSTableView & different columns
- From: Dave Hersey <email@hidden>
- Date: Sat, 02 Jul 2005 21:19:02 -0400
On 7/2/05 7:16 PM, "Sanri Parov" <email@hidden> wrote:
> On Sat, 02 Jul 2005 17:27:30 -0400, Dave Hersey wrote:
>
>> So, if the identifiers are "number" and "button", you'd have number,
>> setNumber, button, and setButton methods in the data object and include the
>> code below in your table's data source. (Here, I was storing the table
>> objects using an array.)
>>
>> - Dave
>
> Thanks Dave,
>
> the example was pretty good, but there's a point: you use an array,
> which is a very good and common way to treat datas.
> In my case, I'd like to set the first column with a progressive row
> number and the second one with a checkbox that's set
> on the checked position if a file exists in a certain location.
> Can I use an array even in this case? What do you suggest?
>
> Thanks again for all the kind help.
The array is just to store all of your table's individual objects--typically
one object in the array per row in your table, unless you're dealing with an
outline view. (BTW, make sure it's a mutable array). So, you could create a
new object type that has whatever attributes you want to display in your
table and then the data source will call the methods that correspond to your
column identifiers to get the data to display. However, if by a "progressive
row number" you mean the row number of the table, then that really does make
sense to handle conditionally within your objectValueForTableColumn method.
The reason is that the row index is passed into that method, and it really
doesn't belong in the data object itself.
I was shying away from the checkbox example because it's a more complicated
example, but I'll get to that in a second. Here's a simpler example first.
Suppose you have a PersonObject class that has strings stored for name,
address, and phone number. If this object has methods called name, address,
and phoneNumber that return NSStrings for the data in question, then you
could just set the table column identifiers to those method names, store the
PersonObjects in an array that your datasource uses, and with the code I
posted before, it all just works. The PersonObjects will have their name,
address, and phoneNumber methods called because the column identifiers in
your table are name, address, and phoneNumber, and the returned NSStrings
will be displayed in the table for each column.
Now suppose that you want to add non-string data. For example, a small image
in each row. Then, you need to change your table to have an NSImageCell
column and add a method to your PersonObject that returns the image to
display. A default NSTable uses text cells for the table data. That's why
returning NSStrings works in the above example.
I'm not sure if you can make an NSTable with a column of NSImageCells using
the 2.1 InterfaceBuilder, but since I'm on Xcode 1.5, I'd make a regular old
column in my table (in InterfaceBuilder) with a column identifier called
"image", and then in awakeFromNib, do this:
tableColumn = [m_TableView tableColumnWithIdentifier: @"image"];
imageCell = [[[NSImageCell alloc] init] autorelease];
[imageCell setImageAlignment: NSImageAlignCenter];
[imageCell setImageScaling: NSScaleNone];
[imageCell setImageFrameStyle: NSImageFrameNone];
[tableColumn setDataCell: imageCell];
That changes the column type to an NSImageCell, and once you implement the
"image" method in your PersonObject, the image will appear in your table. No
changes are required to your table's data source code to handle the new
data. That's the cool thing about this approach. I have a table data source
class that's generic and is reused in different projects. All I change is
the type of object that's being stored in the data source, be it a
PersonObject as above, or something entirely different. The class has
methods for adding and removing objects from the data source (just accessing
the mutable array mentioned earlier), and a few other things for managing
deletion of selected items and such.
Now, if you're still following all of this, let's look at the checkbox
example. Again you have to make a column in InterfaceBuilder. We'll call its
identifier "checkbox", and then (at least for the 1.5 tools) you'd set up
the column in your awakeFromNib or the like as such:
tableColumn = [m_TableView tableColumnWithIdentifier: @"checkbox"];
buttonCell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
[buttonCell setButtonType: NSSwitchButton];
[tableColumn setDataCell: buttonCell];
Here, I've set the column with the "checkbox" identifier to use
NSButtonCells.
In your PersonObject from above, you'd now create a checkbox object at init
time. You'd then have a "checkbox" method that returns this checkbox to the
caller. (I've added this code at the end of this message for clarity.)
If the user can change the checkbox settings in your table, you need to
provide a setCheckbox call that will set the value of your checkbox. This
will be sent with a CFBooleanRef value when the user clicks on your
checkboxes. It's a little confusing because the checkbox method returns an
NSButtonCell (the checkbox) and the setCheckbox method is sent with a
CFBooleanRef that you need to set your checkbox to. In other words, the
object types for the getter and setter calls are different, which is
atypical.
It sounds like the checkbox in your scenario will not be changeable by the
user, since you're setting it's value based on file availability rather than
the whims of the user. So, in that case you want to create a checkbox which
is disabled in your PersonObject's init method. I suppose you'd also want to
have a method in your data source that will run through all your objects and
sync them up with the availability of the files on the user's system.
Something like:
// In your table's data source, add a method that you can call to
// update your objects' checkboxes based on current file availability.
- (void) refresh
{
UInt32 idx, count = (m_dataArray)? [m_dataArray count]: 0;
for (idx = 0; idx < count; idx++)
{
PersonObject *anObject = (PersonObject *)
[m_dataArray objectAtIndex: idx];
// Call a method in the PersonObject to check for file
// availability and set our checkbox appropriately.
if (anObject)
[anObject syncCheckboxToDisk];
}
}
After that, you'd want to reload your table view's data to see the changes.
This refresh method takes awake the generic nature of the table data source
class I've been describing, but if you cared you could write a
"performSelectorOnEachObject" method in that class to keep it generic. In
that case, you'd pass the selector "syncCheckboxToDisk" and your objects
would all be iterated across.
Anyway, with all your classes defined and implemented, you'd create and set
up a PersonObject for each row of data in your table, which you'd do by
adding each object to your table's data source array (in my example). Note
that you are adding one object per row of data, not one object per column.
The PersonObject replies to requests for each column of data that's
displayed. My generic data source class has an addObjectAtIndex method for
handling this, but you just store the data in your storage for the data
source, be it a mutable array, mutable dictionary, whatever.
I don't think you're actually dealing with "PersonObjects" but it was a good
example for this discussion. :)
This is something like I'd end up with. Apologies for any typos.
- Dave
@interface PersonObject : NSObject
{
NSButtonCell *m_checkbox;
NSString *m_name;
NSString *m_address;
NSString *m_phoneNum;
NSString *m_image;
// ...
}
@implementation PersonObject
- (id) init
{
m_name = nil;
m_address = nil;
m_phoneNum = nil;
m_image = nil;
m_checkbox = nil;
if ((self = [super init]) != nil)
{
// Create a small checkbox.
m_checkbox = [[NSButtonCell alloc] init];
[m_checkbox setTitle: @" "];
[m_checkbox setButtonType: NSSwitchButton];
[m_checkbox setBordered: NO];
[m_checkbox setBezeled: NO];
[m_checkbox setState: NSOffState];
[m_checkbox setScrollable: NO];
[m_checkbox setControlSize: NSSmallControlSize];
}
return self;
}
- (void) dealloc
{
// Deallocate the objects we've created
[m_name release];
[m_address release];
[m_phoneNum release];
[m_image release];
[m_checkbox release];
[super dealloc];
}
- (NSString *) name
{
return m_name;
}
- (void) setName: (NSString *name)
{
NSObject *tempObject = m_name;
m_name = [name retain];
[tempObject release];
}
- (NSString *) address
{
return m_address;
}
- (void) setAddress: (NSString *address)
{
NSObject *tempObject = m_address;
m_address = [name retain];
[tempObject release];
}
- (NSString *) phoneNum
{
return m_phoneNum;
}
- (void) setPhoneNum: (NSString *phoneNum)
{
NSObject *tempObject = m_phoneNum;
m_phoneNum = [phoneNum retain];
[tempObject release];
}
- (NSString *) image
{
return m_image;
}
- (NSString *) setImage: (NSImage *) image
{
NSObject *tempObject = m_image;
m_image = [image retain];
[tempObject release];
}
- (id) checkbox
{
return m_checkbox;
}
- (void) setCheckbox: (id) checkboxValue
{
[m_checkbox setIntValue:
((CFBooleanGetValue((CFBooleanRef) checkboxValue) == TRUE)? 1:0)];
}
- (void) syncCheckboxToDisk
{
BOOL bSomeFileIsAvailable = FALSE;
// Is "some file" available?
// bSomeFileIsAvailable = ...
[m_checkbox setIntValue: ((bSomeFileIsAvailable)? 1:0)];
}
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Cocoa-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden