使用复选标记构建NSOutline视图 [英] Building an NSOutline view with Check marks

查看:127
本文介绍了使用复选标记构建NSOutline视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找使用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 to CheckboxCellView
  • 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆