【问题标题】:Remove 'Start Dictation' and 'Special Characters' from menu从菜单中删除“开始听写”和“特殊字符”
【发布时间】:2014-01-26 21:51:07
【问题描述】:

为了在我的 Cocoa 应用程序中启用复制和粘贴,我在菜单中添加了两个新的菜单项(复制和粘贴),并将选择器从每个项拖到第一响应者(复制和粘贴)。但是,复制和粘贴菜单项下方会显示两个额外的项目:“开始听写”和“特殊字符”。

我无法弄清楚它们出现的原因或我如何删除它们。

理想情况下,我什至不希望复制和粘贴菜单项可见。我只是希望我的应用程序的用户能够将内容(即来自电子邮件、文本文档等)粘贴到我的应用程序的一个表单上的文本字段中。

【问题讨论】:

标签: objective-c xcode macos cocoa nsmenuitem


【解决方案1】:

这是我在应用程序中使用的代码,用于将这些自动添加的条目删除到“编辑”菜单:

- (void) applicationDidFinishLaunching: (NSNotification*)aNotification
{
    NSMenu* edit = [[[[NSApplication sharedApplication] mainMenu] itemWithTitle: @"Edit"] submenu];
    if ([[edit itemAtIndex: [edit numberOfItems] - 1] action] == NSSelectorFromString(@"orderFrontCharacterPalette:"))
        [edit removeItemAtIndex: [edit numberOfItems] - 1];
    if ([[edit itemAtIndex: [edit numberOfItems] - 1] action] == NSSelectorFromString(@"startDictation:"))
        [edit removeItemAtIndex: [edit numberOfItems] - 1];
    if ([[edit itemAtIndex: [edit numberOfItems] - 1] isSeparatorItem])
        [edit removeItemAtIndex: [edit numberOfItems] - 1];
}

注意:此代码需要进入 applicationDidFinishLaunching: 或更高版本,如果您将其放在 applicationWillFinishLaunching: 中,这些条目将不会添加到 Edit 菜单中。

另外请注意,我正在使用 NSSelectorFromString,因为使用 @selector 会导致“未知选择器”警告。 (即使有警告,代码也可以工作,但我更喜欢在我的代码中没有警告,所以选择使用NSSelectorFromString 来避免它们。)

【讨论】:

  • 有没有办法隐藏菜单项,使它们不可见?我只是希望用户能够通过快捷命令进行复制、粘贴和剪切。
【解决方案2】:

Mac OS X Internals: A Systems ApproachQt Mac (Re)move "Special Characters..." action in Edit menu 中所述,您可以在加载 nib 之前在 main() 中执行类似的操作(但不支持 API):

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSDisabledDictationMenuItem"];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"];

【讨论】:

    【解决方案3】:

    解决此问题的最快方法是将标题设置为“编辑”(末尾有一个额外的空格)。

    在界面构建器中选择编辑菜单:

    然后从属性检查器中,为标题添加一个额外的空格。

    【讨论】:

    • 在 Xcode 6.4 中工作,确保保存并关闭 Storyboard
    【解决方案4】:

    使用情节提要的 Swift 4 解决方案

    将以下代码添加到您的AppDelegate

    func applicationWillFinishLaunching(_ notification: Notification) {
      UserDefaults.standard.set(true, forKey: "NSDisabledDictationMenuItem")
      UserDefaults.standard.set(true, forKey: "NSDisabledCharacterPaletteMenuItem")
    }
    

    applicationWillFinishLaunching 函数在应用程序生命周期的早期调用,在菜单初始化之前。无需手动破解菜单项。

    【讨论】:

      【解决方案5】:

      在已经提到的三种方法中,设置 UserDefaults 可能是最干净的,但如前所述,它依赖于未记录的 UserDefaults 条目。

      通过菜单中的索引位置(即最后一个位置)删除项目的方法依赖于它们将始终位于菜单末尾的假设。这可能是一个安全的假设,但 Apple 可以在未来做其他事情。

      由于本地化原因,依赖菜单项名称也是有问题的。

      最好使用选择器,如在 Objective-C 中所示,但它在 Swift 中不会那么简单,特别是对于“开始听写...”菜单项。您需要选择器的方法是startDictation(_:),但与在Objective-C 中不同的是,您不能像那样键入它。您需要指定它所属的@objc 类型。因此,只需搜索 Apple 的文档,对吗?祝你好运。它没有记录,并且该方法没有在 Swift 中公开。

      我解决该问题的方法是在我的AppDelegate 中为该方法添加一个存根,然后使用它来获取选择器。你真的只需要一些 Swift 可以使用的类型来形成选择器。 But when the selector is tossed over the wall to the Obj-C side of Cocoa, that type just disappears.重要的是方法的名称、参数的数量、它们的顺序和名称。在这种情况下,与大多数操作方法一样,它需要一个参数(用于发送者):

      @objc public func startDictation(_: Any) { }
      

      AppDelegate 对于快速而肮脏的实现很方便,但在实际使用中,我更喜欢创建一个继承 NSObject 的类,并带有专门用于此类存根方法的 private 初始化程序。这样你就可以确保它们永远不会污染响应者链。基本上制作一个不可实例化的包,您可以使用这些方法来制作选择器。

      现在我们需要一种方法来找到对应的菜单项,所以我在NSMenu上做了一个扩展

      public extension NSMenu
      {
          func lastMenuItem(where condition: (NSMenuItem) -> Bool) -> NSMenuItem?
          {
              for item in items.reversed()
              {
                  if let submenu = item.submenu
                  {
                      if let foundItem = submenu.lastMenuItem(where: condition) {
                          return foundItem
                      }
                  }
                  else if condition(item) { return item }
              }
      
              return nil
          }
      }
      

      我选择反向搜索菜单,假设如果我决定在未来某个日期添加我自己的项目,它们很可能在我要删除的项目之前。然后在AppDelegate:

      func removeUnwantedAutomaticMenus()
      {
          let unwantedActions: [Selector] =
          [
              #selector(AppDelegate.startDictation(_:)),
              #selector(NSApplication.orderFrontCharacterPalette(_:)),
          ]
          for action in unwantedActions {
              NSApp.mainMenu?.lastMenuItem { $0.action == action }?.isHidden = true
          }
      }
      

      如您所见,我选择隐藏项目而不是删除它们,但您当然可以删除它们。剩下的就是在AppDelegate.applicationDidFinishLaunching 中调用removeUnwantedAutomaticMenus()

      如果您以编程方式创建菜单,则可以通过使用NSMenuItem 中的tag 属性标记您添加的项目,然后检查该标签以确保您不会删除/隐藏这些项目,从而使其更加健壮自动添加的。

      另一种方法是继承NSMenu,覆盖其addIteminsertItem 方法以在添加NSMenuItem 之前检查它们的标签。简单地不要添加/插入任何带有不正确标签的NSMenuItems,Apple 自动插入的项目不会有。如果您将 Storyboards 用于您的菜单,这会很痛苦,因为您必须确保每个菜单的类都设置为您的自定义类,并且每个菜单项的标签设置正确。如果您以编程方式创建菜单,则确保一切设置正确会容易得多。

      【讨论】:

        【解决方案6】:

        在 Apple Swift 中,您可以这样做:

        var EditMenu = NSApplication.sharedApplication().mainMenu!.itemWithTitle("Edit")
        if (EditMenu != nil) // Edit-Menu exists, otherwise you would run into an exception when proceeding
        {
            var Count: Int = EditMenu!.submenu!.numberOfItems
            if (EditMenu!.submenu!.itemAtIndex(Count - 1)!.title == "Special Characters…")
            {
                EditMenu!.submenu!.removeItemAtIndex(Count - 1)
            }
            if (EditMenu!.submenu!.itemAtIndex(Count - 2)!.title == "Start Dictation…")
            {
                EditMenu!.submenu!.removeItemAtIndex(Count - 2)
            }
            println("Titel = '\(EditMenu!.submenu!.itemAtIndex(Count - 3)!.title)'")
            if (EditMenu!.submenu!.itemAtIndex(Count - 3)!.title == "")
            {
                EditMenu!.submenu!.removeItemAtIndex(Count - 3)
            }
        }
        

        只需将德语版本的“编辑”替换为“Bearbeiten”即可。分隔符菜单项返回一个空字符串作为标题。

        【讨论】:

          【解决方案7】:

          对于 Swift 4 和 Xcode 9.2,这将是:

          static let EDIT_MENU_TITLE = "Edit"
          static let SPECIAL_CHARACTERS_TITLE = "Emoji & Symbols"
          static let DICTATION_MENU_TITLE = "Start Dictation…"
          

          然后我使用以下函数来启用/禁用编辑菜单:

          func enableEditingMenu( enabled: Bool ) {
              let m = NSApplication.shared().mainMenu
              let mi = m?.item(withTitle: MenuController.EDIT_MENU_TITLE )
              mi?.isEnabled = enabled
              if (mi != nil) { // Edit-Menu exists, otherwise you would run into an exception when proceeding
                  let Count: Int = mi!.submenu!.numberOfItems
                  if (mi!.submenu!.item(at: Count - 1)!.title == MenuController.SPECIAL_CHARACTERS_TITLE) {
                      mi!.submenu!.removeItem(at: Count - 1)
                  }
                  if (mi!.submenu!.item(at: Count - 2)!.title == MenuController.DICTATION_MENU_TITLE) {
                      mi!.submenu!.removeItem(at: Count - 2)
                  }
                  if (mi!.submenu!.item(at: Count - 3)!.title == "") {
                      mi!.submenu!.removeItem(at: Count - 3)
                  }
              }
          }
          

          在需要启用/禁用的视图控制器中这样调用:

          override func viewWillAppear() {
              let mc = MenuController()
              mc.enableEditingMenu( enabled: true )
          }
          
          override func viewWillDisappear() {
              let mc = MenuController()
              mc.enableEditingMenu( enabled: false )
          }
          

          我还确保它在 AppDelegate.swift 中被禁用

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-11-29
            • 1970-01-01
            • 2016-11-09
            • 2020-04-10
            • 1970-01-01
            • 2011-04-11
            • 2016-01-23
            相关资源
            最近更新 更多