Xamarin.Mac のメニュー
この記事では、Xamarin.Mac アプリケーションでのメニューの使用について説明しています。 Xcode および Interface Builder でのメニュー項目とメニュー項目の作成と保守、およびプログラムによる操作について説明します。
Xamarin.Mac アプリケーションで C# と .NET を使用している場合、Objective-C と Xcode を使用する開発者が使用しているのと同じ Cocoa メニューにアクセスできます。 Xamarin.Mac は直接 Xcode と統合できるため、Xcode の Interface Builder を使用して、メニュー バー、メニュー、メニュー項目を作成および保守できます (または、必要に応じて C# コードで直接作成することも可能です)。
メニューは Mac アプリケーションのユーザー エクスペリエンスに不可欠な要素であり、一般的にユーザー インターフェイスのさまざまな部分に表示されます。
- アプリケーションのメニュー バー - これは、すべての Mac アプリケーションの画面の上部に表示されるメイン メニューです。
- コンテキスト メニュー - ユーザーがウィンドウ内の項目を右クリックする、または Ctrl キーを押しながらクリックすると表示されます。
- ステータス バー - これは、画面の上部 (メニュー バーの時計の左側) に表示され、項目が追加されると左側に拡大する、アプリケーション メニュー バーの右端にある領域です。
- Dock メニュー - ユーザーがアプリケーションのアイコンを右クリックするか、Ctrl キーを押しながらクリックする、またはユーザーがアイコンを左クリックしてマウス ボタンを押したままにすると表示される、ドック内の各アプリケーションのメニュー。
- ポップアップ ボタンとプルダウン リスト - ポップアップ ボタンには、選択した項目が表示され、ユーザーがクリックしたときに選択できるオプションの一覧が表示されます。 プルダウン リストは、現在のタスクのコンテキストに特定のコマンドを選択する目的で主に使用されるポップアップ ボタンの一種です。 どちらもウィンドウ内の任意の場所に表示できます。
この記事では、Xamarin.Mac アプリケーションでの Cocoa メニュー バー、メニュー、メニュー項目の操作の基本について説明します。 この記事で使う主要な概念と手法については、まず Hello Mac の記事、特に「Xcode と Interface Builder の概要」と「Outlet と Action」のセクションを参照することを強くお勧めします。
Xamarin.Mac Internals ドキュメントの C# クラス/メソッドの Objective-C への公開に関するセクションも参照することをお勧めします。C# クラスを Objective-C オブジェクトと UI 要素に結び付けるために使われる Register
および Export
属性について説明されています。
アプリケーションのメニュー バー
すべてのウィンドウに独自のメニュー バーをアタッチできる Windows OS 上で実行されているアプリケーションとは異なり、macOS で実行されているすべてのアプリケーションに、そのアプリケーション内のすべてのウィンドウに使用される画面上部に沿って並ぶ 1 つのメニュー バーがあります。
このメニュー バーの項目は、現在のコンテキスト、またはアプリケーションの状態とそのユーザー インターフェイスに基づいて、任意の時点でアクティブ化または非アクティブ化されます。 たとえば、ユーザーがテキスト フィールドを選択した場合、[Edit] メニューの項目 ([Copy] や [Cut] など) が有効になります。
Apple によると、すべての macOS アプリケーションに、アプリケーションのメニュー バーに表示される既定のメニューとメニュー項目の標準セットがあります。
- Apple メニュー - このメニューでは、実行中のアプリケーションに関係なく、ユーザーがいつでも使用できるシステム全体の項目にアクセスできます。 これらの項目は、開発者が変更することはできません。
- アプリ メニュー - このメニューには、アプリケーションの名前が太字で表示され、ユーザーが現在実行中のアプリケーションを識別するのに役立ちます。 アプリケーション全体に適用される項目が含まれており、特定のドキュメントやプロセス (アプリケーションの終了など) は含まれません。
- [File] メニュー - アプリケーションで使用するドキュメントを作成、開く、または保存するために使用する項目があります。 アプリケーションがドキュメント ベースでない場合は、このメニューの名前を変更または削除できます。
- [Edit] メニュー - アプリケーションのユーザー インターフェイスで要素を編集または変更するために使用される [Cut]、[Copy]、[Paste] などのコマンドを保持します。
- [Format] メニュー - アプリケーションがテキストを使用して動作する場合、このメニューには、そのテキストの書式設定を調整するコマンドが保持されます。
- [View] メニュー - アプリケーションのユーザー インターフェイスでのコンテンツの表示方法に影響するコマンドを保持します。
- アプリケーション固有のメニュー - アプリケーションに固有のメニュー (Web ブラウザーのブックマーク メニューなど) があります。 これらはバーの [View] メニューと [Window] メニューの間に表示されます。
- [Window] メニュー - アプリケーションでウィンドウを操作するためのコマンドと、現在開いているウィンドウの一覧が含まれます。
- ヘルプ メニュー - アプリケーションが画面上のヘルプを提供する場合は、ヘルプ メニューがバーの右端のメニューである必要があります。
アプリケーションメニューバーと標準メニューとメニュー項目の詳細については、Apple のヒューマン インターフェイス ガイドラインを参照してください。
既定のアプリケーション メニュー バー
前のセクションで説明したように、新しい Xamarin.Mac プロジェクトを作成するたびに、macOS アプリケーションが通常持つ一般的な項目を含む標準の既定のアプリケーション メニュー バーが自動的に表示されます。 アプリケーションの既定のメニュー バーは、アプリの UI のその他の部分に加えて、Solution Pad 内のプロジェクトの下の Main.storyboard ファイルで定義されます。
Main.storyboard ファイルをダブルクリックして Xcode の Interface Builder で編集用に開くと、メニュー エディター インターフェイスが表示されます。
ここから、[File] メニューの [Open] メニュー項目などの項目をクリックし、[Attributes Inspector] でそのプロパティを編集または調整できます。
メニューと項目の追加、編集、削除については、この記事の後半で説明します。 ここでは、既定で使用できるメニューおよびメニュー項目と、定義済みのアウトレットとアクションのセットを介してこれらがコードに自動的に公開されている方法を確認するだけです (詳細については、「Outlet と Action」ドキュメントを参照してください)。
たとえば、[Open] メニュー項目の [Connection Inspector] をクリックすると、これは openDocument:
アクションに自動的に関連付けられていることがわかります。
[Interface Hierarchy] で [First Responder] を選択し、[Connection Inspector] で下にスクロールすると、[Open] メニュー項目が接続されている openDocument:
アクションの定義が表示されます (アプリケーションに対するその他のいくつかの既定のアクションと一緒に表示され、それらにはコントロールに自動的に接続されたものとされていないものがあります)。
これは、なぜ重要ですか。 次のセクションでは、これらの自動的に定義されたアクションが、他の Cocoa ユーザー インターフェイス要素と連携して、メニュー項目を自動的に有効または無効にする方法と、項目の組み込み機能を提供する方法について説明します。
後で、これらの組み込みアクションを使用して、コードの項目を有効または無効にし、選択されたときに独自の機能を提供します。
組み込みのメニュー機能
UI 項目やコードを追加する前に、新しく作成した Xamarin.Mac アプリケーションを実行した場合は、一部の項目が自動的に関連付けられ、完全な機能が自動的に組み込まれた状態で有効になっていることがわかります ([App] メニューの [Quit] 項目など)。
一方で、[Cut]、[Copy]、[Paste] などの他のメニュー項目は、そうではありません。
アプリケーションを停止し、Solution Pad の Main.storyboard ファイルをダブルクリックして、Xcode の Interface Builder で編集用に開きます。 次に、[Text View] を [Library] から [Interface Editor] のウィンドウのビュー コントローラーにドラッグします。
[Constraint Editor] で、テキスト ビューをウィンドウの端にピン留めし、ウィンドウと一緒に拡大縮小する場所を設定します。これを実行するには、エディターの上部にある 4 つの赤い I ビームをすべてクリックし、[Add 4 Constraints] ボタンをクリックします。
変更をユーザー インターフェイス デザインに保存して Visual Studio for Mac に戻り、変更を Xamarin.Mac プロジェクトと同期します。 次に、アプリケーションを起動し、テキスト ビューにテキストを入力し、選択し、[Edit] メニューを開きます。
[Cut]、[Copy]、[Paste] の各項目が、どれにも 1 行のコードも記述せずに、自動的に有効になり、完全に機能していることに注目してください。
どうなっているのでしょうか? 既定のメニュー項目に関連付けられた組み込みの定義済みアクション (上に示すとおり)、macOS の一部である Cocoa ユーザー インターフェイス要素のほとんどが、特定のアクション (copy:
など) への組み込みフックを持つことを思い出してください。 そのため、それらがウィンドウに追加され、アクティブになり、選択されると、対応するメニュー項目またはそのアクションに関連付けられた項目が、自動的に有効になります。 ユーザーがそのメニュー項目を選択すると、UI 要素に組み込まれた機能が、すべて開発者の介入なしに呼び出されて実行されます。
メニューと項目の有効化と無効化
既定では、ユーザー イベントが発生するたびに、NSMenu
は、アプリケーションのコンテキストに基づいて、表示される各メニューおよびメニュー項目を自動的に有効および無効にします。 項目を有効または無効にするには、次の 3 つの方法があります。
- 自動メニューの有効化 -
NSMenu
が項目の関連付け先アクションに応答する適切なオブジェクトを見つけられると、メニュー項目が有効になります。 たとえば、上のテキスト ビューには、copy:
アクションへの組み込みフックがあります。 - カスタム アクションと validateMenuItem: - ウィンドウまたはビュー コントローラーのカスタム アクションにバインドされている任意のメニュー項目の場合は、
validateMenuItem:
アクションを追加し、メニュー項目を手動で有効または無効にできます。 - 手動でのメニューの有効化 - メニューの各項目を個別に有効または無効にするように、各
NSMenuItem
のEnabled
プロパティを手動で設定します。
システムを選択するには、NSMenu
の AutoEnablesItems
プロパティを設定します。 true
は自動 (既定の動作) であり、false
は手動です。
重要
手動でのメニューの有効化を使用する場合、NSTextView
などの AppKit クラスによって制御されるメニュー項目であっても、どのメニュー項目も自動的に更新されることはありません。 コード内のすべての項目を手動で有効または無効にする責任は、開発者にあります。
validateMenuItem の使用
前述のように、ウィンドウまたはビュー コントローラーのカスタム アクションにバインドされているメニュー項目に対し、validateMenuItem:
アクションを追加し、メニュー項目を手動で有効または無効にすることができます。
次の例では、Tag
プロパティが、NSTextView
で選択したテキストの状態に基づいて、validateMenuItem:
アクションで有効または無効にするメニュー項目の種類を決定するために使用されます。 Tag
プロパティは、各メニュー項目に対して Interface Builder で設定されています。
また、次のコードがビュー コントローラーに追加されました。
[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 つのラップ メニュー項目は、ビュー コントローラー上のアクションに関連付けられていても無効になります。
テキストのセクションが選択され、メニューが再度開かれた場合、2 つのラップ メニュー項目が使用できるようになります。
コード内のメニュー項目の有効化と応答
前述のように、UI デザインに特定の Cocoa ユーザー インターフェイス要素 (テキスト フィールドなど) を追加するだけで、いくつかの既定のメニュー項目が有効になり、コードを記述しなくても自動的に機能します。 次に、Xamarin.Mac プロジェクトに独自の C# コードを追加してメニュー項目を有効にし、ユーザーが選択したときに機能を提供する方法を見てみましょう。
たとえば、ユーザーが [File] メニューの [Open] 項目を使用してフォルダーを選択できるようにするとします。 これをアプリケーション全体の機能にし、特定のウィンドウまたは UI 要素に限定されないようにするために、これを処理するコードをアプリケーション デリゲートに追加します。
Solution Pad で AppDelegate.CS
ファイルをダブルクリックして、編集用に開きます。
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 ();
}
}
ここでアプリケーションを実行し、[File] メニューを開きます。
[Open] メニュー項目が有効になっていることに注意してください。 これを選択すると、開くダイアログが表示されます。
[Open] ボタンをクリックすると、アラート メッセージが表示されます。
ここでの重要な行は [Export ("openDocument:")]
であり、これは、AppDelegate に openDocument:
アクションに応答する void OpenDialog (NSObject sender)
メソッドがあることを NSMenu
示しています。 これまでの説明で、[Open] メニュー項目が、Interface Builder の既定でこのアクションに自動的に関連付けられていたことを思い出してください。
次に、独自のメニュー、メニュー項目、アクションを作成し、コードでそれらに応答する方法を見てみましょう。
最近開いたメニューの操作
既定では、[File] メニューには、ユーザーがアプリで開いた最後のいくつかのファイルを追跡する [Open Recent] 項目が含まれています。 NSDocument
ベースの Xamarin.Mac アプリを作成する場合、このメニューは自動的に処理されます。 その他の種類の Xamarin.Mac アプリについては、このメニュー項目を手動で管理し、応答する必要があります。
[Open Recent] メニューを手動で処理するには、まずこのメニューに対し、次を使用して、新しいファイルが開かれたか保存されたことを通知する必要があります。
// Add document to the Open Recent menu
NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);
アプリで NSDocuments
を使用していない場合でも、[Open Recent] メニューを維持するために NSDocumentController
を使用します。これには、ファイルの場所と一緒に NSUrl
を SharedDocumentController
の NoteNewRecentDocumentURL
メソッドに送信します。
次に、アプリ デリゲートの OpenFile
メソッドをオーバーライドして、[Open Recent] メニューからユーザーが選択した任意のファイルを開く必要があります。 次に例を示します。
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
を返し、ファイルを開けなかったという組み込みの警告がユーザーに表示されます。
[Open Recent] メニューから返されるファイル名とパスにはスペースが含まれている可能性があるため、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
}
}
アプリの要件に基づいて、ユーザーが同じファイルを複数のウィンドウで同時に開かないようにしたい場合があります。 このサンプル アプリでは、ユーザーが既に開いているファイルを選択した場合 ([Open Recent] または [Open] のいずれかのメニュー項目から)、そのファイルを含むウィンドウが前面に表示されます。
これを実現するために、ヘルパー メソッドで次のコードを使用しました。
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;
}
}
Path
プロパティ内のファイルへのパスを保持するように、ViewController
クラスを設計しました。 次は、アプリで現在開いているすべてのウィンドウをループ処理します。 ファイルがいずれかのウィンドウで既に開いている場合は、次を使用すると、そのウィンドウが他のすべてのウィンドウの前面に表示されます。
NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);
一致するものが見つからない場合は、ファイルが読み込まれた新しいウィンドウが開き、このファイルは [Open Recent] メニューに含められます。
// 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 の Interface Builder で編集するために開きます。 [Application Scene] の [First Responder] を選択し、[Attributes Inspector] に切り替えます。
[Attributes Inspector] の下部にある + ボタンをクリックして、新しいカスタム アクションを追加します。
これに、ウィンドウ コントローラーで作成したカスタム アクションと同じ名前を付けます。
Ctrl キーを押しながらメニュー項目からクリックし、[Application Scene] の下にある [First Responder] にドラッグします。 ポップアップ リストから、先ほど作成した新しいアクション (この例の defineKeyword:
) を選択します。
ストーリーボードへの変更内容を保存し、Visual Studio for Mac に戻って変更を同期します。 アプリを実行すると、カスタム アクションを接続したメニュー項目が、アクションが開いているウィンドウに基づいて自動的に有効または無効になり、メニュー項目を選択するとアクションが起動します。
メニューの追加、編集、削除
前のセクションで説明したように、Xamarin.Mac アプリケーションには、特定の UI コントロールが自動的にアクティブにし、応答する既定のメニューとメニュー項目があらかじめ用意されています。 また、これらの既定の項目を有効にして応答するコードをアプリケーションに追加する方法についても説明しました。
このセクションでは、不要なメニュー項目の削除、メニューの再構成、新しいメニュー、メニュー項目、アクションの追加について説明します。
Solution Pad で Main.storyboard ファイルをダブルクリックし、編集するために開きます。
ここでの特定の Xamarin.Mac アプリケーションでは、既定の [View] メニューを使用しないため、削除します。 [Interface Hierarchy] で、メイン メニュー バーに属する [View] メニュー項目を選択します。
Del キーまたは Backspace キーを押してメニューを削除します。 次に、[Format] メニュー内のすべての項目を使用するのではなく、これから使用する項目をサブメニューの下から移動します。 [Interface Hierarchy] で、次のメニュー項目を選択します。
親 [Menu] の下の項目を、現在それらが配置されているサブメニューからドラッグします。
メニューは次のようになります。
次に、[Format] メニューの下から [Text] サブメニューをドラッグし、[Format] メニューと [Window] メニューの間のメイン メニュー バーに配置します。
[Format] メニューの下に戻り、[Font] サブメニュー項目を削除します。 次に、[Format] メニューを選択し、「Font」という名前に変更します。
次に、定義済みのフレーズのカスタム メニューを作成し、このフレーズが、テキスト ビューのテキストが選択されたときに自動的に追加されるようにします。 [Library Inspector] の下部にある検索ボックスに、「メニュー」と入力します。これにより、すべてのメニュー UI 要素が簡単に見つかり操作しやすくなります。
次に、メニューを作成するために次の操作を行います。
[Menu Item] を [Library Inspector] から [Text] メニューと [Window] メニューの間のメニュー バーにドラッグします。
項目の名前を「Phrases」に変更します。
次に、[Library Inspector] から [Menu] をドラッグします。
[Menu] を先ほど作成した新しい [Menu Item] にドロップし、その名前を「Phrases」に変更します。
次に、既定の 3 つの [Menu Items] の名前を「Address」、「Date」、「Greeting」に変更します。
4 番目の [Menu Item] を追加します。これには、[Menu Item] を [Library Inspector] からドラッグし、「Signature」という名前を付けます。
変更をメニュー バーに保存します。
次に、新しいメニュー項目が C# コードに提示されるように、一連のカスタム アクションを作成します。 Xcode で [Assistant] ビューに切り替えます。
次の操作を行います。
Ctrl キーを押しながら [Address] メニュー項目を AppDelegate.h ファイルにドラッグします。
[接続] の種類を [アクション] に切り替えます。
[Name] に「phraseAddress」と入力し、[Connect] ボタンを押して新しいアクションを作成します。
上記の手順を [Date]、[Greeting]、[Signature] の各メニュー項目に対して繰り返します。
変更をメニュー バーに保存します。
次に、コードからコンテンツを調整できるように、テキスト ビューのアウトレットを作成する必要があります。 ViewController.h ファイルを [Assistant Editor] で選択し、documentText
という名前の新しいアウトレットを作成します。
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";
}
アプリケーションを実行すると、[Phrase] メニューのすべての項目がアクティブになり、選択されたときに、フレーズがテキスト ビューに追加されます。
アプリケーション メニュー バーの操作の基本を説明したので、次はカスタム コンテキスト メニューの作成を見てみましょう。
コードからのメニューの作成
Xcode の Interface Builder を使用してメニューとメニュー項目を作成するだけでなく、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);
LanguageFormatCommand
オブジェクトに子 LanguageFormatCommand
オブジェクトが含まれている場合は、サブメニューが作成され、そのメニューを構築するために 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
関数にそのコレクション ([Format] メニューが基本として設定されている) が渡され、次の動的メニューとメニュー項目が作成されます。
メニューと項目の削除
アプリのユーザー インターフェイスからメニューまたはメニュー項目を削除する必要がある場合は、NSMenu
クラスの RemoveItemAt
メソッドを使用して、削除する項目の 0 から始まるインデックスをこれに指定するだけ実行できます。
たとえば、上記のルーチンによって作成されたメニューとメニュー項目を削除するには、次のコードを使用できます。
public void UnpopulateFormattingMenu(NSMenu menu) {
// Remove any additional items
for (int n = (int)menu.Count - 1; n > 4; --n) {
menu.RemoveItemAt (n);
}
}
上記のコードの場合、最初の 4 つのメニュー項目は Xcode の Interface Builder で作成され、アプリで使用可能な場所に配置されるため、動的には削除されません。
コンテキスト メニュー
コンテキスト メニューは、ユーザーがウィンドウ内の項目を右クリックするか、Ctrl キーを押しながらクリックすると表示されます。 既定では、macOS に組み込まれているいくつかの UI 要素には、コンテキスト メニュー (テキスト ビューなど) が既にアタッチされています。 ただし、ウィンドウに追加した UI 要素に対し、独自のカスタム コンテキスト メニューを作成する場合があります。
Xcode で Main.storyboard ファイルを編集し、デザインに [Window] ウィンドウを追加し、[Identity Inspector] でその [Class] を "NSPanel" に設定し、新しい [Assistant] 項目を [Window] メニューに追加します。次に、[Show Segue] を使用して、これを新しいウィンドウにアタッチします。
次の操作を行います。
[Library Inspector] から [Label] を [Panel] ウィンドウにドラッグし、そのテキストを「Property」に設定します。
次に、[Library Inspector] から [Menu] を [View Hierarchy] の [View Controller] にドラッグし、3 つのデフォルト メニュー項目の名前を [Document]、[Text]、[Font] に変更します。
次に、[Property Label] から Ctrl キーを押しながら [Menu] にドラッグします。
ポップアップ ダイアログから [Menu] を選択します。
[Identity Inspector] から、Identity Inspector のクラスを「PanelViewController」に設定します。
同期するには Visual Studio for Mac に戻ってから、Interface Builder に戻ります。
[Assistant Editor] に切り替え、PanelViewController.h ファイルを選択します。
[Document] メニュー項目に
propertyDocument
という名前のアクションを作成します。残りのメニュー項目に対して、アクションの作成を繰り返します。
最後に、[Property Label] に
propertyLabel
という名前のアウトレットを作成します。変更内容を保存し、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";
}
アプリケーションを実行し、パネルのプロパティ ラベルを右クリックすると、カスタム コンテキスト メニューが表示されます。 メニューから項目を選択すると、ラベルの値が変更されます。
次に、ステータス バー メニューの作成を見てみましょう。
ステータス バー メニュー
ステータス バー メニューには、メニューやアプリケーションの状態を反映した画像など、ユーザーとの対話やフィードバックを提供するステータス メニュー項目のコレクションが表示されます。 アプリケーションがバックグラウンドで実行されている場合でも、アプリケーションのステータス バー メニューは有効になり、アクティブになります。 システム全体のステータス バーは、アプリケーション メニュー バーの右側にあり、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);
は、新しいステータス バー項目を作成します。 そこからメニューといくつかのメニュー項目を作成し、先ほど作成したステータス バー項目にメニューを添付します。
アプリケーションを実行すると、新しいステータス バー項目が表示されます。 メニューから項目を選択すると、テキスト ビューのテキストが変更されます。
次は、カスタム Dock メニュー項目の作成を見てみましょう。
カスタム Dock メニュー
ユーザーが Dock でアプリケーションのアイコンを右クリックするか、Ctrl キーを押しながらクリックすると、Mac アプリケーションの Dock メニューが表示されます。
次の手順を実行して、アプリケーションのカスタム Dock メニューを作成しましょう。
Visual Studio for Mac で、アプリケーションのプロジェクトを右クリックし、[Add]>[New File] を選択します新しいファイル ダイアログで、[Xamarin.Mac]>[Empty Interface Definition] を選択し、[Name] に "DockMenu" を使用し、[New] ボタンをクリックして、新しい DockMenu.xib ファイルを作成します。
Solution Pad で DockMenu.xib ファイルをダブルクリックし、Xcode で編集するために開きます。 新しい [Menu] に [Address]、[Date]、[Greeting]、[Signature] の各項目を持たせて作成します
次に、新しいメニュー項目を、上記の「メニューの追加、編集、削除」セクションでカスタム メニュー用に作成した既存のアクションに接続します。 [Connection Inspector] に切り替え、[Interface Hierarchy] の [First Responder] を選択します。 下にスクロールして
phraseAddress:
アクションを見つけます。 そのアクションの円から [Address] メニュー項目に線をドラッグします。他のすべてのメニュー項目に対して繰り返し、対応するアクションにアタッチします。
次に、[Interface Hierarchy] で [Application] を選択します。 [Connection Inspector] で、
dockMenu
アウトレットの円から先ほど作成したメニューに線をドラッグします。変更内容を保存し、Visual Studio for Mac に戻って Xcode と同期します。
Info.plist ファイルをダブルクリックして、編集用に開きます。
画面の下部にある [Source] タブをクリックします。
[Add new entry] をクリックし、緑色のプラス ボタンをクリックし、プロパティ名を "AppleDockMenu" に設定し、値を "DockMenu" (拡張子のない新しい .xib ファイルの名前) に設定します。
ここで、アプリケーションを実行し、Dock でそのアイコンを右クリックすると、新しいメニュー項目が表示されます。
メニューからカスタム項目の 1 つを選択すると、テキスト ビューのテキストが変更されます。
ポップアップ ボタンとプルダウン リスト
ポップアップ ボタンに、選択した項目が表示され、ユーザーがボタンをクリックしたときに選択するオプションの一覧が表示されます。 プルダウン リストは、現在のタスクのコンテキストに特定のコマンドを選択する目的で主に使用されるポップアップ ボタンの一種です。 どちらもウィンドウ内の任意の場所に表示できます。
次の手順を実行して、アプリケーションのカスタム ポップアップ ボタンを作成しましょう。
Xcode で Main.storyboard ファイルを編集し、[Library Inspector] から [Popup Button] を [Contextual Menus] セクションで作成した [Panel] ウィンドウにドラッグします。
新しいメニュー項目を追加し、[Popup] の [Items] のタイトルをそれぞれ [Address]、[Date]、[Greeting]、[Signature] に設定します
次に、新しいメニュー項目を、上記の「メニューの追加、編集、削除」セクションでカスタム メニュー用に作成した既存のアクションに接続します。 [Connection Inspector] に切り替え、[Interface Hierarchy] の [First Responder] を選択します。 下にスクロールして
phraseAddress:
アクションを見つけます。 そのアクションの円から [Address] メニュー項目に線をドラッグします。他のすべてのメニュー項目に対して繰り返し、対応するアクションにアタッチします。
変更内容を保存し、Visual Studio for Mac に戻って Xcode と同期します。
ここで、アプリケーションを実行し、ポップアップから項目を選択すると、テキスト ビューのテキストが変更されます。
プルダウン リストは、ポップアップ ボタンとまったく同じ方法で作成して操作できます。 既存のアクションにアタッチする代わりに、[Contextual Menus] セクションのコンテキスト メニューに対して実行したのと同様に、独自のカスタム アクションを作成できます。
まとめ
この記事では、Xamarin.Mac アプリケーションでのメニューとメニュー項目の操作について詳しく説明しました。 最初にアプリケーションのメニュー バーを調べ、次にコンテキスト メニューを作成し、次にステータス バーのメニューとカスタム Dock メニューについて調べました。 最後に、ポップアップ メニューとプルダウン リストについて説明しました。