共用方式為


使用處理程式自定義控制件

瀏覽範例。 瀏覽範例

您可以自定義處理程式,以增強跨平臺控件的外觀和行為,進一步超越控件 API 所能提供的自定義。 此自定義可修改跨平臺控制件的原生檢視,藉由以下方法之一來修改事件處理程式的映射器:

  • PrependToMapping,它會在套用 .NET MAUI 控制件對應之前修改處理程式的對應程式。
  • ModifyMapping,修改現有的映射。
  • AppendToMapping,它在套用 .NET MAUI 控制映射之後會修改用於處理器的映射器。

每個方法都有相同的簽名,需要兩個參數。

  • string為基礎的索引鍵。 修改 .NET MAUI 所提供的其中一個映射時,必須指定 .NET MAUI 所使用的索引鍵。 .NET MAUI 控制件對應的關鍵值是以介面和屬性名稱為基礎,例如 nameof(IEntry.IsPassword)。 您可以在 dotnet/maui 存放庫中找到抽象化每個跨平臺控件的介面。 如果您想讓處理程式的自定義在每次屬性變更時執行,這是應該使用的關鍵格式。 否則,索引鍵可以是不需要對應至類型所公開屬性名稱的任意值。 例如,MyCustomization可以指定為鍵值,並用於執行任何原生檢視的修改作為自定義。 不過,這種關鍵格式的結果是,只有在第一次修改處理程式的映射器時,您的處理程式自定義才會執行。
  • Action,表示執行處理程式自定義的方法。 Action指定兩個自變數:
    • 提供已自定義之處理程序實例的 handler 參數。
    • 作為 view 引數,提供處理程序所實作的跨平台控制項之實例。

這很重要

處理程式自定義是全域的,而且不會限定在特定控件實例的範圍內。 允許在應用程式中的任何位置進行處理程式自定義。 自定義處理程式之後,它會影響該應用程式中所有該類型的控件。

每個處理程式類別都會透過其 PlatformView 屬性公開跨平臺控件的原生檢視。 您可以存取這個屬性來設定原生檢視屬性、叫用原生檢視方法,以及訂閱原生檢視事件。 此外,處理程式實作的跨平臺控件會透過其 VirtualView 屬性公開。

通過使用條件編譯,處理程式可以根據不同平台進行自定義,以實現跨平台的多重目標代碼。 或者,您可以使用部分類別將程式代碼組織成平臺特定的資料夾和檔案。 如需條件式編譯的詳細資訊,請參閱 條件式編譯

自定義控制件

.NET MAUI Entry 檢視是實作 IEntry 介面的單行文字輸入控件。 將 EntryHandlerEntry 檢視映射至各平台的下列原生檢視:

  • iOS/Mac CatalystUITextField
  • AndroidAppCompatEditText
  • WindowsTextBox
  • iOS/Mac CatalystUITextField
  • AndroidMauiAppCompatEditText
  • WindowsTextBox

下列圖表顯示檢視 Entry 如何透過 EntryHandler 對應到它的原生視圖:

項目處理程序架構。

項目處理程序架構。

類別 Entry 中的 EntryHandler 屬性對應程式會將跨平臺控件屬性對應至原生檢視 API。 這可確保在Entry上設定屬性時,基礎的原生檢視會根據需要進行更新。

屬性映射器可以在每個平臺上修改以自訂 Entry

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryPage : ContentPage
{
    public CustomizeEntryPage()
    {
        InitializeComponent();
        ModifyEntry();
    }

    void ModifyEntry()
    {
        Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
        {
#if ANDROID
            handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
            handler.PlatformView.EditingDidBegin += (s, e) =>
            {
                handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
            };
#elif WINDOWS
            handler.PlatformView.GotFocus += (s, e) =>
            {
                handler.PlatformView.SelectAll();
            };
#endif
        });
    }
}

在此範例中,自訂功能發生在Entry頁面類別中。 因此,當CustomizeEntryPage實例建立後,Android、iOS和Windows上的所有Entry控件將會被自定義。 自定義是透過存取處理元件 PlatformView 屬性來執行,該屬性提供存取原生視圖的途徑,以對應至每個平台上的跨平台控制項。 原生程式代碼接著會在取得焦點時選取 中的所有 Entry 文字,以自定義處理程式。

如需映射器的詳細資訊,請參閱 映射器

自定義特定控件實例

處理常式是全域的,當自定義一個控件的處理常式時,應用程式中所有相同類型的控件都會被自定義。 不過,特定控件實例的處理程式可以透過子類別化控件來自定義,然後只有在控件是子類別化類型時,才修改基底控件類型的處理程式。 例如,若要在包含多個Entry控件的頁面上自定義特定Entry控件,您應該先將控件子類別Entry化:

namespace CustomizeHandlersDemo.Controls
{
    internal class MyEntry : Entry
    {
    }
}

然後,您可以透過其屬性對應程式來自訂 EntryHandler,以僅修改 MyEntry 實例。

Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
{
    if (view is MyEntry)
    {
#if ANDROID
        handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        handler.PlatformView.EditingDidBegin += (s, e) =>
        {
            handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
        };
#elif WINDOWS
        handler.PlatformView.GotFocus += (s, e) =>
        {
            handler.PlatformView.SelectAll();
        };
#endif
    }
});

如果在您的 App 類別中進行處理程序的自定義,則應用程式中的任何 MyEntry 實例都會根據處理程序的修改來自定義。

使用處理程式生命週期自定義控制件

所有處理程式型 .NET MAUI 控制件都支援 HandlerChangingHandlerChanged 事件。 在實作跨平臺控制項的原生檢視可供使用並初始化時,將引發 HandlerChanged 事件。 當控件的處理程式即將從跨平臺控件中移除時,就會觸發HandlerChanging事件。 如需處理程式生命週期事件的詳細資訊,請參閱 處理程式生命週期

處理程式生命週期可用來執行處理程式自定義。 例如,若要訂閱原生檢視事件和取消訂閱原生檢視事件,您必須為自定義的跨平台控制件上的 HandlerChangedHandlerChanging 事件註冊事件處理程式。

<Entry HandlerChanged="OnEntryHandlerChanged"
       HandlerChanging="OnEntryHandlerChanging" />

您可以使用條件編譯或將程式碼組織到平臺特定的資料夾和檔案中,透過使用部分類別來自訂平台相關處理程式。 每個方法都會透過自訂 Entry,確保在取得焦點時自動選取其所有文字。

條件式編譯

下列範例顯示包含HandlerChangedHandlerChanging事件的事件處理程式的程式代碼後置檔案,並使用條件式編譯:

#if ANDROID
using AndroidX.AppCompat.Widget;
#elif IOS || MACCATALYST
using UIKit;
#elif WINDOWS
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
#endif

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryHandlerLifecyclePage : ContentPage
{
    public CustomizeEntryHandlerLifecyclePage()
    {
        InitializeComponent();
    }

    void OnEntryHandlerChanged(object sender, EventArgs e)
    {
        Entry entry = sender as Entry;
#if ANDROID
        (entry.Handler.PlatformView as AppCompatEditText).SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        (entry.Handler.PlatformView as UITextField).EditingDidBegin += OnEditingDidBegin;
#elif WINDOWS
        (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
#endif
    }

    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e)
    {
        if (e.OldHandler != null)
        {
#if IOS || MACCATALYST
            (e.OldHandler.PlatformView as UITextField).EditingDidBegin -= OnEditingDidBegin;
#elif WINDOWS
            (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
#endif
        }
    }

#if IOS || MACCATALYST
    void OnEditingDidBegin(object sender, EventArgs e)
    {
        var nativeView = sender as UITextField;
        nativeView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
    }
#elif WINDOWS
    void OnGotFocus(object sender, RoutedEventArgs e)
    {
        var nativeView = sender as TextBox;
        nativeView.SelectAll();
    }
#endif
}
#if ANDROID
using Microsoft.Maui.Platform;
#elif IOS || MACCATALYST
using UIKit;
#elif WINDOWS
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
#endif

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryHandlerLifecyclePage : ContentPage
{
    public CustomizeEntryHandlerLifecyclePage()
    {
        InitializeComponent();
    }

    void OnEntryHandlerChanged(object sender, EventArgs e)
    {
        Entry entry = sender as Entry;
#if ANDROID
        (entry.Handler.PlatformView as MauiAppCompatEditText).SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        (entry.Handler.PlatformView as UITextField).EditingDidBegin += OnEditingDidBegin;
#elif WINDOWS
        (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
#endif
    }

    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e)
    {
        if (e.OldHandler != null)
        {
#if IOS || MACCATALYST
            (e.OldHandler.PlatformView as UITextField).EditingDidBegin -= OnEditingDidBegin;
#elif WINDOWS
            (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
#endif
        }
    }

#if IOS || MACCATALYST
    void OnEditingDidBegin(object sender, EventArgs e)
    {
        var nativeView = sender as UITextField;
        nativeView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
    }
#elif WINDOWS
    void OnGotFocus(object sender, RoutedEventArgs e)
    {
        var nativeView = sender as TextBox;
        nativeView.SelectAll();
    }
#endif
}

在實作跨平臺控制項的原生檢視已建立和初始化之後,就會引發HandlerChanged事件。 因此,應在其事件處理器中進行原生事件訂閱。 這需要將 PlatformView 處理程式的 屬性轉換成原生檢視的類型或基底類型,以便存取原生事件。 在此範例中,於 iOS、Mac Catalyst 和 Windows 平台上,OnEntryHandlerChanged 事件會訂閱原生檢視的事件,這些事件是在實作 Entry 以使原生檢視取得焦點時引發的。

OnEditingDidBeginOnGotFocus 事件處理程式會在其各自的平台上存取 Entry 的原生視圖,並選取 Entry 中的所有文字。

事件會在 HandlerChanging 從跨平台控件移除現有處理程式之前,以及在建立新處理程式之前被引發。 因此,應在事件處理器中移除原生事件訂閱,並進行其他清理工作。 伴隨 HandlerChangingEventArgs 此事件的物件具有 OldHandlerNewHandler 屬性,這將會分別設定為舊處理程式和新處理程式。 在此範例中,OnEntryHandlerChanging 事件會移除在 iOS、Mac Catalyst 和 Windows 上原生檢視事件的訂閱。

部分類別

與其使用條件式編譯,也可以使用部分類別將控件自定義程式代碼組織成平臺特定的資料夾和檔案。 使用這種方法,您的自訂程式代碼會分成跨平臺部分類別和平臺特定的部分類別:

  • 跨平臺部分類別通常會定義成員,但不會實作成員,而且會為所有平臺建置。 此類別不應該放在您專案的任何 [平臺 ] 子資料夾中,因為這樣做會使它成為平臺特定的類別。
  • 平臺特定的部分類別通常會實作跨平臺部分類別中定義的成員,並針對單一平臺建置。 這個類別應該放在所選平臺之 [平臺 ] 資料夾的子資料夾中。

下列範例顯示跨平臺部分類別:

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryPartialMethodsPage : ContentPage
{
    public CustomizeEntryPartialMethodsPage()
    {
        InitializeComponent();
    }

    partial void ChangedHandler(object sender, EventArgs e);
    partial void ChangingHandler(object sender, HandlerChangingEventArgs e);

    void OnEntryHandlerChanged(object sender, EventArgs e) => ChangedHandler(sender, e);
    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e) => ChangingHandler(sender, e);
}

在此範例中,這兩個事件處理程式會呼叫名為 ChangedHandlerChangingHandler的部分方法,其簽章定義於跨平臺部分類別中。 接著,部分方法的實作會定義在平台專屬的局部類別中,而這個類別應該放在正確的 平台 子資料夾中,以確保建置系統只會在建置特定平台時嘗試建置原生程式碼。 例如,下列程式代碼會在專案的 [平臺Windows] > 資料夾中顯示 CustomizeEntryPartialMethodsPage 類別:

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace CustomizeHandlersDemo.Views
{
    public partial class CustomizeEntryPartialMethodsPage : ContentPage
    {
        partial void ChangedHandler(object sender, EventArgs e)
        {
            Entry entry = sender as Entry;
            (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
        }

        partial void ChangingHandler(object sender, HandlerChangingEventArgs e)
        {
            if (e.OldHandler != null)
            {
                (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
            }
        }

        void OnGotFocus(object sender, RoutedEventArgs e)
        {
            var nativeView = sender as TextBox;
            nativeView.SelectAll();
        }
    }
}

此方法的優點是不需要條件式編譯,而且部分方法不需要在每個平臺上實作。 如果未在平臺上提供實作,則在編譯階段會移除方法和所有對 方法的呼叫。 如需部分方法的相關信息,請參閱 部分方法

如需 .NET MAUI 專案中 Platform 資料夾組織的相關信息,請參閱 部分類別和方法。 如需了解如何設定多重目標以便不必將平台代碼放入 Platforms 資料夾的子資料夾,請參閱 設定多重目標