Deleting From NSOutlineView With the Delete Key

08/20/2008

So lets say you're writing a PyObjC application, and you're deciding how you'll represent your data. You fire open a nib (xib, these days) and stare at NSTableView and NSOutlineView for a while before picking one of the two. Lets say you chose NSOutlineView. You're going to need a way to create new nodes. Well, no sweat, just throw an NSButton into the fray and use the NSAddTemplate icon to make it clear what it does.

Here comes the big question though: how do you remove data from your shiny new NSOutlineView? The easy answer is to add another button (perhaps with the NSRemoveTemplate icon, you audacious rascal). If you're feeling really ambitious you may even bind a key to that button so that the delete key works.

Let a little time pass, though, and you'll start to have a nagging feeling. Something just doesn't feel right. You're never using that remove button, you're always pressing the delete key. Hell, once you meant to hit add and accidentally deleted something. You're finally ready to accept the truth: you just want to press the delete key, and you don't want no stinking remove button.

To accomplish that you'll need to subclass NSOutlineView (or NSTableView if thats what you're using). It'll look like this:

from Foundation import *
class WRLOutlineView(NSOutlineView):
    def keyDown_(self, event):
        key = event.charactersIgnoringModifiers()[0]
        flags = event.modifierFlags() & 0x00FF
        if ord(key) == 127 and flags == 0:
            # Do your removal magic here.
            pass
        else:
            super(DeletableOutlineView,self).keyDown_(event)

Its a pretty simple trick, but can be a bit confusing to figure out. Specifically, it can be bewildering to try to figure out what non-textual characters are in Python. Here it was difficult to figure out how to reference the delete character so that I could check to see if the incoming key was a match. Thankfully the builtin function ord turns ascii characters into their integer value, which made the comparison easy.

For completeness, here is the code I was using to actually remove the node from the table. At the line # Do your removal magic here. I had the code:

self.delegate().removeList_(self)

I was using a custom datasource and delegate, and in the delegate the removeList_ method looked like this:

@objc.IBAction
def removeList_(self,sender):
    item = self.outlineView.itemAtRow_(self.outlineView.selectedRow())
    parent = self.outlineView.parentForItem_(item)
    self.data.remove(item)
    self.outlineView.reloadItem_reloadChildren_(parent,True)

In that snippet self.data is an instance of a class containing my tree structure, and self.data.remove is a method that will recursively search the tree structure to remove the node supplied as the argument.

I'll be continuing to scribble down these short PyObjC tips and tricks as I run into them, so let me know if there is anything you're particularly interested in looking at and I'll merrily investigate.

I'm always glad to hear your questions and comments.

All Rights Reserved, Will Larson 2007 - 2014.