Xamarin.Mac のダイアログ

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

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

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

開いているダイアログ ボックス

この記事では、Xamarin.Mac アプリケーションでのダイアログとモーダル Windows の操作の基本について説明します。 この記事で使用する主要な概念と手法については、「 Hello, Mac 」の記事、特 に「Xcode とインターフェイス ビルダーアウトレットとアクション の概要」セクションを最初に説明することを強くお勧めします。

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

ダイアログの概要

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

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

  • ドキュメント モーダル - ドキュメント モーダル ダイアログを使用すると、ユーザーは無視されるまで、特定のドキュメント内で他の操作を実行できなくなります。
  • アプリ モーダル - アプリ モーダル ダイアログを使用すると、ユーザーはアプリケーションを閉じるまで操作できなくなります。
  • モードレス モードレス ダイアログを使用すると、ユーザーはドキュメント ウィンドウを操作しながらダイアログの設定を変更できます。

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

モーダル ウィンドウの例

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

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

モーダル シートの例

環境設定ウィンドウ

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

基本設定ウィンドウの例

ダイアログを開く

[開く] ダイアログでは、アプリケーションでアイテムを一貫して検索して開く方法をユーザーに提供します。

開いているダイアログ ボックス

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

[印刷] ダイアログは、次の両方のフリー フローティング ダイアログ ボックスとして表示できます。

印刷ダイアログ ボックス

または、シートとして表示することもできます。

印刷シート

[ページ設定] ダイアログは、次の両方のフリー フローティング ダイアログ ボックスとして表示できます。

ページ設定ダイアログ

または、シートとして表示することもできます。

ページ設定シート

[保存] ダイアログ

[保存] ダイアログボックスを使用すると、ユーザーはアプリケーションにアイテムを一貫して保存できます。 [保存] ダイアログには、 最小 (折りたたみ) という 2 つの状態があります。

保存ダイアログ

展開された状態は次のとおりです。

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

[最小限の保存] ダイアログボックスはシートとして表示することもできます。

最小限の保存シート

[展開された保存] ダイアログと同様に、次の操作を行います。

展開された保存シート

詳細については、「Apple の OS X ヒューマン インターフェイス ガイドライン」の「ダイアログ」セクションを参照してください。

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

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

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

  1. ソリューション エクスプローラーで、Xcode のMain.storyboardインターフェイス ビルダーで編集用の ファイルを開きます。

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

    ライブラリからビュー コントローラーを選択する

  3. [Identity Inspector]\(ID インスペクター\) で、[クラス名] に「」と入力CustomDialogControllerします。

    クラス名を CustomDialogController に設定します。

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

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

    Xcode での UI の設計

  6. ダイアログを開く UI 要素からダイアログのウィンドウにコントロールをドラッグして、アプリのメイン ウィンドウから新しいビュー コントローラーに モーダル セグエ を作成します。 識別子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 アプリケーションでの Windows の使用の詳細については、 Windows の使用 に関するドキュメントを参照してください。

ユーザー設定シートの作成

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

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

  1. ソリューション エクスプローラーで、Xcode のMain.storyboardインターフェイス ビルダーで編集用の ファイルを開きます。

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

    ライブラリからビュー コントローラーを選択する

  3. ユーザー インターフェイスを設計する:

    UI デザイン

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

    シート セグエタイプの選択

  5. ID インスペクターで、ビュー コントローラーのクラスに次の名前を付けます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;
    }
}

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

シートの例

環境設定ダイアログの作成

インターフェイス ビルダーでユーザー設定ビューをレイアウトする前に、ユーザー設定の切り替えを処理するカスタム セグエの種類を追加する必要があります。 新しいクラスをプロジェクトに追加し、 と呼びます 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 のMain.storyboardインターフェイス ビルダーで編集用のファイルを開きます。

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

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

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

    新しいウィンドウの追加

  4. アタッチされたビュー コントローラーのコピーを作成します。ユーザー設定ビューにはタブが表示されます。

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

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

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

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

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

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

    ツール バーのレイアウト

  8. Control-Clickし、各 ツール バー ボタン から上記で作成したビューにドラッグします。 [カスタム セグエの種類] を選択します。

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

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

    セグエ クラスの設定

  10. デザイン 画面のメニュー バー Designerで、[アプリケーション メニュー] から [環境設定...] を選択し、control キーを押しながらクリックして [環境設定] ウィンドウにドラッグし、Show segue を作成します。

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

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

コードを実行し、[アプリケーション] メニューから [環境設定...] を選択すると、ウィンドウが表示されます。

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

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

設定の保存と読み込み

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

最初に、プロジェクトに新 AppPreferences しいクラスを追加し、 から NSObject継承します。 ユーザー設定は、 データ バインディングとKey-Value コーディング を使用するように設計されています。これにより、ユーザー設定フォームを作成して維持するプロセスがはるかに簡単になります。 Preferences は少量の単純なデータ型で 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
    }
}

このクラスには、 などのSaveIntLoadColorLoadIntSaveColorいくつかのヘルパー ルーチンが含まれています。これにより、NSUserDefaults操作が容易になります。 また、 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 要素に接続します。 インターフェイス ビルダーで、基本設定ビュー コントローラーを選択し、 IDENTITY Inspector に切り替えて、コントローラーのカスタム クラスを作成します。

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

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

次に、ストーリーボード ファイルをダブルクリックして、インターフェイス ビルダーで再度開きます (上記の変更を確認してください)。 基本設定インターフェイスをビルドするために必要なすべての UI コントロールをビューにドラッグします。 各コントロールについて、 バインド インスペクター に切り替え、 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
    }
}

これにより、環境設定ウィンドウが閉じると、開いているすべての Windows にユーザー設定の変更が送信されます。

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

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

これらの変更をすべて行った状態で、ユーザーがアプリの [環境設定] を編集して [環境設定] ウィンドウを閉じると、開いているすべての Windows に変更が適用されます。

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

[開く] ダイアログ

[ダイアログを開く] を使用すると、アプリケーション内のアイテムを一貫して検索して開く方法がユーザーに提供されます。 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 ()、[ダイアログを開く] を表示し、ユーザーがファイルまたはディレクトリ (プロパティで指定) を選択できるようにし、ユーザーが [開く] ボタンをクリックすると を返1します。

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

プログラムを実行し、[ファイル] メニューから [開く...] 項目を選択すると、次の情報が表示されます。

開いているダイアログ ボックス

[印刷とページ設定] ダイアログ

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 の NSPrintPanelNSPageLayout のドキュメントを参照してください。

[保存] ダイアログ

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

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

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、アプリケーションを実行し、[ファイル] メニューの [名前を付けて保存] を選択すると、次の内容が表示されます。

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

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

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

プロパティを ShowSaveAsSheet に設定するとtrue、アプリケーションを実行し、[ファイル] メニューの [名前を付けて保存] を選択すると、次の内容が表示されます。

シートを保存する

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

展開された保存シート

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

まとめ

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