2017-01-04 10 views
5

Я пытаюсь (неудачно) построить NSOutlineView с управлением TreeController. Я прошел кучу учебников, но все они предварительно загружают данные перед началом чего-либо, и это не сработает для меня.Использование NSTreeController с NSOutlineView

У меня есть простой класс для устройства:

import Cocoa 
class Device: NSObject { 
    let name : String 
    var children = [Service]() 
    var serviceNo = 1 
    var count = 0 

    init(name: String){ 
     self.name = name 
    } 

    func addService(serviceName: String){ 
     let serv = "\(serviceName) # \(serviceNo)" 
     children.append(Service(name: serv)) 
     serviceNo += 1 
     count = children.count 
    } 

    func isLeaf() -> Bool { 
     return children.count < 1 
    } 
} 

У меня также есть еще более простой класс для 'Сервис':

import Cocoa 
class Service: NSObject { 

    let name: String 

    init(name: String){ 
     self.name = name 
    } 
} 

Наконец, у меня есть ViewController:

class ViewController: NSViewController { 

    var stepper = 0 

    dynamic var devices = [Device]() 
    override func viewDidLoad() { 
     super.viewDidLoad() 
    } 

    override var representedObject: Any? { 
     didSet { 
     // Update the view, if already loaded. 
     } 
    } 

    @IBAction func addDeviceAction(_ sender: Any) { 
     let str = "New Device #\(stepper)" 
     devices.append(Device(name: str)) 
     stepper += 1 
     print("Added Device: \(devices[devices.count-1].name)")  
    } 

    @IBAction func addService(_ sender: Any) { 
     for i in 0..<devices.count { 
      devices[i].addService(serviceName: "New Service") 
     } 
    } 
} 

Очевидно, у меня есть две кнопки, одна из которых добавляет «устройство» и добавляет «сервис» к каждому устройству.

То, что я не может, произойдут какие-либо из этих данных в NSOutlineView. Я установил объект в TreeController в контроллере свойства в режим: класс и класс: устройства, и без установки детей, граф, или свойство листьев я получаю (предсказуемо):

2017-01-04 17:20: 19.337129 OutlineTest [12550: 1536405] Внимание: [класс объекта: устройство] childrenKeyPath не может быть nil. Для устранения этого сообщения журнала, установите атрибут childrenKeyPath в Interface Builder

Если я затем установить свойство детей в «детские» дела идут очень плохо:

2017-01-04 17:23: 11.150627 OutlineTest [12695: 1548039] [Общие] [addObserver: forKeyPath: options: context:] не поддерживается. Ключевой путь: дети

Все, что я пытаюсь сделать, это настроить NSOutlineView принимать входные данные из NSTreeController так, что, когда новая «Device» добавляется к устройствам [] массив, он показывает в Просмотр структуры.

Если бы кто-нибудь мог указать мне в правильном направлении, я был бы очень благодарен.

Огромная благодарность Уоррену за чрезвычайно полезную работу. У меня есть (в основном) работа.Несколько вещей, которые я также необходимы, чтобы сделать, в дополнение к предложениям Уоррен:

Set the datastore for the TreeController Установите хранилищу для контроллера дерева

Bind to the TreeView свяжи OutlineView к TreeController

Bind the Column to the TreeController свяжи Колонка в TreeController

Bind the Table View Cell to the Table Cell View Связать ячейку TableView с ячейкой таблицы (да, действительно)

После того, как все , что было сделано, я должен был играть с фактическим датасторе немного:

var name = "Bluetooth Devices Root" 
var deviceStore = [Device]() 
@IBOutlet var treeController: NSTreeController! 
@IBOutlet weak var outlineView: NSOutlineView! 


override func viewDidLoad() { 
    super.viewDidLoad() 
    deviceStore.append(Device(name: "Bluetooth Devices")) 
    self.treeController.content = self 
} 

override var representedObject: Any? { 
    didSet { 
    // Update the view, if already loaded. 
    } 
} 

@IBAction func addDeviceAction(_ sender: Any) { 
    if(deviceStore[0].name == "Bluetooth Devices"){ 
     deviceStore.remove(at: 0) 
    } 

Оказывается, корень не может быть ребенок-менее в начале, по крайней мере, насколько я могу сказать. Когда я добавлю ребенка, я могу удалить значение владельца места, и дерево, похоже, работает (в основном), как я хочу. Еще одна вещь, что я должен перезагрузить данные и повторного контура при изменении данных:

outlineView.reloadData() 
outlineView.setNeedsDisplay() 

Без этого ничего. У меня все еще нет правильного обновления данных (см. Комментарии ниже ответа Уоррена), но я почти там.

+0

Вы можете попробовать установить массив в целом, вместо того, чтобы вставить, таким образом notifs КВО должен вызвать правильно. – Cristik

+0

Я не могу этого сделать. Я действительно буду сканировать сетевые устройства и добавлять их по мере их обнаружения, как только это будет работать, и я не могу их предварительно загрузить и им придется вставлять их по мере их нахождения. – Davidgs

+0

Вы можете сделать это с помощью неизменяемого массива вместо записи 'devices.append (Device (...))', вы пишете 'devices = devices + [Device (...)]', таким образом, значение 'devices' изменяется каждый раз при обнаружении нового устройства. – Cristik

ответ

5

Чтобы указать очевидное, NSTreeController управляет деревом объектов, все из которых должны отвечать на следующие три вопроса/запроса.

  • У вас нет листьев. У вас нет детей? = leafKeyPath
  • Если вы не лист, сколько у вас детей? = countKeyPath
  • Дайте мне ваши дети! = childrenKeyPath

Его просто установить в IB или программно. Достаточно стандартный набор свойств.

  • isLeaf
  • childCount
  • children

Но его совершенно произвольно и может быть любой набор свойств, которые отвечают на эти вопросы.

Обычно я установил протокол с именем TreeNode и сделаю все его объекты соответствующими ему.

@objc protocol TreeNode:class { 
    var isLeaf:Bool { get } 
    var childCount:Int { get } 
    var children:[TreeNode] { get } 
} 

Для вашего Device объекта вы ответить на 2 из 3 вопрос с isLeaf и children, но не дают ответа на вопрос ChildCount.

Дети вашего устройства являются объектами Service, и они не отвечают ни на одно из причин, по которым вы получаете исключения.

Так, чтобы исправить свой код возможное решение ...

Служба объекта Объект

class Service: NSObject, TreeNode { 

    let name: String 
    init(name: String){ 
     self.name = name 
    } 

    var isLeaf:Bool { 
     return true 
    } 
    var childCount:Int { 
     return 0 
    } 
    var children:[TreeNode] { 
     return [] 
    } 
} 

Устройство

class Device: NSObject, TreeNode { 
    let name : String 
    var serviceStore = [Service]() 

    init(name: String){ 
     self.name = name 
    } 

    var isLeaf:Bool { 
     return serviceStore.isEmpty 
    } 
    var childCount:Int { 
     return serviceStore.count 
    } 
    var children:[TreeNode] { 
     return serviceStore 
    } 

} 

И ужасная вещь, чтобы сделать из MVC но удобный для этого ответа. Корневой объект.

class ViewController: NSViewController, TreeNode { 

    var deviceStore = [Device]() 
    var name = "Henry" //whatever you want to name your root 

    var isLeaf:Bool { 
     return deviceStore.isEmpty 
    } 
    var childCount:Int { 
     return deviceStore.count 
    } 
    var children:[TreeNode] { 
     return deviceStore 
    } 

} 

Итак, все, что вам нужно сделать, это установить содержимое вашего treeController. Предположим, у вас есть IBOutlet к нему в вашем ViewController.

class ViewController: NSViewController, TreeNode { 

    @IBOutlet var treeController:NSTreeController! 
    @IBOutlet var outlineView:NSOutlineView! 


    override func viewDidLoad() { 
     super.viewDidLoad() 
     treeController.content = self 
    } 

Теперь каждый раз, когда вы добавляете устройство или добавить службу просто позвоните reloadItem на outlineView (что необходимо также выход к)

@IBAction func addDeviceAction(_ sender: Any) { 
     let str = "New Device #\(stepper)" 
     devices.append(Device(name: str)) 
     stepper += 1 
     print("Added Device: \(devices[devices.count-1].name)") 
     outlineView.reloadItem(self, reloadChildren: true)  
    } 

Вот основы и должен вам начать, но docs для NSOutlineView & NSTreeController имеют намного больше информации.

EDIT

В дополнение к выше вещи вам нужно, чтобы связать ваш взгляд наброски к контроллеру дерева.

Сначала убедитесь, что ваш режим просмотра находится в режиме просмотра.

place outline view in view mode

Далее связать столбец таблицы arrangedObjects на контроллере дерева.

bind column to tree controller

Последнее связывают текст ячейки с соответствующим ключом пути. В вашем случае это имя. objectValue - ссылка на ваш объект в ячейке.

bind data to cell

+0

Спасибо Уоррен! Это здорово, и я воспользовался исключениями. К сожалению, я думаю, что я пропустил какой-то шаг, так как я все еще не знаю ничего в своем OutlineView. Я уверен, что есть некоторые настройки в IB или в другом месте, которые я пропустил. – Davidgs

+0

См. Редактирование - он включает в себя шаги привязки –

+0

Спасибо Уоррен! Это, конечно, лучше. OutlineView теперь по крайней мере загружает корневой объект. Он по-прежнему не добавляет никаких новых объектов по мере их создания. Надеюсь, сегодня я смогу отслеживать эту часть.У меня есть tableView, который обновляет контент от ArrayController, и это очень просто и легко сделать работу. Этот TreeController/OutlineView кажется серьезно поврежденным устройством! – Davidgs