Udostępnij przez


Niestandardowe dołączone właściwości

Dołączona właściwość jest koncepcją XAML. Dołączone właściwości są zwykle definiowane jako wyspecjalizowana forma właściwości zależności. W tym temacie wyjaśniono, jak zaimplementować dołączoną właściwość jako właściwość zależności i jak zdefiniować konwencję dostępu niezbędną do użycia właściwości dołączonej w języku XAML.

Wymagania wstępne

Zakładamy, że rozumiesz właściwości zależności z perspektywy konsumenta istniejących właściwości zależności i zapoznasz się z omówieniem właściwości zależności. Należy również zapoznać się z omówieniem dołączonych właściwości. Aby postępować zgodnie z przykładami w tym temacie, należy również zrozumieć język XAML i wiedzieć, jak napisać podstawową aplikację środowiska uruchomieniowego systemu Windows przy użyciu języka C++, C# lub Visual Basic.

Scenariusze dotyczące dołączonych właściwości

Możesz utworzyć dołączoną właściwość, jeśli istnieje powód, aby mechanizm ustawiania właściwości był dostępny dla klas innych niż klasa definiująca. Najbardziej typowe scenariusze dotyczące tego rozwiązania to obsługa układów i usług. Przykłady istniejących właściwości układu to Canvas.ZIndex i Canvas.Top. W scenariuszu układu elementy, które istnieją jako elementy podrzędne do elementów sterujących układem, mogą wyrażać wymagania układu do ich elementów nadrzędnych indywidualnie, każdy ustawia wartość właściwości, którą element nadrzędny definiuje jako dołączoną właściwość. Przykładem scenariusza obsługi usług w interfejsie API środowiska uruchomieniowego systemu Windows jest zestaw dołączonych właściwości programu ScrollViewer, takich jak ScrollViewer.IsZoomChainingEnabled.

Ostrzeżenie

Istniejące ograniczenie implementacji XAML środowiska uruchomieniowego systemu Windows polega na tym, że nie można animować niestandardowej dołączonej właściwości.

Rejestrowanie niestandardowej właściwości załączonej

Jeśli definiujesz dołączoną właściwość ściśle do użycia w innych typach, klasa, w której zarejestrowana właściwość nie musi pochodzić z klasy DependencyObject. Należy jednak mieć parametr docelowy dla dostępników, używając klasy DependencyObject, jeśli stosujesz typowy model, w którym dołączona właściwość jest także właściwością zależności, aby można było użyć magazynu właściwości pomocniczych.

Zdefiniuj dołączoną właściwość jako właściwość zależności, deklarując publicznąstatycznątylko do odczytu właściwość typu DependencyProperty. Tę właściwość definiuje się przy użyciu wartości zwracanej metody RegisterAttached . Nazwa właściwości musi być zgodna z dołączoną nazwą właściwości, którą określisz jako parametr RegisterAttachedname z ciągiem "Property" dodanym na końcu. Jest to ustanowiona konwencja nazewnictwa identyfikatorów właściwości zależności w odniesieniu do właściwości, które reprezentują.

Głównym obszarem, w którym definiowanie niestandardowej dołączonej właściwości różni się od niestandardowej właściwości zależności, jest sposób definiowania akcesorów lub opakowań. Zamiast używać techniki opakowywania opisanej we właściwościach zależności niestandardowych, należy również podać statyczne metody GetPropertyName i SetPropertyName jako akcesory dołączonej właściwości. Akcesory są używane głównie przez analizator XAML, chociaż każdy inny wywołujący może również używać ich do ustawiania wartości poza scenariuszami XAML.

Ważne

Jeśli nie zdefiniujesz poprawnie metod dostępu, procesor XAML nie może uzyskać dostępu do dołączonej właściwości i każdy, kto próbuje go użyć, prawdopodobnie otrzyma błąd analizatora XAML. Ponadto narzędzia do projektowania i kodowania często opierają się na konwencjach nazewnictwa identyfikatorów "*Property", gdy napotykają niestandardową właściwość zależności w odwoływanym zestawie.

Accessors

Sygnatura metody dostępu GetPropertyName musi być taka.

public static valueTypeGetPropertyName(DependencyObject target)

W przypadku programu Microsoft Visual Basic jest to.

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

Obiekt docelowy może być bardziej określonym typem w implementacji, ale musi pochodzić z obiektu DependencyObject. Wartość zwracana ValueType może być również bardziej określonym typem w implementacji. Podstawowy typ obiektu jest akceptowalny, ale często chcesz, aby dołączona właściwość wymuszała bezpieczeństwo typu. Zastosowanie typowania w sygnaturach 'getter' i 'setter' jest zalecaną techniką bezpieczeństwa typów.

Sygnatura metody dostępu SetPropertyName musi być taka.

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

W przypadku języka Visual Basic jest to.

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

Obiekt docelowy może być bardziej określonym typem w implementacji, ale musi pochodzić z obiektu DependencyObject. Obiekt wartości i jego valueType mogą być bardziej określonym typem w twojej implementacji. Pamiętaj, że wartość tej metody to dane wejściowe pochodzące z procesora XAML, gdy napotka on dołączoną właściwość w adiustacji. Musi istnieć konwersja typu lub obsługa istniejącego rozszerzenia znaczników dla używanego typu, aby można było utworzyć odpowiedni typ na podstawie wartości atrybutu (która ostatecznie jest tylko ciągiem). Podstawowy typ Object jest akceptowalny, ale często potrzebujesz większego bezpieczeństwa typów. Aby to osiągnąć, umieść wymuszanie typu w metodzie dostępu.

Uwaga / Notatka

Istnieje również możliwość zdefiniowania dołączonej właściwości, w której zamierzone użycie odbywa się za pomocą składni elementu właściwości. W takim przypadku nie potrzebujesz konwersji typu dla wartości, ale musisz upewnić się, że wartości, które zamierzasz utworzyć, mogą być skonstruowane za pomocą XAML. VisualStateManager.VisualStateGroups jest przykładem istniejącej dołączonej właściwości, która obsługuje tylko użycie elementu właściwości.

Przykład kodu

W tym przykładzie przedstawiono rejestrację właściwości zależności (przy użyciu metody RegisterAttached), a także akcesorów Get oraz Set dla niestandardowej dołączonej właściwości. W tym przykładzie dołączona nazwa właściwości to IsMovable. W związku z tym metody dostępu muszą mieć nazwy GetIsMovable i SetIsMovable. Właścicielem podłączonej właściwości jest klasa usługi o nazwie GameService, która nie ma własnego interfejsu użytkownika; jej celem jest jedynie świadczenie usług dla podłączonej właściwości, gdy używana jest właściwość GameService.IsMovable.

Definiowanie dołączonej właściwości w języku C++/CX jest nieco bardziej złożone. Musisz zdecydować, jak podzielić pomiędzy plikiem nagłówkowym a plikiem kodu. Proponuję uwidocznić identyfikator jako właściwość tylko z akcesorem get, z powodów omówionych w właściwościach zależności niestandardowych. W języku C++/CX należy jawnie zdefiniować tę relację pola właściwości, a nie polegać na słowach kluczowych .NET readonly i niejawnym zapleczu prostych właściwości. Należy również przeprowadzić rejestrację dołączonej właściwości w funkcji pomocniczej, która jest uruchamiana tylko raz, gdy aplikacja najpierw uruchamia się, ale przed załadowaniem wszystkich stron XAML, które wymagają dołączonej właściwości. Typowym miejscem wywoływania pomocniczych funkcji rejestracji dla wszelkich właściwości zależnych lub dołączonych jest konstruktor App / Application w kodzie pliku app.xaml w Twojej aplikacji.

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

Ustawianie niestandardowej właściwości dołączonej z znaczników XAML

Po zdefiniowaniu dołączonej właściwości i dołączeniu członków wspierających jako część niestandardowego typu, należy udostępnić definicje do użycia w XAML. W tym celu należy zamapować przestrzeń nazw XAML, która będzie odwoływać się do przestrzeni nazw kodu zawierającej odpowiednią klasę. W przypadkach, gdy zdefiniowano właściwość dołączoną jako część biblioteki, należy dołączyć tę bibliotekę jako część pakietu aplikacji.

Mapowanie przestrzeni nazw XML dla języka XAML jest zwykle umieszczane w elemecie głównym strony XAML. Na przykład dla klasy o nazwie GameService w przestrzeni nazw UserAndCustomControls zawierającej dołączone definicje właściwości pokazane w poprzednich fragmentach kodu mapowanie może wyglądać następująco.

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

Za pomocą mapowania można ustawić GameService.IsMovable dołączoną właściwość na dowolnym elemencie, który pasuje do definicji docelowej, w tym istniejącego typu zdefiniowanego przez środowisko uruchomieniowe Windows.

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

Jeśli ustawiasz właściwość elementu, który znajduje się również w tej samej zamapowanej przestrzeni nazw XML, nadal musisz dołączyć prefiks nazwy dołączonej właściwości. Jest to spowodowane tym, że prefiks kwalifikuje typ właściciela. Nie można zakładać, że atrybut dołączonej właściwości należy do tej samej przestrzeni nazw XML co element, w którym jest uwzględniony atrybut, mimo że według normalnych reguł XML atrybuty mogą dziedziczyć przestrzeń nazw z elementów. Jeśli na przykład ustawiasz GameService.IsMovable na niestandardowy typ ImageWithLabelControl (nie pokazano definicji), a nawet jeśli obie zostały zdefiniowane w tej samej przestrzeni nazw kodu zamapowanej na ten sam prefiks, XAML nadal będzie taki sam.

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

Uwaga / Notatka

Jeśli piszesz interfejs użytkownika XAML w języku C++/CX, musisz dołączyć nagłówek dla typu niestandardowego, który definiuje dołączoną właściwość, za każdym razem, gdy strona XAML używa tego typu. Każda strona XAML ma powiązany nagłówek pliku code-behind (.xaml.h). W tym miejscu należy dołączyć (przy użyciu #include) nagłówek zawierający definicję typu właściciela właściwości dołączonej.

Ustawienie niestandardowej właściwości przypisanej programowo

Dostęp do niestandardowej dołączonej właściwości można również uzyskać z poziomu kodu imperatywnego. Poniższy kod pokazuje, jak to zrobić.

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

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

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

Typ wartości niestandardowej właściwości dołączonej

Typ, który jest używany jako typ wartości niestandardowej dołączonej właściwości, wpływa na użycie, definicję lub zarówno na użycie, jak i na definicję. Typ wartości dołączonej właściwości jest zadeklarowany w kilku miejscach: w podpisach metod dostępu Get i Set, a także jako parametr propertyType wywołania RegisterAttached.

Najczęstszym typem wartości dla dołączonych właściwości (niestandardowych lub w inny sposób) jest prosty ciąg. Wynika to z faktu, że dołączone właściwości są zwykle przeznaczone do użycia atrybutów XAML, a użycie ciągu jako typu wartości utrzymuje właściwości lekkie. Inne typy pierwotne, które mają natywną konwersję na metody ciągów znaków, takie jak typy całkowite, zmiennoprzecinkowe lub wartości wyliczeniowe, są również często stosowane jako typy wartości dla dołączonych właściwości. Można użyć innych typów wartości — tych, które nie obsługują konwersji ciągów natywnych — jako dołączonej wartości właściwości. Jednak wiąże się to z wyborem dotyczącym użycia lub implementacji:

  • Możesz pozostawić dołączoną właściwość tak, jak jest, ale dołączona właściwość może obsługiwać użycie tylko wtedy, gdy dołączona właściwość jest elementem właściwości, a wartość jest zadeklarowana jako element obiektu. W tym przypadku typ właściwości musi obsługiwać użycie XAML jako element obiektu. W przypadku istniejących klas odwołań środowiska uruchomieniowego systemu Windows sprawdź składnię XAML, aby upewnić się, że typ obsługuje użycie elementu obiektu XAML.
  • Dołączoną właściwość można pozostawić tak, jak jest, ale używać jej tylko w użyciu atrybutu za pomocą techniki referencyjnej XAML, takiej jak powiązanie lub staticResource , które można wyrazić jako ciąg.

Więcej informacji na temat przykładu Canvas.Left

W wcześniejszych przykładach użycia właściwości dołączonych pokazaliśmy różne sposoby ustawiania właściwości Canvas.Left. Ale co to zmienia w tym, jak kanwa współdziała z twoim obiektem i kiedy to się dzieje? Przeanalizujemy ten konkretny przykład, ponieważ jeśli zaimplementujesz dołączoną właściwość, warto zobaczyć, co jeszcze typowa klasa właściciela dołączonej właściwości zamierza wykonać z dołączonymi wartościami właściwości, jeśli znajdzie je na innych obiektach.

Główną funkcją kanwy jest działanie jako pozycjonowany absolutnie kontener układu w interfejsie użytkownika. Elementy podrzędne Canvas są przechowywane we właściwości zdefiniowanej przez klasę bazową Children. Spośród wszystkich paneli Canvas jest jedyną, która używa pozycjonowania bezwzględnego. Model obiektów wspólnego typu UIElement zostałby nadmiernie rozbudowany poprzez dodanie właściwości, które mogą interesować jedynie Canvas i w przypadku konkretnych sytuacji UIElement, gdy są one elementami podrzędnymi UIElement. Definiowanie właściwości kontrolnych układu Canvas jako właściwości dołączanych, których każdy UIElement może używać, pozwala utrzymać model obiektów w czystości.

Aby być praktycznym panelem, Canvas ma zachowanie, które zastępuje metody Measure i Arrange na poziomie ramy. W tym miejscu Canvas faktycznie sprawdza wartości dołączonych właściwości dla jego elementów podrzędnych. Częścią zarówno wzorca Miara, jak i wzorca Rozmieść jest pętla, która iteruje nad dowolną zawartością, a panel ma właściwość Children, która określa, co ma być uważane za element podrzędny panelu. Dlatego zachowanie układu Canvas iteruje przez te elementy podrzędne i wykonuje statyczne wywołania Canvas.GetLeft i Canvas.GetTop dla każdego elementu podrzędnego, aby sprawdzić, czy dołączone właściwości zawierają wartość inną niż domyślna (domyślnie jest 0). Te wartości są następnie używane do bezwzględnego pozycjonowania każdego elementu podrzędnego w dostępnym obszarze układu Canvas zgodnie z określonymi wartościami dostarczonymi przez poszczególne elementy podrzędne i zatwierdzane za pomocą Rozmieść.

Kod wygląda podobnie do tego pseudokodu.

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
}

Uwaga / Notatka

Aby uzyskać więcej informacji na temat sposobu działania paneli, zobacz Omówienie paneli niestandardowych XAML.