【问题标题】:NSOutlineView detect when children are collapsedNSOutlineView 检测子项何时折叠
【发布时间】:2019-08-06 15:37:51
【问题描述】:

我正在尝试将项目的实际折叠/展开状态存储在 NSOutlineView 中,以便以后可以恢复。 NSOutlineViewDelegate上有两种方法可用:

  • outlineViewItemDidExpand(Notification)
  • outlineViewItemDidCollapse(Notification)

问题在于,这些方法不仅适用于用户单击的项目,还适用于可折叠的子项。示例:

- a
-- b
--- c

a 被折叠时,outlineViewItemDidCollapse 被调用两次,一次用于b,一次用于a。将两者都标记为 collapsed 是不正确的,因为 b 仍应展开,c 应在再次展开 a 后可见。所以b 的实际状态应该是expanded

当用户按住 Option 键单击 a 时,所有子项也会折叠 (outlineView.collapseItem(item, collapseChildren: true))。再次展开a 后,b 应该保持折叠状态。在这种情况下,b 的状态应该是collapsed

两种不同的状态:

  • a折叠b展开(但由于父级而隐藏)
  • a: collapsed, b: collapsed(由于父级而隐藏)

有什么方法可以区分这两个动作/状态,以便我以后可以正确恢复它?

【问题讨论】:

标签: cocoa nsoutlineview


【解决方案1】:

一些想法:

NSOutlineView可以保存和恢复展开的项目(autosaveExpandedItems)。可以从NSUserDefaults 检索设置。密钥是NSOutlineView Items <autosaveName>

子类NSOutlineView 并覆盖expandItem(_:expandChildren:)collapseItem(_:collapseChildren:)。不会为孩子调用这些方法。

使用outlineViewItemWillExpand(_:)outlineViewItemWillCollapse(_:) 中的当前事件可能会确定哪个项目是展开或折叠的。

【讨论】:

  • 子类化 NSOutlineView 看起来不错,感谢您的建议!
【解决方案2】:

已编辑以给出修改后的答案...

显然,一旦给定容器的父对象被折叠,就没有简单的方法来恢复给定容器是展开还是折叠。很明显,大纲视图的内部工作方式会记住一些东西——可能就像存储单元格视图的显示按钮单元格的状态一样简单,或者它可能在树控制器或其节点中设置一个标志——但无论如何都没有直接编程接口。我怀疑您必须在模型对象中跟踪它。

为此,向模型项添加一个布尔属性,例如:

@property BOOL currentlyExpanded;

然后您需要实现两个委托方法outlineViewItemDidExpand:outlineViewItemWillCollapse:,就像这样(假设您使用树形控制器作为大纲视图):

- (void)outlineViewItemDidExpand:(NSNotification *)notification {
    NSTreeNode * node = [notification.userInfo objectForKey:@"NSObject"];
    NSOutlineView * ov = notification.object;
    MyModelItem * item = [node representedObject];

    /*
      because we can only expand a visible container, we merely note
      that this container is now expanded in our view. This will be 
      called for every container that is expanded, so we don't have to 
      think about it much.
    */
    item.currentlyExpanded = YES;
}

- (void)outlineViewItemWillCollapse:(NSNotification *)notification {
    NSTreeNode * node = [notification.userInfo objectForKey:@"NSObject"];
    NSOutlineView * ov = notification.object;
    MyModelItem * item = [node representedObject];

    /* 
      Elements are collapsed from top to bottom. A collapsed parent 
      means the collapse started someplace farther up the chain than 
      our current item, so the expansion state of the current item is 
      not going to change unless the option key is held down, or you 
      implement a collapseItem:collapseChildren: with the second 
      parameter as YES. This accounts for the first; you'll have to 
      deal with the second in code.
    */

    BOOL optionKeyIsDown = [[NSApp currentEvent] modifierFlags] && NSEventModifierFlagOption;

    if ([ov isItemExpanded:[node parentNode]] || optionKeyIsDown) {
        item.currentlyExpanded = NO;
    }   
}

这些应该使模型项属性currentlyExpanded 与大纲视图的内部扩展表(无论是什么)同步。如果您想引用它或将其存储在数据库中,您可以直接从模型对象中访问它。

我处理位掩码的方式引发了警告,但我懒得修复它...

在编辑后保留最后一部分,因为我认为这是很好的信息...

通常您不必担心这些; NSOutlineView 会自行“做正确的事”。如果用户点击了一个容器的展开三角形然后重新打开它,所有的子容器都将保持它们的展开/折叠状态;如果用户选项单击控制三角形,所有子容器将被标记为展开或折叠(取决于用户是选项打开还是选项关闭父容器)。除非您想要一些特殊的行为(通常会在委托方法 outlineView:shouldCollapseItem:outlineView:shouldExpandItem: 中设置),否则不要打扰它。

如果您尝试跨应用调用保留展开状态,请将 NSOutlineView 属性 autosaveExpandedItems 设置为 true。无需记账...

【讨论】:

  • isItemExpanded 不幸的是,所有嵌套子级在其父级折叠时都是false,即使子级实际上在幕后展开。 shouldCollapseItem/shouldExpandItem 在父级折叠/展开时也会为所有子级调用。 autosaveExpandedItems 通常确实是解决方案,除非您想管理它的存储位置(例如数据库)或需要额外的簿记逻辑。有什么方法可以提取autosaveExpandedItems存储的状态吗?
  • 关于isItemExpanded: 的那一点让我感到惊讶;这是不直观的。这就是我假设事情会按照我认为的方式运作的结果。让我玩一下,看看我想出了什么
  • Apple 出于某种原因认为hidden == collapsed。这里有一个你可以玩的快速要点:gist 只需创建一个新项目并连接来自 IB 的视图/窗口出口
  • @floorish - 好的,我玩了一下,我知道你可能需要做什么。我会重写我的答案来解释。我不太确定如何使用您提到的“隐藏 == 折叠”的东西。虽然我认为这是真的,而且 NSTableView 有一种检索隐藏行的方法,但我看不出它有什么帮助。但我会在编辑之前考虑一下。
  • @floorish - 好的,但请注意文档建议不要尝试继承 NSOutlineView。
猜你喜欢
  • 2013-06-15
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-15
  • 2011-08-04
  • 1970-01-01
相关资源
最近更新 更多