Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Arrow keys with NSTableView

Is it possible to navigate an NSTableView's editable cell around the NSTableView using arrow keys and enter/tab? For example, I want to make it feel more like a spreadsheet.

The users of this application are expected to edit quite a lot of cells (but not all of them), and I think it would be easier to do so if they didn't have to double-click on each cell.

like image 468
dreamlax Avatar asked Mar 04 '09 22:03

dreamlax


2 Answers

In Sequel Pro we used a different (and in my eyes simpler) method: We implemented control:textView:doCommandBySelector: in the delegate of the TableView. This method is hard to find -- it can be found in the NSControlTextEditingDelegate Protocol Reference. (Remember that NSTableView is a subclass of NSControl)

Long story short, here's what we came up with (we didn't override left/right arrow keys, as those are used to navigate within the cell. We use Tab to go left/right)

Please note that this is just a snippet from the Sequel Pro source code, and does not work as is

- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
{
    NSUInteger row, column;

    row = [tableView editedRow];
    column = [tableView editedColumn];

    // Trap down arrow key
    if (  [textView methodForSelector:command] == [textView methodForSelector:@selector(moveDown:)] )
    {
        NSUInteger newRow = row+1;
        if (newRow>=numRows) return TRUE; //check if we're already at the end of the list
        if (column>= numColumns) return TRUE; //the column count could change

        [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
        [tableContentView editColumn:column row:newRow withEvent:nil select:YES];
        return TRUE;
    }

    // Trap up arrow key
    else if (  [textView methodForSelector:command] == [textView methodForSelector:@selector(moveUp:)] )
    {
        if (row==0) return TRUE; //already at the beginning of the list
        NSUInteger newRow = row-1;

        if (newRow>=numRows) return TRUE;
        if (column>= numColumns) return TRUE;

        [tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
        [tableContentView editColumn:column row:newRow withEvent:nil select:YES];
        return TRUE;
    }
like image 169
Jakob Egger Avatar answered Sep 21 '22 16:09

Jakob Egger


Well it isn't easy but I managed to do it without having to use RRSpreadSheet or even another control. Here's what you have to do:

  1. Create a subclass of NSTextView, this will be the field editor. For this example the name MyFieldEditorClass will be used and myFieldEditor will refer to an instance of this class.

  2. Add a method to MyFieldEditorClass called "- (void) setLastKnownColumn:(unsigned)aCol andRow:(unsigned) aRow" or something similar, and have it save both the input parameter values somewhere.

  3. Add another method called "setTableView:" and have it save the NSTableView object somewhere, or unless there is another way to get the NSTableView object from the field editor, use that.

  4. Add another method called - (void) keyDown:(NSEvent *) event. This is actually overriding the NSResponder's keyDown:. The source code should be (be aware that StackOverflow's MarkDown is changing < and > to &lt; and &gt;):

    - (void) keyDown:(NSEvent *) event
    {
        unsigned newRow = row, newCol = column;
        switch ([event keyCode])
        {
            case 126: // Up
                if (row)
                newRow = row - 1;
                break;
    
            case 125: // Down
                if (row < [theTable numberOfRows] - 1)
                    newRow = row + 1;
                break;
    
            case 123: // Left
                if (column > 1)
                    newCol = column - 1;
                break;
    
            case 124: // Right
                if (column < [theTable numberOfColumns] - 1)
                    newCol = column + 1;
                break;
    
            default:
                [super keyDown:event];
                return;
        }
    
        [theTable selectRow:newRow byExtendingSelection:NO];
        [theTable editColumn:newCol row:newRow withEvent:nil select:YES];
        row = newRow;
        column = newCol;
    }
    
  5. Give the NSTableView in your nib a delegate, and in the delegate add the method:

    - (BOOL) tableView:(NSTableView *)aTableView shouldEditColumn:(NSTableColumn *) aCol row:aRow
    {
        if ([aTableView isEqual:TheTableViewYouWantToChangeBehaviour])
            [myFieldEditor setLastKnownColumn:[[aTableView tableColumns] indexOfObject:aCol] andRow:aRow];
        return YES;
    }
    
  6. Finally, give the Table View's main window a delegate and add the method:

    - (id) windowWillReturnFieldEditor:(NSWindow *) aWindow toObject:(id) anObject
    {
        if ([anObject isEqual:TheTableViewYouWantToChangeBehaviour])
        {
            if (!myFieldEditor)
            {
                myFieldEditor = [[MyFieldEditorClass alloc] init];
                [myFieldEditor setTableView:anObject];
            }
            return myFieldEditor;
        }
        else
        {
            return nil;
        }
    }
    

Run the program and give it a go!

like image 34
dreamlax Avatar answered Sep 23 '22 16:09

dreamlax