在 Xamarin.Mac 應用程式中使用 C# 和 .NET 時,您可以存取開發人員在 、Swift 和 Xcode 中Objective-C運作的相同使用者控制件。 由於 Xamarin.Mac 直接與 Xcode 整合,因此您可以使用 Xcode 的 介面產生器 來建立和維護使用者控件(或選擇性地直接在 C# 程式代碼中建立它們)。
雖然 macOS 提供豐富的內建使用者控件,但有時候您可能需要建立自定義控件,以提供現成的功能,或符合自定義 UI 主題(例如遊戲介面)。
在本文中,我們將討論在 Xamarin.Mac 應用程式中建立可重複使用的自定義使用者介面控件的基本概念。 強烈建議您先完成 Hello,Mac 文章,特別是 Xcode 和 Interface Builder 和 Outlets 和 Actions 簡介小節,因為它涵蓋我們將在本文中使用的重要概念和技術。
您可能也想要查看 Xamarin.Mac Internals 檔的公開 C# 類別/方法Objective-C一節,它也會說明 Register 用來將 C# 類別連接至Objective-C物件和 UI 元素的 和 Export 命令。
自定義控件簡介
如上所述,有時候您可能需要建立可重複使用的自定義使用者介面控件,為您的 Xamarin.Mac 應用程式的 UI 提供獨特的功能,或建立自定義 UI 主題(例如遊戲介面)。
在這些情況下,您可以輕鬆地繼承自 NSControl ,並建立自定義工具,以透過 C# 程式代碼或 Xcode 的介面產生器新增至應用程式的 UI。 繼承自 NSControl 自定義控件,將會自動擁有內建使用者介面控件擁有的所有標準功能(例如 NSButton)。
如果您的自訂使用者介面控制項只顯示資訊(例如自訂圖表和圖形工具),您可能會想要繼承自 NSView 而非 NSControl。
無論使用哪一個基類,建立自定義控件的基本步驟都相同。
在本文中,會建立自定義翻轉開關元件,以提供唯一的使用者介面主題,以及建置功能完整的自定義使用者介面控件範例。
建置自定義控件
由於我們正在建立的自定義控件將會回應使用者輸入(滑鼠左鍵點選),因此我們將繼承自 NSControl。 如此一來,我們的自定義控件將會自動擁有內建使用者介面控件擁有的所有標準功能,並像標準 macOS 控制件一樣回應。
在 Visual Studio for Mac 中,開啟您想要建立自定義使用者介面控件的 Xamarin.Mac 專案(或建立新的使用者介面控件)。 新增 類別並呼叫它 NSFlipSwitch:
接下來,編輯 類別 NSFlipSwitch.cs ,使其看起來如下:
using Foundation;
using System;
using System.CodeDom.Compiler;
using AppKit;
using CoreGraphics;
namespace MacCustomControl
{
[Register("NSFlipSwitch")]
public class NSFlipSwitch : NSControl
{
#region Private Variables
private bool _value = false;
#endregion
#region Computed Properties
public bool Value {
get { return _value; }
set {
// Save value and force a redraw
_value = value;
NeedsDisplay = true;
}
}
#endregion
#region Constructors
public NSFlipSwitch ()
{
// Init
Initialize();
}
public NSFlipSwitch (IntPtr handle) : base (handle)
{
// Init
Initialize();
}
[Export ("initWithFrame:")]
public NSFlipSwitch (CGRect frameRect) : base(frameRect) {
// Init
Initialize();
}
private void Initialize() {
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}
#endregion
#region Draw Methods
public override void DrawRect (CGRect dirtyRect)
{
base.DrawRect (dirtyRect);
// Use Core Graphic routines to draw our UI
...
}
#endregion
#region Private Methods
private void FlipSwitchState() {
// Update state
Value = !Value;
}
#endregion
}
}
我們繼承自 NSControl 並使用 Register 命令向 和 Xcode 介面產生器公開此類別的第一件事,就是要注意的自定義類別 Objective-C :
[Register("NSFlipSwitch")]
public class NSFlipSwitch : NSControl
在下列各節中,我們將詳細探討上述程式代碼的其餘部分。
追蹤控制件的狀態
由於我們的自定義控件是一個參數,因此我們需要一種方法來追蹤開關的開啟/關閉狀態。 我們會在 中 NSFlipSwitch處理下列程式代碼:
private bool _value = false;
...
public bool Value {
get { return _value; }
set {
// Save value and force a redraw
_value = value;
NeedsDisplay = true;
}
}
切換狀態變更時,我們需要更新UI的方法。 我們藉由強制控件使用 NeedsDisplay = true重新繪製其UI來執行此動作。
如果我們的控制項需要更多單一開啟/關閉狀態(例如具有3個位置的多重狀態交換器),我們可以使用 列舉 來追蹤狀態。 在我們的範例中,簡單的 bool 會執行。
我們也新增協助程式方法,以交換開啟與關閉之間的切換狀態:
private void FlipSwitchState() {
// Update state
Value = !Value;
}
稍後,我們將展開此協助程序類別,以在切換狀態變更時通知呼叫端。
繪製控件的介面
我們將使用核心圖形繪圖例程,在運行時間繪製自定義控件的使用者介面。 我們必須先開啟控件的圖層,才能這麼做。 我們會使用下列私人方法來執行此動作:
private void Initialize() {
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}
這個方法會從每個控件的建構函式呼叫,以確保控件已正確設定。 例如:
public NSFlipSwitch (IntPtr handle) : base (handle)
{
// Init
Initialize();
}
接下來,我們需要覆寫 DrawRect 方法,並新增 Core Graphic 例程來繪製控件:
public override void DrawRect (CGRect dirtyRect)
{
base.DrawRect (dirtyRect);
// Use Core Graphic routines to draw our UI
...
}
當控件的狀態變更時,我們將調整控件的視覺表示法(例如從 [開啟 ] 變更為 [關閉]。 每當狀態變更時,我們都可以使用 NeedsDisplay = true 命令來強制重新繪製新狀態的控件。
回應使用者輸入
有兩種基本方式可將使用者輸入新增至自定義控件: 覆寫滑鼠處理例程 或 手勢辨識器。 我們使用哪一種方法,將會根據控件所需的功能。
重要
針對您建立的任何自定義控件,您應該使用覆寫方法或手勢辨識器,但兩者都不能同時彼此衝突。
使用覆寫方法處理用戶輸入
繼承自 NSControl 的物件有 NSView數個用於處理滑鼠或鍵盤輸入的覆寫方法。 在我們的範例控件中,當用戶按兩下具有滑鼠左鍵的控件時,我們想要在開啟和關閉之間翻轉開關的狀態。 我們可以將下列覆寫方法新增至 NSFlipSwitch 類別以處理此作業:
#region Mouse Handling Methods
// --------------------------------------------------------------------------------
// Handle mouse with Override Methods.
// NOTE: Use either this method or Gesture Recognizers, NOT both!
// --------------------------------------------------------------------------------
public override void MouseDown (NSEvent theEvent)
{
base.MouseDown (theEvent);
FlipSwitchState ();
}
public override void MouseDragged (NSEvent theEvent)
{
base.MouseDragged (theEvent);
}
public override void MouseUp (NSEvent theEvent)
{
base.MouseUp (theEvent);
}
public override void MouseMoved (NSEvent theEvent)
{
base.MouseMoved (theEvent);
}
## endregion
在上述程式代碼中,我們會呼叫 FlipSwitchState 方法(如上定義)來翻轉 方法中的 MouseDown 參數開啟/關閉狀態。 這也會強制重新繪製控件以反映目前的狀態。
使用手勢辨識器處理用戶輸入
您可以選擇性地使用手勢辨識器來處理與控件互動的使用者。 拿掉上面新增的覆寫、編輯 Initialize 方法,並使其看起來如下:
private void Initialize() {
this.WantsLayer = true;
this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
// --------------------------------------------------------------------------------
// Handle mouse with Gesture Recognizers.
// NOTE: Use either this method or the Override Methods, NOT both!
// --------------------------------------------------------------------------------
var click = new NSClickGestureRecognizer (() => {
FlipSwitchState();
});
AddGestureRecognizer (click);
}
在這裡,我們會建立新的 NSClickGestureRecognizer ,並呼叫方法來 FlipSwitchState 變更使用者按兩下時,使用滑鼠左鍵來變更切換的狀態。 方法會將 AddGestureRecognizer (click) 手勢辨識器新增至 控件。
同樣地,我們使用的方法取決於我們嘗試使用自定義控件完成的工作。 如果需要對用戶互動的低階存取,請使用覆寫方法。 如果需要預先定義的功能,例如按下滑鼠,請使用手勢辨識器。
回應狀態變更事件
當使用者變更自定義控件的狀態時,我們需要一種方式來回應程式代碼中的狀態變更(例如按兩下自定義按鈕時執行某些動作)。
若要提供這項功能,請編輯 NSFlipSwitch 類別並新增下列程序代碼:
#region Events
public event EventHandler ValueChanged;
internal void RaiseValueChanged() {
if (this.ValueChanged != null)
this.ValueChanged (this, EventArgs.Empty);
// Perform any action bound to the control from Interface Builder
// via an Action.
if (this.Action !=null)
NSApplication.SharedApplication.SendAction (this.Action, this.Target, this);
}
## endregion
接下來,編輯 FlipSwitchState 方法,使其看起來如下:
private void FlipSwitchState() {
// Update state
Value = !Value;
RaiseValueChanged ();
}
首先,我們提供事件 ValueChanged ,我們可以在 C# 程式代碼中新增處理程式,以便在使用者變更參數狀態時執行動作。
其次,因為我們的自定義控件繼承自 NSControl,所以它會自動擁有可在 Xcode 介面產生器中指派的動作 。 若要在狀態變更時呼叫此 動作 ,我們會使用下列程序代碼:
if (this.Action !=null)
NSApplication.SharedApplication.SendAction (this.Action, this.Target, this);
首先,我們會檢查動作是否已指派給控件。 接下來,如果已定義動作,我們會呼叫 動作 。
使用自定義控制件
透過完全定義的自定義控件,我們可以使用 C# 程式代碼或在 Xcode 的介面產生器中將其新增至 Xamarin.Mac 應用程式的 UI。
若要使用 Interface Builder 新增控件,請先執行 Xamarin.Mac 專案的全新建置,然後按兩下 Main.storyboard 檔案,在 Interface Builder 中開啟它以進行編輯:
接下來,將 拖曳 Custom View 到使用者介面設計中:
在仍選取 [自定義檢視] 之後,切換至 [識別偵測器 ],並將檢視的 [類別 ] 變更為 NSFlipSwitch:
切換至助理 編輯器 ,並建立 自定義控件的輸出 點(請務必在檔案中 ViewController.h 系結它,而不是 .m 檔案):
儲存您的變更,返回 Visual Studio for Mac,並允許變更同步處理。 ViewController.cs 編輯檔案,並讓 ViewDidLoad 方法看起來如下:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Do any additional setup after loading the view.
OptionTwo.ValueChanged += (sender, e) => {
// Display the state of the option switch
Console.WriteLine("Option Two: {0}", OptionTwo.Value);
};
}
在這裡,我們會回應 ValueChanged 我們在 類別上 NSFlipSwitch 定義的事件,並在使用者單擊 控件時寫出目前的 Value 。
或者,我們可以返回 Interface Builder,並在控件上定義 動作 :
同樣地,編輯 ViewController.cs 檔案並新增下列方法:
partial void OptionTwoFlipped (Foundation.NSObject sender) {
// Display the state of the option switch
Console.WriteLine("Option Two: {0}", OptionTwo.Value);
}
重要
您應該在介面產生器中使用 事件 或定義 動作 ,但不應該同時使用這兩種方法,或者它們可能會彼此衝突。
摘要
本文已詳細探討如何在 Xamarin.Mac 應用程式中建立可重複使用的自定義使用者介面控件。 我們已瞭解如何繪製自定義控件 UI、回應滑鼠和使用者輸入的兩個主要方式,以及如何將新控件公開至 Xcode 介面產生器中的動作。
相關連結
- Hello, Mac
- 資料繫結和鍵值編碼
- OS X 人性化介面指導方針 \(英文\)
- 處理滑鼠事件






