Objective c 如何防止对NSOutlineView的父节点进行排序?
我不想对NSOultineView的父节点进行排序 我的大纲视图的数据源是一个NSTreeController 单击列标题时,我只希望从层次结构的第二级对树及其子节点进行排序,并使父节点保持相同的顺序 更新 这就是我如何将列绑定到值并分配排序描述符的方法Objective c 如何防止对NSOutlineView的父节点进行排序?,objective-c,macos,cocoa,nsoutlineview,Objective C,Macos,Cocoa,Nsoutlineview,我不想对NSOultineView的父节点进行排序 我的大纲视图的数据源是一个NSTreeController 单击列标题时,我只希望从层次结构的第二级对树及其子节点进行排序,并使父节点保持相同的顺序 更新 这就是我如何将列绑定到值并分配排序描述符的方法 [newColumn bind:@"value" toObject:currentItemsArrayController withKeyPath:[NSString stringWithFormat:@"arrangedObjects
[newColumn bind:@"value" toObject:currentItemsArrayController withKeyPath:[NSString stringWithFormat:@"arrangedObjects.%@", metadata.columnBindingKeyPath] options:bindingOptions];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:metadata.columnSortKeyPath ascending:YES selector:metadata.columnSortSelector];
[newColumn setSortDescriptorPrototype:sortDescriptor];
您可以使用自定义比较器 为了演示此方法的基本思想,假设您使用
NSTreeNode
作为树节点类。所有根节点都存储在名为content
的NSArray
中,三个根节点分别是cat
、dog
和fish
:
NSTreeNode *cat = [NSTreeNode treeNodeWithRepresentedObject:@"cat"];
// ...the same for dog and fish
self.content = @[cat, dog, fish];
然后,创建您的NSSortDescriptor
原型。请注意,您应该使用self
作为键,而不是正在比较的字符串(在本例中为representedObject
)来访问原始节点对象。在比较器中,检查对象是否包含在根对象数组中。如果YES
,只需返回sensorderedname
,因为它将保持内容
数组中的初始顺序不变,否则使用比较:
方法进行标准比较
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES comparator:^NSComparisonResult(NSTreeNode *obj1, NSTreeNode *obj2) {
if ([self.content containsObject:obj1] && [self.content containsObject:obj2]) {
return NSOrderedSame;
}
return [obj1.representedObject compare:obj2.representedObject];
}];
[[outlineView.tableColumns firstObject] setSortDescriptorPrototype:sortDescriptor];
编辑2 如果有多个列需要单独排序,则不能使用
self
作为每一列的键,因为该键在所有列的sortDescriptorPrototype
s中应该是唯一的。在这种情况下,您可以创建一个自定义对象作为representedObject
,并将所有数据(包括指向对象中树节点的指针)包装回去
编辑1
上述方法的正确性要求NSOutlineView
使用稳定的排序算法对其行进行排序。也就是说,相同的项目在排序前后始终保持相同的相对顺序。然而,我在苹果的文档中找不到任何关于这里使用的排序算法稳定性的证据,尽管根据我的经验,上述方法实际上是可行的
如果感到不安全,可以根据当前顺序是升序还是降序显式比较根树节点。为此,您需要一个ivar
来保存当前订单。只需实现outlineView:sortDescriptorsDidChange:
delegate方法:
- (BOOL)outlineView:(NSOutlineView *)outlineView sortDescriptorsDidChange:(NSArray *)oldDescriptors {
ascending = ![oldDescriptors.firstObject ascending];
return YES;
}
并将返回指定名称
更改为:
if ([self.content containsObject:obj1] && [self.content containsObject:obj2]) {
NSUInteger index1 = [self.content indexOfObject:obj1];
NSUInteger index2 = [self.content indexOfObject:obj2];
if (ascending)
return (index1 < index2) ? NSOrderedAscending : NSOrderedDescending;
else
return (index1 < index2) ? NSOrderedDescending : NSOrderedAscending;
}
这样,当用户单击标题时,您也可以得到通知。之后,作为KVO流程的一部分,不要忘记实施以下观察方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"sortDescriptors"]) {
// Again, as a simple demonstration, the following line of code only deals with the first sort descriptor. You should modify it to suit your need.
ascending = [[change[NSKeyValueChangeNewKey] firstObject] ascending];
}
}
为了防止内存泄漏,您必须在解除分配
outlineView
之前(如果不是更早的话)删除此观察者。如果您不熟悉KVO,请务必检查。您是否尝试设置您单击的NSTableColumn实例的自定义sortDescriptorPrototype
?@Astoria是!我已经尝试过了,它对该列有效(使用来自父节点的数据)。但是子节点在其他列中有数据,应该可以通过单击此类列标题对子节点进行排序。但是,当单击这两列时,父节点顺序也会受到影响。请尝试为这两列设置sortDescriptorPrototype
。@Astoria否此方法无效。我有40个专栏。这些子项具有所有这些列的字段。而且它们必须是可排序的(为孩子们)。如果我只是删除排序,那么孩子们就不能再排序了。嘿,谢谢你,回答得很好。我刚刚阅读了您的编辑:不幸的是,我不能使用outlineView:sortDescriptorsDidChange:因为我没有使用数据源方法(而是以编程方式绑定值)。因此,如果我添加一个数据源,我必须覆盖所有必需的方法。(相反,我仍在研究比较器方法,请尽快回来)。@Patrick如果您无法实现outlineView:sortDescriptorsDidChange:
,您可以手动将观察者附加到outlineView
的sortDescriptors
数组:[outlineView addObserver:self forKeyPath:@“sortDescriptors”选项:NSKeyValueObservingOptionNew context:nil]代码>通过这种方式,当用户单击标题时,您也可以收到通知。是的,很酷。我现在使用的是观察者:但是,我如何在这一点上引入比较函数呢?请看我问题中的更新,我已经添加了代码。我有许多自定义列,我正在为它们绑定并设置不同的排序描述符。您必须确保所有列的排序描述符的键都不同,并且您可以通过使用的键路径访问根节点对象。请参阅我的更新。在我的回答中,我使用了一个比较器,它是一个块。如果您更喜欢自定义排序选择器,那就好了,因为它的工作原理与块几乎相同,只是您必须在比较对象的类中实现排序选择器。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"sortDescriptors"]) {
// Again, as a simple demonstration, the following line of code only deals with the first sort descriptor. You should modify it to suit your need.
ascending = [[change[NSKeyValueChangeNewKey] firstObject] ascending];
}
}