Xamarin.Mac のメニュー

この記事では、Xamarin.Mac アプリケーションでのメニューの操作について説明します。 Xcode と Interface Builder でのメニュー項目とメニュー項目の作成とメインを記述し、プログラムで操作する方法について説明します。

Xamarin.Mac アプリケーションで C# と .NET を使用する場合は、開発者が作業しているのと Xcode と同じ Cocoa メニューに Objective-C アクセスできます。 Xamarin.Mac は Xcode と直接統合されるため、Xcode のインターフェイス ビルダーを使用して、メニュー バー、メニュー、メニュー項目を作成してメインできます (または必要に応じて、C# コードで直接作成することもできます)。

メニューは Mac アプリケーションのユーザー エクスペリエンスに不可欠な要素であり、一般的にユーザー インターフェイスのさまざまな部分に表示されます。

  • アプリケーションのメニュー バー - これは、すべての Mac アプリケーションの画面の上部に表示されるメイン メニューです。
  • コンテキスト メニュー - ユーザーがウィンドウ内の項目を右クリックまたは control キーを押しながらクリックすると表示されます。
  • ステータス バー - これは、画面の上部 (メニュー バーの時計の左側) に表示され、項目が追加されると左側に拡大する、アプリケーション メニュー バーの右端にある領域です。
  • Dock メニュー - ユーザーがアプリケーションのアイコンを右クリックまたはコントロールでクリックしたとき、またはユーザーがアイコンを左クリックしてマウス ボタンを下に押したときに表示される、ドック内の各アプリケーションのメニュー。
  • ポップアップ ボタンとプルダウン リスト - ポップアップ ボタンには、選択した項目が表示され、ユーザーがクリックしたときに選択できるオプションの一覧が表示されます。 プルダウン リストは、通常、現在のタスクのコンテキストに固有のコマンドを選択するために使用されるポップアップ ボタンの一種です。 どちらもウィンドウ内の任意の場所に表示できます。

An example menu

この記事では、Xamarin.Mac アプリケーションでの Cocoa メニュー バー、メニュー、メニュー項目の操作の基本について説明します。 この記事で使用する 主要な概念と手法については、まず Hello Mac の記事、特 に Xcode とインターフェイス ビルダーアウトレットとアクション の概要に関するセクションを参照することを強くお勧めします。

Xamarin.Mac Internals ドキュメントのセクションへの C# クラス/メソッドObjective-Cの公開に関するページも参照してください。C# クラスをオブジェクトと UI 要素に結び付けるために使用される属性Objective-CについてもExport説明Registerしています。

アプリケーションのメニュー バー

すべてのウィンドウに独自のメニュー バーをアタッチできる Windows OS 上で実行されているアプリケーションとは異なり、macOS で実行されているすべてのアプリケーションには、そのアプリケーション内のすべてのウィンドウに使用される画面の上部に沿って実行される 1 つのメニュー バーがあります。

A menu bar

このメニュー バーの項目は、アプリケーションの現在のコンテキストまたは状態とそのユーザー インターフェイスに基づいて、任意の時点でアクティブ化または非アクティブ化されます。 たとえば、ユーザーがテキスト フィールドを選択すると、[編集] メニューの項目 (コピー切り取りなど) が有効になります。

Apple と既定では、すべての macOS アプリケーションには、アプリケーションのメニュー バーに表示されるメニューとメニュー項目の標準セットがあります。

  • Apple メニュー - このメニューでは、実行中のアプリケーションに関係なく、ユーザーが常に使用できるシステム全体の項目にアクセスできます。 これらの項目は、開発者が変更することはできません。
  • [アプリ] メニュー - このメニューには、アプリケーションの名前が太字で表示され、ユーザーが現在実行中のアプリケーションを識別するのに役立ちます。 アプリケーション全体に適用される項目が含まれており、特定のドキュメントやプロセス (アプリケーションの終了など) は含まれません。
  • [ファイル] メニュー - アプリケーションで使用するドキュメントを作成、開く、または保存するために使用する項目。 アプリケーションがドキュメント ベースでない場合は、このメニューの名前を変更または削除できます。
  • [編集] メニュー - アプリケーションのユーザー インターフェイスで要素を編集または変更するために使用される、切り取り、コピー貼り付けなどのコマンドを保持します。
  • [書式] メニュー - アプリケーションがテキストで動作する場合、このメニューには、そのテキストの書式設定を調整するコマンドが保持されます。
  • [表示] メニュー - アプリケーションのユーザー インターフェイスでのコンテンツの表示 (表示) 方法に影響するコマンドを保持します。
  • アプリケーション固有の メニュー - アプリケーションに固有のメニュー (Web ブラウザーのブックマーク メニューなど) です。 バーの [表示] メニューと [ウィンドウ] メニューの間に表示されます。
  • ウィンドウ メニュー - アプリケーションでウィンドウを操作するためのコマンドと、現在開いているウィンドウの一覧が含まれています。
  • ヘルプ メニュー - アプリケーションが画面上のヘルプを提供する場合は、ヘルプ メニューがバーの右端のメニューである必要があります。

アプリケーション メニュー バーと標準メニューとメニュー項目の詳細については、Apple の ヒューマン インターフェイス ガイドラインを参照してください。

既定のアプリケーション メニュー バー

新しい Xamarin.Mac プロジェクトを作成するたびに、macOS アプリケーションが通常持つ一般的な項目を含む標準の既定のアプリケーション メニュー バーが自動的に表示されます (前のセクションで説明したように)。 アプリケーションの既定のメニュー バーは、Solution Pad のプロジェクトの下にある Main.storyboard ファイル (アプリの UI の残りの部分と共に) で定義されます。

Select the main storyboard

Main.storyboard ファイルをダブルクリックして Xcode のインターフェイス ビルダーで編集用に開くと、メニュー エディター インターフェイスが表示されます。

Editing the UI in Xcode, showing the Main dot storyboard.

ここから、[ファイル] メニューの [開く] メニュー項目などの項目をクリックし、[属性インスペクター] でそのプロパティを編集または調整できます。

Editing a menu's attributes

メニューと項目の追加、編集、削除については、この記事の後半で説明します。 ここでは、既定で使用できるメニューとメニュー項目と、定義済みのアウトレットとアクションのセットを介してコードに自動的に公開されている方法を確認するだけです (詳細については、アウトレットとアクションのドキュメントを参照してください)。

たとえば、[開く] メニュー項目の [接続インスペクター] をクリックすると、アクションに自動的にopenDocument:接続されていることがわかります。

Viewing the attached action

インターフェイス階層で最初のレスポンダー選択し、[接続インスペクター] で下にスクロールすると、[開く] メニュー項目がアタッチされているアクションのopenDocument:定義が表示されます (アプリケーションの他のいくつかの既定のアクションと、コントロールに自動的に接続されていないアクション)。

Viewing all attached actions

これは、なぜ重要ですか。 次のセクションでは、これらの自動的に定義されたアクションが他の Cocoa ユーザー インターフェイス要素と連携して、メニュー項目を自動的に有効または無効にする方法と、項目の組み込み機能を提供する方法について説明します。

後で、これらの組み込みアクションを使用して、コードの項目を有効または無効にし、選択されたときに独自の機能を提供します。

組み込みのメニュー機能

UI 項目やコードを追加する前に、新しく作成した Xamarin.Mac アプリケーションを実行した場合は、アプリ メニューの [終了] 項目など、一部の項目が自動的にワイヤードアップされ、自動的に有効になっていることがわかります (完全に機能が自動的に組み込まれています)。

An enabled menu item

切り取り、コピー貼り付けなどの他のメニュー項目は次のものではありません。

Disabled menu items

アプリケーションを停止し、Solution Pad の Main.storyboard ファイルをダブルクリックして Xcode のインターフェイス ビルダーで編集するために開きます。 次に、ライブラリからインターフェイス エディターでウィンドウのビュー コントローラーにテキスト ビューをドラッグします

Selecting a Text View from the Library

制約エディターで、テキスト ビューをウィンドウの端にピン留めし、エディターの上部にある 4 つの赤い I ビームをすべてクリックし、[4 つの制約の追加] ボタンをクリックしてウィンドウで拡大および縮小する場所を設定します。

Editing the contraints

変更をユーザー インターフェイス デザインに保存し、Visual Studio for Mac を切り替えて、変更を Xamarin.Mac プロジェクトと同期します。 次に、アプリケーションを起動し、テキスト ビューにテキストを入力して選択し、[編集] メニューを開きます。

The menu items are automatically enabled/disabled

1 行のコードを 記述することなく、切り取りコピー貼り付けの 項目が自動的に有効になり、完全に機能していることに注目してください。

どうなっているのでしょうか? macOS の一部である Cocoa ユーザー インターフェイス要素のほとんどは、(たとえば copy:) 特定のアクションへのフックを組み込んでいる、既定のメニュー項目 (上に示したように) に接続された組み込みの定義済みアクションを思い出してください。 そのため、それらがウィンドウに追加され、アクティブになり、選択されると、対応するメニュー項目またはそのアクションに関連付けられている項目が自動的に有効になります。 ユーザーがそのメニュー項目を選択すると、UI 要素に組み込まれている機能が呼び出されて実行されます。すべて開発者の介入なしに実行されます。

メニューと項目の有効化と無効化

既定では、ユーザー イベントが発生するたびに、 NSMenu アプリケーションのコンテキストに基づいて、表示される各メニュー項目とメニュー項目が自動的に有効および無効になります。 項目を有効または無効にするには、次の 3 つの方法があります。

  • [自動メニューの 有効化] - 項目がワイヤードアップされているアクションに応答する適切なオブジェクトが見つかると NSMenu 、メニュー項目が有効になります。 たとえば、アクションへのフック copy: が組み込まれている上のテキスト ビューなどです。
  • カスタム アクションと validateMenuItem: - ウィンドウまたはビュー コントローラーのカスタム アクションに バインドされているメニュー項目の場合は、アクションを追加し、メニュー項目を validateMenuItem: 手動で有効または無効にすることができます。
  • [手動で有効にする ] メニュー - メニューの各項目を Enabled 個別に有効または無効にするように、それぞれの NSMenuItem プロパティを手動で設定します。

システムを選択するには、次のNSMenuプロパティを設定しますAutoEnablesItemstrue は自動 (既定の動作) であり、 false 手動です。

重要

手動メニュー有効化を使用する場合、AppKit クラス NSTextViewによって制御されるメニュー項目であっても、どのメニュー項目も自動的に更新されません。 コード内のすべての項目を手動で有効または無効にする必要があります。

validateMenuItem の使用

前述のように、ウィンドウまたはビュー コントローラーのカスタム アクションに バインドされているメニュー項目については、アクションを追加し、メニュー項目を validateMenuItem: 手動で有効または無効にすることができます。

次の例では、プロパティを Tag 使用して、アクションで有効または無効にするメニュー項目の種類を validateMenuItem: 、選択したテキスト NSTextViewの状態に基づいて決定します。 このプロパティは Tag 、各メニュー項目のインターフェイス ビルダーで設定されています。

Setting the Tag property

次のコードがビュー コントローラーに追加されました。

[Action("validateMenuItem:")]
public bool ValidateMenuItem (NSMenuItem item) {

    // Take action based on the menu item type
    // (As specified in its Tag)
    switch (item.Tag) {
    case 1:
        // Wrap menu items should only be available if
        // a range of text is selected
        return (TextEditor.SelectedRange.Length > 0);
    case 2:
        // Quote menu items should only be available if
        // a range is NOT selected.
        return (TextEditor.SelectedRange.Length == 0);
    }

    return true;
}

このコードが実行され、テキストが選択 NSTextViewされていない場合、2 つの折り返しメニュー項目は無効になります (ビュー コントローラー上のアクションにワイヤリングされている場合でも)。

Showing disabled items

テキストのセクションが選択され、メニューが再度開かれた場合、2 つの折り返しメニュー項目を使用できます。

Showing enabled items

コード内のメニュー項目の有効化と応答

前述のように、UI デザインに特定の Cocoa ユーザー インターフェイス要素 (テキスト フィールドなど) を追加するだけで、いくつかの既定のメニュー項目が有効になり、コードを記述しなくても自動的に機能します。 次に、Xamarin.Mac プロジェクトに独自の C# コードを追加してメニュー項目を有効にし、ユーザーが選択したときに機能を提供する方法を見てみましょう。

たとえば、ユーザーが [ファイル] メニューの [開く] 項目を使用してフォルダーを選択できるようにするとします。 これはアプリケーション全体の関数であり、Give ウィンドウまたは UI 要素に限定されないため、これを処理するコードをアプリケーション デリゲートに追加します。

Solution Pad、ファイルをAppDelegate.CSダブルクリックして編集用に開きます。

Selecting the app delegate

DidFinishLaunching メソッドの下に次のコードを追加します。

[Export ("openDocument:")]
void OpenDialog (NSObject sender)
{
    var dlg = NSOpenPanel.OpenPanel;
    dlg.CanChooseFiles = false;
    dlg.CanChooseDirectories = true;

    if (dlg.RunModal () == 1) {
        var alert = new NSAlert () {
            AlertStyle = NSAlertStyle.Informational,
            InformativeText = "At this point we should do something with the folder that the user just selected in the Open File Dialog box...",
            MessageText = "Folder Selected"
        };
        alert.RunModal ();
    }
}

ここでアプリケーションを実行し、[ファイル] メニューを開きます。

The File menu

[開く] メニュー項目が有効になっていることに注意してください。 これを選択すると、開いているダイアログが表示されます。

An open dialog

[開く] ボタンをクリックすると、アラート メッセージが表示されます。

An example dialog message

ここでの重要な行は[Export ("openDocument:")]、AppDelegate にアクションにopenDocument:応答するメソッドvoid OpenDialog (NSObject sender)があることを示NSMenuしています。 上記から覚えている場合は、[開く] メニュー項目は、インターフェイス ビルダーで既定でこのアクションに自動的にワイヤードアップされます。

Viewing the attached actions

次に、独自のメニュー、メニュー項目、アクションを作成し、コードでそれらに応答する方法を見てみましょう。

最近開いたメニューの操作

既定では、[ファイル] メニューには、ユーザーがアプリで開いた最後のいくつかのファイルを追跡する [最近開く] 項目が含まれています ベースの Xamarin.Mac アプリを作成する NSDocument 場合、このメニューは自動的に処理されます。 その他の種類の Xamarin.Mac アプリについては、このメニュー項目を手動で管理および応答する必要があります。

[最近使ったファイルを開く] メニューを手動で処理するには、まず、次のコマンドを使用して新しいファイルが開かれたか保存されたことを通知する必要があります。

// Add document to the Open Recent menu
NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

アプリが使用NSDocumentsしていない場合でも、ファイルの場所を持つファイルをNSDocumentControllerメソッドに送信NSUrlして[最近開く]メニューをメインNoteNewRecentDocumentURLSharedDocumentController使用します。

次に、アプリ デリゲートのメソッドをOpenFileオーバーライドして、[最近使ったファイルを開く] メニューからユーザーが選択したファイルを開く必要があります。 次に例を示します。

public override bool OpenFile (NSApplication sender, string filename)
{
    // Trap all errors
    try {
        filename = filename.Replace (" ", "%20");
        var url = new NSUrl ("file://"+filename);
        return OpenFile(url);
    } catch {
        return false;
    }
}

ファイルを開くことができる場合は戻 true り、それ以外の場合は戻り false 、ファイルを開けなかったという組み込みの警告がユーザーに表示されます。

[最近開く] メニューから返されるファイル名とパスにはスペースが含まれている可能性があるため、作成する前にこの文字を適切にエスケープするNSUrl必要があります。エラーが発生します。 これを行うには、次のコードを使用します。

filename = filename.Replace (" ", "%20");

最後に、ファイルを指すオブジェクトを作成 NSUrl し、アプリ デリゲートでヘルパー メソッドを使用して新しいウィンドウを開き、ファイルを読み込みます。

var url = new NSUrl ("file://"+filename);
return OpenFile(url);

すべてをまとめるために、AppDelegate.cs ファイルの実装例を見てみましょう。

using AppKit;
using Foundation;
using System.IO;
using System;

namespace MacHyperlink
{
    [Register ("AppDelegate")]
    public class AppDelegate : NSApplicationDelegate
    {
        #region Computed Properties
        public int NewWindowNumber { get; set;} = -1;
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void DidFinishLaunching (NSNotification notification)
        {
            // Insert code here to initialize your application
        }

        public override void WillTerminate (NSNotification notification)
        {
            // Insert code here to tear down your application
        }

        public override bool OpenFile (NSApplication sender, string filename)
        {
            // Trap all errors
            try {
                filename = filename.Replace (" ", "%20");
                var url = new NSUrl ("file://"+filename);
                return OpenFile(url);
            } catch {
                return false;
            }
        }
        #endregion

        #region Private Methods
        private bool OpenFile(NSUrl url) {
            var good = false;

            // Trap all errors
            try {
                var path = url.Path;

                // Is the file already open?
                for(int n=0; n<NSApplication.SharedApplication.Windows.Length; ++n) {
                    var content = NSApplication.SharedApplication.Windows[n].ContentViewController as ViewController;
                    if (content != null && path == content.FilePath) {
                        // Bring window to front
                        NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);
                        return true;
                    }
                }

                // Get new window
                var storyboard = NSStoryboard.FromName ("Main", null);
                var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

                // Display
                controller.ShowWindow(this);

                // Load the text into the window
                var viewController = controller.Window.ContentViewController as ViewController;
                viewController.Text = File.ReadAllText(path);
                viewController.SetLanguageFromPath(path);
                viewController.View.Window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
                viewController.View.Window.RepresentedUrl = url;

                // Add document to the Open Recent menu
                NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

                // Make as successful
                good = true;
            } catch {
                // Mark as bad file on error
                good = false;
            }

            // Return results
            return good;
        }
        #endregion

        #region actions
        [Export ("openDocument:")]
        void OpenDialog (NSObject sender)
        {
            var dlg = NSOpenPanel.OpenPanel;
            dlg.CanChooseFiles = true;
            dlg.CanChooseDirectories = false;

            if (dlg.RunModal () == 1) {
                // Nab the first file
                var url = dlg.Urls [0];

                if (url != null) {
                    // Open the document in a new window
                    OpenFile (url);
                }
            }
        }
        #endregion
    }
}

アプリの要件に基づいて、ユーザーが同じファイルを複数のウィンドウで同時に開く必要がない場合があります。 このサンプル アプリでは、ユーザーが既に開いているファイル ([最近使ったファイルを開く] メニュー項目または [開く] メニュー項目から) を選択した場合、そのファイルを含むウィンドウが前面に表示されます。

これを実現するために、ヘルパー メソッドで次のコードを使用しました。

var path = url.Path;

// Is the file already open?
for(int n=0; n<NSApplication.SharedApplication.Windows.Length; ++n) {
    var content = NSApplication.SharedApplication.Windows[n].ContentViewController as ViewController;
    if (content != null && path == content.FilePath) {
        // Bring window to front
        NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);
        return true;
    }
}

プロパティ内の ViewController ファイルへのパスを保持するようにクラスを Path 設計しました。 次に、アプリで現在開いているすべてのウィンドウをループ処理します。 ファイルがいずれかのウィンドウで既に開いている場合は、次を使用して他のすべてのウィンドウの前面に表示されます。

NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);

一致するものが見つからない場合は、ファイルが読み込まれた新しいウィンドウが開き、[最近使ったファイルを開く] メニューにファイルが表示されます。

// Get new window
var storyboard = NSStoryboard.FromName ("Main", null);
var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

// Display
controller.ShowWindow(this);

// Load the text into the window
var viewController = controller.Window.ContentViewController as ViewController;
viewController.Text = File.ReadAllText(path);
viewController.SetLanguageFromPath(path);
viewController.View.Window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
viewController.View.Window.RepresentedUrl = url;

// Add document to the Open Recent menu
NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

カスタム ウィンドウ アクションの操作

標準のメニュー項目に 事前に接続されている組み込みの First Responder アクションと同様に、新しいカスタム アクションを作成し、Interface Builder のメニュー項目に接続できます。

まず、アプリのいずれかのウィンドウ コントローラーでカスタム アクションを定義します。 次に例を示します。

[Action("defineKeyword:")]
public void defineKeyword (NSObject sender) {
    // Preform some action when the menu is selected
    Console.WriteLine ("Request to define keyword");
}

次に、Solution Pad でアプリのストーリーボード ファイルをダブルクリックして、Xcode のインターフェイス ビルダーで編集するために開きます。 アプリケーション シーンで最初のレスポンダー選択し、[属性インスペクター] に切り替えます。

The Attributes Inspector

属性インスペクター+下部にあるボタンをクリックして、新しいカスタム アクションを追加します。

Adding a new action

ウィンドウ コントローラーで作成したカスタム アクションと同じ名前を付けます。

Editing the action name

Control キーを押しながらメニュー項目をクリックし、アプリケーション シーンの下の最初のレスポンダードラッグします。 ポップアップ リストから、先ほど作成した新しいアクション (defineKeyword: この例では) を選択します。

Attaching an action

ストーリーボードへの変更を保存し、Visual Studio for Mac に戻って変更を同期します。 アプリを実行すると、カスタム アクションを接続したメニュー項目が (アクションが開いているウィンドウに基づいて) 自動的に有効または無効になり、メニュー項目を選択するとアクションが起動します。

Testing the new action

メニューの追加、編集、および削除

前のセクションで説明したように、Xamarin.Mac アプリケーションには、特定の UI コントロールが自動的にアクティブ化および応答する既定のメニューとメニュー項目があらかじめ用意されています。 また、これらの既定の項目を有効にして応答するコードをアプリケーションに追加する方法についても説明しました。

このセクションでは、不要なメニュー項目の削除、メニューの再構成、新しいメニュー、メニュー項目、アクションの追加について説明します。

Solution Pad で Main.storyboard ファイルを ダブルクリックして、編集用に開 きます。

Double-clicking the storyboard file to edit the UI in Xcode.

特定の Xamarin.Mac アプリケーションでは、既定 の [表示 ] メニューを使用しないため、削除します。 インターフェイス階層で、メインメニュー バーの一部である [表示] メニュー項目を選択します。

Selecting the View menu item

Delete キーまたは Backspace キーを押してメニューを削除します。 次に、[書式] メニューのすべての項目を使用する予定はありません。使用する項目をサブメニューの下から移動します。 インターフェイス階層、次のメニュー項目を選択します。

Highlighting multiple items

メニュー の下にある項目を、現在のサブメニューからドラッグします。

Dragging menu items to the parent menu

メニューは次のようになります。

The items in the new location

次に、[書式] メニューの下から [テキスト] サブメニューをドラッグし、[書式] メニューと [ウィンドウ] メニューの間の メイン メニュー バーに配置します。

The Text menu

[書式] メニューの下に戻り、[フォント] サブメニュー項目を削除しましょう。 次に、[書式] メニューを選択し、"Font" という名前を変更します。

The Font menu

次に、テキスト ビューのテキストが選択されたときに自動的に追加される定義済みのフレーズのカスタム メニューを作成しましょう。 ライブラリインスペクターの下部にある検索ボックスに、「メニュー」と入力します。これにより、すべてのメニュー UI 要素を簡単に見つけて操作できるようになります。

The Library Inspector

次に、メニューを作成するために次の操作を行います。

  1. ライブラリ インスペクターからメニュー バーの [テキスト] メニューと [ウィンドウ] メニューの間にメニュー項目ドラッグします。

    Selecting a new menu item in the Library

  2. 項目 "Phrases" の名前を変更します。

    Setting the menu name

  3. 次に、ライブラリインスペクターからメニュードラッグします。

    Selecting a menu from the Library

  4. 先ほど作成した新しいメニュー項目にメニューをドロップし、その名前を "Phrases" に変更します。

    Editing the menu name

  5. 次に、既定 の 3 つのメニュー項目 の名前を "Address"、"Date"、および "Greeting" に変更してみましょう。

    The Phrases menu

  6. ライブラリ インスペクターからメニュー項目ドラッグし、それを "Signature" と呼んで、4 番目メニュー項目を追加してみましょう。

    Editing the menu item name

  7. 変更をメニュー バーに保存します。

次に、新しいメニュー項目が C# コードに公開されるように、一連のカスタム アクションを作成してみましょう。 Xcode でアシスタント ビューに切り替えてみましょう。

Creating the required actions

次の操作を行います。

  1. [アドレス] メニュー項目から AppDelegate.h ファイルに Control キーを押しながらドラッグします。

  2. [接続の種類] を [アクション] に切り替えます。

    Selecting the action type

  3. "phraseAddress" という名前入力し、[接続] ボタンを押して新しいアクションを作成します。

    Configuring the action by entering a name.

  4. [日付]、[あいさつ]、[署名] メニュー項目に対して、上記の手順を繰り返します。

    The completed actions

  5. 変更をメニュー バーに保存します。

次に、コードからコンテンツを調整できるように、テキスト ビューの出口を作成する必要があります。 アシスタント エディターで ViewController.h ファイルを選択し、次の名前documentTextの新しいアウトレットを作成します。

Creating an outlet

Visual Studio for Mac に戻り、Xcode から変更を同期します。 次に、 ViewController.cs ファイルを編集し、次のようにします。

using System;

using AppKit;
using Foundation;

namespace MacMenus
{
    public partial class ViewController : NSViewController
    {
        #region Application Access
        public static AppDelegate App {
            get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
        }
        #endregion

        #region Computed Properties
        public override NSObject RepresentedObject {
            get {
                return base.RepresentedObject;
            }
            set {
                base.RepresentedObject = value;
                // Update the view, if already loaded.
            }
        }

        public string Text {
            get { return documentText.Value; }
            set { documentText.Value = value; }
        } 
        #endregion

        #region Constructors
        public ViewController (IntPtr handle) : base (handle)
        {
        }
        #endregion

        #region Override Methods
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            // Do any additional setup after loading the view.
        }

        public override void ViewWillAppear ()
        {
            base.ViewWillAppear ();

            App.textEditor = this;
        }

        public override void ViewWillDisappear ()
        {
            base.ViewDidDisappear ();

            App.textEditor = null;
        }
        #endregion
    }
}

これにより、クラスの外部にあるテキスト ビューのテキストが ViewController 公開され、ウィンドウがフォーカスを取得または失ったときにアプリ デリゲートに通知されます。 次に、 AppDelegate.cs ファイルを編集し、次のようにします。

using AppKit;
using Foundation;
using System;

namespace MacMenus
{
    [Register ("AppDelegate")]
    public partial class AppDelegate : NSApplicationDelegate
    {
        #region Computed Properties
        public ViewController textEditor { get; set;} = null;
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void DidFinishLaunching (NSNotification notification)
        {
            // Insert code here to initialize your application
        }

        public override void WillTerminate (NSNotification notification)
        {
            // Insert code here to tear down your application
        }
        #endregion

        #region Custom actions
        [Export ("openDocument:")]
        void OpenDialog (NSObject sender)
        {
            var dlg = NSOpenPanel.OpenPanel;
            dlg.CanChooseFiles = false;
            dlg.CanChooseDirectories = true;

            if (dlg.RunModal () == 1) {
                var alert = new NSAlert () {
                    AlertStyle = NSAlertStyle.Informational,
                    InformativeText = "At this point we should do something with the folder that the user just selected in the Open File Dialog box...",
                    MessageText = "Folder Selected"
                };
                alert.RunModal ();
            }
        }

        partial void phrasesAddress (Foundation.NSObject sender) {

            textEditor.Text += "Xamarin HQ\n394 Pacific Ave, 4th Floor\nSan Francisco CA 94111\n\n";
        }

        partial void phrasesDate (Foundation.NSObject sender) {

            textEditor.Text += DateTime.Now.ToString("D");
        }

        partial void phrasesGreeting (Foundation.NSObject sender) {

            textEditor.Text += "Dear Sirs,\n\n";
        }

        partial void phrasesSignature (Foundation.NSObject sender) {

            textEditor.Text += "Sincerely,\n\nKevin Mullins\nXamarin,Inc.\n";
        }
        #endregion
    }
}

ここでは、Interface Builder で定義した AppDelegate アクションとアウトレットを使用できるように、部分クラスを作成しました。 また、どのウィンドウが現在フォーカスされているかを追跡するためにも公開 textEditor します。

カスタム メニューとメニュー項目を処理するには、次のメソッドを使用します。

partial void phrasesAddress (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Xamarin HQ\n394 Pacific Ave, 4th Floor\nSan Francisco CA 94111\n\n";
}

partial void phrasesDate (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += DateTime.Now.ToString("D");
}

partial void phrasesGreeting (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Dear Sirs,\n\n";
}

partial void phrasesSignature (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Sincerely,\n\nKevin Mullins\nXamarin,Inc.\n";
}

アプリケーションを実行すると、[フレーズ] メニューのすべての項目がアクティブになり、選択するとテキスト ビューに give フレーズが追加されます。

An example of the app running

アプリケーション メニュー バーの操作の基本を説明したので、カスタム コンテキスト メニューの作成を見てみましょう。

コードからメニューを作成する

Xcode のインターフェイス ビルダーを使用してメニューとメニュー項目を作成するだけでなく、Xamarin.Mac アプリでコードからメニュー、サブメニュー、またはメニュー項目を作成、変更、または削除する必要がある場合があります。

次の例では、その場で動的に作成されるメニュー項目とサブメニューに関する情報を保持するクラスが作成されます。

using System;
using System.Collections.Generic;
using Foundation;
using AppKit;

namespace AppKit.TextKit.Formatter
{
    public class LanguageFormatCommand : NSObject
    {
        #region Computed Properties
        public string Title { get; set; } = "";
        public string Prefix { get; set; } = "";
        public string Postfix { get; set; } = "";
        public List<LanguageFormatCommand> SubCommands { get; set; } = new List<LanguageFormatCommand>();
        #endregion

        #region Constructors
        public LanguageFormatCommand () {

        }

        public LanguageFormatCommand (string title)
        {
            // Initialize
            this.Title = title;
        }

        public LanguageFormatCommand (string title, string prefix)
        {
            // Initialize
            this.Title = title;
            this.Prefix = prefix;
        }

        public LanguageFormatCommand (string title, string prefix, string postfix)
        {
            // Initialize
            this.Title = title;
            this.Prefix = prefix;
            this.Postfix = postfix;
        }
        #endregion
    }
}

メニューと項目の追加

このクラスを定義すると、次のルーチンはオブジェクトの LanguageFormatCommandコレクションを解析し、渡された既存のメニュー (Interface Builder で作成) の下部に追加することで、新しいメニューとメニュー項目を再帰的に作成します。

private void AssembleMenu(NSMenu menu, List<LanguageFormatCommand> commands) {
    NSMenuItem menuItem;

    // Add any formatting commands to the Formatting menu
    foreach (LanguageFormatCommand command in commands) {
        // Add separator or item?
        if (command.Title == "") {
            menuItem = NSMenuItem.SeparatorItem;
        } else {
            menuItem = new NSMenuItem (command.Title);

            // Submenu?
            if (command.SubCommands.Count > 0) {
                // Yes, populate submenu
                menuItem.Submenu = new NSMenu (command.Title);
                AssembleMenu (menuItem.Submenu, command.SubCommands);
            } else {
                // No, add normal menu item
                menuItem.Activated += (sender, e) => {
                    // Apply the command on the selected text
                    TextEditor.PerformFormattingCommand (command);
                };
            }
        }
        menu.AddItem (menuItem);
    }
}

空白Titleのプロパティを持つオブジェクトLanguageFormatCommandの場合、このルーチンは、メニュー セクション間に区切り記号メニュー項目 (薄い灰色の線) を作成します。

menuItem = NSMenuItem.SeparatorItem;

タイトルを指定すると、そのタイトルを含む新しいメニュー項目が作成されます。

menuItem = new NSMenuItem (command.Title);

オブジェクトに LanguageFormatCommandLanguageFormatCommand オブジェクトが含まれている場合は、サブメニューが作成され、そのメニューを AssembleMenu 構築するためにメソッドが再帰的に呼び出されます。

menuItem.Submenu = new NSMenu (command.Title);
AssembleMenu (menuItem.Submenu, command.SubCommands);

サブメニューがない新しいメニュー項目の場合、ユーザーが選択しているメニュー項目を処理するコードが追加されます。

menuItem.Activated += (sender, e) => {
    // Do something when the menu item is selected
    ...
};

メニュー作成のテスト

上記のすべてのコードを配置して、次のオブジェクトの LanguageFormatCommand コレクションが作成された場合:

// Define formatting commands
FormattingCommands.Add(new LanguageFormatCommand("Strong","**","**"));
FormattingCommands.Add(new LanguageFormatCommand("Emphasize","_","_"));
FormattingCommands.Add(new LanguageFormatCommand("Inline Code","`","`"));
FormattingCommands.Add(new LanguageFormatCommand("Code Block","```\n","\n```"));
FormattingCommands.Add(new LanguageFormatCommand("Comment","<!--","-->"));
FormattingCommands.Add (new LanguageFormatCommand ());
FormattingCommands.Add(new LanguageFormatCommand("Unordered List","* "));
FormattingCommands.Add(new LanguageFormatCommand("Ordered List","1. "));
FormattingCommands.Add(new LanguageFormatCommand("Block Quote","> "));
FormattingCommands.Add (new LanguageFormatCommand ());

var Headings = new LanguageFormatCommand ("Headings");
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 1","# "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 2","## "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 3","### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 4","#### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 5","##### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 6","###### "));
FormattingCommands.Add (Headings);

FormattingCommands.Add(new LanguageFormatCommand ());
FormattingCommands.Add(new LanguageFormatCommand("Link","[","]()"));
FormattingCommands.Add(new LanguageFormatCommand("Image","![](",")"));
FormattingCommands.Add(new LanguageFormatCommand("Image Link","[![](",")](LinkImageHere)"));

また、関数にAssembleMenu渡されたコレクション ([書式] メニューを基本として設定) は、次の動的メニューとメニュー項目が作成されます。

The new menu items in the running app

メニューと項目の削除

アプリのユーザー インターフェイスからメニューまたはメニュー項目を削除する必要がある場合は、削除する項目の NSMenu 0 から始まるインデックスを指定するだけで、クラスのメソッドを使用RemoveItemAtできます。

たとえば、上記のルーチンによって作成されたメニューとメニュー項目を削除するには、次のコードを使用できます。

public void UnpopulateFormattingMenu(NSMenu menu) {

    // Remove any additional items
    for (int n = (int)menu.Count - 1; n > 4; --n) {
        menu.RemoveItemAt (n);
    }
}

上記のコードの場合、最初の 4 つのメニュー項目は Xcode のインターフェイス ビルダーで作成され、アプリで使用可能な場所に配置されるため、動的に削除されません。

コンテキスト メニュー

コンテキスト メニューは、ユーザーがウィンドウ内の項目を右クリックまたは control キーを押しながらクリックしたときに表示されます。 既定では、macOS に組み込まれているいくつかの UI 要素には、コンテキスト メニュー (テキスト ビューなど) が既にアタッチされています。 ただし、ウィンドウに追加した UI 要素に対して独自のカスタム コンテキスト メニューを作成したい場合があります。

Xcode で Main.storyboard ファイルを編集し、デザインにウィンドウ ウィンドウを追加し、そのクラスIdentity Inspector"NSPanel" に設定し、ウィンドウ メニューに新しいアシスタント項目を追加して、Show Segue を使用して新しいウィンドウに添付してみましょう。

Setting the segue type in the Main dot storyboard file.

次の操作を行います。

  1. ライブラリインスペクタからパネルウィンドウにラベルドラッグし、そのテキストを「プロパティ」に設定します。

    Editing the label's value

  2. 次に、ライブラリインスペクタからビュー階層のビューコントローラにメニューをドラッグし、3つのデフォルトメニュー項目ドキュメント、テキストフォントの名前を変更します。

    The required menu items

  3. 次に、プロパティ ラベルからメニューに control キーを押しながらドラッグします

    Dragging to create a segue

  4. ポップアップ ダイアログでメニューを選択 します

    Setting the segue type by selecting menu from Outlets in the Label context menu.

  5. Identity Inspector から、ビュー コントローラーのクラスを "PanelViewController" に設定します。

    Setting the segue class

  6. 同期するには Visual Studio for Mac に戻り、インターフェイス ビルダーに戻ります。

  7. アシスタント エディターに切り替え、PanelViewController.h ファイルを選択します。

  8. 次の名前propertyDocumentの [ドキュメント] メニュー項目のアクションを作成します。

    Configuring the action named propertyDocument.

  9. 再メインメニュー項目に対してアクションの作成を繰り返します。

    Repeating actions for the remaining menu items.

  10. 最後に、次のプロパティ propertyLabelラベルのアウトレットを作成します。

    Configuring the outlet

  11. 変更を保存し、Visual Studio for Mac に戻って Xcode と同期します。

PanelViewController.cs ファイルを編集し、次のコードを追加します。

partial void propertyDocument (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Document";
}

partial void propertyFont (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Font";
}

partial void propertyText (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Text";
}

アプリケーションを実行し、パネルのプロパティ ラベルを右クリックすると、カスタム コンテキスト メニューが表示されます。 メニューから項目を選択すると、ラベルの値が変更されます。

The contextual menu running

次に、ステータス バー メニューの作成を見てみましょう。

ステータス バーのメニュー

ステータス バー メニューには、メニューやアプリケーションの状態を反映した画像など、ユーザーとの対話やフィードバックを提供するステータス メニュー項目のコレクションが表示されます。 アプリケーションがバックグラウンドで実行されている場合でも、アプリケーションのステータス バー メニューが有効になり、アクティブになります。 システム全体のステータス バーは、アプリケーション メニュー バーの右側にあり、macOS で現在使用できる唯一のステータス バーです。

AppDelegate.cs ファイルを編集し、メソッドをDidFinishLaunching次のようにしましょう。

public override void DidFinishLaunching (NSNotification notification)
{
    // Create a status bar menu
    NSStatusBar statusBar = NSStatusBar.SystemStatusBar;

    var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable);
    item.Title = "Text";
    item.HighlightMode = true;
    item.Menu = new NSMenu ("Text");

    var address = new NSMenuItem ("Address");
    address.Activated += (sender, e) => {
        PhraseAddress(address);
    };
    item.Menu.AddItem (address);

    var date = new NSMenuItem ("Date");
    date.Activated += (sender, e) => {
        PhraseDate(date);
    };
    item.Menu.AddItem (date);

    var greeting = new NSMenuItem ("Greeting");
    greeting.Activated += (sender, e) => {
        PhraseGreeting(greeting);
    };
    item.Menu.AddItem (greeting);

    var signature = new NSMenuItem ("Signature");
    signature.Activated += (sender, e) => {
        PhraseSignature(signature);
    };
    item.Menu.AddItem (signature);
}

NSStatusBar statusBar = NSStatusBar.SystemStatusBar; では、システム全体のステータス バーにアクセスできます。 var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable); は、新しいステータス バー項目を作成します。 そこからメニューといくつかのメニュー項目を作成し、先ほど作成したステータス バー項目にメニューを添付します。

アプリケーションを実行すると、新しいステータス バー項目が表示されます。 メニューから項目を選択すると、テキスト ビューのテキストが変更されます。

The status bar menu running

次に、カスタム ドック メニュー項目の作成を見てみましょう。

カスタム ドック メニュー

ユーザーがドックでアプリケーションのアイコンを右クリックまたは control キーを押しながらクリックすると、Mac アプリケーションのドック メニューが表示されます。

A custom dock menu

次の手順を実行して、アプリケーションのカスタム ドック メニューを作成しましょう。

  1. Visual Studio for Mac で、アプリケーションのプロジェクトを右クリックし、[新しいファイルの追加>]を選択します。新しいファイル ダイアログで、Xamarin.Mac の空のインターフェイス定義を選択し、[名前] に "DockMenu" を使用し、[新規] ボタンをクリックして新しい DockMenu.xib ファイルを作成します。>

    Adding an empty interface definition

  2. Solution Pad で DockMenu.xib ファイルをダブルクリックし、Xcode で編集するために開きます。 次の項目を含む新しいメニューを作成します:住所日付あいさつ、署名

    Laying out the UI

  3. 次に、上記の「メニューの追加、編集、削除」セクションでカスタム メニュー用に 作成した既存のアクションに新しいメニュー 項目を接続してみましょう。 接続インスペクターに切り替え、インターフェイス階層の最初のレスポンダー選択します 下にスクロールしてアクションを phraseAddress: 見つけます。 そのアクションの円から [アドレス] メニュー項目に行をドラッグします。

    Dragging a line to the Address menu item.

  4. 他のすべてのメニュー項目に対して繰り返し、対応するアクションに添付します。

    Repeating for other menu items attaching them to their corresponding actions.

  5. 次に、インターフェイス階層内の アプリケーション選択します。 接続インスペクター、コンセントの円dockMenuから先ほど作成したメニューに線をドラッグします。

    Dragging the wire up the outlet

  6. 変更を保存し、Visual Studio for Mac に戻って Xcode と同期します。

  7. Info.plist ファイルをダブルクリックして、編集用に開きます。

    Editing the Info.plist file

  8. 画面の 下部にある [ソース ] タブをクリックします。

    Selecting the Source view

  9. [新しいエントリの追加] をクリックし、緑色のプラスボタンをクリックし、プロパティ名を "AppleDockMenu" に設定し、値を "DockMenu" (拡張子のない新しい .xib ファイルの名前) に設定します。

    Adding the DockMenu item

ここで、アプリケーションを実行し、Dock でそのアイコンを右クリックすると、新しいメニュー項目が表示されます。

An example of the dock menu running

メニューからカスタム項目の 1 つを選択すると、テキスト ビューのテキストが変更されます。

ポップアップ ボタンとプルダウン リスト

ポップアップ ボタンには、選択した項目が表示され、ユーザーがクリックしたときに選択するオプションの一覧が表示されます。 プルダウン リストは、通常、現在のタスクのコンテキストに固有のコマンドを選択するために使用されるポップアップ ボタンの一種です。 どちらもウィンドウ内の任意の場所に表示できます。

次の手順を実行して、アプリケーションのカスタム ポップアップ ボタンを作成しましょう。

  1. Xcode で Main.storyboard ファイルを編集し、[ライブラリ インスペクター] から [コンテキスト メニュー] セクションで作成したパネル ウィンドウにポップアップ ボタンドラッグします。

    Adding a popup button

  2. 新しいメニュー項目を追加し、[ポップアップ] 内のアイテムのタイトルを [住所]、[日付]、[案内応答]、および [署名] に設定します

    Configuring the menu items

  3. 次に、上記の「メニューの追加、編集、削除」セクションでカスタム メニュー用に 作成した既存のアクションに、新しいメニュー 項目を接続してみましょう。 接続インスペクターに切り替え、インターフェイス階層の最初のレスポンダー選択します 下にスクロールしてアクションを phraseAddress: 見つけます。 そのアクションの円から [アドレス] メニュー項目に行をドラッグします。

    Dragging to wire up an action

  4. 他のすべてのメニュー項目に対して繰り返し、対応するアクションに添付します。

    All required actions

  5. 変更を保存し、Visual Studio for Mac に戻って Xcode と同期します。

アプリケーションを実行し、ポップアップから項目を選択すると、テキスト ビューのテキストが変更されます。

An example of the popup running

ポップアップ ボタンとまったく同じ方法で、プルダウン リストを作成して操作できます。 既存のアクションにアタッチする代わりに、[コンテキスト メニュー] セクションのコンテキスト メニューの場合と同様に、独自のカスタム アクションを作成できます。

まとめ

この記事では、Xamarin.Mac アプリケーションでのメニューとメニュー項目の操作について詳しく説明しました。 まず、アプリケーションのメニュー バーを調べ、次にコンテキスト メニューの作成を見て、次にステータス バーのメニューとカスタム ドック メニューを調べました。 最後に、ポップアップ メニューとプルダウン リストについて説明しました。