我有一个基于视图的NSTableView,带有自定义NSTableCellView和自定义NSTableRowView。我自定义了这两个类,因为我想改变每一行的外观。通过实现[NSTableRowView draw ...]方法,我可以更改背景,选择,分隔符和拖动目标高亮。
我的问题是:如何更改右键单击行并出现菜单时出现的突出显示?
例如,这是常态:
我想将方形高光改为圆形,如下所示:
我想这可以通过调用drawMenuHighlightInRect:或类似的方法在NSTableRowView中完成,但我找不到它。另外,如果我在我的子类中定制了所有绘图方法,并且我不调用超类,那么NSTableRowView类如何才能这样做呢?这是由表本身绘制的吗?
经过一些实验后,我发现可以通过将tableview设置为源列表来实现圆形突出显示。尽管如此,我想知道如何在可能的情况下自定义它。
答案 0 :(得分:9)
我知道我为OP提供任何帮助有点晚了,但希望这可以让其他人节省一点时间。我将NSTableRowView
子类化以实现右键单击上下文菜单突出显示(为什么Apple没有公共绘图方法来覆盖它超出我的范围)。这是它的全部荣耀:
<强> BSDSourceListRowView.h 强>
#import <Cocoa/Cocoa.h>
@interface BSDSourceListRowView : NSTableRowView
// This needs to be set when a context menu is shown.
@property (nonatomic, assign, getter = isShowingMenu) BOOL showingMenu;
@end
<强> BSDSourceListRowView.m 强>
#import "BSDSourceListRowView.h"
@implementation BSDSourceListRowView
- (void)drawBackgroundInRect:(NSRect)dirtyRect
{
[super drawBackgroundInRect:dirtyRect];
// Context menu highlight:
if ( self.isShowingMenu ) {
[self drawContextMenuHighlight];
}
}
- (void)drawContextMenuHighlight
{
BOOL selected = self.isSelected;
CGFloat insetY = ( selected ) ? 2.f : 1.f;
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(self.bounds, 2.f, insetY) xRadius:6.f yRadius:6.f];
NSColor *fillColor, *strokeColor;
if ( selected ) {
fillColor = [NSColor clearColor];
strokeColor = [NSColor whiteColor];
} else {
fillColor = [NSColor colorWithCalibratedRed:95.f/255.f green:159.f/255.f blue:1.f alpha:0.12f];
strokeColor = [NSColor alternateSelectedControlColor];
}
[fillColor setFill];
[strokeColor setStroke];
[path setLineWidth:2.f];
[path fill];
[path stroke];
}
- (void)drawSelectionInRect:(NSRect)dirtyRect
{
[super drawSelectionInRect:dirtyRect];
if ( self.isShowingMenu ) {
[self drawContextMenuHighlight];
}
}
- (void)setShowingMenu:(BOOL)showingMenu
{
if ( showingMenu == _showingMenu )
return;
_showingMenu = showingMenu;
[self setNeedsDisplay:YES];
}
@end
随意使用它,更改任何一个,或做任何你想做的任何事情。玩得开心!
针对Swift 3.x进行了更新:
<强> SourceListRowView.swift 强>
import Cocoa
open class SourceListRowView : NSTableRowView {
open var isShowingMenu: Bool = false {
didSet {
if isShowingMenu != oldValue {
needsDisplay = true
}
}
}
override open func drawBackground(in dirtyRect: NSRect) {
super.drawBackground(in: dirtyRect)
if isShowingMenu {
drawContextMenuHighlight()
}
}
override open func drawSelection(in dirtyRect: NSRect) {
super.drawSelection(in: dirtyRect)
if isShowingMenu {
drawContextMenuHighlight()
}
}
private func drawContextMenuHighlight() {
let insetY: CGFloat = isSelected ? 2 : 1
let path = NSBezierPath(roundedRect: bounds.insetBy(dx: 2, dy: insetY), xRadius: 6, yRadius: 6)
let fillColor, strokeColor: NSColor
if isSelected {
fillColor = .clear
strokeColor = .white
} else {
fillColor = NSColor(calibratedRed: 95/255, green: 159/255, blue: 1, alpha: 0.12)
strokeColor = .alternateSelectedControlColor
}
fillColor.setFill()
strokeColor.setStroke()
path.lineWidth = 2
path.fill()
path.stroke()
}
}
注意:我实际上并没有运行它,但我很确定这应该可以在Swift中实现。
答案 1 :(得分:1)
这已经有点老了,但是我已经浪费了很多时间,因此发布我的解决方案以防它对任何人有帮助:
A。子类NSTableView: 覆盖右键单击事件,计算单击位置以获得正确的行,并将其传输到我的自定义NSTableRowView!
class TableView: NSTableView {
override func rightMouseDown(with event: NSEvent) {
let location = event.locationInWindow
let toMyOrigin = self.superview?.convert(location, from: nil)
let rowIndex = self.row(at: toMyOrigin!)
if (rowIndex < 0 || self.numberOfRows < rowIndex) {
return
}
if let isRowExists = self.rowView(atRow: rowIndex, makeIfNecessary: false) {
if let isMyTypeRow = isRowExists as? MyNSTableRowView {
isMyTypeRow.costumRightMouseDown(with: event)
}
}
}
}
B。子类MyNSTableRowView 以编程方式呈现NSMenu
class MyNSTableRowView: NSTableRowView {
//My custom selection colors, don't have to implement this if you are ok with the default system highlighted background color
override func drawSelection(in dirtyRect: NSRect) {
if self.selectionHighlightStyle != .none {
let selectionRect = NSInsetRect(self.bounds, 0, 0)
Colors.tabSelectedBackground.setStroke()
Colors.tabSelectedBackground.setFill()
let selectionPath = NSBezierPath.init(roundedRect: selectionRect, xRadius: 0, yRadius: 0)
selectionPath.fill()
selectionPath.stroke()
}
}
func costumRightMouseDown(with event: NSEvent) {
let menu = NSMenu.init(title: "Actions:")
menu.addItem(NSMenuItem.init(title: "Some", action: #selector(foo), keyEquivalent: "a"))
NSMenu.popUpContextMenu(menu, with: event, for: self)
}
@objc func foo() {
}
}
答案 2 :(得分:0)
我同意MCMatan的观点,您不能通过更改任何抽签来对其进行调整。该框将保留。
我采用了绕过默认菜单启动的方法,但是将上下文菜单设置保留为NSTableView中的默认菜单。我认为这是一种更简单的方法。
我从NSTableView派生并添加以下内容:
public private(set) var rightClickedRow: Int = -1
override func rightMouseDown(with event: NSEvent)
{
guard let menu = self.menu else { return }
let windowClickLocation = event.locationInWindow
let outlineClickLocation = convert(windowClickLocation, from: nil)
rightClickedRow = row(at: outlineClickLocation)
menu.popUp(positioning: nil, at: outlineClickLocation, in: self)
}
override func rightMouseUp(with event: NSEvent) {
rightClickedRow = -1
}
我的rightClickedRow
与表视图的clickedRow
类似。我有一个NSViewController来照顾我的表,并将其设置为表的菜单委托。我可以在委托调用中使用rightClickedRow,例如menuNeedsUpdate()
。
答案 3 :(得分:0)
几个答案描述了如何绘制自定义上下文单击突出显示。但是,AppKit将继续绘制默认值。有一个简单的技巧可以阻止这种情况,我不希望它在注释中迷路:子类NSTableView
并覆盖-menuForEvent:
// NSTableView subclass
override func menu(for event: NSEvent) -> NSMenu?
{
// DO NOT call super's implementation.
return self.menu
}
在这里,我假设您已经在IB中为tableView分配了一个菜单,或者已通过编程方式设置了tableView的menu属性。 NSTableView
的默认实现-menuForEvent:
是上下文菜单突出显示的地方。
现在,我们不调用super的menuForEvent:
实现,右键单击时,tableView的clickedRow
属性将始终为-1
,这意味着我们的menuItems不会定位到tableView的正确行。
但是不用担心,我们可以为他们完成Apple Engineering的工作。在我们的自定义NSTableView
子类上,我们覆盖clickedRow
属性:
class MyTableView: NSTableView
{
private var _clickedRow: Int = -1
override var clickedRow: Int {
get { return _clickedRow }
set { _clickedRow = newValue }
}
}
现在,我们更新-menuForEvent:
方法:
// NSTableView subclass
override func menu(for event: NSEvent) -> NSMenu?
{
let location: CGPoint = convert(event.locationInWindow, from: nil)
clickedRow = row(at: location)
return self.menu
}
太好了。我们解决了这个问题。转到下一件事情:
正如其他人所建议的,将自定义Bool
属性添加到您的NSTableRowView
子类中。然后,在您的绘图代码中,检查该值,以决定是否绘制自定义上下文高光。但是,使用相同的NSTableView
方法设置该值的正确位置:
// NSTableView subclass
override func menu(for event: NSEvent) -> NSMenu?
{
let location: CGPoint = convert(event.locationInWindow, from: nil)
clickedRow = row(at: location)
if clickedRow > 0,
let rowView: MyCustomRowView = rowView(atRow: tableRow, makeIfNecessary: false) as? MyCustomRowView
{
rowView.isContextualMenuTarget = true
}
return self.menu
}
上面,我创建了MyCustomRowView
(NSTableRowView
的子类)并添加了一个自定义属性:isContextualMenuTarget
。该自定义属性如下所示:
// NSTableRowView subclass
var isContextualMenuTarget: Bool = false {
didSet {
needsDisplay = true
}
}
在绘制方法中,我检查该属性的值,如果为true,则绘制我的自定义突出显示。
您有一个控制器,用于为tableView实现数据源和委托方法。该控制器也可能是tableView菜单的委托。 (您可以在IB中或以编程方式进行分配。)
无论菜单对象是什么对象,都应实现menuDidClose:
方法。在这里,我在Objective-C中工作,因为我的控制器仍然是ObjC:
// NSMenuDelegate object
- (void) menuDidClose:(NSMenu *)menu
{
// We use a custom flag on our rowViews to draw our own contextual menu highlight, so we need to reset that.
[_outlineView enumerateAvailableRowViewsUsingBlock:^(__kindof MyCustomRowView * _Nonnull rowView, NSInteger row) {
rowView.isContextualMenuTarget = NO;
}];
}
性能说明:我的tableView的条目永远不会超过50个。如果您的表中有数千行可见行,则最好保存设置为isContextualMenuTarget=true
的rowView,然后直接在-menuDidClose:
中访问该rowView,这样就不必枚举所有rowViews。
单列:此示例假定单列tableView的每一行都具有相同的NSMenu。您可以针对多行和/或每行不同的NSMenu调整相同的技术。
这就是您如何击败AppKit直到达到您想要的效果。
答案 4 :(得分:-1)
我会看一下NSTableRowView
documentation。它是负责在基于视图的NSTableView
中绘制选择和拖动反馈的类。