带有 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)!
}
}
我有一个 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)!
}
}