How can I make the button inside the NSTableViewRow respond to the presented object

I have been struggling with this problem for quite some time. I am working on the file manager module, so far I could do everything fine except for the cancel button. For some reason, when I click the cancel button for a specific row, the button action targets multiple rows at the same time.

File Copy Manager

After days of researching the problem, I was able to get the object to successfully cancel the operation represented by the string using:

-(IBAction)btnCancelOperationClick:(id)sender {
    NSInteger row = [_tableView rowForView:sender];
    if (row != -1) {
        FileCopyOperation *opr = [_fileCopyOperations objectAtIndex:row];
        [opr cancel];
        [_fileCopyOperations removeObjectAtIndex:row];
        [_tableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:row] withAnimation:NSTableViewAnimationEffectFade];
    }
}

      

This works well, I can undo the operation and update the table. Everything else works as intended, but there must be something wrong with my code or bindings. I load this cell from the tip and then register this icon using:

[_tableView registerNib:[[NSNib alloc]initWithNibNamed:@"FileCopyCell" bundle:nil] forIdentifier:@"FileCopyCell"];

      

I made the QueueController a file owner and connected everything like this:

Bindings

I would really appreciate it if someone could point me in the right direction for this to work correctly. Thanks in advance!

Edit to add more sample code.

-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    FileCopyCell *cell = [tableView makeViewWithIdentifier:@"FileCopyCell" owner:self];
    FileCopyOperation *opr = [_fileCopyOperations objectAtIndex:row];

    [cell.fileName setStringValue:[NSString stringWithFormat:@"Copying \"%@\"",opr.fName]];
    [cell.progressBar setDoubleValue:((opr.bWritten.doubleValue / opr.fSize.doubleValue) * 100)];
    [cell.totalBytes setStringValue:[NSString stringWithFormat:@"of %@",[NSByteCountFormatter stringFromByteCount:opr.fSize.longLongValue countStyle:NSByteCountFormatterCountStyleFile]]];
    [cell.status setStringValue:[NSString stringWithFormat:@"%@",[NSByteCountFormatter stringFromByteCount:opr.bWritten.longLongValue countStyle:NSByteCountFormatterCountStyleFile]]];
    [cell.icon setImage:[[NSWorkspace sharedWorkspace]iconForFile:opr.srcURL.path]];
    [cell.cancelButton setTarget:self];
    return cell;
}

-(void)windowDidLoad {
    [super windowDidLoad];
    _fileCopyOperations = [NSMutableArray new];
    windowFrame = [self.window frame];
    rows = 0;

    [_tableView registerNib:[[NSNib alloc]initWithNibNamed:@"FileCopyCell" bundle:nil] forIdentifier:@"FileCopyCell"];

    if (!fileCopyManager) {
        fileCopyManager = [FileCopyManager sharedFileCopyManager];
        [fileCopyManager.fileCopyQueue addObserver:self forKeyPath:@"operationCount" options:NSKeyValueObservingOptionNew context:(void*)fileCopyManager];
    }

    [_scrollView setHasHorizontalScroller:NO];
    [_scrollView setHasVerticalScroller:NO];
}

      

+3


source to share


2 answers


The best approach is to subclass NSTableCellView

and let you handle its own actions and the object it represents. For example, a cell representing an instance Foo

can have two properties: Foo

and fooController

. When the ( nonatomic

) Foo

setter is called , the cell can update its own UI to represent the passed in Foo

. When the controller Foo

creates a table cell, it can instantiate itself FooCell

, set itself as fooController

and assign an instance, Foo

and let the cell itself handle itself. The target of the Undo button can be its own cell, and when an action is called -cancel:

it can specify its own fooController

what to do (since the controllerFoo

responsible for updating the queue and table view), and since it is referencing its own Foo

, it can pass that to the controller with some method -cancelFoo:(Foo *)theFoo

, without relying on the controller to look up its index (which might be inaccurate if you animate rows appearing and disappearing. or the user quickly unbinds on the row, but their deletion is delayed and updated asynchronously).



Nice and clean. Clear and orderly containment / separation of duties. The cell handles its own updates and user actions and knows its own foo; the controller foo processes its collection foo, its table view, and the assignment of foos to foo's cells.

+2


source


Thanks to Joshua Nozzi, following his recommendation, I moved the button action from a controller to a cell class. In the class, I used the method below to access the presented object and send a message [operation cancel]

.

-(IBAction)cancelOpr:(id)sender {
    NSButton *button = (NSButton*)sender;
    FileCopyOperation *opr = [(NSTableCellView*)[button superview]objectValue];
    [opr cancel];
    // This code calls the controller [removeObject:] method that takes care of cleaning everything out and updates the GUI accordingly.
    AppDelegate *ad = (AppDelegate*)[[NSApplication sharedApplication]delegate];
    QueueWindowController *qc = [ad getQueueController];
    [qc.fileCopyOperations performSelectorOnMainThread:@selector(removeObject:) withObject:opr waitUntilDone:YES];
}

      



You can get the complete project here .

enter image description here

+1


source







All Articles