Как программно добавить пользовательский NSToolbarItem в существующую панель инструментов - PullRequest
0 голосов
/ 27 апреля 2019

Мне сложно добавить пользовательский NSToolbarItem в мою существующую панель инструментов.

NSToolbar был создан в NSWindowController, затем у меня есть функция для программного наполнения элементов панели инструментов, код:

public func populateFileToolbarItem(_ toolbar: NSToolbar) -> Void{
    let itemId = NSToolbarItem.Identifier("FILE_OPEN")
    let index = toolbar.items.count
    var toolbarItem: NSToolbarItem
    toolbarItem = NSToolbarItem(itemIdentifier: itemId)
    toolbarItem.label = String("File")
    toolbarItem.paletteLabel = String("Open File")
    toolbarItem.toolTip = String("Open file to be handled")
    toolbarItem.tag = index
    toolbarItem.target = self
    toolbarItem.isEnabled = true
    toolbarItem.action = #selector(browseFile)
    toolbarItem.image = NSImage.init(named:NSImage.folderName)
    toolbar.insertItem(withItemIdentifier: itemId, at: index)
}

Затем я вызвал эту функцию, чтобы добавить элемент панели инструментов на существующую панель инструментов в windowController

.......
  populateFileToolbarItem((self.window?.toolbar)!)
  self.window?.toolbar?.insertItem(withItemIdentifier: NSToolbarItem.Identifier.flexibleSpace, at: (self.window?.toolbar?.items.count)!)
  self.window?.toolbar?.insertItem(withItemIdentifier: NSToolbarItem.Identifier.print, at: (self.window?.toolbar?.items.count)!)
  print("after toolbaritems were inserted into toolbar. \(String(describing: self.window?.toolbar?.items.count))") 
......

Распечатка консоли показывает, что на панель инструментов были добавлены только два элемента панели инструментов.

.......
after toolbaritems were inserted into toolbar. Optional(2)

А на панели инструментов нет пользовательских элементов.

У любого есть опыт, пожалуйста, сообщите!

Ответы [ 2 ]

1 голос
/ 27 апреля 2019

Чтобы добавить / удалить элементы с панели инструментов, вам необходим делегат на панели инструментов: NSToolbarDelegate.

Вот шаблон для реализации, которую я использую (вероятно, больше, чем вы хотите).

Код Boilerplate для создания элементов панели инструментов различных типов:


struct ToolbarIdentifiers {
    static let mainToolbar = NSToolbar.Identifier(stringLiteral: "MainToolbar")
    static let navGroupItem = NSToolbarItem.Identifier(rawValue: "NavGroupToolbarItem")
    static let shareItem = NSToolbarItem.Identifier(rawValue: "ShareToolBarItem")
    static let addItem = NSToolbarItem.Identifier(rawValue: "AddToolbarItem")
    static let statusItem = NSToolbarItem.Identifier(rawValue: "StatusToolbarItem")
    static let filterItem = NSToolbarItem.Identifier(rawValue: "FilterToolbarItem")
    static let sortItem = NSToolbarItem.Identifier(rawValue: "SortToolbarItem")
    static let cloudUploadItem = NSToolbarItem.Identifier(rawValue: "UploadToolbarItem")
    static let cloudDownloadItem = NSToolbarItem.Identifier(rawValue: "DownloadToolbarItem")
    static let leftButtonItem = NSToolbarItem.Identifier(rawValue: "leftButtonToolbarItem")
    static let rightButtonItem = NSToolbarItem.Identifier(rawValue: "rightButtonToolbarItem")
    static let hideShowItem = NSToolbarItem.Identifier(rawValue: "hideShowToolbarItem")
}

// Base toolbar item type, extended for segmented controls, buttons, etc.
struct ToolbarItem {
    let identifier: NSToolbarItem.Identifier
    let label: String
    let paletteLabel: String
    let tag: ToolbarTag
    let image: NSImage?
    let width: CGFloat
    let height: CGFloat
    let action: Selector?
    weak var target: AnyObject?
    var menuItem: NSMenuItem? = nil // Needs to be plugged in after App has launched.
    let group: [ToolbarItem]

    init(_ identifier: NSToolbarItem.Identifier, label: String = "", tag: ToolbarTag = .separator, image: NSImage? = nil,
         width: CGFloat = 38.0, height: CGFloat = 28.0,
         action: Selector? = nil, target: AnyObject? = nil, group: [ToolbarItem] = [], paletteLabel: String = "") {
        self.identifier = identifier
        self.label = label
        self.paletteLabel = paletteLabel
        self.tag = tag
        self.width = width
        self.height = height
        self.image = image
        self.action = action
        self.target = target
        self.group = group
    }
}
// Image button -- creates NSToolbarItem
extension ToolbarItem {
    func imageButton() -> NSToolbarItem {
        let item = NSToolbarItem(itemIdentifier: identifier)
        item.label = label
        item.paletteLabel = label
        item.menuFormRepresentation = menuItem // Need this for text-only to work
        item.tag = tag.rawValue
        let button = NSButton(image: image!, target: target, action: action)
        button.widthAnchor.constraint(equalToConstant: width).isActive = true
        button.heightAnchor.constraint(equalToConstant: height).isActive = true
        button.title = ""
        button.imageScaling = .scaleProportionallyDown
        button.bezelStyle = .texturedRounded
        button.tag = tag.rawValue
        button.focusRingType = .none
        item.view = button
        return item
    }
}
// Segmented control -- creates NSToolbarItemGroup containing multiple instances of NSToolbarItem
extension ToolbarItem {
    func segmentedControl() -> NSToolbarItemGroup {
        let itemGroup = NSToolbarItemGroup(itemIdentifier: identifier)
        let control = NSSegmentedControl(frame: NSRect(x: 0, y: 0, width: width, height: height))
        control.segmentStyle = .texturedSquare
        control.trackingMode = .momentary
        control.segmentCount = group.count
        control.focusRingType = .none
        control.tag = tag.rawValue

        var items = [NSToolbarItem]()
        var iSeg = 0
        for segment in group {
            let item = NSToolbarItem(itemIdentifier: segment.identifier)
            items.append(item)
            item.label = segment.label
            item.tag = segment.tag.rawValue
            item.action = action
            item.target = target
            control.action = segment.action // button & container send to separate handlers
            control.target = segment.target
            control.setImage(segment.image, forSegment: iSeg)
            control.setImageScaling(.scaleProportionallyDown, forSegment: iSeg)
            control.setWidth(segment.width, forSegment: iSeg)
            control.setTag(segment.tag.rawValue, forSegment: iSeg)
            iSeg += 1
        }
        itemGroup.paletteLabel = paletteLabel
        itemGroup.subitems = items
        itemGroup.view = control
        return itemGroup
    }
}
// Text field -- creates NSToolbarItem containing NSTextField
extension ToolbarItem {
    func textfieldItem() -> NSToolbarItem {
        let item = NSToolbarItem(itemIdentifier: identifier)
        item.label = ""
        item.paletteLabel = label
        item.tag = tag.rawValue
        let field = NSTextField(string: label)
        field.widthAnchor.constraint(equalToConstant: width).isActive = true
        field.heightAnchor.constraint(equalToConstant: height).isActive = true
        field.tag = tag.rawValue
        field.isSelectable = false
        item.view = field
        return item
    }
}
// Menu item -- creates an empty NSMenuItem so that user can click on the label
// definitely a work-around till we implement the menus
extension ToolbarItem {
    mutating func createMenuItem(_ action: Selector) {
        let item = NSMenuItem()
        item.action = action
        item.target = target
        item.title = label
        item.tag = tag.rawValue
        self.menuItem = item
    }
}
/*
 * Create specialized toolbar items with graphics, labels, actions, etc
 * Encapsulates implementation-specific details in code, because the table-driven version was hard to read.
 */
struct InitializeToolbar {
}
extension InitializeToolbar {
    static func navGroupItem(_ action: Selector, segmentAction: Selector, target: AnyObject) -> ToolbarItem {
        var group = [ToolbarItem]()
        group.append(ToolbarItem(NSToolbarItem.Identifier(rawValue: "BackToolbarItem"), label: "Prev", tag: .navPrev,
                                 image: NSImage(named: NSImage.goBackTemplateName), action: segmentAction, target: target))
        group.append(ToolbarItem(NSToolbarItem.Identifier(rawValue: "FwdToolbarItem"), label: "Next", tag: .navNext,
                                 image: NSImage(named: NSImage.goForwardTemplateName), action: segmentAction, target: target))
        let item = ToolbarItem(ToolbarIdentifiers.navGroupItem, tag: .navGroup, width: 85, height: 28,
                               action: action, target: target, group: group, paletteLabel: "Navigation")
        return item
    }
}
extension InitializeToolbar {
    static func hideShowItem(_ action: Selector, segmentAction: Selector, target: AnyObject) -> ToolbarItem {
        var group = [ToolbarItem]()
        group.append(ToolbarItem(NSToolbarItem.Identifier(rawValue: "HideLeftItem"), label: "", tag: .leftButton,
                                 image: NSImage(named: "leftButton"), action: segmentAction, target: target))
        group.append(ToolbarItem(NSToolbarItem.Identifier(rawValue: "HideRightItem"), label: "", tag: .rightButton,
                                 image: NSImage(named: "rightButton"), action: segmentAction, target: target))
        let item = ToolbarItem(ToolbarIdentifiers.hideShowItem, tag: .hideShow, width: 85, height: 28,
                               action: action, target: target, group: group, paletteLabel: "Hide/Show")
        return item
    }
}
extension InitializeToolbar {
    static func addItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
        let item = ToolbarItem(ToolbarIdentifiers.addItem, label: "Add", tag: .add, image: NSImage(named: NSImage.addTemplateName), action: action, target: target)
        return item
    }
}
extension InitializeToolbar {
    static func shareItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
        let item = ToolbarItem(ToolbarIdentifiers.shareItem, label: "Share", tag: .share, image: NSImage(named: NSImage.shareTemplateName), action: action, target: target)
        return item
    }
}
extension InitializeToolbar {
    static func filterItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
        let item = ToolbarItem(ToolbarIdentifiers.filterItem, label: "Filter", tag: .filter, image: NSImage(named: "filter"), action: action, target: target)
        return item
    }
}
extension InitializeToolbar {
    static func sortItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
        let item = ToolbarItem(ToolbarIdentifiers.sortItem, label: "Sort", tag: .sort, image: NSImage(named: "sort"), action: action, target: target)
        return item
    }
}
extension InitializeToolbar {
    static func cloudDownloadItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
        let item = ToolbarItem(ToolbarIdentifiers.cloudDownloadItem, label: "Down", tag: .cloudDownload, image: NSImage(named: "cloudDownload"), action: action, target: target)
        return item
    }
}
extension InitializeToolbar {
    static func cloudUploadItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
        let item = ToolbarItem(ToolbarIdentifiers.cloudUploadItem, label: "Up", tag: .cloudUpload, image: NSImage(named: "cloudUpload"), action: action, target: target)
        return item
    }
}
extension InitializeToolbar {
    static func leftButtonItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
        let item = ToolbarItem(ToolbarIdentifiers.leftButtonItem, label: "", tag: .leftButton, image: NSImage(named: "leftButton"), action: action, target: target)
        return item
    }
}
extension InitializeToolbar {
    static func rightButtonItem(_ action: Selector, target: AnyObject) -> ToolbarItem {
        let item = ToolbarItem(ToolbarIdentifiers.rightButtonItem, label: "", tag: .rightButton, image: NSImage(named: "rightButton"), action: action, target: target)
        return item
    }
}

extension InitializeToolbar {
    static func textItem() -> ToolbarItem {
        return ToolbarItem(ToolbarIdentifiers.statusItem, label: "Watch This Space", tag: .status, width: 300, height: 24)
    }
}

Вот класс панели инструментов, который реализует инициализатор и делегат:

/*
 * Initializer builds a specialized toolbar.
 */
enum ToolbarTag: Int {
    case separator = 1
    case navGroup
    case navPrev
    case navNext
    case add
    case share
    case filter
    case sort
    case cloudDownload
    case cloudUpload
    case leftButton
    case rightButton
    case hideShow
    case status
}
class Toolbar: NSObject, NSToolbarDelegate, Actor {
    var actorDelegate: ActorDelegate?
    var identifier: NSUserInterfaceItemIdentifier?
    var toolbarItemList = [ToolbarItem]()
    var toolbarItemIdentifiers: [NSToolbarItem.Identifier] { return toolbarItemList.map({ $0.identifier }) }
    var toolbarDefaultItemList = [ToolbarItem]()
    var toolbarDefaultItemIdentifiers: [NSToolbarItem.Identifier] { return toolbarDefaultItemList.map({ $0.identifier }) }

    // Delegate toolbar actions
    @objc func controlSentAction(_ sender: Any) {
        guard let control = sender as? NSControl else { return }
        guard let tag = ToolbarTag(rawValue: control.tag) else { return }
        actorDelegate?.actor(self, initiator: control, tag: tag, obj: nil)
    }
    @objc func segmentedControlSentAction(_ sender: Any) {
        guard let segmented = sender as? NSSegmentedControl else { return }
        guard let tag = ToolbarTag(rawValue: segmented.tag(forSegment: segmented.selectedSegment)) else { return }
        actorDelegate?.actor(self, initiator: segmented, tag: tag, obj: nil)
    }
    // These don't get called at the moment
    @objc func toolbarItemSentAction(_ sender: Any) { ddt("toolbarItemSentAction") }
    @objc func menuSentAction(_ sender: Any) { ddt("menuSentAction") }

    // Toolbar initialize
    init(_ window: Window) {
        super.init()
        identifier = Identifier.View.toolbar

        let toolbar = NSToolbar(identifier: ToolbarIdentifiers.mainToolbar)
        toolbar.centeredItemIdentifier = ToolbarIdentifiers.statusItem

        // Build the initial toolbar
        // Text field
        toolbarItemList.append(ToolbarItem(.flexibleSpace))
        toolbarItemList.append(InitializeToolbar.textItem())
        toolbarItemList.append(ToolbarItem(.flexibleSpace))
        // Show/Hide
        toolbarItemList.append(InitializeToolbar.hideShowItem(#selector(toolbarItemSentAction), segmentAction: #selector(segmentedControlSentAction), target: self))
        // Save initial toolbar as default
        toolbarDefaultItemList = toolbarItemList
        // Also allow these, just to demo adding
        toolbarItemList.append(InitializeToolbar.cloudDownloadItem(#selector(controlSentAction), target: self))
        toolbarItemList.append(InitializeToolbar.sortItem(#selector(controlSentAction), target: self))

        toolbar.allowsUserCustomization = true
        toolbar.displayMode = .default
        toolbar.delegate = self
        window.toolbar = toolbar
    }

    deinit {
        ddt("deinit", caller: self)
    }
}
/*
 * Implement NSToolbarDelegate
 */
extension Toolbar {

    // Build toolbar
    func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
        guard let item = toolbarItemList.firstIndex(where: { $0.identifier == itemIdentifier }) else { return nil }
        switch toolbarItemList[item].identifier {
        case ToolbarIdentifiers.navGroupItem, ToolbarIdentifiers.hideShowItem:
            return toolbarItemList[item].segmentedControl()
        case ToolbarIdentifiers.addItem, ToolbarIdentifiers.shareItem, ToolbarIdentifiers.sortItem, ToolbarIdentifiers.filterItem, ToolbarIdentifiers.cloudUploadItem, ToolbarIdentifiers.cloudDownloadItem,
             ToolbarIdentifiers.leftButtonItem, ToolbarIdentifiers.rightButtonItem:
            return toolbarItemList[item].imageButton()
        case ToolbarIdentifiers.statusItem:
            return toolbarItemList[item].textfieldItem()
        default:
            return nil
        }
    } // end of toolbar

    func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return toolbarDefaultItemIdentifiers;
    }

    func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return toolbarItemIdentifiers
    }

    func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return []
    }

    func toolbarWillAddItem(_ notification: Notification) {
    }

    func toolbarDidRemoveItem(_ notification: Notification) {
    }
} // End of extension

Начальная панель инструментов:

* +1012 *enter image description here

Раскрывающийся список настроек, который делает Какао для вас:

enter image description here

После добавления облачной кнопки:

enter image description here Надеюсь, что это полезно.

Добавлено для уточнения 28.04.2009:

Мой класс панели инструментов не является подклассом NSToolbar. Его инициализатору передается ссылка на окно, так что в конце он устанавливает панель инструментов окна на созданную им панель инструментов:

    init(_ window: Window) {
        super.init()
        identifier = Identifier.View.toolbar

**** stuff removed for clarity ****

        let toolbar = NSToolbar(identifier: ToolbarIdentifiers.mainToolbar)
        toolbar.allowsUserCustomization = true
        toolbar.displayMode = .default
        toolbar.delegate = self
        window.toolbar = toolbar
    }

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

Протокол "Актер" является частью моей системы координации, не важен для построения самой панели инструментов. Мне бы пришлось включить все демонстрационное приложение, чтобы показать это, и я предполагаю, что у вас есть собственный дизайн для передачи действий панели инструментов вашим контроллерам / моделям.

Это приложение Xcode 10.2 / Swift 5, хотя я не думаю, что оно использует какие-либо новые функции Swift 5.

0 голосов
/ 29 апреля 2019

Как работают панели инструментов

Чтобы создать панель инструментов, вы должны создать делегата, который предоставляет важную информацию:

  • Список идентификаторов панели инструментов по умолчанию. Этот список используется при возврате к значениям по умолчанию и построении начальной панели инструментов. Набор элементов панели инструментов по умолчанию также можно указать с помощью элементов панели инструментов, имеющихся в библиотеке Interface Builder.
  • Список разрешенных идентификаторов предметов. Список разрешенных элементов используется для построения палитры настройки, если панель инструментов настраивается.
  • Элемент панели инструментов для данного идентификатора элемента.

Например, добавить FlexibleSpace, печать и пользовательский элемент:

class MyWindowController: NSWindowController, NSToolbarDelegate {

    var toolbarIdentifier = NSToolbarItem.Identifier("FILE_OPEN")

    func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return [NSToolbarItem.Identifier.flexibleSpace, NSToolbarItem.Identifier.print, toolbarIdentifier]
    }

    func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return [NSToolbarItem.Identifier.flexibleSpace, NSToolbarItem.Identifier.print, toolbarIdentifier]
    }

    func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
        willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
        if itemIdentifier == toolbarIdentifier {
            let toolbarItem = NSToolbarItem(itemIdentifier: toolbarIdentifier)
            toolbarItem.label = String("File")
            toolbarItem.paletteLabel = String("Open File")
            toolbarItem.toolTip = String("Open file to be handled")
            toolbarItem.isEnabled = true
            toolbarItem.target = self
            toolbarItem.action = #selector(browseFile)
            toolbarItem.image = NSImage.init(named:NSImage.folderName)
            return toolbarItem
        }
        else {
            return NSToolbarItem(itemIdentifier: itemIdentifier)
        }
    }

}

Также возможно добавить некоторые или все стандартные и / или пользовательские элементы в IB.

...