次の方法で共有


Xamarin.Mac のダイアログ

Xamarin.Mac アプリケーションで C# と .NET を使用している場合、Objective-CXcode で開発者が使用しているのと同じダイアログおよびモーダル ウィンドウにアクセスできます。 Xamarin.Mac は直接 Xcode と統合できるため、Xcode の Interface Builder を使用して、モーダル ウィンドウを作成および管理できます (または、必要に応じて、C# コードで直接作成することもできます)。

ユーザー操作に応答してダイアログが表示され、通常はユーザーがアクションを完了する方法が提供されます。 ダイアログを閉じる前に、ユーザーからの応答が必要です。

ウィンドウは、モードレス状態 (複数のドキュメントを一度に開くことができるテキスト エディターなど) またはモーダル (アプリケーションを続行する前に閉じる必要がある [エクスポート] ダイアログなど) で使用できます。

[開く] ダイアログ ボックス

この記事では、Xamarin.Mac アプリケーションでのダイアログおよびモーダル ウィンドウの操作の基本について説明します。 この記事で使用する主要な概念と手法については、まず Hello Mac の記事、特に「Xcode と Interface Builder の概要」および「アウトレットとアクション」のセクションを参照することを強くお勧めします。

Xamarin.Mac Internals ドキュメントの「C# クラス/メソッドの Objective-C への公開」セクションも参照することをお勧めします。C# クラスを Objective-C オブジェクトと UI 要素に結び付けるために使われる Register および Export コマンドについて説明されています。

ダイアログの概要

ユーザー アクション (ファイルの保存など) に応答してダイアログが表示され、ユーザーがそのアクションを完了する方法が提供されます。 ダイアログを閉じる前に、ユーザーからの応答が必要です。

Apple によると、ダイアログを表示するには、次の 3 つの方法があります。

  • Document Modal - [Document Modal] ダイアログを使用すると、ダイアログが閉じられるまで、ユーザーは特定のドキュメント内で他の操作を行うことができなくなります。
  • App Modal - [App Modal] ダイアログを使用すると、ダイアログが閉じられるまで、ユーザーはアプリケーションを操作できなくなります。
  • Modeless - [Modeless] ダイアログを使用すると、ユーザーはドキュメント ウィンドウを操作しながらダイアログの設定を変更できます。

標準 NSWindow は、モーダルで表示することで、カスタマイズされたダイアログとして使用できます。

モーダル ウィンドウの例

ドキュメント モーダル ダイアログ シート

"シート" は、特定のドキュメント ウィンドウにアタッチされたモーダル ダイアログであり、ユーザーはダイアログを閉じるまでウィンドウを操作できなくなります。 シートは表示元のウィンドウにアタッチされ、1 つのウィンドウに対して一度に開くことができるシートは 1 つだけです。

モーダル シートの例

基本設定ウィンドウ

基本設定ウィンドウは、ユーザーが頻繁に変更しないアプリケーションの設定を含むモードレス ダイアログです。 基本設定ウィンドウには、多くの場合、ユーザーがさまざまな設定グループを切り替えることができるツール バーが含まれています。

[基本設定] ウィンドウの例

オープン ダイアログ

オープン ダイアログでは、ユーザーがアプリケーション内でアイテムを検索して開くための一貫性のある方法を提供します。

[開く] ダイアログ ボックス

macOS には、アプリケーションで表示できる標準の印刷およびページ設定ダイアログが用意されているため、ユーザーが使用するすべてのアプリケーションで一貫した印刷エクスペリエンスを実現できます。

印刷ダイアログは、次のようにフリー フローティング ダイアログ ボックスとして表示することも、

[印刷] ダイアログ ボックス

次のようにシートとして表示することもできます。

印刷シート

ページ設定ダイアログは、次のようにフリー フローティング ダイアログ ボックスとして表示することも、

[ページ設定] ダイアログ ボックス

次のようにシートとして表示することもできます。

[ページ設定] シート

保存ダイアログ

保存ダイアログでは、ユーザーはアプリケーションにアイテムを保存するための一貫性のある方法を提供します。 保存ダイアログには、最小 (折りたたみとも呼ばれます) 状態と

[保存] ダイアログ

展開状態の 2 つがあります。

展開された [保存] ダイアログ

最小保存ダイアログは、シートとして表示することもできます。

最小化された [保存] シート

展開保存ダイアログも同様です。

展開された [保存] シート

詳細については、Apple の OS X Human Interface Guidelinesダイアログに関するセクションを参照してください。

プロジェクトへのモーダル ウィンドウの追加

メイン ドキュメント ウィンドウとは別に、Xamarin.Mac アプリケーションでは、環境設定やインスペクター パネルなど、他の種類のウィンドウをユーザーに表示することが必要になる場合があります。

新しいウィンドウを追加するには、次の操作を行います:

  1. ソリューション エクスプローラーで、Xcode の Interface Builder で編集するために Main.storyboard ファイルを開きます。

  2. 新しいビュー コントローラーをデザイン サーフェイスにドラッグします。

    ライブラリからのビュー コントローラーの選択

  3. ID インスペクターで、クラス名として CustomDialogController を入力します。

    クラス名を CustomDialogController に設定。

  4. Visual Studio for Mac に戻り、Xcode と同期して CustomDialogController.h ファイルを作成できるようにします。

  5. Xcode に戻り、インターフェイスを設計します。

    Xcode での UI の設計

  6. アプリのメインウィンドウから新しいビュー コントローラーへのモーダル セグエを作成するには、ダイアログを開く UI 要素からダイアログのウィンドウに Control キーを押しながらドラッグします。 Identifier を割り当てます ModalSegue:

    モーダル セグエ

  7. アクションアウトレットを接続します。

    アクティビティの構成

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

CustomDialogController.cs ファイルを次のようにします。

using System;
using Foundation;
using AppKit;

namespace MacDialog
{
    public partial class CustomDialogController : NSViewController
    {
        #region Private Variables
        private string _dialogTitle = "Title";
        private string _dialogDescription = "Description";
        private NSViewController _presentor;
        #endregion

        #region Computed Properties
        public string DialogTitle {
            get { return _dialogTitle; }
            set { _dialogTitle = value; }
        }

        public string DialogDescription {
            get { return _dialogDescription; }
            set { _dialogDescription = value; }
        }

        public NSViewController Presentor {
            get { return _presentor; }
            set { _presentor = value; }
        }
        #endregion

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

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

            // Set initial title and description
            Title.StringValue = DialogTitle;
            Description.StringValue = DialogDescription;
        }
        #endregion

        #region Private Methods
        private void CloseDialog() {
            Presentor.DismissViewController (this);
        }
        #endregion

        #region Custom Actions
        partial void AcceptDialog (Foundation.NSObject sender) {
            RaiseDialogAccepted();
            CloseDialog();
        }

        partial void CancelDialog (Foundation.NSObject sender) {
            RaiseDialogCanceled();
            CloseDialog();
        }
        #endregion

        #region Events
        public EventHandler DialogAccepted;

        internal void RaiseDialogAccepted() {
            if (this.DialogAccepted != null)
                this.DialogAccepted (this, EventArgs.Empty);
        }

        public EventHandler DialogCanceled;

        internal void RaiseDialogCanceled() {
            if (this.DialogCanceled != null)
                this.DialogCanceled (this, EventArgs.Empty);
        }
        #endregion
    }
}

このコードでは、ダイアログのタイトルと説明を設定するいくつかのプロパティと、取り消された、または受け入れられたダイアログに対応するいくつかのイベントを公開します。

次に、ViewController.cs ファイルを編集し、PrepareForSegue メソッドをオーバーライドして、次のようにします:

public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on the segue name
    switch (segue.Identifier) {
    case "ModalSegue":
        var dialog = segue.DestinationController as CustomDialogController;
        dialog.DialogTitle = "MacDialog";
        dialog.DialogDescription = "This is a sample dialog.";
        dialog.DialogAccepted += (s, e) => {
            Console.WriteLine ("Dialog accepted");
            DismissViewController (dialog);
        };
        dialog.Presentor = this;
        break;
    }
}

このコードは、Xcode の Interface Builder で定義したセグエをダイアログに初期化し、タイトルと説明を設定します。 また、ユーザーがダイアログ ボックスで行う選択も処理します。

アプリケーションを実行し、カスタム ダイアログを表示できます。

[例] のダイアログ

Xamarin.Mac アプリケーションでのウィンドウの使用の詳細については、Microsoft の「Windows の操作」ドキュメントを参照してください。

カスタム シートの作成

"シート" は、特定のドキュメント ウィンドウにアタッチされたモーダル ダイアログであり、ユーザーはダイアログを閉じるまでウィンドウを操作できなくなります。 シートは表示元のウィンドウにアタッチされ、1 つのウィンドウに対して一度に開くことができるシートは 1 つだけです。

Xamarin.Mac でカスタム シートを作成するには、次の手順を実行します。

  1. ソリューション エクスプローラーで、Xcode の Interface Builder で編集するために Main.storyboard ファイルを開きます。

  2. 新しいビュー コントローラーをデザイン サーフェイスにドラッグします。

    ライブラリからのビュー コントローラーの選択

  3. ユーザー インターフェイスを設計します。

    UI デザイン

  4. メイン ウィンドウから新しいビュー コントローラーにシート セグエを作成します。

    シート セグエの種類の選択

  5. Identity Inspectorで、ビュー コントローラーの Class という名前を付けます SheetViewController

    クラス名を SheetViewController に設定。

  6. 必要なアウトレットアクションを定義します。

    必要なアウトレットとアクションの定義

  7. 変更内容を保存し、Visual Studio for Mac に戻って同期します。

次に、SheetViewController.cs ファイルを編集して、次のようにします。

using System;
using Foundation;
using AppKit;

namespace MacDialog
{
    public partial class SheetViewController : NSViewController
    {
        #region Private Variables
        private string _userName = "";
        private string _password = "";
        private NSViewController _presentor;
        #endregion

        #region Computed Properties
        public string UserName {
            get { return _userName; }
            set { _userName = value; }
        }

        public string Password {
            get { return _password;}
            set { _password = value;}
        }

        public NSViewController Presentor {
            get { return _presentor; }
            set { _presentor = value; }
        }
        #endregion

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

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

            // Set initial values
            NameField.StringValue = UserName;
            PasswordField.StringValue = Password;

            // Wireup events
            NameField.Changed += (sender, e) => {
                UserName = NameField.StringValue;
            };
            PasswordField.Changed += (sender, e) => {
                Password = PasswordField.StringValue;
            };
        }
        #endregion

        #region Private Methods
        private void CloseSheet() {
            Presentor.DismissViewController (this);
        }
        #endregion

        #region Custom Actions
        partial void AcceptSheet (Foundation.NSObject sender) {
            RaiseSheetAccepted();
            CloseSheet();
        }

        partial void CancelSheet (Foundation.NSObject sender) {
            RaiseSheetCanceled();
            CloseSheet();
        }
        #endregion

        #region Events
        public EventHandler SheetAccepted;

        internal void RaiseSheetAccepted() {
            if (this.SheetAccepted != null)
                this.SheetAccepted (this, EventArgs.Empty);
        }

        public EventHandler SheetCanceled;

        internal void RaiseSheetCanceled() {
            if (this.SheetCanceled != null)
                this.SheetCanceled (this, EventArgs.Empty);
        }
        #endregion
    }
}

次に、ViewController.cs ファイルを編集し、PrepareForSegue メソッドを編集して、次のようにします:

public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on the segue name
    switch (segue.Identifier) {
    case "ModalSegue":
        var dialog = segue.DestinationController as CustomDialogController;
        dialog.DialogTitle = "MacDialog";
        dialog.DialogDescription = "This is a sample dialog.";
        dialog.DialogAccepted += (s, e) => {
            Console.WriteLine ("Dialog accepted");
            DismissViewController (dialog);
        };
        dialog.Presentor = this;
        break;
    case "SheetSegue":
        var sheet = segue.DestinationController as SheetViewController;
        sheet.SheetAccepted += (s, e) => {
            Console.WriteLine ("User Name: {0} Password: {1}", sheet.UserName, sheet.Password);
        };
        sheet.Presentor = this;
        break;
    }
}

アプリケーションを実行してシートを開くと、ウィンドウにアタッチされます。

[例] のシート

基本設定ダイアログの作成

Interface Builder で基本設定ビューをレイアウトする前に、基本設定の切り替えを処理するカスタム セグエの種類を追加する必要があります。 プロジェクトに新しいクラスを追加して、ReplaceViewSeque という名前を付けます。 クラスを編集し、次のようにします。

using System;
using AppKit;
using Foundation;

namespace MacWindows
{
    [Register("ReplaceViewSeque")]
    public class ReplaceViewSeque : NSStoryboardSegue
    {
        #region Constructors
        public ReplaceViewSeque() {

        }

        public ReplaceViewSeque (string identifier, NSObject sourceController, NSObject destinationController) : base(identifier,sourceController,destinationController) {

        }

        public ReplaceViewSeque (IntPtr handle) : base(handle) {
        }

        public ReplaceViewSeque (NSObjectFlag x) : base(x) {
        }
        #endregion

        #region Override Methods
        public override void Perform ()
        {
            // Cast the source and destination controllers
            var source = SourceController as NSViewController;
            var destination = DestinationController as NSViewController;

            // Is there a source?
            if (source == null) {
                // No, get the current key window
                var window = NSApplication.SharedApplication.KeyWindow;

                // Swap the controllers
                window.ContentViewController = destination;

                // Release memory
                window.ContentViewController?.RemoveFromParentViewController ();
            } else {
                // Swap the controllers
                source.View.Window.ContentViewController = destination;

                // Release memory
                source.RemoveFromParentViewController ();
            }

        }
        #endregion

    }

}

カスタム セグエを作成したら、基本設定を処理するための新しいウィンドウを Xcode の Interface Builder に追加できます。

新しいウィンドウを追加するには、次の操作を行います:

  1. ソリューション エクスプローラーで、Xcode の Interface Builder で編集するために Main.storyboard ファイルを開きます。

  2. 新しいウィンドウ コントローラーをデザイン サーフェイスにドラッグします。

    ライブラリからウィンドウ コントローラーを選択する

  3. メニュー バー デザイナーの近くにウィンドウを配置します。

    新しいウィンドウの追加

  4. 基本設定ビューにはタブが存在するため、アタッチされたビュー コントローラのコピーを作成します。

    必要なビュー コントローラーの追加

  5. ライブラリから新しいツール バー コントローラーをドラッグします。

    ライブラリからツール バー コントローラーを選択する

  6. それを、デザイン サーフェイスのウィンドウにドロップします。

    新しいツール バー コントローラーの追加

  7. ツール バーのデザインをレイアウトします。

    ツール バーをレイアウトする

  8. Control キーを押しながらクリックして、各ツール バー ボタンから上で作成したビューにドラッグします。 カスタム セグエの種類を選択します。

    カスタム セグエ型の設定。

  9. 新しいセグエを選択し、[Class]ReplaceViewSegue に設定します。

    セグエ クラスの設定

  10. デザイン サーフェイスのメニュー バー デザイナーで、[Application] メニューから [Preferences...] を選択し、Control キーを押しながらクリックし、[Preferences] ウィンドウにドラッグして、[Show] セグエを作成します。

    環境設定を環境設定ウィンドウにドラッグしてセグエの種類を設定。

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

コードを実行し、[Application] メニューから [Preferences...] を選択すると、ウィンドウが表示されます。

プロファイルという単語を表示する [環境設定] ウィンドウの例。

ウィンドウとツール バーの操作の詳細については、Microsoft のウィンドウツール バーに関するドキュメントを参照してください。

基本設定の保存と読み込み

一般的な macOS アプリでは、ユーザーがアプリのユーザー設定に変更を加えると、それらの変更は自動的に保存されます。 Xamarin.Mac アプリでこれを処理する最も簡単な方法は、すべてのユーザー設定を管理し、システム全体で共有するための 1 つのクラスを作成することです。

まず、新しい AppPreferences クラスをプロジェクトに追加し、NSObject から継承します。 基本設定は、データ バインディングとキー値コーディングを使用するように設計されています。これにより、基本設定フォームを作成および保守するプロセスが大幅に簡単になります。 基本設定は少量の単純なデータ型で構成されるため、組み込みの NSUserDefaults を使用して値を格納および取得します。

AppPreferences.cs ファイルを編集し、次のようにします。

using System;
using Foundation;
using AppKit;

namespace SourceWriter
{
    [Register("AppPreferences")]
    public class AppPreferences : NSObject
    {
        #region Computed Properties
        [Export("DefaultLanguage")]
        public int DefaultLanguage {
            get {
                var value = LoadInt ("DefaultLanguage", 0);
                return value;
            }
            set {
                WillChangeValue ("DefaultLanguage");
                SaveInt ("DefaultLanguage", value, true);
                DidChangeValue ("DefaultLanguage");
            }
        }

        [Export("SmartLinks")]
        public bool SmartLinks {
            get { return LoadBool ("SmartLinks", true); }
            set {
                WillChangeValue ("SmartLinks");
                SaveBool ("SmartLinks", value, true);
                DidChangeValue ("SmartLinks");
            }
        }

        // Define any other required user preferences in the same fashion
        ...

        [Export("EditorBackgroundColor")]
        public NSColor EditorBackgroundColor {
            get { return LoadColor("EditorBackgroundColor", NSColor.White); }
            set {
                WillChangeValue ("EditorBackgroundColor");
                SaveColor ("EditorBackgroundColor", value, true);
                DidChangeValue ("EditorBackgroundColor");
            }
        }
        #endregion

        #region Constructors
        public AppPreferences ()
        {
        }
        #endregion

        #region Public Methods
        public int LoadInt(string key, int defaultValue) {
            // Attempt to read int
            var number = NSUserDefaults.StandardUserDefaults.IntForKey(key);

            // Take action based on value
            if (number == null) {
                return defaultValue;
            } else {
                return (int)number;
            }
        }

        public void SaveInt(string key, int value, bool sync) {
            NSUserDefaults.StandardUserDefaults.SetInt(value, key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }

        public bool LoadBool(string key, bool defaultValue) {
            // Attempt to read int
            var value = NSUserDefaults.StandardUserDefaults.BoolForKey(key);

            // Take action based on value
            if (value == null) {
                return defaultValue;
            } else {
                return value;
            }
        }

        public void SaveBool(string key, bool value, bool sync) {
            NSUserDefaults.StandardUserDefaults.SetBool(value, key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }

        public string NSColorToHexString(NSColor color, bool withAlpha) {
            //Break color into pieces
            nfloat red=0, green=0, blue=0, alpha=0;
            color.GetRgba (out red, out green, out blue, out alpha);

            // Adjust to byte
            alpha *= 255;
            red *= 255;
            green *= 255;
            blue *= 255;

            //With the alpha value?
            if (withAlpha) {
                return String.Format ("#{0:X2}{1:X2}{2:X2}{3:X2}", (int)alpha, (int)red, (int)green, (int)blue);
            } else {
                return String.Format ("#{0:X2}{1:X2}{2:X2}", (int)red, (int)green, (int)blue);
            }
        }

        public NSColor NSColorFromHexString (string hexValue)
        {
            var colorString = hexValue.Replace ("#", "");
            float red, green, blue, alpha;

            // Convert color based on length
            switch (colorString.Length) {
            case 3 : // #RGB
                red = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(0, 1)), 16) / 255f;
                green = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(1, 1)), 16) / 255f;
                blue = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(2, 1)), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, 1.0f);
            case 6 : // #RRGGBB
                red = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
                green = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
                blue = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, 1.0f);
            case 8 : // #AARRGGBB
                alpha = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
                red = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
                green = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
                blue = Convert.ToInt32(colorString.Substring(6, 2), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, alpha);
            default :
                throw new ArgumentOutOfRangeException(string.Format("Invalid color value '{0}'. It should be a hex value of the form #RBG, #RRGGBB or #AARRGGBB", hexValue));
            }
        }

        public NSColor LoadColor(string key, NSColor defaultValue) {

            // Attempt to read color
            var hex = NSUserDefaults.StandardUserDefaults.StringForKey(key);

            // Take action based on value
            if (hex == null) {
                return defaultValue;
            } else {
                return NSColorFromHexString (hex);
            }
        }

        public void SaveColor(string key, NSColor color, bool sync) {
            // Save to default
            NSUserDefaults.StandardUserDefaults.SetString(NSColorToHexString(color,true), key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }
        #endregion
    }
}

このクラスには、NSUserDefaults の操作を作業を容易にするために、SaveIntLoadIntSaveColorLoadColor などのいくつかのヘルパー ルーチンが含まれています。 また、NSUserDefaults には NSColors を処理するための組み込みの方法がないため、NSColorToHexString および NSColorFromHexString メソッドを使用して、簡単に格納および取得できる Web ベースの 16 進文字列 (#RRGGBBAA、ここで AA はアルファ透明度) に色を変換します。

AppDelegate.cs ファイルで、アプリ全体で使用される AppPreferences オブジェクトのインスタンスを作成します。

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

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

        public AppPreferences Preferences { get; set; } = new AppPreferences();
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion

        ...

基本設定を基本設定ビューに関連付ける

次に、Preference クラスを、上で作成した基本設定ウィンドウとビューの UI 要素に関連付けます。 Interface Builder で、基本設定ビュー コントローラーを選択し、ID インスペクターに切り替えて、コントローラーのカスタム クラスを作成します。

ID インスペクター

Visual Studio for Mac に戻って変更内容を同期し、新しく作成したクラスを編集のために開きます。 クラスを次のようにします。

using System;
using Foundation;
using AppKit;

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

        #region Computed Properties
        [Export("Preferences")]
        public AppPreferences Preferences {
            get { return App.Preferences; }
        }
        #endregion

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

このクラスでは、ここで次の 2 つのことを行っていることに注意してください。1 つ目は、AppDelegate へのアクセスを容易にするヘルパー App プロパティがあります。 2 つ目は、Preferences プロパティは、このビューに配置された UI コントロールとのデータ バインディングのためにグローバル AppPreferences クラスを公開しています。

次に、ストーリーボード ファイルをダブルクリックして Interface Builder で再度開きます (上で行った変更を確認します)。 基本設定インターフェイスをビルドするために必要な UI コントロールをビューにドラッグします。 コントロールごとに、Binding Inspector に切り替え、AppPreference クラスの個々のプロパティにバインドします。

バインド インスペクター

必要なすべてのパネル (ビュー コントローラー) と基本設定プロパティについて、上記の手順を繰り返します。

開いているすべてのウィンドウに基本設定の変更を適用する

前述のように、一般的な macOS アプリでは、ユーザーがアプリのユーザー設定のいずれかに変更を加えると、それらの変更が自動的に保存され、ユーザーがアプリケーションで開いている可能性があるすべてのウィンドウに適用されます。

アプリの基本設定とウィンドウを慎重に計画および設計することで、エンド ユーザーは最小限のコーディング作業でこのプロセスをにスムーズかつ透過的に行えるようになります。

App Preferences を使用するウィンドウの場合は、次のヘルパー プロパティをコンテンツ ビュー コントローラーに追加して、AppDelegate へのアクセスを容易にします。

#region Application Access
public static AppDelegate App {
    get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
}
#endregion

次に、ユーザーの設定に基づいてコンテンツまたは動作を構成するクラスを追加します。

public void ConfigureEditor() {

    // General Preferences
    TextEditor.AutomaticLinkDetectionEnabled = App.Preferences.SmartLinks;
    TextEditor.AutomaticQuoteSubstitutionEnabled = App.Preferences.SmartQuotes;
    ...

}

ユーザーの設定に準拠していることを確認するために、ウィンドウが最初に開かれたときに構成メソッドを呼び出す必要があります。

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

    // Configure editor from user preferences
    ConfigureEditor ();
    ...
}

次に、AppDelegate.cs ファイルを編集し、次のメソッドを追加して、開いているすべてのウィンドウに基本設定の変更を適用します。

public void UpdateWindowPreferences() {

    // Process all open windows
    for(int n=0; n<NSApplication.SharedApplication.Windows.Length; ++n) {
        var content = NSApplication.SharedApplication.Windows[n].ContentViewController as ViewController;
        if (content != null ) {
            // Reformat all text
            content.ConfigureEditor ();
        }
    }

}

次に、PreferenceWindowDelegate クラスをプロジェクトに追加し、次のようにします。

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

namespace SourceWriter
{
    public class PreferenceWindowDelegate : NSWindowDelegate
    {
        #region Application Access
        public static AppDelegate App {
            get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
        }
        #endregion

        #region Computed Properties
        public NSWindow Window { get; set;}
        #endregion

        #region constructors
        public PreferenceWindowDelegate (NSWindow window)
        {
            // Initialize
            this.Window = window;

        }
        #endregion

        #region Override Methods
        public override bool WindowShouldClose (Foundation.NSObject sender)
        {

            // Apply any changes to open windows
            App.UpdateWindowPreferences();

            return true;
        }
        #endregion
    }
}

これにより、基本設定ウィンドウを閉じたときに、開いているすべてのウィンドウに基本設定の変更が送信されます。

最後に、基本設定ウィンドウ コントローラーを編集し、上記で作成したデリゲートを追加します。

using System;
using Foundation;
using AppKit;

namespace SourceWriter
{
    public partial class PreferenceWindowController : NSWindowController
    {
        #region Constructors
        public PreferenceWindowController (IntPtr handle) : base (handle)
        {
        }
        #endregion

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

            // Initialize
            Window.Delegate = new PreferenceWindowDelegate(Window);
            Toolbar.SelectedItemIdentifier = "General";
        }
        #endregion
    }
}

これらの変更がすべて適切に行われると、ユーザーがアプリの基本設定を編集し、基本設定ウィンドウを閉じると、すべての開いているウィンドウに変更が適用されます。

他のいくつかの開いているウィンドウと共に表示される [環境設定] ウィンドウの例。

オープン ダイアログ

オープン ダイアログでは、ユーザーがアプリケーション内でアイテムを検索して開くための一貫性のある方法を提供します。 Xamarin.Mac アプリケーションでオープン ダイアログを表示するには、次のコードを使用します。

var dlg = NSOpenPanel.OpenPanel;
dlg.CanChooseFiles = true;
dlg.CanChooseDirectories = false;
dlg.AllowedFileTypes = new string[] { "txt", "html", "md", "css" };

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

    if (url != null) {
        var path = url.Path;

        // Create a new window to hold the text
        var newWindowController = new MainWindowController ();
        newWindowController.Window.MakeKeyAndOrderFront (this);

        // Load the text into the window
        var window = newWindowController.Window as MainWindow;
        window.Text = File.ReadAllText(path);
        window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
        window.RepresentedUrl = url;

    }
}

上記のコードでは、新しいドキュメント ウィンドウを開き、ファイルのコンテンツを表示します。 このコードは、アプリケーションで必要な関数に置き換える必要があります。

次のプロパティは、NSOpenPanel の使用時に使用できます。

  • CanChooseFiles - true の場合、ユーザーはファイルを選択できます。
  • CanChooseDirectories - true の場合、ユーザーはディレクトリを選択できます。
  • AllowsMultipleSelection - true の場合、ユーザーは一度に複数のファイルを選択できます。
  • ResolveAliases - true の場合、別名を選択すると、元のファイルのパスに解決されます。
  • AllowedFileTypes - ユーザーが拡張子または UTI として選択できるファイルの種類の文字列配列です。 既定値は null で、任意のファイルを開くことができます。

RunModal () メソッドは、オープン ダイアログを表示し、ユーザーが (プロパティで指定されている) ファイルまたはディレクトリを選択できるようにし、ユーザーが [Open] ボタンをクリックした場合に 1 を返します。

オープン ダイアログは、ユーザーが選択したファイルまたはディレクトリを、URL プロパティで URL の配列として返します。

プログラムを実行し、[File] メニューから [Open...] i項目を選択すると、次の内容が表示されます。

[開く] ダイアログ ボックス

印刷およびページ設定ダイアログ

macOS には、アプリケーションで表示できる標準の印刷およびページ設定ダイアログが用意されているため、ユーザーが使用するすべてのアプリケーションで一貫した印刷エクスペリエンスを実現できます。

次のコードは、標準の印刷ダイアログを表示します。

public bool ShowPrintAsSheet { get; set;} = true;
...

[Export ("showPrinter:")]
void ShowDocument (NSObject sender) {
    var dlg = new NSPrintPanel();

    // Display the print dialog as dialog box
    if (ShowPrintAsSheet) {
        dlg.BeginSheet(new NSPrintInfo(),this,this,null,new IntPtr());
    } else {
        if (dlg.RunModalWithPrintInfo(new NSPrintInfo()) == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to print the document here...",
                MessageText = "Print Document",
            };
            alert.RunModal ();
        }
    }
}

ShowPrintAsSheet プロパティを false に設定し、アプリケーションを実行して印刷ダイアログを表示すると、次の内容が表示されます。

[印刷] ダイアログ ボックス

ShowPrintAsSheet プロパティを true に設定し、アプリケーションを実行して印刷ダイアログを表示すると、次の内容が表示されます。

印刷シート

次のコードは、ページ レイアウト ダイアログを表示します。

[Export ("showLayout:")]
void ShowLayout (NSObject sender) {
    var dlg = new NSPageLayout();

    // Display the print dialog as dialog box
    if (ShowPrintAsSheet) {
        dlg.BeginSheet (new NSPrintInfo (), this);
    } else {
        if (dlg.RunModal () == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to print the document here...",
                MessageText = "Print Document",
            };
            alert.RunModal ();
        }
    }
}

ShowPrintAsSheet プロパティを false に設定し、アプリケーションを実行して印刷レイアウト ダイアログを表示すると、次の内容が表示されます。

[ページ設定] ダイアログ ボックス

ShowPrintAsSheet プロパティを true に設定し、アプリケーションを実行して印刷レイアウト ダイアログを表示すると、次の内容が表示されます。

[ページ設定] シート

印刷およびページ設定ダイアログの操作の詳細については、Apple の NSPrintPanel および NSPageLayout に関するドキュメントを参照してください。

保存ダイアログ

保存ダイアログでは、ユーザーはアプリケーションにアイテムを保存するための一貫性のある方法を提供します。

次のコードは、標準の保存ダイアログを表示します。

public bool ShowSaveAsSheet { get; set;} = true;
...

[Export("saveDocumentAs:")]
void ShowSaveAs (NSObject sender)
{
    var dlg = new NSSavePanel ();
    dlg.Title = "Save Text File";
    dlg.AllowedFileTypes = new string[] { "txt", "html", "md", "css" };

    if (ShowSaveAsSheet) {
        dlg.BeginSheet(mainWindowController.Window,(result) => {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to save the document here...",
                MessageText = "Save Document",
            };
            alert.RunModal ();
        });
    } else {
        if (dlg.RunModal () == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to save the document here...",
                MessageText = "Save Document",
            };
            alert.RunModal ();
        }
    }

}

AllowedFileTypes プロパティは、ユーザーがファイルを保存するために選択できるファイルの種類の文字列配列です。 ファイルの種類は、拡張子または UTI として指定できます。 既定値は null で、任意のファイルの種類を使用できます。

ShowSaveAsSheet プロパティを false に設定し、アプリケーションを実行して [File] メニューから [Save As...] を選択すると、次の内容が表示されます。

[保存] ダイアログ ボックス

ユーザーはダイアログを展開できます。

展開された [保存] ダイアログ ボックス

ShowSaveAsSheet プロパティを true に設定し、アプリケーションを実行して [File] メニューから [Save As...] を選択すると、次の内容が表示されます。

[保存] シート

ユーザーはダイアログを展開できます。

展開された [保存] シート

保存ダイアログの操作の詳細については、Apple の NSSavePanel に関するドキュメントを参照してください。

まとめ

この記事では、Xamarin.Mac アプリケーションでのモーダル ウィンドウ、シート、標準システム ダイアログ ボックスの操作について詳しく説明しました。 モーダル ウィンドウ、シート、ダイアログのさまざまな種類と使用方法、Xcode の Interface Builder でモーダル ウィンドウとシートを作成および保守する方法、および C# コードでモーダル ウィンドウ、シート、ダイアログを操作する方法について説明しました。