NSOutlineView:移除折叠三角形和缩进。

27

我正在为一个项目使用NSOutlineView,但有两个问题无法解决:

  • 如何删除树节点的展开三角形。像iTunes这样的应用程序似乎能够做到这一点:

alt text

是否有某种NSOutlineView Delegate方法用于此?还是需要对其进行子类化处理?

  • 如何禁用项目的缩进。我尝试使用setIndentationPerLevel:并将其设置为0,以及在Interface Builder中将列缩进设置为0,但似乎没有任何效果。
8个回答

45

你找对人了。就在一个星期前,我也曾经遇到过这个问题。

移除披露三角形:在你的NSOutlineView子类中实现frameOfOutlineCellAtRow:方法并返回NSZeroRect(当然只有你想要隐藏特定行的三角形时才需要这样做)。

- (NSRect)frameOfOutlineCellAtRow:(NSInteger)row {
    return NSZeroRect;
}

禁用缩进: 大纲视图的标准布局会在最左边预留空间来绘制三角形以便展开项使用。但是你可以通过指定不同的绘制框架来覆盖个别项上的这种行为。你可以通过响应此消息,在子类中实现该功能:

- (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row {
    NSRect superFrame = [super frameOfCellAtColumn:column row:row];


    if ((column == 0) /* && isGroupRow */) {
        return NSMakeRect(0, superFrame.origin.y, [self bounds].size.width, superFrame.size.height);
    }
    return superFrame;
}

2
您可以通过将indentationMarkerFollowsCell设置为false来影响缩进而无需子类化outlineView。 - pickwick

38

以后参考,隐藏可展开的NSOutlineView项中的披露三角形的最干净和最简单的方法是通过在代理中实现NSOutlineViewDelegate协议的outlineView:shouldShowOutlineCellForItem:方法:

-(BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item
{
    // replace this with your logic to determine whether the
    // disclosure triangle should be hidden for a particular item
    return [item hidesDisclosureTriangle];
}

4
当我尝试这样做时,我不能再(以编程方式)折叠这些项目。这是我的错误吗? - Max Seelemann
4
这个委托方法似乎并没有被用于基于视图的大纲视图。在这种情况下,我发现在IB中将缩进设置为0可以达到所需的效果。 - Ira Cooke

10

我不得不将上述两种方法结合起来,因为单独使用outlineView:shouldShowOutlineCellForItem:不能移除为揭示三角形所保留的空间(但它可以移除三角形本身)。

代理:

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item {
    return NO;
}

NSOutlineView的子类:

@implementation ExpandedOutlineView

#define kOutlineCellWidth 11
#define kOutlineMinLeftMargin 6

- (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row {
    NSRect superFrame = [super frameOfCellAtColumn:column row:row];
    if (column == 0) {
        // expand by kOutlineCellWidth to the left to cancel the indent
        CGFloat adjustment = kOutlineCellWidth;

        // ...but be extra defensive because we have no clue what is going on here
        if (superFrame.origin.x - adjustment < kOutlineMinLeftMargin) {
            NSLog(@"%@ adjustment amount is incorrect: adjustment = %f, superFrame = %@, kOutlineMinLeftMargin = %f", NSStringFromClass([self class]), (float)adjustment, NSStringFromRect(superFrame), (float)kOutlineMinLeftMargin);
            adjustment = MAX(0, superFrame.origin.x - kOutlineMinLeftMargin);
        }

        return NSMakeRect(superFrame.origin.x - adjustment, superFrame.origin.y, superFrame.size.width + adjustment, superFrame.size.height);
    }
    return superFrame;
}

@end

结果:

无顶级缩进的NSOutlineView截图


请注意,第0列可能不是大纲列。想象一下左侧的复选框以进行包含(最好的位置是它们都对齐)。最好询问大纲视图的-outlineTableColumn,而不是假设它是第0列。 - Joshua Nozzi
在这种情况下,为该列使用唯一标识符。 - Neelam Verma
我同意frameOfCellAtColumn:row:的观点: 但是,如果在outlineView:shouldShowOutlineCellForItem:中返回NO,则会防止折叠该项。 - Chrstpsln
1
只要 NSOutlineView 没有打开 usesAutomaticRowHeights,这个功能就可以很好地工作。 - Padraig

2
使用PXSourceList。它拥有你所寻找的样式和非常好的API。

谢谢Dave,它确实有一个非常好的API。我正在结合Marcel的提示使用它来去除缩进,以达到我想要的效果 :-) - indragie
如果您使用PXSourceList,您可以通过在要显示为该样式的组上返回YESPXSourceListDelegate消息-sourceList:isGroupAlwaysExpanded:来实现该样式。 - Alex Rozanski
非常好的参考资料。PXSourceList看起来像是一个巨大的时间节省器。谢谢,Dave,特别是Alex。 :-) - Joshua Nozzi

0

指定每个级别的意图的正确方法是通过覆盖 NSOutlineView 的 indentationPerLevel

class NoIndentOutlineView: NSOutlineView {
    override var indentationPerLevel: CGFloat {
        get {
            return 0;
        }
        set {
            // Do nothing
        }
    }
}

这将确保子元素与父元素具有相同的缩进。


0
以上是关于删除披露三角形及其占用空间的优秀答案。要实现“无展开列表且所有项目在同一级别”的效果,只需将若干个顶级项目放入用作数据源的数组中并实现即可。
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
    return false
}

0

这是关于隐藏披露按钮的问题。如果你想在可展开项上全局隐藏它,可以这样做:

extension NSOutlineView {
    override open func makeView(withIdentifier identifier: NSUserInterfaceItemIdentifier, owner: Any?) -> NSView? {
        if identifier == NSOutlineView.disclosureButtonIdentifier {
            return nil
        } else {
            return super.makeView(withIdentifier: identifier, owner: owner)
        }
    }
}

否则,您可以子类化NSOutlineView并覆盖makeView函数。

我知道这个问题是针对obj-c的。但我必须解决这个问题,我的项目是Swift。


-3

我在Swift中尝试过这个方法,它可以正常工作。但我不确定这是否是一种合适的方式。在Obj-C中应该有相应的方法:

extension NSTableRowView {
    override open func layout() {
        super.layout()
        if let cell = subviews.first as? NSTableCellView, cell.objectValue is <YourHeaderCellClass> {
            subviews.first?.setFrameOrigin(NSZeroPoint)
        }
    }
}

请确保在数据源方法中返回 YourHeaderCellClass 对象。
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any

1
很抱歉要给你的回答点个踩,但这种做法非常危险。你正在对子视图数组中的索引进行假设,这是一个超出你控制范围的实现细节。在所有 Cocoa 相关的事情中,最好不要与框架作对,而是首先搜索可以重写的委托方法(请参见上面的其他答案)。 - Martin Winter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接