Поделиться через


Пользовательские присоединенные свойства

Присоединенное свойство — это концепция XAML. Присоединенные свойства обычно определяются как специализированная форма свойства зависимостей. В этом разделе объясняется, как реализовать присоединенное свойство в качестве свойства зависимостей и как определить соглашение о доступе, необходимое для использования присоединенного свойства в XAML.

Предпосылки

Предполагается, что вы понимаете свойства зависимостей с точки зрения потребителя существующих свойств зависимостей, и что вы ознакомились с обзором свойств зависимостей. Кроме того, необходимо ознакомиться с общими сведениями о присоединенных свойствах. Чтобы следовать примерам в этом разделе, вы также должны понять XAML и узнать, как писать базовое приложение среды выполнения Windows с помощью C++, C#или Visual Basic.

Сценарии для присоединенных свойств

Вы можете создать присоединенное свойство, если существует причина иметь механизм настройки свойств, доступный для классов, отличных от определяющего класса. Наиболее распространёнными сценариями для этого являются поддержка дизайна и сервисов. Примерами существующих свойств макета являются Canvas.ZIndex и Canvas.Top. В сценарии макета элементы, которые существуют как дочерние элементы для управляющих макетом элементов, могут выразить свои требования по макету к родительским элементам индивидуально, задавая значение свойства, которое родитель определяет как зависимое свойство. Пример сценария поддержки служб в API среды выполнения Windows — набор присоединенных свойств ScrollViewer, таких как ScrollViewer.IsZoomChainingEnabled.

Предупреждение

Существующее ограничение реализации XAML среды выполнения Windows заключается в том, что вы не можете анимировать пользовательский присоединенный атрибут.

Регистрация пользовательского присоединенного свойства

Если вы определяете присоединенное свойство строго для использования в других типах, класс, в котором зарегистрировано свойство, не обязательно наследуется от DependencyObject. Но у вас должен быть целевой параметр для методов доступа; используйте DependencyObject, если вы следуете типичной модели, при которой ваше присоединенное свойство также является свойством зависимости, чтобы воспользоваться резервным хранилищем свойств.

Определите присоединенное свойство как свойство зависимости путем объявления общедоступногостатическоготолько для чтения свойства типа DependencyProperty. Это свойство определяется с помощью возвращаемого значения метода RegisterAttached . Имя свойства должно соответствовать имени присоединенного свойства, которое вы указываете в качестве параметра RegisterAttachedимя, со строкой "Property", добавленной в конец. Это установленное соглашение об именовании идентификаторов свойств зависимостей в отношении свойств, которые они представляют.

Основной аспект, в котором определение пользовательского присоединенного свойства отличается от пользовательского свойства зависимости, заключается в том, как вы определяете методы доступа или оболочки. Вместо использования метода оболочки, описанного в свойствах настраиваемых зависимостей, необходимо также предоставить статические методы GetPropertyName и SetPropertyName в качестве методов доступа для присоединенного свойства. Аксессоры используются в основном анализатором XAML, хотя любой другой вызывающий объект также может использовать их для задания значений в сценариях, не связанных с XAML.

Это важно

Если вы не определили методы доступа правильно, обработчик XAML не может получить доступ к присоединенному свойству, и любой пользователь, который пытается использовать его, скорее всего, получит ошибку синтаксического анализатора XAML. Кроме того, средства проектирования и программирования часто используют соглашения "*Property" для именования идентификаторов, когда обнаруживают пользовательское свойство зависимости в использованной сборке.

Accessors

Подпись для аксессора GetPropertyName должна быть именно такой.

public static valueTypeGetPropertyName(DependencyObject target)

Для Microsoft Visual Basic это так.

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

Целевой объект может иметь более конкретный тип в реализации, но должен быть производным от DependencyObject. Возвращаемое значение ValueType также может иметь более конкретный тип в реализации. Базовый тип объекта является допустимым, но часто требуется, чтобы присоединенное свойство применялось для обеспечения безопасности типов. Использование ввода в подписях getter и setter является рекомендуемой техникой безопасности типов.

Подпись для метода доступа SetPropertyName должна быть такой.

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

Для Visual Basic это так.

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

Целевой объект может иметь более конкретный тип в реализации, но должен быть производным от DependencyObject. Объект value и его valueType могут иметь более конкретный тип в реализации. Помните, что значение этого метода — это входные данные, поступающие из обработчика XAML, когда оно обнаруживает присоединенное свойство в разметке. Необходимо иметь поддержку преобразования типов или существующего расширения разметки для используемого типа, чтобы соответствующий тип можно было создать из значения атрибута (который в конечном счете является просто строкой). Базовый тип объекта является допустимым, но часто требуется дополнительная безопасность типов. Для этого поместите ограничение типов в аксессоры.

Замечание

Кроме того, можно определить присоединенное свойство, в котором предполагаемое использование осуществляется с помощью синтаксиса элемента свойства. В этом случае для значений не требуется преобразование типов, но необходимо убедиться, что значения, которые вы планируете, можно создать в XAML. VisualStateManager.VisualStateGroups является примером существующего присоединенного свойства, которое поддерживает использование только элемента свойства.

Пример кода

В этом примере показана регистрация свойств зависимостей (с помощью метода RegisterAttached ), а также методов доступа Get и Set для настраиваемого присоединенного свойства. В примере имя присоединенного свойства — IsMovable. Таким образом, методы доступа должны быть именованы GetIsMovable и SetIsMovable. Владелец присоединенного свойства — это класс службы с именем GameService , который не имеет собственного пользовательского интерфейса. Его назначение заключается только в предоставлении присоединенных служб свойств при использовании присоединенного свойства GameService.IsMovable .

Определение присоединенного свойства в C++/CX немного сложнее. Необходимо решить, как распределить элементы между файлом заголовка и файлом кода. Кроме того, следует предоставить идентификатор как свойство только с аксессором get по причинам, рассмотренным в настраиваемых свойствах зависимости. В C++/CX необходимо явно определить связь между свойством и полем, вместо использования ключевого слова readonly из .NET и неявной поддержки простых свойств. Кроме того, необходимо выполнить регистрацию присоединенного свойства в вспомогательной функции, которая запускается только один раз, когда приложение сначала запускается, но до загрузки всех страниц XAML, которым требуется присоединенное свойство. Обычно вспомогательные функции для регистрации любых зависимых или присоединенных свойств следует вызывать в конструкторе App / Application в коде вашего файла 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));
}

Настройка настраиваемого присоединенного свойства из разметки XAML

После того как вы определите присоединенное свойство и включите поддерживающие его члены в состав пользовательского типа, необходимо сделать определения доступными для использования в XAML. Для этого необходимо сопоставить пространство имен XAML, которое будет ссылаться на пространство имен кода, содержащее соответствующий класс. В случаях, когда вы определили присоединенное свойство как часть библиотеки, необходимо включить эту библиотеку в состав пакета приложения для приложения.

Сопоставление пространства имен XML для XAML обычно помещается в корневой элемент страницы XAML. Например, для класса, именованного GameService, в пространстве имен UserAndCustomControls, содержащего определения присоединенных свойств, показанных в предыдущих фрагментах, сопоставление может выглядеть так.

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

С помощью сопоставления можно задать присоединенное свойство GameService.IsMovable для любого элемента, соответствующего определению целевого объекта, включая существующий тип, определяемый Windows Runtime.

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

Если свойство задано в элементе, который также находится в том же сопоставленном пространстве имен XML, необходимо включить префикс в имя присоединенного свойства. Это связано с тем, что префикс квалифисирует тип владельца. Атрибут присоединенного свойства нельзя считать находящимся в том же XML-пространстве имен, что и элемент, в котором этот атрибут используется, несмотря на то, что по обычным правилам XML атрибуты могут наследовать пространство имен от элементов. Например, если вы устанавливаете GameService.IsMovable в пользовательский тип ImageWithLabelControl (определение не показано), и даже если оба были определены в одном и том же пространстве имен кода, сопоставленном с одним и тем же префиксом, XAML будет выглядеть так же, как и раньше.

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

Замечание

Если вы пишете пользовательский интерфейс XAML с помощью C++/CX, необходимо включить заголовок для пользовательского типа, определяющего присоединенное свойство, в любое время, когда страница XAML использует этот тип. Каждая страница XAML содержит связанный заголовок кода (.xaml.h). Здесь необходимо включить (с помощью #include) заголовок, в котором описан тип владельца присоединенного свойства.

Императивная настройка вашего пользовательского присоединенного свойства

Вы также можете получить доступ к пользовательскому присоединенному свойству из императивного кода. В приведенном ниже коде показано, как.

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

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

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

Тип значения пользовательского присоединенного свойства

Тип, который используется в качестве типа значения для настраиваемого присоединенного свойства, влияет на его использование, определение или на то и другое. Тип значения присоединенного свойства объявлен в нескольких местах: в сигнатурах методов доступа Get и Set, а также в качестве параметра propertyType вызова RegisterAttached.

Наиболее распространенный тип значения для присоединенных свойств (настраиваемый или другой) — простая строка. Это связано с тем, что присоединенные свойства обычно предназначены для использования атрибутов XAML, и при использовании строки в качестве типа значения свойства облегчаются. Другие примитивы, которые имеют собственные методы преобразования в строку, такие как целые числа, числа с плавающей запятой или значения перечислений, также являются распространенными типами значений для присоединенных свойств. Вы можете использовать другие типы значений, которые не поддерживают собственное преобразование строк в качестве значения присоединенного свойства. Однако это влечет за собой выбор использования или реализации:

  • Присоединенное свойство можно оставить как есть, но присоединенное свойство может поддерживать использование только в том месте, где присоединенное свойство является элементом свойства, и значение объявляется как элемент объекта. В этом случае тип свойства должен поддерживать использование XAML в качестве элемента объекта. Для существующих ссылочных классов среды выполнения Windows проверьте синтаксис XAML, чтобы убедиться, что тип поддерживает использование элемента объекта XAML.
  • Присоединенное свойство можно оставить как есть, но использовать его только в использовании атрибутов с помощью метода ссылки XAML, например Binding или StaticResource , которые можно выразить как строку.

Дополнительные сведения о примере Canvas.Left

В предыдущих примерах использования присоединенных свойств мы показали различные способы задания присоединенного свойства Canvas.Left . Каким образом это меняет взаимодействие Холста с вашим объектом и когда это происходит? Мы рассмотрим этот конкретный пример далее, так как если вы реализуете присоединенное свойство, интересно увидеть, что еще типичный класс владельца присоединенного свойства намерен делать с присоединенными свойствами, когда они обнаруживаются на других объектах.

Основная функция Холста - быть контейнером макета с абсолютным позиционированием в интерфейсе пользователя. Дочерние элементы Холста хранятся в определяемом свойстве базового класса Children. Из всех панелей Canvas является единственным, использующим абсолютную позицию. Он раздувал объектную модель общего типа UIElement , чтобы добавить свойства, которые могут быть связаны только с Canvas и теми конкретными случаями UIElement , где они являются дочерними элементами UIElement. Определение свойств управления макетом Холста в качестве присоединяемых свойств, которые может использовать любой UIElement, позволяет поддерживать более чистую объектную модель.

Чтобы быть практичной панелью, Canvas имеет функциональность, которая переопределяет методы Measure и Arrange уровня фреймворка. Это место, где Canvas фактически проверяет присоединённые значения свойств на дочерних объектах. Часть шаблонов Measure и Arrange — это цикл, который выполняет итерацию по любому содержимому, а панель имеет свойство Children, которое явно указывает, что считается дочерним элементом панели. Поэтому поведение макета Canvas выполняет итерацию этих дочерних элементов и делает статические вызовы Canvas.GetLeft и Canvas.GetTop на каждом дочернем элементе, чтобы узнать, содержат ли эти присоединенные свойства значение, отличное от значения по умолчанию (по умолчанию — 0). Затем эти значения используются для абсолютного размещения каждого дочернего элемента в доступном пространстве макета холста в соответствии с определенными значениями, предоставляемыми каждым дочерним элементом, и зафиксированным с помощью упорядочивания.

Код выглядит примерно так, как этот псевдокод.

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
}

Замечание

Дополнительные сведения о работе панелей см. в обзоре пользовательских панелей XAML.