带有NSCheckBox单元格的NSTableView - 如何拦截行选择?

时间:2015-05-02 05:29:01

标签: objective-c macos cocoa swift nstableview

我有NSTableView个单元格包含NSCheckBox。我想改变它的行为,以便可以点击表格中的任何行,并且该行中的复选框会相应地打开/关闭。

我认为可以用

完成
    func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
}

此方法提供单击行的索引,我可以使用它来以编程方式从那里切换复选框。但问题是NSCheckBox拦截其行上的鼠标点击。有没有办法禁用它,以便可以完全点击行?将NSCheckBox的启用状态设置为false将允许此操作,但它也会使复选框及其标题变灰。

编辑:

澄清我需要的内容:(基于视图的)表格单元格视图包含一个复选框。如果用户单击该复选框,则会切换,但如果在没有复选框的位置单击该行,则不会执行任何操作。我希望单击该行,然后相应地切换复选框。所以基本上我希望复选框是不可点击的,并且表格行在单击行时通知其中的复选框。

3 个答案:

答案 0 :(得分:2)

在基于单元格的表格视图中,您可以实施-selectionShouldChangeInTableView:。我认为这也适用于基于视图的表视图。

- (BOOL)selectionShouldChangeInTableView:(NSTableView *)tableView
{
    NSInteger clickedColumn = tableView.clickedColumn;
    NSInteger attributeEnabledColumn = [tableView columnWithIdentifier:@"attributeEnabled"];

    if (clickedColumn == attributeEnabledColumn) {
        NSInteger clickedRow = tableView.clickedRow;

        if (clickedRow >= 0) {
            NSArrayController  *attributesArrayController = self.attributesArrayController;
            NSMutableDictionary *attributeRow = [attributesArrayController.arrangedObjects objectAtIndex:clickedRow];
            BOOL attributeEnabled = [attributeRow[kAttributeSelectionAttributeEnabledKey] boolValue];

            attributeRow[kAttributeSelectionAttributeEnabledKey] = @(!attributeEnabled);
        }

        return NO;
    }

    return YES;
}

答案 1 :(得分:1)

到目前为止,您的方法很好,您可以点击表格行,这会切换复选框状态。

正如您所说,可以单击该复选框,而不会触发表格行选择。您需要子类化NSTableCellView并将此子类分配给单元格的类属性。在该自定义类文件中,您可以对复选框进行响应以切换并更改表的基础数据源。

import Cocoa

class MyTableCellView: NSTableCellView {
    @IBOutlet weak var checkbox: NSButton!  // the checkbox

    @IBAction func checkboxToggle(sender: AnyObject) {
        // modify the datasource here
    }
}

编辑:

以下是一个代码段,当用户点击表格单元格的任意位置时,会切换复选框:

func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
    if let cell = tableView.viewAtColumn(0, row: row, makeIfNecessary: false) as? MyTableCellView {
        cell.checkbox.state = cell.checkbox.state == NSOnState ? NSOffState : NSOnState
    }

    return false
}

请注意,它仍然需要表格单元格的子类,您可以在其中放置@IBOutlet复选框,以便在代码中访问它。

答案 2 :(得分:1)

Apple通过以下NSEvent类方法为您提供拦截多种NSEvent类型的机会:

enter image description here

每当type属性与您传入上述方法的第一个参数的掩码匹配的事件执行时,block(第二个参数)执行。这个区块让你有机会做很多事情:你可以进行额外的处理然后让事件像往常一样继续,你可以修改事件,或者你完全取消事件。至关重要的是,您在此块中放置的任何内容都会在任何其他事件处理之前发生

在下面的代码段中,只要点击一个复选框,传入的事件就会被修改,以使事件的行为与点击发生在复选框之外,但在复选框内部NSTableCellView superview。

func applicationDidFinishLaunching(aNotification: NSNotification) {

    NSEvent.addLocalMonitorForEventsMatchingMask(.LeftMouseDownMask,
        handler: { (theEvent) -> NSEvent! in

        var retval: NSEvent? = theEvent
        let winPoint = theEvent.locationInWindow

        // Did the mouse-down event take place on a row in the table?
        if let row = self.tableView.rowContainingWindowPoint(winPoint) {

            // Get the view on which the mouse down event took place
            let view = self.tableView.viewAtColumn(0,
                row: row,
                makeIfNecessary: true) as! NSTableCellView

            // In my demo-app the checkbox is the NSTableCellView's last subview
            var cb = view.subviews.last! as! NSButton
            let cbBoundsInWindowCoords = cb.convertRect(cb.bounds, toView: nil)

            // Did the click occur on the checkbox part of the view?
            if CGRectContainsPoint(cbBoundsInWindowCoords, theEvent.locationInWindow) {

                // Create a modified event, where the <location> property has been 
                // altered so that it looks like the click took place in the 
                // NSTableCellView itself.
                let newLocation = view.convertPoint(view.bounds.origin, toView: nil)
                retval = theEvent.cloneEventButUseAdjustedWindowLocation(newLocation)
            }
        }

        return retval

    })
}

func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool {

    if let view = self.tableView.viewAtColumn(0,
        row: row,
        makeIfNecessary: true) as? NSTableCellView {

        var checkbox = view.subviews.last! as! NSButton
        checkbox.state = (checkbox.state == NSOnState) ? NSOffState : NSOnState
    }
    return true
}

////////////////////////////////////////////////////////////////

extension NSTableView {

    // Get the row number (if any) that coincides with a specific
    // point - where the point is in window coordinates.
    func rowContainingWindowPoint(windowPoint: CGPoint) -> Int? {
        var rowNum: Int?
        var tableRectInWindowCoords = convertRect(bounds, toView: nil)
        if CGRectContainsPoint(tableRectInWindowCoords, windowPoint) {
            let tabPt = convertPoint(windowPoint, fromView: nil)
            let indexOfClickedRow = rowAtPoint(tabPt)
            if indexOfClickedRow > -1 && indexOfClickedRow < numberOfRows {
                rowNum = indexOfClickedRow
            }
        }
        return rowNum
    }
}

extension NSEvent {

    // Create an event based on another event. The created event is identical to the
    // original except for its <location> property.
    func cloneEventButUseAdjustedWindowLocation(windowLocation: CGPoint) -> NSEvent {
        return NSEvent.mouseEventWithType(type,
            location: windowLocation,
            modifierFlags: modifierFlags,
            timestamp: timestamp,
            windowNumber: windowNumber,
            context: context,
            eventNumber: eventNumber,
            clickCount: clickCount,
            pressure: pressure)!
    }
}