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

NSTableView with NSCheckBox cells - how to intercept row selection?

我有一个 NSTableView 单元格包含 NSCheckBox。我想更改它的行为,以便可以单击 table 中的任何行,并且该行中的复选框相应地切换 on/off。

我想这可以用

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

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

编辑:

说明我需要什么:(view-based) table 单元格视图包含一个复选框。如果用户单击复选框,它会切换,但是当在没有复选框的任何地方单击该行时,什么也不会发生。我希望单击该行并相应地切换复选框。所以基本上我希望复选框是 non-clickable 并且 table 行在单击该行时通知其中的复选框。

到目前为止,您的方法没有问题,您可以单击 table 行,这会切换复选框状态。

如您所说,可以单独单击复选框,这不会触发 table 行选择。您需要 subclass NSTableCellView 并将此 subclass 分配给单元格的 class 属性。在该自定义 class 文件中,您可以对复选框切换做出反应并更改 table.

的基础数据源
import Cocoa

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

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

编辑:

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

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
}

请注意,它仍然需要 table 单元格的子 class,您将 @IBOutlet 放在复选框中,以便在代码中访问它。

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

- (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;
}

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

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

在下面的代码片段中,只要单击一个复选框,传入的事件就会被 篡改 以使事件的行为与单击发生在复选框之外但在复选框的 NSTableCellView 超级视图。


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)!
    }
}