Dialogs Contents Overview

Transcription

Dialogs Contents Overview
Dialogs
Using Dialogs and Modal Windows in a Xamarin.Mac application
Contents
This article will cover the following topics in detail:
Introduction to Dialogs
Adding a Modal Window to a Project
Creating a Custom Sheet
Loading UI from a XIB
Opening and Closing the Sheet
Using the Sheet
Creating a Preferences Dialog
Displaying a New Preference View
Displaying the First View and Switching Views
Displaying the Preference Window
The Open Dialog
The Print and Page Setup Dialogs
The Save Dialog
Overview
When working with C# and .NET in a Xamarin.Mac application, you have access to the same Dialogs and Modal
Windows that a developer working in in Objective-C and Xcode does. Because Xamarin.Mac integrates directly
with Xcode, you can use Xcode's Interface Builder to create and maintain your Modal Windows (or optionally
create them directly in C# code).
A dialog appears in response to a user action and typically provides ways users can complete the action. A
dialog requires a response from the user before it can be closed.
Windows can be used in a Modeless state (such as a text editor that can have multiple documents open at once)
or Modal (such as an Export dialog that must be dismissed before the application can continue).
In this article, we'll cover the basics of working with Dialogs and Modal Windows in a Xamarin.Mac application. It
is highly suggested that you work through the Hello, Mac article first, specifically the Introduction to Xcode and
Interface Builder and Outlets and Actions sections, as it covers key concepts and techniques that we'll be using
in this article.
You may want to take a look at the Exposing C# classes / methods to Objective-C section of the Xamarin.Mac
Internals document as well, it explains the Register and Export commands used to wire-up your C# classes
to Objective-C objects and UI Elements.
Introduction to Dialogs
A dialog appears in response to a user action (such as saving a file) and provides a way for users to complete
that action. A dialog requires a response from the user before it can be closed.
According to Apple, there are three ways to present a Dialog:
Document Modal - A Document Modal dialog prevents the user from doing anything else within a given
document until it is dismissed.
App Modal - An App Modal dialog prevents the user from interacting with the application until it is
dismissed.
Modeless A Modeless Dialog enables users to change settings in the dialog while still interacting with the
document window.
Modal Window
Any standard NSWindow can be used as a customized dialog by displaying it modally:
Document Modal Dialog Sheets
A Sheet is a modal dialog that is attached to a given document window, preventing users from interacting with the
window until they dismiss the dialog. A Sheet is attached to the window from which it emerges and only one
sheet can be open for a window at any one time.
Preferences Windows
A Preferences Window is a modeless dialog that contains the application's settings that the user changes
infrequently. Preferences Windows often include a Toolbar that allows the user to switch between different
groups of settings:
Open Dialog
The Open Dialog gives users a consistent way to find and open an item in an application:
Print and Page Setup Dialogs
OS X provides standard Print and Page Setup Dialogs that your application can display so that users can have a
consistent printing experience in every application they use.
The Print Dialog can be displayed as both a free floating dialog box:
Or it can be displayed as a Sheet:
The Page Setup Dialog can be displayed as both a free floating dialog box:
Or it can be displayed as a Sheet:
Save Dialogs
The Save Dialog gives users a consistent way to save an item in an application. The Save Dialog has two states:
Minimal (also known as Collapsed):
And the Expanded state:
The Minimal Save Dialog can also be displayed as a Sheet:
As can the Expanded Save Dialog:
For more information, see the Dialogs section of Apple's OS X Human Interface Guidelines
Adding a Modal Window to a Project
Aside from the main document window, a Xamarin.Mac application might need to display other types of windows
to the user, such as Preferences or Inspector Panels.
To add a new window, do the following:
1. In the Solution Explorer, right-click on the Project and select Add > New File...
2. In the New File dialog box, select Xamarin.Mac > Cocoa Window with Controller:
3. Enter CustomDialog for the Name and click the New button.
4. Double-click the CustomDialog.xib file to open it for editing in Interface Builder:
5. Design your interface:
6. Wire-up any Actions and Outlets:
7. Save your changes and return to Xamarin Studio to sync with Xcode.
Add the following code to the MainWindow.cs file to open the window modally:
[Export ("showDialog:")]
void ShowDialog (NSObject sender) {
// Load the new window
var dialog = new CustomDialogController ();
// Display the window modally
NSApplication.SharedApplication.RunModalForWindow (dialog.Window);
}
Next, add this code to the CustomDialog.cs file to close the Modal Dialog:
[Export ("dialogClose:")]
void DialogClose (NSObject sender) {
NSApplication.SharedApplication.StopModal ();
this.Close ();
}
[Export ("dialogCancel:")]
void DialogCancel (NSObject sender) {
NSApplication.SharedApplication.AbortModal ();
this.Close ();
}
The NSApplication.SharedApplication.StopModal () method is used for the OK action of the dialog,
the NSApplication.SharedApplication.AbortModal () method is used for the Cancel action.
We can run our application and display the custom dialog:
For more information about using windows in a Xamarin.Mac application, please see our Working with Windows
documentation.
Creating a Custom Sheet
A Sheet is a modal dialog that is attached to a given document window, preventing users from interacting with the
window until they dismiss the dialog. A Sheet is attached to the window from which it emerges and only one
sheet can be open for a window at any one time.
To create a Custom Sheet in Xamarin.Mac, let's do the following:
1. In the Solution Explorer, right-click on the Project and select Add > New File...
2. In the New File dialog box, select Xamarin.Mac > Cocoa Window:
3. Enter LoginSheet for the Name and click the New button.
4. Edit the LoginSheet.cs file and change the class definition to the following:
public partial class LoginSheet : NSPanel
5. Double-click the CustomDialog.xib file to open it for editing in Interface Builder:
6. Delete the existing Window.
7. Drag a Panel from the Library Inspector to the Interface Editor:
8. Switch to the Identity Inspector and enter LoginSheet for the Class:
9. Switch to the Attribute Inspector and make all of the windows properties look like the following:
10. Design your user interface:
11. Open the Assistant View, select the LoginScreen.h file and create an outlet for your sheet's window:
12. Create any required Actions and Outlets for your UI elements.
13. Save your changes and return to Xamarin Studio to sync changes.
14. In the Solution Explorer, right-click on the Project and select Add > New File...
15. In the New File dialog box, select General > Empty Class:
16. Enter LoginSheetController for the Name and click the New button.
Next, edit the LoginSheetController.cs file and make it look like the following:
using System;
using Foundation;
using AppKit;
namespace MacWindows
{
public class LoginSheetController : NSObject
{
#region Computed Properties
[Export("window")]
public LoginSheet Window { get; set;}
[Outlet]
public NSTextField loginPassword { get; set; }
[Outlet]
public NSTextField loginUser { get; set; }
public bool Canceled { get; set;}
public string UserID {
get { return loginUser.StringValue; }
set { loginUser.StringValue = value; }
}
public string Password {
get { return loginPassword.StringValue; }
set { loginPassword.StringValue = value; }
}
#endregion
#region Constructors
public LoginSheetController ()
{
// Load the .xib file for the sheet
NSBundle.LoadNib ("LoginSheet", this);
}
#endregion
#region Public Methods
public void ShowSheet(NSWindow inWindow) {
NSApplication.SharedApplication.BeginSheet (Window, inWindow);
}
public void CloseSheet() {
NSApplication.SharedApplication.EndSheet (Window);
Window.Close();
}
#endregion
#region Button Handlers
[Export ("loginCancel:")]
void LoginCancel (NSObject sender) {
Canceled = true;
CloseSheet();
RaiseLoginCanceled ();
}
[Export ("loginOK:")]
void LoginOK (NSObject sender) {
Canceled = false;
CloseSheet();
RaiseLoginRequested ();
}
#endregion
#region Events
public delegate void LoginCanceledDelegate();
public event LoginCanceledDelegate LoginCanceled;
internal void RaiseLoginCanceled() {
if (this.LoginCanceled != null) {
this.LoginCanceled();
}
}
public delegate void LoginRequestedDelegate(string userID, string
password);
public event LoginRequestedDelegate LoginRequested;
internal void RaiseLoginRequested() {
if (this.LoginRequested != null) {
this.LoginRequested(UserID, Password);
}
}
#endregion
}
}
Before we use our new sheet, let's look at a few important concepts here:
Loading UI from a XIB
Before you can load a User Interface from a .xib file, the class that you are attempting to load from must inherit
from NSObject (or from a base class that does) and there must a window property to accept the loaded UI. The
following code does this:
public class LoginSheetController : NSObject
...
[Export("window")]
public LoginSheet Window { get; set;}
...
public LoginSheetController ()
{
// Load the .xib file for the sheet
NSBundle.LoadNib ("LoginSheet", this);
}
Because LoginSheetController class spawns our LoginSheet panel, it becomes the target of any Outlets
or Actions that we defined in Interface Builder, not the LoginSheet class. So we need to define our Outlets or
Actions in the LoginSheetController.cs file:
[Outlet]
public NSTextField loginPassword { get; set; }
[Outlet]
public NSTextField loginUser { get; set; }
...
[Export ("loginCancel:")]
void LoginCancel (NSObject sender) {
...
}
[Export ("loginOK:")]
void LoginOK (NSObject sender) {
...
}
Opening and Closing the Sheet
We use the NSApplication.SharedApplication object to display our sheet on a given window and to
detach it when we are finished:
public void ShowSheet(NSWindow inWindow) {
NSApplication.SharedApplication.BeginSheet (Window, inWindow);
}
public void CloseSheet() {
NSApplication.SharedApplication.EndSheet (Window);
Window.Close();
}
Note that we still have to call the Close method on the Sheet's Window to actually remove it from the screen.
Using the Sheet
With the Sheet's design created in Interface Builder, wired-up to the required Actions and Outlets and our
controller class created, we are ready to use the sheet. The following code displays the Sheet attached to a
window:
var sheet = new LoginSheetController ();
sheet.LoginRequested += (userID, password) => {
Console.WriteLine("User ID: {0}, Password: {1}", userID, password);
};
// Where "this" is an NSWindow
sheet.ShowSheet (this);
If we run our application and open the Sheet, it will be attached to the window:
Note: If your Sheet appears detached from your window, double check that you have all of the properties in the
Attribute Inspector set exactly as shown in the code above.
Creating a Preferences Dialog
To add a new window, do the following:
1. In the Solution Explorer, right-click on the Project and select Add > New File...
2. In the New File dialog box, select Xamarin.Mac > Cocoa Window with Controller:
3. Enter PreferencesWindow for the Name and click the New button.
4. Edit the PreferencesWindow.cs file and change the class definition to the following:
public partial class PreferencesWindow : NSPanel
5. Double-click the PreferencesWindow.xib file to open it for editing in Interface Builder:
6. Design your interface using a by adding a Toolbar and several Toolbar Items to act as tabs:
7. For each Toolbar Item make sure that you check Selectable and give it a unique Identifier:
8. Ensure that your Toolbar isn't customizable:
9. Lock the size of your Window:
10. Add a Custom View just below the Toolbar, make it fill the rest of the content area, and set it to shrink and
grow with the window:
11. Wire-up Actions for all of your Toolbar Items and create an Outlet for the Toolbar and the Custom View:
12. Save your changes and return to Xamarin Studio to sync with Xcode.
Now we will need to create custom views for each of our Toolbar Items. We'll be keeping these in separate files
to making them easy to maintain so you'll need to do the follow steps for each tab needed:
1. In the Solution Explorer, right-click on the Project and select Add > New File...
2. In the New File dialog box, select Xamarin.Mac > Cocoa Window with Controller:
3. Enter PreferencesGlobal for the Name and click the New button.
4. Double-click the PreferencesGlobal.xib file to open it for editing in Interface Builder and make the
view the size of the panelContainer we created above:
5. Design your UI for the given preference panel:
6. Wire-up and needed Actions and Outlets.
7. Save your changes and return to Xamarin Studio to sync with Xcode.
8. Repeat for each Toolbar Item.
Next, let's modify the PreferencesWindow.cs file to automatically select the first Toolbar item and display
that's items information when the window is first opened. We'll also add the code to display each tab. Make the
PreferencesWindow.cs file look like the following:
using System;
using Foundation;
using AppKit;
using CoreGraphics;
namespace MacWindows
{
public partial class PreferencesWindow : NSPanel
{
#region Private Variables
private NSViewController _subviewController = null;
private NSView _subview = null;
#endregion
#region Constructors
public PreferencesWindow (IntPtr handle) : base (handle)
{
}
[Export ("initWithCoder:")]
public PreferencesWindow (NSCoder coder) : base (coder)
{
}
#endregion
#region Private Methods
private void ShowPanel(NSViewController controller) {
// Is there a view already being displayed?
if (_subview != null) {
// Yes, remove it from the view
_subview.RemoveFromSuperview ();
// Release memory
_subview = null;
_subviewController = null;
}
// Save values
_subviewController = controller;
_subview = controller.View;
// Define frame and display
_subview.Frame = new CGRect (0, 0, panelContainer.Frame.Width,
panelContainer.Frame.Height);
panelContainer.AddSubview (_subview);
}
#endregion
#region Override Methods
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Automatically select the first item
mainToolbar.SelectedItemIdentifier = "global";
ShowPanel (new preferenceGlobalController ());
}
#endregion
#region Toolbar Handlers
[Export ("preferencesProfile:")]
void PreferencesProfile (NSObject sender) {
mainToolbar.SelectedItemIdentifier = "profile";
ShowPanel (new PreferencesProfileController ());
}
[Export ("preferencesGlobal:")]
void PreferencesGlobal (NSObject sender) {
mainToolbar.SelectedItemIdentifier = "global";
ShowPanel (new preferenceGlobalController ());
}
[Export ("preferencesKeyboard:")]
void PreferencesKeyboard (NSObject sender) {
mainToolbar.SelectedItemIdentifier = "keyboard";
ShowPanel (new PreferencesKeyboardController ());
}
[Export ("preferencesVIOP:")]
void PreferencesVOIP (NSObject sender) {
mainToolbar.SelectedItemIdentifier = "voip";
ShowPanel (new PreferencesVOIPController ());
}
#endregion
}
}
Before we use the preference window, let's look at a few important concepts here:
Displaying a New Preference View
When the user selects a different preference tab (Toolbar Item) we need to switch the view displayed to the one
that the user is working with. The following code handles that:
private NSViewController _subviewController = null;
private NSView _subview = null;
...
private void ShowPanel(NSViewController controller) {
// Is there a view already being displayed?
if (_subview != null) {
// Yes, remove it from the view
_subview.RemoveFromSuperview ();
// Release memory
_subview = null;
_subviewController = null;
}
// Save values
_subviewController = controller;
_subview = controller.View;
// Define frame and display
_subview.Frame = new CGRect (0, 0, panelContainer.Frame.Width,
panelContainer.Frame.Height);
panelContainer.AddSubview (_subview);
}
It looks to see it we already have a view displayed, if so it removes it from the screen. Next it takes the view that
has been passed in (as loaded from a View Controller) resizes it to fit in the Content Area and adds it to the
content for display.
Displaying the First View and Switching Views
When the Preference Window is first displayed, we need to select the first Toolbar Item and display the first
item's view. This code handles that:
// Automatically select the first item
mainToolbar.SelectedItemIdentifier = "global";
ShowPanel (new preferenceGlobalController ());
Note that global is the unique Identifier that we assigned the Toolbar Item in Interface Builder.
As each Toolbar Item is selected by the user, the following Actions handle switching the view:
[Export ("preferencesProfile:")]
void PreferencesProfile (NSObject sender) {
mainToolbar.SelectedItemIdentifier = "profile";
ShowPanel (new PreferencesProfileController ());
}
[Export ("preferencesGlobal:")]
void PreferencesGlobal (NSObject sender) {
mainToolbar.SelectedItemIdentifier = "global";
ShowPanel (new preferenceGlobalController ());
}
[Export ("preferencesKeyboard:")]
void PreferencesKeyboard (NSObject sender) {
mainToolbar.SelectedItemIdentifier = "keyboard";
ShowPanel (new PreferencesKeyboardController ());
}
[Export ("preferencesVIOP:")]
void PreferencesVOIP (NSObject sender) {
mainToolbar.SelectedItemIdentifier = "voip";
ShowPanel (new PreferencesVOIPController ());
}
Displaying the Preference Window
Add the following code to AppDelegate.cs display your preference window:
[Export("applicationPreferences:")]
void ShowPreferences (NSObject sender)
{
var preferences = new PreferencesWindowController ();
preferences.Window.MakeKeyAndOrderFront (this);
}
If we run the code and select the Preferences... from the Application Menu, the window will be displayed:
For more information on working with Windows and Toolbars, please see our Windows and Toolbars
documentation.
The Open Dialog
The Open Dialog gives users a consistent way to find and open an item in an application. To display an Open
Dialog in a Xamarin.Mac application, use the following code:
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) {
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;
}
}
In the above code, we are opening a new document window to display the contents of the file. You'll need to
replace this code with functionality is required by your application.
The following properties are available when working with a NSOpenPanel:
CanChooseFiles - If true the user can select files.
CanChooseDirectories - If true the user can select directories.
AllowsMultipleSelection - If true the user can select more than one file at a time.
ResolveAliases - If true selecting and alias, resolves it to the original file's path.
The RunModal () method displays the Open Dialog and allow the user to select files or directories (as specified
by the properties) and returns 1 if the user clicks the Open button.
The Open Dialog returns the user's selected files or directories as an array of URLs in the URL property.
If we run the program and select the Open... item from the File menu, the following is displayed:
The Print and Page Setup Dialogs
OS X provides standard Print and Page Setup Dialogs that your application can display so that users can have a
consistent printing experience in every application they use.
The following code will show the standard Print Dialog:
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 ();
}
}
}
If we set the ShowPrintAsSheet property to false, run the application and display the print dialog, the
following will be displayed:
If set the ShowPrintAsSheet property to true, run the application and display the print dialog, the following will
be displayed:
The following code will display the Page Layout Dialog:
[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 ();
}
}
}
If we set the ShowPrintAsSheet property to false, run the application and display the print layout dialog, the
following will be displayed:
If set the ShowPrintAsSheet property to true, run the application and display the print layout dialog, the
following will be displayed:
For more information about working with the Print and Page Setup Dialogs, please see Apple's NSPrintPanel,
NSPageLayout and Introduction to Printing documentation.
The Save Dialog
The Save Dialog gives users a consistent way to save an item in an application.
The following code will show the standard Save Dialog:
public bool ShowSaveAsSheet { get; set;} = true;
...
[Export("saveDocumentAs:")]
void ShowSaveAs (NSObject sender)
{
var dlg = new NSSavePanel ();
dlg.Title = "Save Text File";
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 ();
}
}
}
If we set the ShowSaveAsSheet property to false, run the application and select Save As... from the File
menu, the following will be displayed:
The user can expand the dialog:
If we set the ShowSaveAsSheet property to true, run the application and select Save As... from the File menu,
the following will be displayed:
The user can expand the dialog:
For more more information on working with the Save Dialog, please see Apple's NSSavePanel documentation.
Summary
This article has taken a detailed look at working with Modal Windows, Sheets and the standard system Dialog
Boxes in a Xamarin.Mac application. We saw the different types and uses of Modal Windows, Sheets and
Dialogs, how to create and maintain Modal Windows and Sheets in Xcode's Interface Builder and how to work
with Modal Windows, Sheets and Dialogs in C# code.