Thursday, July 24, 2008

NSTableView - without bindings

I'm supposed to be working on Hillegass, but I got interested in another problem. On my old pages, I showed some examples of using bindings with NSTableView. I want to revisit this issue, and work through a series of examples starting with a one-way binding where the table view is bound to our data source, and working up to a more complex case where the data source is also bound to the table view (where changes to the GUI are reflected automatically in the data source).

On the left is the window of my app when it starts up, and on the right is the window after I've clicked the button a few times.



We have a button labeled "go" and an NSTableView (without scrollbars and not editable). The NSTableView is connected in IB as an outlet of the AppDelegate called tv. The button is connected to an action of the same name in the AppDelegate.

In order to serve as a data source for the table view, the AppDelegate implements the methods shown at the end of the listing. The data comes from an NSMutableArray called ds. We set up the data source in applicationDidFinishLaunching_() by calling addObjectsFromArray_() and feeding it a Python list of NSNumber objects. In the same function, we tell the tv that we are the data source.

The action happens in the go_() method. We pick a random NSNumber object from the data source, double its value, and replace that object in the data source. At the end, we tell the tv to reloadData. All manual, but it works. Here is the code:


from Foundation import *
from AppKit import *
import random

class PyX1AppDelegate(NSObject):
tv = objc.IBOutlet()
ds = NSMutableArray.alloc().init()

def applicationDidFinishLaunching_(self, sender):
NSLog("Application did finish launching.")
self.ds.addObjectsFromArray_(
[NSNumber.numberWithInt_(1),
NSNumber.numberWithInt_(2),
NSNumber.numberWithInt_(3)])
NSLog("%s" % self.ds.description())
self.tv.setDataSource_(self)

@objc.IBAction
def go_(self,sender):
NSLog("go: %s" % self.ds.description())
N = self.ds.count()
i = random.choice(range(N))
n = self.ds.objectAtIndex_(i).intValue()
self.ds.replaceObjectAtIndex_withObject_(
i,NSNumber.numberWithInt_(n * 2))
m = self.ds.objectAtIndex_(i).intValue()
NSLog("%i %i %i" % (i,n,m))
self.tv.reloadData()

def numberOfRowsInTableView_(self,tv):
return self.ds.count()

def tableView_objectValueForTableColumn_row_(
self,tv,c,r):
return self.ds.objectAtIndex_(r).intValue()


Because of the way the bridge works, we could also do without the Cocoa objects and use a simple list of ints.