Dela via


Anpassade bifogade egenskaper

En bifogad egenskap är ett XAML-koncept. Anslutna egenskaper definieras vanligtvis som en specialiserad form av beroendeegenskap. Det här avsnittet beskriver hur du implementerar en bifogad egenskap som en beroendeegenskap och hur du definierar den accessorkonvention som krävs för att din anslutna egenskap ska kunna användas i XAML.

Förutsättningar

Vi förutsätter att du förstår beroendeegenskaper ur ett konsumentperspektiv och att du har läst Översikt över beroendeegenskaper. Du bör också ha läst Översikt över bifogade egenskaper. Om du vill följa exemplen i det här avsnittet bör du också förstå XAML och veta hur du skriver en grundläggande Windows Runtime-app med C++, C#eller Visual Basic.

Scenarier för bifogade egenskaper

Du kan skapa en bifogad egenskap när det finns en anledning att ha en egenskapsinställningsmekanism tillgänglig för andra klasser än den definierande klassen. De vanligaste scenarierna för detta är stöd för layout och tjänster. Exempel på befintliga layoutegenskaper är Canvas.ZIndex och Canvas.Top. I ett layoutscenario kan element som finns som underordnade element till layoutkontrollerande element uttrycka layoutkrav till sina överordnade element individuellt, var och en anger ett egenskapsvärde som den överordnade definierar som en bifogad egenskap. Ett exempel på tjänstesupportscenariot i Windows Runtime-API:et är en uppsättning anslutna egenskaper för ScrollViewer, till exempel ScrollViewer.IsZoomChainingEnabled.

Varning

En befintlig begränsning för Windows Runtime XAML-implementeringen är att du inte kan animera din anpassade bifogade egenskap.

Registrera en anpassad bifogad egenskap

Om du definierar den anslutna egenskapen strikt för användning på andra typer behöver klassen där egenskapen är registrerad inte härledas från DependencyObject. Men du måste ha målparametern så att accessorerna använder DependencyObject om du följer den typiska modellen där din bifogade egenskap också är en beroendeegenskap, så att du kan använda egenskapslagret för stöd.

Definiera din anslutna egenskap som en beroendeegenskap genom att deklarera en offentligstatiskskrivskyddad egenskap av typen DependencyProperty. Du definierar den här egenskapen med hjälp av returvärdet för metoden RegisterAttached . Egenskapsnamnet måste matcha det kopplade egenskapsnamnet som du anger som parametern RegisterAttachedname med strängen "Property" som har lagts till i slutet. Detta är den etablerade konventionen för namngivning av identifierare för beroendeegenskaper i förhållande till de egenskaper som de representerar.

Det huvudsakliga området där definitionen av en anpassad bifogad egenskap skiljer sig från en anpassad beroendeegenskap är hur du definierar accessorerna eller omslutningarna. I stället för att använda omslutningstekniken som beskrivs i Anpassade beroendeegenskaper måste du även ange statiska Get PropertyName- och SetPropertyName-metoder som accessorer för den anslutna egenskapen. Accessorerna används främst av XAML-parsern, även om alla andra användare också kan använda dem för att ange värden i icke-XAML-scenarier.

Viktigt!

Om du inte definierar åtkomsterna korrekt kan XAML-processorn inte komma åt din anslutna egenskap och alla som försöker använda den får förmodligen ett XAML-parsningsfel. Dessutom förlitar sig design- och kodverktyg ofta på "*"-Propertykonventionerna för namngivning av identifierare när de stöter på en anpassad beroendeegenskap i en refererad sammansättning.

Accessors

Signaturen för GetPropertyName-accessorn måste vara så här.

public static valueTypeGetPropertyName(DependencyObject target)

För Microsoft Visual Basic är det detta.

Public Shared Function Get PropertyName(ByVal target As DependencyObject) As valueType)

Målobjektet kan vara av en mer specifik typ i implementeringen, men måste härledas från DependencyObject. ValueType-returvärdet kan också vara av en mer specifik typ i implementeringen. Den grundläggande objekttypen är acceptabel, men ofta vill du att den anslutna egenskapen ska framtvinga typsäkerhet. Användning av att skriva in getter- och settersignaturerna är en rekommenderad typsäkerhetsteknik.

Signaturen för SetPropertyName-åtkomstorn måste vara den här.

public static void Set PropertyName(DependencyObject target ,valueType value)

För Visual Basic är det detta.

Public Shared Sub Set PropertyName(ByVal target As DependencyObject, ByVal value AsvalueType)

Målobjektet kan vara av en mer specifik typ i implementeringen, men måste härledas från DependencyObject. Värdeobjektet och dess valueType kan vara av en mer specifik typ i implementeringen. Kom ihåg att värdet för den här metoden är de indata som kommer från XAML-processorn när den stöter på din anslutna egenskap i markering. Det måste finnas stöd för typkonvertering eller befintligt påläggstillägg för den typ du använder, så att lämplig typ kan skapas från ett attributvärde (vilket i slutändan bara är en sträng). Den grundläggande objekttypen är acceptabel, men ofta vill du ha ytterligare typsäkerhet. Det gör du genom att lägga in typkontrollmekanismen i åtkomstgivarna.

Anmärkning

Det går också att definiera en bifogad egenskap där den avsedda användningen sker via syntax för egenskapselement. I så fall behöver du inte typkonvertering för värdena, men du måste försäkra dig om att de värden du avser kan konstrueras i XAML. VisualStateManager.VisualStateGroups är ett exempel på en befintlig bifogad egenskap som endast stöder användning av egenskapselement.

Kodexempel

Det här exemplet visar registrering av beroendeegenskap (med metoden RegisterAttached ) samt get- ochset-åtkomsterna för en anpassad bifogad egenskap. I exemplet är IsMovabledet kopplade egenskapsnamnet . Därför måste åtkomsterna namnges GetIsMovable och SetIsMovable. Ägaren till den bifogade egenskapen är en tjänstklass med namnet GameService som inte har ett eget användargränssnitt. Syftet är bara att tillhandahålla de anslutna egenskapstjänsterna när den anslutna egenskapen GameService.IsMovable används.

Det är lite mer komplext att definiera den anslutna egenskapen i C++/CX. Du måste bestämma hur du ska dela upp mellan headerfilen och kodfilen. Du bör också exponera identifieraren som en egenskap med endast en get-accessor , av skäl som beskrivs i Anpassade beroendeegenskaper. I C++/CX måste du definiera relationen mellan egenskap och fält explicit i stället för att förlita dig på readonly i .NET och implicit stöd för enkla egenskaper. Du måste också utföra registreringen av den anslutna egenskapen i en hjälpfunktion som bara körs en gång, när appen först startar men innan alla XAML-sidor som behöver den anslutna egenskapen läses in. Den vanliga platsen där du anropar hjälpfunktionerna för egenskapsregistrering för alla beroenden eller anslutna egenskaper finns i / i koden för din app.xaml-fil.

public class GameService : DependencyObject
{
    public static readonly DependencyProperty IsMovableProperty = 
    DependencyProperty.RegisterAttached(
      "IsMovable",
      typeof(Boolean),
      typeof(GameService),
      new PropertyMetadata(false)
    );
    public static void SetIsMovable(UIElement element, Boolean value)
    {
        element.SetValue(IsMovableProperty, value);
    }
    public static Boolean GetIsMovable(UIElement element)
    {
        return (Boolean)element.GetValue(IsMovableProperty);
    }
}
Public Class GameService
    Inherits DependencyObject

    Public Shared ReadOnly IsMovableProperty As DependencyProperty = 
        DependencyProperty.RegisterAttached("IsMovable",  
        GetType(Boolean), 
        GetType(GameService), 
        New PropertyMetadata(False))

    Public Shared Sub SetIsMovable(ByRef element As UIElement, value As Boolean)
        element.SetValue(IsMovableProperty, value)
    End Sub

    Public Shared Function GetIsMovable(ByRef element As UIElement) As Boolean
        GetIsMovable = CBool(element.GetValue(IsMovableProperty))
    End Function
End Class
// GameService.idl
namespace UserAndCustomControls
{
    [default_interface]
    runtimeclass GameService : Windows.UI.Xaml.DependencyObject
    {
        GameService();
        static Windows.UI.Xaml.DependencyProperty IsMovableProperty{ get; };
        static Boolean GetIsMovable(Windows.UI.Xaml.DependencyObject target);
        static void SetIsMovable(Windows.UI.Xaml.DependencyObject target, Boolean value);
    }
}

// GameService.h
...
    static Windows::UI::Xaml::DependencyProperty IsMovableProperty() { return m_IsMovableProperty; }
    static bool GetIsMovable(Windows::UI::Xaml::DependencyObject const& target) { return winrt::unbox_value<bool>(target.GetValue(m_IsMovableProperty)); }
    static void SetIsMovable(Windows::UI::Xaml::DependencyObject const& target, bool value) { target.SetValue(m_IsMovableProperty, winrt::box_value(value)); }

private:
    static Windows::UI::Xaml::DependencyProperty m_IsMovableProperty;
...

// GameService.cpp
...
Windows::UI::Xaml::DependencyProperty GameService::m_IsMovableProperty =
    Windows::UI::Xaml::DependencyProperty::RegisterAttached(
        L"IsMovable",
        winrt::xaml_typename<bool>(),
        winrt::xaml_typename<UserAndCustomControls::GameService>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(false) }
);
...
// GameService.h
#pragma once

#include "pch.h"
//namespace WUX = Windows::UI::Xaml;

namespace UserAndCustomControls {
    public ref class GameService sealed : public WUX::DependencyObject {
    private:
        static WUX::DependencyProperty^ _IsMovableProperty;
    public:
        GameService::GameService();
        void GameService::RegisterDependencyProperties();
        static property WUX::DependencyProperty^ IsMovableProperty
        {
            WUX::DependencyProperty^ get() {
                return _IsMovableProperty;
            }
        };
        static bool GameService::GetIsMovable(WUX::UIElement^ element) {
            return (bool)element->GetValue(_IsMovableProperty);
        };
        static void GameService::SetIsMovable(WUX::UIElement^ element, bool value) {
            element->SetValue(_IsMovableProperty,value);
        }
    };
}

// GameService.cpp
#include "pch.h"
#include "GameService.h"

using namespace UserAndCustomControls;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Documents;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::UI::Xaml::Media;

GameService::GameService() {};

GameService::RegisterDependencyProperties() {
    DependencyProperty^ GameService::_IsMovableProperty = DependencyProperty::RegisterAttached(
         "IsMovable", Platform::Boolean::typeid, GameService::typeid, ref new PropertyMetadata(false));
}

Ange din anpassade bifogade egenskap från XAML-kod.

När du har definierat den bifogade egenskapen och inkluderat dess supportmedlemmar som en del av en anpassad typ måste du sedan göra definitionerna tillgängliga för XAML-användning. För att göra detta måste du mappa ett XAML-namnområde som refererar till kodnamnområdet som innehåller relevant klass. Om du har definierat den bifogade egenskapen som en del av ett bibliotek måste du inkludera det biblioteket som en del av apppaketet för appen.

En XML-namnområdesmappning för XAML placeras vanligtvis i rotelementet på en XAML-sida. För klassen med namnet GameService i namnområdet UserAndCustomControls som innehåller de kopplade egenskapsdefinitioner som visas i föregående kodfragment kan mappningen till exempel se ut så här.

<UserControl
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:uc="using:UserAndCustomControls"
  ... >

Med hjälp av mappningen kan du ange din GameService.IsMovable anslutna egenskap för alla element som matchar måldefinitionen, inklusive en befintlig typ som Windows Runtime definierar.

<Image uc:GameService.IsMovable="True" .../>

Om du anger egenskapen för ett element som också finns inom samma mappade XML-namnområde måste du fortfarande inkludera prefixet på det bifogade egenskapsnamnet. Det beror på att prefixet kvalificerar ägartypen. Det går inte att anta att den bifogade egenskapens attribut ligger inom samma XML-namnområde som elementet där attributet ingår, även om attribut enligt vanliga XML-regler kan ärva namnrymden från element. Om du till exempel anger GameService.IsMovable en anpassad typ av ImageWithLabelControl (definitionen visas inte) och även om båda har definierats i samma kodnamnområde som mappats till samma prefix, skulle XAML fortfarande vara detta.

<uc:ImageWithLabelControl uc:GameService.IsMovable="True" .../>

Anmärkning

Om du skriver ett XAML-användargränssnitt med C++/CX måste du inkludera huvudet för den anpassade typ som definierar den anslutna egenskapen, varje gång en XAML-sida använder den typen. Varje XAML-sida har en associerad kodbakom-fil (.xaml.h). Det är här du bör inkludera (med #include) headerfilen för definitionen av den bifogade egenskapens ägande typ.

Ange din anpassade bifogade egenskap genom imperativa kommandon

Du kan också komma åt en anpassad bifogad egenskap från imperativ kod. Koden nedan visar hur.

<Image x:Name="gameServiceImage"/>
// MainPage.h
...
#include "GameService.h"
...

// MainPage.cpp
...
MainPage::MainPage()
{
    InitializeComponent();

    GameService::SetIsMovable(gameServiceImage(), true);
}
...

Värdetyp för en anpassad bifogad egenskap

Den typ som används som värdetyp för en anpassad bifogad egenskap påverkar användningen, definitionen eller både användning och definition. Den kopplade egenskapens värdetyp deklareras på flera platser: i signaturerna för både Get och Set accessormetoderna, och även som propertyType parametern för RegisterAttached anropet.

Den vanligaste värdetypen för anslutna egenskaper (anpassad eller på annat sätt) är en enkel sträng. Det beror på att bifogade egenskaper vanligtvis är avsedda för XAML-attributanvändning och att använda en sträng som värdetyp håller egenskaperna lätta. Andra primitiver som har intern konvertering till strängmetoder, till exempel heltal, dubbel eller ett uppräkningsvärde, är också vanliga som värdetyper för anslutna egenskaper. Du kan använda andra värdetyper – de som inte stöder intern strängkonvertering – som det anslutna egenskapsvärdet. Detta innebär dock att du kan välja mellan användning eller implementering:

  • Du kan lämna den bifogade egenskapen som den är, men den anslutna egenskapen kan endast stödja användning där den bifogade egenskapen är ett egenskapselement och värdet deklareras som ett objektelement. I det här fallet måste egenskapstypen ha stöd för XAML-användning som ett objektelement. För befintliga Windows Runtime-referensklasser kontrollerar du XAML-syntaxen för att kontrollera att typen stöder XAML-objektelementanvändning.
  • Du kan lämna den bifogade egenskapen som den är, men använd den bara i en attributanvändning via en XAML-referensteknik, till exempel en bindning eller StaticResource som kan uttryckas som en sträng.

Mer om exemplet Canvas.Left

I tidigare exempel på anslutna egenskapsanvändningar visade vi olika sätt att ange egenskapen Canvas.Left . Men vad ändrar det om hur en Canvas interagerar med ditt objekt, och när händer det? Vi undersöker det här exemplet ytterligare, för om du implementerar en bifogad egenskap är det intressant att se vad mer en typisk bifogad egenskapsägarklass avser att göra med dess kopplade egenskapsvärden om den hittar dem på andra objekt.

Huvudfunktionen för en Canvas är att vara en absolut-positionerad layoutcontainer i användargränssnittet. Ett Canvass barn lagras i en basklassdefinierad egenskap Children. Av alla paneler är Canvas den enda som använder absolut positionering. Det skulle ha blåst upp objektmodellen hos den gemensamma UIElement-typen genom att lägga till egenskaper som kanske bara är viktiga för Canvas och de specifika UIElement-fall där de är barnelement i ett UIElement. Om du definierar egenskaper för layoutkontroll för en arbetsyta som ska kopplas till egenskaper som alla UIElement kan använda, blir objektmodellen renare.

För att vara en praktisk panel har Canvas ett beteende som åsidosätter metoderna Mått på ramverksnivå och Ordna . Det är här Canvas faktiskt söker efter kopplade egenskapsvärden på sina underordnade. En del av både Mått- och Ordna-mönstren är en loop som itererar över vilket som helst innehåll, och en panel har egenskapen Barn som tydligt anger vilket element som ska betraktas som ett barn till en panel. Layoutbeteendet för Canvas itererar därför genom dessa underordnade objekt och gör statiska anrop till Canvas.GetLeft och Canvas.GetTop för varje underordnat objekt för att se om de bifogade egenskaperna innehåller ett värde som inte är standard (standardvärdet är 0). Dessa värden används sedan för att absolut placeras varje barn i det tillgängliga layoututrymmet i Canvas enligt de specifika värden som tillhandahålls av varje barn och utförs med hjälp av 'Arrange'.

Koden liknar följande pseudokod.

protected override Size ArrangeOverride(Size finalSize)
{
    foreach (UIElement child in Children)
    {
        double x = (double) Canvas.GetLeft(child);
        double y = (double) Canvas.GetTop(child);
        child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
    }
    return base.ArrangeOverride(finalSize); 
    // real Canvas has more sophisticated sizing
}

Anmärkning

Mer information om hur paneler fungerar finns i Översikt över anpassade XAML-paneler.