Ale*_*lex 7 cocoa contextmenu objective-c highlight nstableview
我有一个基于视图的NSTableView与自定义NSTableCellView和自定义NSTableRowView.我自定义了这两个类,因为我想改变每一行的外观.通过实现[NSTableRowView draw ...]方法,我可以更改背景,选择,分隔符和拖动目标高亮显示.
我的问题是:如何更改右键单击行并出现菜单时出现的突出显示?
例如,这是常态:

我想将方形高光改为圆形,如下所示:

我想这可以通过调用drawMenuHighlightInRect:或类似的方法在NSTableRowView中完成,但我找不到它.另外,如果我在我的子类中定制了所有绘图方法,并且我不调用超类,那么NSTableRowView类如何才能这样做呢?这是由表本身绘制的吗?
经过一些实验,我发现可以通过将tableview设置为源列表来实现圆形突出显示.尽管如此,我想知道如何在可能的情况下自定义它.
我知道我为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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
随意使用它,更改任何一个,或做任何你想做的任何事情.玩得开心!
针对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()
}
}
Run Code Online (Sandbox Code Playgroud)
注意:我实际上没有运行它,但我很确定这应该在Swift中完成.
有几个答案描述了如何绘制自定义上下文单击突出显示。然而,AppKit 将继续绘制默认的一个。有一个简单的技巧可以阻止这种情况,我不想让它在评论中丢失:子类NSTableView和覆盖-menuForEvent:
// NSTableView subclass
override func menu(for event: NSEvent) -> NSMenu?
{
// DO NOT call super's implementation.
return self.menu
}
Run Code Online (Sandbox Code Playgroud)
在这里,我假设您已经在 IB 中为 tableView 分配了一个菜单,或者已经以编程方式设置了 tableView 的菜单属性。NSTableView的默认实现-menuForEvent:是绘制上下文菜单突出显示的内容。
现在我们没有调用 super 的实现menuForEvent:,当我们右键单击时,clickedRow我们的 tableView 的属性将始终是-1,这意味着我们的 menuItems 将不会定位 tableView 的正确行。
但不用担心,我们可以为他们完成 Apple 工程部的工作。在我们的自定义NSTableView子类中,我们重写该clickedRow属性:
class MyTableView: NSTableView
{
private var _clickedRow: Int = -1
override var clickedRow: Int {
get { return _clickedRow }
set { _clickedRow = newValue }
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们更新-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
}
Run Code Online (Sandbox Code Playgroud)
伟大的。我们解决了这个问题。继续下一件事:
正如其他人所建议的,将自定义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
}
Run Code Online (Sandbox Code Playgroud)
上面,我创建了MyCustomRowView( 的子类NSTableRowView)并添加了一个自定义属性:isContextualMenuTarget。该自定义属性如下所示:
// NSTableRowView subclass
var isContextualMenuTarget: Bool = false {
didSet {
needsDisplay = true
}
}
Run Code Online (Sandbox Code Playgroud)
在我的绘图方法中,我检查该属性的值,如果为真,则绘制我的自定义突出显示。
您有一个为 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;
}];
}
Run Code Online (Sandbox Code Playgroud)
性能说明:我的 tableView 的条目永远不会超过 50 个。如果您有一个包含数千个可见行的表,那么最好保存您设置的 rowView isContextualMenuTarget=true,然后直接访问该 rowView,-menuDidClose:这样您就不必枚举所有 rowView。
Single-Column:此示例假设单列 tableView 的每一行都有相同的 NSMenu。您可以将相同的技术应用于多列和/或每行不同的 NSMenu。
这就是你如何打败 AppKit,直到它满足你的要求。
| 归档时间: |
|
| 查看次数: |
2502 次 |
| 最近记录: |