使用复选标记构建NSOutline视图 [英] Building an NSOutline view with Check marks
问题描述
我正在寻找使用Apple推荐的正确方法将复选框添加到NSOutlineview-但是从文档中看不出来。
如何添加允许用户使用的行为,如果我单击父复选框,它将选择子项,而如果我单击它,它将取消选择子项
编辑:我简化了我的问题,并添加了图片以使其更清晰(希望如此)
我的方法:
我一直在使用Code Different的出色答案在我的Mac应用程序中构建大纲视图。
我的解决方案涉及创建一个数组,以将所选项目保存在viewcontroller,然后创建用于添加和删除的函数
var selectedItems:[Int]?
@objc func cellWasClicked(sender:NSButton){
let newCheckBoxState = sender.state
let tag = sender.tag
switch newCheckBoxState {
case NSControl.StateValue.on:
print( adding- \(sender.tag))
case NSControl.StateValue.off:
print( removing- \(sender.tag ))
默认值:
print( unhandled button state \(newCheckBoxState))
}
我通过分配给复选框的标签来识别复选框
为了将来的Google员工的利益,我将重复我在
- 选择第一列中的单元格并将其标识符更改为
isSelectedCell
- 选择第二列中的单元格并将其标识符更改为
titleCell
一致性在这里很重要。单元格的标识符应等于其列的标识符+ Cell
。
< h1>带有复选框的单元格
默认 NSTableCellView
包含不可编辑的文本字段。我们需要一个复选框,所以我们必须设计自己的单元格。
CheckboxCellView.swift
import可可
///`CheckboxCelView`用于将更改传达给另一个对象的一组方法
协议CheckboxCellViewDelegate {
func checkboxCellView(_ cell :CheckboxCellView,didChangeState状态:NSControl.StateValue)
}
类CheckboxCellView:NSTableCellView {
///复选框按钮
@IBOutlet弱变量checkboxButton:NSButton!
///在大纲视图中代表行的项目
///我们可能会将此单元格用于多个大纲视图,因此让我们将其设为通用
var项目:任何?
///单元格的委托
var委托:CheckboxCellViewDelegate?
覆盖函数awakeFromNib(){
checkboxButton.target = self
checkboxButton.action = #selector(self.didChangeState(_ :))
}
///通知委托人复选框的状态已更改
@objc私有函数didChangeState(_ sender:NSObject){
委托?.checkboxCellView(self,didChangeState:checkboxButton.state)
}
}
连接插座
- 删除
isSelected
列中的默认文本字段 - 将a对象库中的复选框
- 选择
NSTableCellView
并将其类更改为CheckboxCellView
- 打开助手编辑器并连接插座
视图控制器
最后是视图控制器的代码:
import可可
///在大纲视图中代表一行的类。为大纲视图中的列添加所需数量的属性
///。
class OutlineViewRow {
var title:字符串
var isSelected:Bool
var子级:[OutlineViewRow]
init(title:String,isSelected:Bool ,儿童:[OutlineViewRow] = []){
self.title =标题
self.isSelected = isSelected
self.children =儿童
}
func setIsSelected(_ isSelected:Bool,递归:Bool){
self.isSelected = isSelected
if递归{
self.children.forEach {$ 0.setIsSelected(isSelected,recursive:true) }
}
}
}
///一个枚举,表示大纲视图中的列列表。 Enum优于
///字符串文字,因为它们是在编译时检查的。在
///上重复相同的字符串,然后再重复一次是容易出错的。但是,您需要使用此处使用的原始值在
/// Interface Builder中创建列标识符。
枚举OutlineViewColumn:字符串{
case isSelected = isSelected
case title = title
init?(_ tableColumn:NSTableColumn){
self.init(rawValue:tableColumn.identifier.rawValue)
}
var cellIdentifier:NSUserInterfaceItemIdentifier {
return NSUserInterfaceItemIdentifier(self.rawValue + Cell)
}
}
类ViewController:NSViewController {
@IBOutlet弱var outlineView:NSOutlineView!
///大纲视图的行
让行:[OutlineViewRow] = {
var child1 = OutlineViewRow(title: p1-child1,isSelected:true)
var child2 = OutlineViewRow(title: p1-child2,isSelected:true)
var child3 = OutlineViewRow(title: p1-child3,isSelected:true)
let parent1 = OutlineViewRow (标题: parent1,isSelected:真,子代:[child1,child2,child3])
child1 = OutlineViewRow(标题: p2-child1,isSelected:真)
child2 = OutlineViewRow(标题: p2-child2,isSelected:是)
child3 = OutlineViewRow(标题: p2-child3,isSelected:是)
let parent2 = OutlineViewRow(title: parent2, isSelected:true,子代:[child1,child2,child3])
child1 = OutlineViewRow(title: p3-child1,isSelected:true)
child2 = OutlineViewRow(title: p3 -child2,isSelected:true)
child3 = OutlineViewRow(title: p3-child3,isS当选:true)
let parent3 = OutlineViewRow(title: parent3,isSelected:true,子代:[child1,child2,child3])
child3 = OutlineViewRow(title: p4- child3,isSelected:true)
child2 = OutlineViewRow(标题: p4-child2,isSelected:true,孩子:[child3])
child1 = OutlineViewRow(标题: p4-child1,isSelected :true,子代:[child2])
let parent4 = OutlineViewRow(title: parent4,isSelected:true,子代:[child1])$ b
$ b return [parent1,parent2,parent3, parent4]
}()
覆盖func viewDidLoad(){
super.viewDidLoad()
outlineView.dataSource = self
outlineView.delegate = self
}
}
扩展ViewController:NSOutlineViewDataSource,NSOutlineViewDelegate {
///返回一行有多少个孩子。 item == nil表示根行(不可见)
func outlineView(_ outlineView:NSOutlineView,numberOfChildrenOfItem项:Any?)-> Int {
切换项{
case nil:返回rows.count
case让行作为OutlineViewRow:return row.children.count
默认值:return 0
}
}
///返回表示行的对象。 NSOutlineView将其称为 item
func outlineView(_ outlineView:NSOutlineView,子索引:Int,ofItem项目:Any?)->任何{
切换项{
case nil:返回rows [index]
case让行作为OutlineViewRow:return row.children [index]
默认值:return NSNull()
}
}
///返回是否可以扩展该行
func outlineView(_ outlineView:NSOutlineView,isItemExpandable item:Any)->布尔{{
切换项{
case nil:return!rows.isEmpty
case let row as OutlineViewRow:return!row.children.isEmpty
默认值:返回false
}
}
///返回显示大纲视图每个单元格内容的视图
func outlineView(_ outlineView:NSOutlineView,viewFor tableColumn:NSTableColumn ?, item :任何)-> NSView? {
后卫让item = item as? OutlineViewRow,让column = OutlineViewColumn(tableColumn!)否则{return nil}
切换列{
case .isSelected:
let cell = outlineView.makeView(withIdentifier:column.cellIdentifier ,店主:自我)为! CheckboxCellView
cell.checkboxButton.state = item.isSelected? .on:.off
cell.delegate =自我
cell.item =项目
返回单元格
case .title:
let cell = outlineView。 makeView(withIdentifier:column.cellIdentifier,owner:self)为! NSTableCellView
cell.textField?.stringValue = item.title
返回单元格
}
}
}
扩展ViewController:CheckboxCellViewDelegate {
///一个委托函数,我们可以从已选中列中的复选框进行更新
func checkboxCellView(_单元格:CheckboxCellView,didChangeState状态:NSControl.StateValue){
保护让item = cell.item为? OutlineViewRow else {return}
//如果state == .on
item.setIsSelected(state == .on,递归:true)
则选择该行及其子级
//比在每个孩子上调用reload更为有效,因为折叠的孩子是
//不重新加载。当它们变得可见时,将重新加载它们
outlineView.reloadItem(item,reloadChildren:true)
}
}
结果
I am looking to add checkboxes to NSOutlineview using the correct Apple recommended method - however its not clear from the documentation.
How do I add the behavour to allow users whereby if I click a parent checkbox, then it will select the children, and if I unclick it - it will deselect the children of that item?
edit: I have simplified my question and added image to make it clearer ( hopefully)
My Approach: I have been using the wonderful answer by Code Different to build an Outline view in my mac app. https://stackoverflow.com/a/45384599/559760 - I chose to populate the NSoutLine view using a manual process instead of using CocoaBindings.
I added in a stack view including a check box which seems to be the right approach:
My solution involves creating an array to hold the selected items in the viewcontroller and then creating functions for adding and removing
var selectedItems: [Int]?
@objc func cellWasClicked(sender: NSButton) {
let newCheckBoxState = sender.state
let tag = sender.tag
switch newCheckBoxState {
case NSControl.StateValue.on:
print("adding- \(sender.tag)")
case NSControl.StateValue.off:
print("removing- \(sender.tag)")
default:
print("unhandled button state \(newCheckBoxState)")
}
I identify the checkbutton by the tag that was assigned to the checkbox
In the interest of future Googlers I will repeat things I've written in my other answer. The difference here is this has the extra requirement that a column is editable and I have refined the technique.
The key to NSOutlineView
is that you must give an identifier to each row, be it a string, a number or an object that uniquely identifies the row. NSOutlineView
calls this the item
. Based on this item
, you will query your data model to populate the outline.
In this answer we will setup an outline view with 2 columns: an editable Is Selected column and a non-editable Title column.
Interface Builder setup
- Select the first column and set its identifier to
isSelected
- Select the second column and set its identifier to
title
- Select the cell in the first column and change its identifier to
isSelectedCell
- Select the cell in the second column and change its identifier to
titleCell
Consistency is important here. The cell's identifier should be equal to its column's identifier + Cell
.
The cell with a checkbox
The default NSTableCellView
contains a non-editable text field. We want a check box so we have to design our own cell.
CheckboxCellView.swift
import Cocoa
/// A set of methods that `CheckboxCelView` use to communicate changes to another object
protocol CheckboxCellViewDelegate {
func checkboxCellView(_ cell: CheckboxCellView, didChangeState state: NSControl.StateValue)
}
class CheckboxCellView: NSTableCellView {
/// The checkbox button
@IBOutlet weak var checkboxButton: NSButton!
/// The item that represent the row in the outline view
/// We may potentially use this cell for multiple outline views so let's make it generic
var item: Any?
/// The delegate of the cell
var delegate: CheckboxCellViewDelegate?
override func awakeFromNib() {
checkboxButton.target = self
checkboxButton.action = #selector(self.didChangeState(_:))
}
/// Notify the delegate that the checkbox's state has changed
@objc private func didChangeState(_ sender: NSObject) {
delegate?.checkboxCellView(self, didChangeState: checkboxButton.state)
}
}
Connecting the outlet
- Delete the default text field in the
isSelected
column - Drag in a checkbox from Object Library
- Select the
NSTableCellView
and change its class toCheckboxCellView
- Turn on the Assistant Editor and connect the outlet
The View Controller
And finally the code for the view controller:
import Cocoa
/// A class that represents a row in the outline view. Add as many properties as needed
/// for the columns in your outline view.
class OutlineViewRow {
var title: String
var isSelected: Bool
var children: [OutlineViewRow]
init(title: String, isSelected: Bool, children: [OutlineViewRow] = []) {
self.title = title
self.isSelected = isSelected
self.children = children
}
func setIsSelected(_ isSelected: Bool, recursive: Bool) {
self.isSelected = isSelected
if recursive {
self.children.forEach { $0.setIsSelected(isSelected, recursive: true) }
}
}
}
/// A enum that represents the list of columns in the outline view. Enum is preferred over
/// string literals as they are checked at compile-time. Repeating the same strings over
/// and over again are error-prone. However, you need to make the Column Identifier in
/// Interface Builder with the raw value used here.
enum OutlineViewColumn: String {
case isSelected = "isSelected"
case title = "title"
init?(_ tableColumn: NSTableColumn) {
self.init(rawValue: tableColumn.identifier.rawValue)
}
var cellIdentifier: NSUserInterfaceItemIdentifier {
return NSUserInterfaceItemIdentifier(self.rawValue + "Cell")
}
}
class ViewController: NSViewController {
@IBOutlet weak var outlineView: NSOutlineView!
/// The rows of the outline view
let rows: [OutlineViewRow] = {
var child1 = OutlineViewRow(title: "p1-child1", isSelected: true)
var child2 = OutlineViewRow(title: "p1-child2", isSelected: true)
var child3 = OutlineViewRow(title: "p1-child3", isSelected: true)
let parent1 = OutlineViewRow(title: "parent1", isSelected: true, children: [child1, child2, child3])
child1 = OutlineViewRow(title: "p2-child1", isSelected: true)
child2 = OutlineViewRow(title: "p2-child2", isSelected: true)
child3 = OutlineViewRow(title: "p2-child3", isSelected: true)
let parent2 = OutlineViewRow(title: "parent2", isSelected: true, children: [child1, child2, child3])
child1 = OutlineViewRow(title: "p3-child1", isSelected: true)
child2 = OutlineViewRow(title: "p3-child2", isSelected: true)
child3 = OutlineViewRow(title: "p3-child3", isSelected: true)
let parent3 = OutlineViewRow(title: "parent3", isSelected: true, children: [child1, child2, child3])
child3 = OutlineViewRow(title: "p4-child3", isSelected: true)
child2 = OutlineViewRow(title: "p4-child2", isSelected: true, children: [child3])
child1 = OutlineViewRow(title: "p4-child1", isSelected: true, children: [child2])
let parent4 = OutlineViewRow(title: "parent4", isSelected: true, children: [child1])
return [parent1, parent2, parent3, parent4]
}()
override func viewDidLoad() {
super.viewDidLoad()
outlineView.dataSource = self
outlineView.delegate = self
}
}
extension ViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
/// Returns how many children a row has. `item == nil` means the root row (not visible)
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
switch item {
case nil: return rows.count
case let row as OutlineViewRow: return row.children.count
default: return 0
}
}
/// Returns the object that represents the row. `NSOutlineView` calls this the `item`
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
switch item {
case nil: return rows[index]
case let row as OutlineViewRow: return row.children[index]
default: return NSNull()
}
}
/// Returns whether the row can be expanded
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
switch item {
case nil: return !rows.isEmpty
case let row as OutlineViewRow: return !row.children.isEmpty
default: return false
}
}
/// Returns the view that display the content for each cell of the outline view
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
guard let item = item as? OutlineViewRow, let column = OutlineViewColumn(tableColumn!) else { return nil }
switch column {
case .isSelected:
let cell = outlineView.makeView(withIdentifier: column.cellIdentifier, owner: self) as! CheckboxCellView
cell.checkboxButton.state = item.isSelected ? .on : .off
cell.delegate = self
cell.item = item
return cell
case .title:
let cell = outlineView.makeView(withIdentifier: column.cellIdentifier, owner: self) as! NSTableCellView
cell.textField?.stringValue = item.title
return cell
}
}
}
extension ViewController: CheckboxCellViewDelegate {
/// A delegate function where we can act on update from the checkbox in the "Is Selected" column
func checkboxCellView(_ cell: CheckboxCellView, didChangeState state: NSControl.StateValue) {
guard let item = cell.item as? OutlineViewRow else { return }
// The row and its children are selected if state == .on
item.setIsSelected(state == .on, recursive: true)
// This is more efficient than calling reload on every child since collapsed children are
// not reloaded. They will be reloaded when they become visible
outlineView.reloadItem(item, reloadChildren: true)
}
}
Result
这篇关于使用复选标记构建NSOutline视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!