Condividi tramite


Proprietà associate personalizzate

Una proprietà associata è un concetto XAML. Le proprietà associate sono in genere definite come forme specializzate di proprietà di dipendenza. Questo argomento spiega come implementare una proprietà associata come proprietà di dipendenza e come definire la convenzione della funzione di accesso necessaria per poter usare la proprietà associata in XAML.

Prerequisiti

Si presuppone una certa familiarità con le proprietà di dipendenza dal punto di vista di un utente di proprietà di dipendenza esistenti e con l'argomento Panoramica delle proprietà di dipendenza. È anche necessario avere letto Panoramica delle proprietà associate. Per seguire gli esempi in questo argomento, è necessario avere familiarità con XAML ed essere in grado di scrivere un'app Windows Runtime di base usando C++, C# o Visual Basic.

Scenari per proprietà associate

È possibile creare una proprietà associata quando è necessario disporre di un meccanismo di impostazione delle proprietà per le classi diverse da quella di definizione. In quest'ottica gli scenari più comuni sono il supporto per servizi e layout. Esempi di proprietà layout sono Canvas.ZIndex e Canvas.Top. In uno scenario layout, gli elementi esistenti come elementi figlio di elementi di controllo del layout possono indicare i requisiti di layout ai relativi elementi padre, impostando ciascuno un valore di proprietà che l'elemento padre definisce come proprietà associata. Un esempio di scenario di supporto per servizi nell'API Windows Runtime è un set delle proprietà associate di ScrollViewer, ad esempio ScrollViewer.IsZoomChainingEnabled.

Avviso

Una limitazione esistente dell'implementazione XAML di Windows Runtime è l'impossibilità di animare la proprietà associata personalizzata.

Registrazione di una proprietà associata personalizzata

Se si definisce la proprietà associata in modo rigoroso per l'uso su altri tipi, la classe in cui è registrata la proprietà non deve derivare necessariamente da DependencyObject. Tuttavia, è necessario che il parametro di destinazione per le funzioni di accesso usino DependencyObject se si adotta il tipico modello in cui la proprietà associata è anche una proprietà di dipendenza, in modo che sia possibile usare l'archivio proprietà di supporto.

Definire la proprietà associata come proprietà di dipendenza dichiarando una proprietà di sola lettura statica pubblica di tipo DependencyProperty. Si definisce questa proprietà usando il valore restituito del metodo RegisterAttached. Il nome della proprietà deve corrispondere al nome della proprietà associata specificato come parametro RegisterAttached name , con la stringa "Property" aggiunta alla fine. Si tratta della convenzione stabilita per la denominazione degli identificatori delle proprietà di dipendenza in relazione alle proprietà rappresentate.

L'area principale in cui la definizione di una proprietà associata personalizzata si differenzia da una proprietà di dipendenza personalizzata è la modalità di definizione delle funzioni di accesso o dei wrapper. Anziché usare la tecnica del wrapper descritta in Proprietà di dipendenza personalizzate, è anche necessario fornire metodi GetPropertyName e SetPropertyName statici come funzioni di accesso per la proprietà associata. Le funzioni di accesso vengono usate principalmente dal parser XAML, anche se qualsiasi altro chiamante può usarle per impostare valori in scenari non XAML.

Importante

Se non si definiscono correttamente le funzioni di accesso, il processore XAML non può accedere alla proprietà associata e chiunque tenti di usarla otterrà probabilmente un errore del parser XAML. Inoltre, gli strumenti di progettazione e codifica spesso si basano sulle convenzioni "*Property" per la denominazione degli identificatori quando rilevano una proprietà di dipendenza personalizzata in un assembly a cui si fa riferimento.

Funzioni di accesso

La firma per la funzione di accesso GetPropertyName deve essere questa.

public staticvalueType GetPropertyName (DependencyObject target)

Per Microsoft Visual Basic, è questa.

Public Shared Function GetPropertyName(ByVal target As DependencyObject) As valueType)

L'oggetto target può essere di un tipo più specifico nell'implementazione, ma deve derivare da DependencyObject. Il valore restituito valueType può essere di un tipo più specifico nell'implementazione. Il tipo Object di base è accettabile, ma spesso si desidera che la proprietà associata applichi l'indipendenza dati tipi. La digitazione delle firme getter e setter è una tecnica consigliata per l'indipendenza dai tipi.

La firma per la funzione di accesso SetPropertyName deve essere questa.

public static void SetPropertyName(DependencyObject target ,valueType value)

Per Visual Basic, è questa.

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

L'oggetto target può essere di un tipo più specifico nell'implementazione, ma deve derivare da DependencyObject. L'oggetto value e il relativo valueType possono essere di un tipo più specifico nell'implementazione. Tenere presente che il valore per questo metodo è l'input che proviene dal processore XAML quando rileva la proprietà associata nel markup. Per il tipo usato deve essere disponibile la conversione del tipo o il supporto per l'estensione di markup esistente, in modo che sia possibile creare il tipo appropriato da un valore attributo (che è semplicemente una stringa). Il tipo Object di base è accettabile, ma spesso si auspica una maggiore indipendenza dai tipi. A tale scopo, inserire l'imposizione del tipo nelle funzioni di accesso.

Nota

È anche possibile definire una proprietà associata in cui l'utilizzo previsto avviene tramite la sintassi degli elementi di proprietà. In questo caso, la conversione dei tipi non è necessaria per i valori, ma è necessario assicurarsi che sia possibile creare i valori desiderati in XAML. VisualStateManager.VisualStateGroups è un esempio di una proprietà associata esistente che supporta solo l'utilizzo degli elementi di proprietà.

Esempio di codice

Questo esempio mostra la registrazione della proprietà di dipendenza (usando il metodo RegisterAttached), nonché le funzioni di accesso Get e Set, per una proprietà associata personalizzata. Nell'esempio, la proprietà associata è denominata IsMovable. Pertanto, le funzioni di accesso devono essere chiamate GetIsMovable e SetIsMovable. Il proprietario della proprietà associata è una classe di servizio denominata GameService che non ha un'interfaccia utente propria. Lo scopo è quello di fornire solo i servizi di proprietà associata quando si usa la proprietà associata GameService.IsMovable.

La definizione della proprietà associata in C++/CX è un po' più complessa. È necessario decidere come fattorizzare intestazione e file di codice. Inoltre, sarebbe opportuno esporre l'identificatore come proprietà con una sola funzione di accesso get, per i motivi discussi in Proprietà di dipendenza personalizzate. In C++/CX è necessario definire questa relazione proprietà-campo in modo esplicito anziché basarsi sulla parola chiave .NET readonly e il supporto implicito di proprietà semplici. È anche necessario eseguire la registrazione della proprietà associata in una funzione helper che viene eseguita una volta sola, quando l'app viene avviata per la prima volta, ma prima che vengano caricate le pagine XAML che necessitano della proprietà associata. La posizione tipica per chiamare le funzioni helper di registrazione della proprietà per qualsiasi o tutte le proprietà associate o di dipendenza è all'interno del costruttore App / Application nel codice per il file app.xaml.

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

Impostazione della proprietà associata personalizzata dal markup XAML

Dopo aver definito la proprietà associata e aver incluso i relativi membri di supporto come parte di un tipo personalizzato, è quindi necessario rendere disponibili le definizioni per l'utilizzo di XAML. A tale scopo, è necessario eseguire il mapping di uno spazio dei nomi XAML che farà riferimento allo spazio dei nomi del codice che contiene la classe rilevante. Nei casi in cui la proprietà associata sia stata definita come parte di una libreria, è necessario includere quella libreria come parte del pacchetto dell'app.

Un mapping dello spazio dei nomi XML per XAML viene in genere inserito nell'elemento radice di una pagina XAML. Ad esempio, per la classe denominata GameService nello spazio dei nomi UserAndCustomControls che contiene definizioni di proprietà associate mostrate nei frammenti di codice precedenti, il mapping potrebbe essere simile al seguente.

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

Usando il mapping, è possibile impostare la proprietà associata GameService.IsMovable su qualsiasi elemento corrispondente alla definizione della destinazione, incluso un tipo esistente definito da Windows Runtime.

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

Se si imposta la proprietà su un elemento che si trova anch'esso all'interno dello stesso spazio dei nomi XML mappato, è comunque necessario includere il prefisso nel nome della proprietà associata. Questo perché il prefisso qualifica il tipo di proprietario. L'attributo della proprietà associata non può essere considerato all'interno dello stesso spazio dei nomi XML dell'elemento in cui è incluso l'attributo, anche se, in base alle normali regole XML, gli attributi possono ereditare lo spazio dei nomi dagli elementi. Ad esempio, se si imposta GameService.IsMovable su un tipo personalizzato di ImageWithLabelControl (definizione non mostrata), e anche se entrambi sono stati definiti nello stesso spazio dei nomi del codice mappato allo stesso prefisso, il codice XAML sarà comunque questo.

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

Nota

Se si scrive un'interfaccia utente XAML con C++/CX, è necessario includere l'intestazione per il tipo personalizzato che definisce la proprietà associata, ogni volta che una pagina XAML usa quel tipo. A ogni pagina XAML è associata un'intestazione code-behind (.xaml.h). È qui che si dovrebbe includere (usando #include) l'intestazione per la definizione del tipo di proprietario della proprietà associata.

Impostazione imperativa della proprietà associata personalizzata

È anche possibile accedere a una proprietà associata personalizzata dal codice imperativo. Il codice seguente mostra come.

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

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

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

Tipo di valore di una proprietà associata personalizzata

Il tipo utilizzato come tipo di valore di una proprietà associata personalizzata influisce sull'utilizzo, sulla definizione o su entrambi. Il tipo di valore della proprietà associata è dichiarato in diverse posizioni: nelle firme dei metodi delle funzioni di accesso Get e Set e anche come parametro propertyType della chiamata RegisterAttached.

Il tipo di valore più comune per le proprietà associate (personalizzate o altro) è una stringa semplice. Questo è dovuto al fatto che le proprietà associate sono in genere destinate all'utilizzo degli attributi XAML e l'uso di una stringa come tipo di valore mantiene le proprietà leggere. Altre primitive con conversione nativa in metodi stringa, ad esempio integer, double o un valore di enumerazione, sono comuni come tipi di valore per le proprietà associate. È possibile usare altri tipi di valore, quelli che non supportano la conversione di stringhe native, come valore della proprietà associata. Questo comporta, tuttavia, una scelta in merito a utilizzo o implementazione:

  • È possibile lasciare invariata la proprietà associata, tuttavia la proprietà associata può supportare l'utilizzo solo dove la proprietà associata è un elemento di proprietà e il valore viene dichiarato come elemento oggetto. In questo caso, il tipo di proprietà deve supportare l'utilizzo XAML come elemento oggetto. Per le classi di riferimento di Windows Runtime esistenti, controllare la sintassi XAML per assicurarsi che il tipo supporti l'utilizzo degli elementi oggetto XAML.
  • È possibile lasciare invariata la proprietà associata, ma usarla solo in un utilizzo di attributo tramite una tecnica di riferimento XAML, ad esempio Binding o StaticResource che è possibile esprimere come stringa.

Altre informazioni sull'esempio Canvas.Left

Gli esempi precedenti relativi all'utilizzo delle proprietà associate hanno mostrato modi diversi per impostare la proprietà associata Canvas.Left. Ma cosa cambia nel modo in cui un oggetto Canvas interagisce con l'oggetto e quando accade tutto questo? Questo particolare esempio verrà approfondito perché se si implementa una proprietà associata, è interessante vedere cos'altro intende fare una tipica classe proprietario di proprietà associate con i relativi valori se li trova su altri oggetti.

La funzione principale di un oggetto Canvas è fornire un contenitore di layout con posizione assoluta nell'interfaccia utente. Gli elementi figlio di un oggetto Canvas sono archiviati in una proprietà definita dalla classe base Children. Di tutti i pannelli Canvas è l'unico a usare il posizionamento assoluto. Avrebbe aumentato le dimensioni del modello a oggetti del tipo UIElement comune per aggiungere proprietà che potrebbero essere di interesse solo per Canvas e quei casi UIElement particolari in cui sono elementi figlio di un elemento UIElement. Definendo le proprietà del controllo layout di un oggetto Canvas come proprietà associate che qualsiasi UIElement può usare si mantiene il modello a oggetti più pulito.

Per essere un pannello pratico, l'oggetto Canvas ha un comportamento che esegue l'override dei metodi Measure e Arrange a livello di framework. È qui che l'oggetto Canvas verifica effettivamente la presenza di valori di proprietà associate sui relativi elementi figlio. Parte di entrambi i pattern Measure e Arrange è data da un loop che si ripete sul contenuto e un pannello ha la proprietà Children che rende espliciti gli elementi che devono essere considerati elementi figlio di un pannello. Pertanto, il comportamento di layout dell'oggetto Canvas si ripete su questi elementi figlio e rende statiche le chiamate Canvas.GetLeft e Canvas.GetTop su ciascun elemento figlio per vedere se tali proprietà associate contengono un valore non predefinito (il valore predefinito è 0). Questi valori vengono quindi usati per posizionare in modo assoluto ogni elemento figlio nello spazio del layout disponibile dell'oggetto Canvas in base ai valori specifici forniti da ogni elemento figlio e confermati tramite Arrange.

Il codice ha un aspetto simile a questo pseudocodice.

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
}

Nota

Per altre informazioni sul funzionamento dei pannelli, vedere Panoramica dei pannelli personalizzati XAML.