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

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

Необходимые компоненты

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

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

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

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

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

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

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

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

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

Важно!

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

Методы доступа

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

public staticvalueTypeGetPropertyName(DependencyObject target)

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

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

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

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

public static void SetPropertyName(DependencyObject target ,valueType value)

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

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

<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 имеет поведение, которое переопределяет методы измерения и упорядочивания платформы. Это место, где Canvas фактически проверка для вложенных значений свойств на его дочерних объектах. Часть шаблонов меры и упорядочивания — это цикл, который выполняет итерацию по любому содержимому, а панель имеет свойство Children, которое делает его явным, что должно считаться дочерним элементом панели. Поэтому поведение макета холста выполняет итерацию этих дочерних элементов и делает статические вызовы 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.