从菜单中删除“开始听写”和“特殊字符”

时间:2014-01-26 21:51:07

标签: objective-c xcode macos cocoa nsmenuitem

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

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

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

7 个答案:

答案 0 :(得分:11)

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

- (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来避免它们。)

答案 1 :(得分:8)

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"];

答案 2 :(得分:4)

解决此问题的最快方法是将标题设置为"编辑" (最后有一个额外的空间)。

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

enter image description here

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

enter image description here

答案 3 :(得分:1)

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

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

由于本地化原因,依赖菜单项名称也存在问题。

最好使用选择器,如 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
    }
}

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

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

另一种方法是对 NSMenu 进行子类化,覆盖其 addIteminsertItem 方法以在添加之前检查 NSMenuItem 的标签。只需不要添加/插入任何带有错误标签的 NSMenuItem,Apple 自动插入的项目不会有这些标签。如果您在菜单中使用 Storyboard,那会有点麻烦,因为您必须确保每个菜单的类都设置为您的自定义类,并且每个菜单项的标签设置正确。如果您以编程方式创建菜单,则可以更轻松地确保一切设置正确。

答案 4 :(得分:0)

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

将以下代码添加到您的AppDelegate

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

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

答案 5 :(得分:-1)

在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”即可。分隔符菜单项返回一个空字符串作为标题。

答案 6 :(得分:-1)

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

switch

然后我使用以下功能启用/禁用编辑菜单:

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)
        }
    }
}

我还确保在AppDelegate.swift

中禁用它