Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Una propiedad adjunta es un concepto XAML. Las propiedades adjuntas normalmente se definen como una forma especializada de propiedad de dependencia. En este tema se explica cómo implementar una propiedad adjunta como una propiedad de dependencia y cómo definir la convención de descriptor de acceso necesaria para que la propiedad adjunta se pueda usar en XAML.
Prerrequisitos
Se supone que comprende las propiedades de dependencia desde la perspectiva de un consumidor de las propiedades de dependencia existentes y que ha leído la información general sobre las propiedades de dependencia. También debe haber leído Información general sobre las propiedades adjuntas. Para seguir los ejemplos de este tema, también debes comprender XAML y saber cómo escribir una aplicación básica de Windows Runtime con C++, C# o Visual Basic.
Escenarios para propiedades adjuntas
Puede crear una propiedad adjunta cuando hay un motivo para tener un mecanismo de configuración de propiedades disponible para las clases que no sean la clase de definición. Los escenarios más comunes para esto son el soporte de diseño y de servicios. Algunos ejemplos de propiedades de diseño existentes son Canvas.ZIndex y Canvas.Top. En un escenario de diseño, los elementos que existen como elementos secundarios para los elementos de control de diseño pueden expresar los requisitos de diseño a sus elementos primarios individualmente, cada uno de los cuales establece un valor de propiedad que el elemento primario define como una propiedad adjunta. Un ejemplo del escenario de soporte técnico de servicios en la API de Windows Runtime es el conjunto de propiedades adjuntas de ScrollViewer, como ScrollViewer.IsZoomChainingEnabled.
Advertencia
Una limitación existente de la implementación XAML de Windows Runtime es que no puedes aplicar animación a tu propiedad adjunta personalizada.
Registro de una propiedad adjunta personalizada
Si va a definir la propiedad adjunta estrictamente para su uso en otros tipos, la clase donde se registra la propiedad no tiene que derivar de DependencyObject. Es necesario usar el parámetro de destino de los accesores con DependencyObject si sigue el modelo típico donde su propiedad adjunta también es una propiedad de dependencia, para poder utilizar el almacén de propiedades de respaldo.
Defina la propiedad adjunta como una propiedad de dependencia declarando una propiedad públicaestáticade solo lectura de tipo DependencyProperty. Esta propiedad se define mediante el valor devuelto del método RegisterAttached . El nombre de la propiedad debe coincidir con el nombre de propiedad adjunto que especifique como parámetro RegisterAttachedname , con la cadena "Property" agregada al final. Esta es la convención establecida para asignar un nombre a los identificadores de las propiedades de dependencia en relación con las propiedades que representan.
El área principal en la que la definición de una propiedad adjunta personalizada difiere de una propiedad de dependencia personalizada es la forma en que se definen los descriptores de acceso o los contenedores. En lugar de usar la técnica contenedora descrita en Propiedades de dependencia personalizadas, también debe proporcionar métodos estáticos GetPropertyName y SetPropertyName como accesores para la propiedad adjunta. El analizador XAML usa principalmente los accesores, aunque cualquier otro llamador también puede usarlos para establecer valores en escenarios no XAML.
Importante
Si no defines correctamente los descriptores de acceso, el procesador XAML no puede acceder a tu propiedad adjunta y cualquier persona que intente usarla probablemente obtendrá un error del analizador XAML. Además, las herramientas de diseño y codificación suelen basarse en las convenciones "*Property" para asignar nombres a identificadores cuando encuentran una propiedad de dependencia personalizada en un ensamblado al que se hace referencia.
Accessors
La firma del descriptor de acceso GetPropertyName debe ser esta.
public static
valueTypeGetPropertyName(DependencyObject target)
Para Microsoft Visual Basic, es esto.
Public Shared Function Get
PropertyName(ByVal target As DependencyObject) As valueType)
El objeto de destino puede ser de un tipo más específico en la implementación, pero debe derivar de DependencyObject. El valor devuelto ValueType también puede ser de un tipo más específico en la implementación. El tipo básico Object es aceptable, pero a menudo deseará que la propiedad adjunta aplique la seguridad de tipos. El uso de tipado en las firmas de getter y setter es una técnica recomendada de seguridad de tipos.
La firma del descriptor de acceso SetPropertyName debe ser esta.
public static void Set
PropertyName(DependencyObject target ,valueType value)
Para Visual Basic, es esto.
Public Shared Sub Set
PropertyName(ByVal target As DependencyObject, ByVal value AsvalueType)
El objeto de destino puede ser de un tipo más específico en la implementación, pero debe derivar de DependencyObject. El objeto value y su valueType pueden ser de un tipo más específico en la implementación. Recuerde que el valor de este método es la entrada que procede del procesador XAML cuando encuentra la propiedad adjunta en el marcado. Debe haber compatibilidad con la conversión de tipos o la extensión de marcado existente para el tipo que use, de modo que se pueda crear el tipo adecuado a partir de un valor de atributo (que en última instancia es simplemente una cadena). El tipo de objeto básico es aceptable, pero a menudo querrá mayor seguridad de tipos. Para ello, coloque la aplicación de tipos en los descriptores de acceso.
Nota:
También es posible definir una propiedad adjunta en la que el uso previsto es a través de la sintaxis del elemento de propiedad. En ese caso, no necesita la conversión de tipos para los valores, pero sí tiene que asegurarse de que los valores que pretende se pueden construir en XAML. VisualStateManager.VisualStateGroups es un ejemplo de una propiedad adjunta existente que solo admite el uso de elementos de propiedad.
Ejemplo de código
En este ejemplo se muestra el registro de propiedades de dependencia (mediante el método RegisterAttached ), así como los descriptores de acceso Get y Set , para una propiedad adjunta personalizada. En el ejemplo, el nombre de la propiedad adjunta es IsMovable. Por lo tanto, los descriptores de acceso deben denominarse GetIsMovable y SetIsMovable. El propietario de la propiedad adjunta es una clase de servicio denominada GameService que no tiene una interfaz de usuario propia; su propósito es proporcionar solo los servicios de propiedad adjunta cuando se usa la propiedad adjunta GameService.IsMovable .
Definir la propiedad adjunta en C++/CX es un poco más compleja. Debe decidir cómo factorizar entre el encabezado y el archivo de código. Además, debe exponer el identificador como una propiedad con solo un accesor get, por motivos descritos en Propiedades de dependencia personalizadas. En C++/CX, debe definir explícitamente esta relación entre propiedad y campo, en lugar de confiar en el uso del modificador readonly de .NET y en el soporte implícito de las propiedades simples. También debes realizar el registro de la propiedad adjunta dentro de una función auxiliar que solo se ejecuta una vez, cuando se inicia la aplicación por primera vez, pero antes de que se carguen las páginas XAML que necesiten la propiedad adjunta. El lugar típico para llamar a las funciones auxiliares de registro de propiedades para cualquier propiedad adjunta o dependiente es desde el constructor App / Application en el código de tu archivo 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));
}
Configuración de la propiedad adjunta personalizada desde el marcado XAML
Después de definir la propiedad adjunta e incluir sus miembros de soporte como parte de un tipo personalizado, debes hacer que las definiciones estén disponibles para el uso de XAML. Para ello, debes asignar un espacio de nombres XAML que haga referencia al espacio de nombres de código que contiene la clase pertinente. En los casos en los que haya definido la propiedad adjunta como parte de una biblioteca, debe incluir esa biblioteca como parte del paquete de la aplicación para la aplicación.
Normalmente, una asignación de espacio de nombres XML para XAML se coloca en el elemento raíz de una página XAML. Por ejemplo, para la clase denominada GameService en el espacio de nombres UserAndCustomControls que contiene las definiciones de propiedades adjuntas que se muestran en fragmentos de código anteriores, la asignación podría tener este aspecto.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:uc="using:UserAndCustomControls"
... >
Con la asignación, puedes establecer la GameService.IsMovable propiedad adjunta en cualquier elemento que corresponda con tu definición de destino, incluyendo un tipo existente que define Windows Runtime.
<Image uc:GameService.IsMovable="True" .../>
Si va a establecer la propiedad en un elemento que también está dentro del mismo espacio de nombres XML asignado, aún debe incluir el prefijo en el nombre de la propiedad adjunta. Esto se debe a que el prefijo califica el tipo de propietario. No se puede suponer que el atributo de la propiedad adjunta está dentro del mismo espacio de nombres XML que el elemento donde se incluye el atributo, aunque, mediante reglas XML normales, los atributos pueden heredar el espacio de nombres de los elementos. Por ejemplo, si estableces GameService.IsMovable en un tipo personalizado de ImageWithLabelControl (no se muestra la definición) e incluso si ambos se definieron en el mismo espacio de nombres de código asignado al mismo prefijo, el XAML seguirá siendo este.
<uc:ImageWithLabelControl uc:GameService.IsMovable="True" .../>
Nota:
Si estás escribiendo una interfaz de usuario XAML con C++/CX, debes incluir el encabezado para el tipo personalizado que define la propiedad adjunta, siempre que una página XAML use ese tipo. Cada página XAML tiene un encabezado de código subyacente asociado (.xaml.h). Aquí es donde debe incluir (mediante #include) el encabezado para la definición del tipo de propietario de la propiedad adjunta.
Establecimiento de la propiedad adjunta personalizada de forma imperativa
También puede acceder a una propiedad adjunta personalizada desde código imperativo. El código siguiente muestra cómo.
<Image x:Name="gameServiceImage"/>
// MainPage.h
...
#include "GameService.h"
...
// MainPage.cpp
...
MainPage::MainPage()
{
InitializeComponent();
GameService::SetIsMovable(gameServiceImage(), true);
}
...
Tipo de valor de una propiedad adjunta personalizada
El tipo que se usa como el tipo de valor de una propiedad adjunta personalizada afecta al uso, la definición o tanto el uso como la definición. El tipo de valor de la propiedad adjunta se declara en varios lugares: en las firmas de los métodos de acceso Get y Set, y también como el parámetro propertyType de la llamada RegisterAttached.
El tipo de valor más común para las propiedades adjuntas (personalizado o de otro modo) es una cadena simple. Esto se debe a que las propiedades adjuntas suelen ser diseñadas para el uso de atributos XAML y el uso de una cadena como tipo de valor mantiene las propiedades ligeras. Otros primitivos que tienen conversión nativa a métodos de cadena, como enteros, dobles o un valor de enumeración, también son comunes como tipos de valor para las propiedades adjuntas. Puede usar otros tipos de valor (los que no admiten la conversión de cadenas nativas) como valor de propiedad adjunta. Sin embargo, esto implica tomar una decisión sobre el uso o la implementación:
- Puede dejar la propiedad adjunta tal cual, pero la propiedad adjunta solo puede admitir el uso donde la propiedad adjunta es un elemento de propiedad y el valor se declara como un elemento de objeto. En este caso, el tipo de propiedad tiene que admitir el uso de XAML como un elemento de objeto. Para las clases de referencia de Windows Runtime existentes, compruebe la sintaxis XAML para asegurarse de que el tipo admite el uso de elementos de objeto XAML.
- Puedes dejar la propiedad adjunta tal como está, pero úsala solo en un uso de atributo mediante una técnica de referencia XAML como Binding o StaticResource, que puede expresarse como una cadena.
Más información sobre el ejemplo Canvas.Left
En ejemplos anteriores de usos de propiedades adjuntas, mostramos diferentes formas de establecer la propiedad adjunta Canvas.Left . ¿Pero qué cambia en la forma en que un Canvas interactúa con tu objeto y cuándo ocurre? Examinaremos este ejemplo concreto con más detalle, porque si implementa una propiedad adjunta, es interesante ver lo que una clase típica propietaria de propiedades adjuntas pretende hacer con sus valores de propiedad adjunta si los encuentra en otros objetos.
La función principal de un canvas es ser un contenedor de diseño con posición absoluta en la interfaz de usuario. Los hijos de un Canvas se almacenan en una propiedad definida en la clase base Children. De todos los paneles Canvas es el único que usa posicionamiento absoluto. Habría sobredimensionado el modelo de objetos del tipo UIElement común para agregar propiedades que solo podrían preocuparse por Canvas y aquellos casos de UIElement concretos en los que son elementos secundarios de un UIElement. Definir las propiedades de control del diseño de un Canvas como propiedades adjuntas que cualquier UIElement pueda utilizar mantiene el modelo de objetos más limpio.
Para ser un panel práctico, Canvas tiene un comportamiento que invalida los métodos Measure y Arrange de nivel de marco. Aquí es donde Canvas comprueba realmente si hay valores de propiedad adjuntos en sus elementos secundarios. Parte de los patrones Measure y Arrange es un bucle que recorre en iteración cualquier contenido y un panel tiene la propiedad Children que hace que sea explícito lo que se supone que se debe considerar el elemento secundario de un panel. Por lo tanto, el comportamiento de diseño de Canvas recorre en iteración estos elementos secundarios y realiza llamadas estáticas canvas.GetLeft yCanvas.GetTop en cada elemento secundario para ver si esas propiedades adjuntas contienen un valor no predeterminado (el valor predeterminado es 0). Estos valores se utilizan para posicionar absolutamente cada elemento secundario en el espacio disponible de diseño en Canvas según los valores específicos proporcionados por cada elemento secundario y aplicados mediante Arrange.
El código tiene un aspecto similar al de este pseudocódigo.
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:
Para obtener más información sobre cómo funcionan los paneles, consulta Introducción a los paneles personalizados xaml.