NSOutlineView Problem with Child / Parent element - objective-c

I am implementing NSOutlineView and having implemented following method,
-(void) initOutlineView{
pMyOutlineView = [[[MyUICustomOutlineView alloc] initWithFrame:clipViewBounds]
autorelease];
NSTableColumn* firstColumn = [[[NSTableColumn alloc] initWithIdentifier:#"firstColumn"] autorelease];
[firstColumn setWidth:25];
[pMyOutlineView addTableColumn:firstColumn];
NSTableColumn* secondColumn = [[[NSTableColumn alloc] initWithIdentifier:#"secondColumn"] autorelease];
NSTextFieldCell *pCell = [[NSTextFieldCell alloc]init];
[secondColumn setDataCell:pCell];
[secondColumn setWidth:180];
[pMyOutlineView addTableColumn:secondColumn];
[pMyOutlineView setRowHeight:30];
pNodeArray = [[NSMutableArray alloc]initWithCapacity:10];
PointerNode *pNode = pointerList.getHead();
int idx =0;
void *ptr = nil;
while ( contact ) {
[pNodeArray insertObject:[NSValue valueWithPointer:(void *)pNode]
atIndex:idx];
pNode = pNode->getNext();
idx++;
}
[pMyOutlineView setDataSource:self];
// this is to tell myCustomOutlineView to delegate Menu and Mouse event to
// this interface
[pMyOutlineView setDataDelegate:self];
[scrollView setDocumentView:pMyOutlineView];
[pMyOutlineView setDelegate:self];
}
And Implemented following delegate Method
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
// Here it will come for the top level element
if(item==nil)
return pointerList.size();
/*
If its not NULL then it must be some child element
*/
if([item isKindOfClass:[NSValue class]])
{
// yes it may have children
PointerNode *pNode = (PointerNode *)[item pointerValue];
if(pNode->hasChildren()){
return pNode->getNoOfChild();
}
}
return 0; // means this element not going to have any children
}
Some other method
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
and get data
i am following some blogs , tutorial,
the problem what i am facing is as par documentation it should hit in numberOfChildrenOfItem for each element where we need to calculate and send the No of child for that item,
but the problem what i am facing, its coming to above function only once and for that too item is nil, i.e. its not coming for other element,
Am i missing any method that needs to be delegate
other method which i override is as below,
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)aTableColumn byItem:(id)item
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
Please tell me , which method i am missing

In outlineView:numberOfChildrenOfItem:, you need to test if item is nil and, if so, return the number of children of the top level object. Quoting the documentation,
If item is nil, this method should return the number of children for the top-level item.
If you’re returning 0 when item is nil, the outline view considers that there are no children for the top level item, hence it won’t send this message again to your data source object.

#Rohan did you implemented the datasource methods or not.
Check out the documentation for the NSOutlineView
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSOutlineView_Class/Reference/Reference.html

Related

cocoa-How to use tableViewSelectionDidChange:?

I have a NSTableview in my view and I want to do some other thing when the user select a particular row. I tried tableViewSelectionDidChange method but it seemed not working.
-(void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSInteger row = [self.InfoTable selectedRow];
if (row == -1) {
return;
}else{
self.NumberInputTextField.stringValue = studentsInTable[row][0];
self.NameInputTextField.stringValue = studentsInTable[row][1];
self.ClassnumberInputTextField.stringValue = studentsInTable[row][1];
}
}
and I have
#interface ViewController : NSViewController <NSTableViewDelegate,NSTableViewDataSource>
and
self.InfoTable.dataSource = self;
self.InfoTable.dataSource = self;
Also, I've googled but haven't found a useful answer.
Can any one give me a hint on it?
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification is delegate method but you are only setting dataSource to self.
[self.InfoTable setDelegate:self];
If you have a cell-based or view-based NSTableView then nothing as such is required. Just make sure the tableView's delegate is set to the controller class, and you implement them.
Or you can do the above with codes:
self.InfoTable.dataSource = self;
self.InfoTable.delegate = self; //Note you used dataSource twice
And make sure you implement these methods if you are not doing binding to load the tableView.
- (void)tableViewSelectionDidChange:(NSNotification *)notification{
NSLog(#"Your seleceted a row...");
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.anArray count];
}
- (id) tableView:(NSTableView *)TableView
objectValueForTableColumn:(NSTableColumn *)tableColumn
row:(int)row {
return self.anArray[row];
}

Populating NSTableView from NSMutableArray at button pressed

in an OSX app i'm currently developping to get familiar with obj-c, I want to populate a TableView. After some hours spent reading way too much blog posts, I can't understand how to add a row in my TableView.
Here is what I've done following this guide:
I have an NSMutableArray in my ViewController, this ViewController implement both interfaces NSTableViewDataSource and NSTableViewDelegate. And I implement both methodes as indicated in the guide. I also have a button and a tableView. When I click on the button, I fill my array with my own object, that's works great.
But what I want now, is when my array is populated, my tableview is too. I'm aware I need to bind those two in some way, but I have no idea how, can someone give some indication ?
Here is my code for my ViewController:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableViewEpisodes.delegate = self;
self.tableViewEpisodes.dataSource = self;
}
- (IBAction)btRefresh:(id)sender {
CalendarReader* reader = [[CalendarReader alloc]init];
self.episodes = [Episode getEpisodeFromEKEvents:[reader getLastMonthEventsForCalendarName:#"TV Shows"]];
[self.tableViewEpisodes reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(NSTableView *)tableView
{
return [self.episodes count];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColum row:(NSInteger)row {
// Retrieve to get the #"MyView" from the pool or,
// if no version is available in the pool, load the Interface Builder version
NSTableCellView *result = [tableView makeViewWithIdentifier:#"MyView" owner:self];
// Set the stringValue of the cell's text field to the nameArray value at row
result.textField.stringValue = [self.episodes objectAtIndex:row];
// Return the result
return result;
}
First, you are creating a cell view with an identifier which you have not declared, you need to do something like this (assuming you correctly adopted the UITableView protocol in your class):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *Ident = #"Ident";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Ident];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:Ident] autorelease];
}
[cell.textLabel setText: [yourArray objectAtIndex:indexPath.row];
return cell;
}
This is a delegate method for your NSTableView. It is called when the view is loaded so you need to provide a data source at runtime.
Second, I'm assuming you want one section with a number of rows equal to your data array. If this is so, you need to change the delegate method:
- (NSInteger)numberOfSectionsInTableView:(NSTableView *)tableView
to:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
Finally, keep in mind these above methods populate the ROWS, not the COLUMNS as you have it now. Once you populate your array, you need to invoke the method:
[yourTableView reloadData]
In order to refresh the table.
Hope this helps.
Thanks to #bryan-wheeler, I notice a message log when testing his code, and I found out, I was not implementing the correct method: here is my code for my ViewController now:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableViewEpisodes.delegate = self;
self.tableViewEpisodes.dataSource = self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return 1;
}
-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{
return [self.episodes objectAtIndex:rowIndex];
}
For beginner like me, you'll also have to implement the NSCopying protocol for the class stored in your data source array.
Right now, I only have one element in my TableView and it only show its memory address, but I'll update this answer as soon as I found out how to make it works for future beginner in my case.
EDIT: OK, it works !! My mistake was that: in the tableView:objectValueForTableColumn:row: method, I though I needed to return the Object representing the row given in parameter, but I had to return the one for the AND the cell given in parameter, now I found out, it's pretty obvious, but as a French, I didn't understand the method name correctly. Here is my code now:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableViewEpisodes.delegate = self;
self.tableViewEpisodes.dataSource = self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [self.episodes count];
}
-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{
if([[aTableColumn title] isEqual: #"Serie's name"]){
return [[self.episodes objectAtIndex:rowIndex] seriesName];
}else if([[aTableColumn title] isEqual: #"Season number"]){
return [NSString stringWithFormat:#"%ld", (long)[[self.episodes objectAtIndex:rowIndex] seasonNumber]];
}else if([[aTableColumn title] isEqual: #"Episode number"]){
return [NSString stringWithFormat:#"%ld", (long)[[self.episodes objectAtIndex:rowIndex]episodeNumber]];
}else{
return nil;
}
}
There is some optimisation to do for sure, feel free to propose. But it's doing the job.

Tooltip for Cell-based NSTableView

I have a view which (among other elements) holds a cell based NSTableView. I made the ViewController the delegate of the table. The view controller is created programmatically and added to an NSStatusItem.
I implemented:
- (BOOL)tableView:(NSTableView *)tableView shouldShowCellExpansionForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
return YES;
}
- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(NSCell *)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation {
return #"A tooltip";
}
- (BOOL)tableView:(NSTableView *)tableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
return YES;
}
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
NSLog(#"Display");
}
But when I run my code the only one of these methods that gets called is:
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex;
Any idea what might be wrong?
My real objective is to dinamycally define the tooltips for my cells.
I've just needed to solve this problem for my app's development.
These delegate methods you mentioned above are only for the expansion tooltip of a text cell, that shows full text when truncated.
A possible solution is to add an observer of NSViewFrameDidChangeNotification in which the tooltip rectangles are rearranged using addToolTipRect:owner:userData:. The code is something like:
NSTableView *tableView; // the target tableview
- (void)tableViewSizeDidChange:(NSNotification *)theNotification {
[tableView removeAllToolTips];
for (NSInteger row = 0; row < tableView.numberOfRows; row ++) {
NSRect rowRect = [tableView rectOfRow:row];
for (NSInteger col = 0; col < tableView.numberOfColumns; col ++) {
NSRect colRect = [tableView rectOfColumn:col];
NSRect cellRect = NSIntersectionRect(rowRect, colRect);
[tableView addToolTipRect:cellRect owner:
/* Tooltip string for cell at row and col */ userData:NULL];
}}
}
- (void)windowDidLoad {
...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(tableViewSizeDidChange:)
name:NSViewFrameDidChangeNotification object:tableView];
...
You may also need to call tableViewSizeDidChange: when a table column was moved or resized.

NSOutlineView, setFloatsGroupRows: not working

I know it should work, I can see it in sample projects, but in my sample project I can't force outline view to float group rows. The code is straightforward, everything works except that feature. Probably I'm missing something obvious.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self.outlineView setFloatsGroupRows:YES];
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
if (item == nil)
return [#[#"1", #"2",#"3",#"4",#"5"] objectAtIndex:index];
return nil;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
return NO;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
if (item == nil)
return 5;
return 0;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
return item;
}
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSTableCellView *tableCellView = [outlineView makeViewWithIdentifier:[tableColumn identifier] owner:self];
return tableCellView;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
{
if ([#[#"1", #"2",#"3",#"4",#"5"] indexOfObject:item] == 0)
return YES;
return NO;
}
Edit I added NSTableView alongside NSOutlineView to my project, added the same datasource and delegate methods and table view's group row flows, when outline view's one still doesn't. I'm wondering what I am missing, why outline view's group row doesn't want to fly?..
Solved. In NSOultineView group rows float only over its child rows. They don't float over other non group rows but NSTableView does. So if you have a group row without child items like in my example then it won't float.
In my opinion it's a bug or at least there shoud be an option for both cases (for at least a consistency with a table view behaviour).
Make sure self.outlineView IBOutlet is connected to the NSOutlineView in the xib.

Little circle-line bar stuck at top of NSOutlineView when rearranging using drag and drop

I've got NSOutlineView that, at the moment, only lists children of the root item. Everything works except when I try to rearrange items using drag & drop. The line with the circle in front of it always stays above the top row and doesn't follow my items when they need to be dragged between other items. However, I still get the index positions properly so I can still rearrange them in the datasource properly. I don't want to rearrange items by dragging them onto each other, I only want to rearrange the items off the root level (like the VLC playlist on the Mac).
Here are my four required methods:
//queue is a C-based data structure
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
if(item == nil){
return queue.count; //each list item
}else{
return 0; //no children of each list item allowed
}
}
-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
if(item != nil) return nil;
return [[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithLong:index],#"index",
[NSString stringWithFormat:#"%s",queue.item[index].filepath],#"filePath",
nil] copy];
}
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
if(item == nil){
return YES;
}else{
return NO;
}
}
-(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
if(item == nil) return nil;
return [item objectForKey:[tableColumn identifier]];
}
And my drag methods:
-(BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pasteboard
{
[pasteboard declareTypes:[NSArray arrayWithObject:LOCAL_REORDER_PASTEBOARD_TYPE] owner:self];
[pasteboard setData:[NSData data] forType:LOCAL_REORDER_PASTEBOARD_TYPE];
return YES;
}
-(NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
NSUInteger op = NSDragOperationNone;
if(index != NSOutlineViewDropOnItemIndex){
op = NSDragOperationMove;
}
return op;
}
I'm kind of confused about what desired behavior you want. Does this sample project achieve the desired drag and drop highlighting behavior (it shows the o–––––– indicator between each row, which is how VLC appears to work here for me):
http://www.markdouma.com/developer/NSOutlineViewFinagler.zip
(Note that it doesn't include code yet to actually carry through the reordering of the items).
If not, can you describe again what it currently does now and what you want it to do?
Also, you didn't include code for it here, but did you make sure to register the outline view for the LOCAL_REORDER_PASTEBOARD_TYPE drag type? I would do it in awakeFromNib:
- (void)awakeFromNib {
[self.outlineView registerForDraggedTypes:#[LOCAL_REORDER_PASTEBOARD_TYPE]];
}
That's necessary for the outline view to allow itself to be a drag destination.