Bahasa

Atribut terlampir kustom

Properti terlampir adalah konsep XAML. Properti terlampir biasanya didefinisikan sebagai bentuk khusus properti dependensi. Topik ini menjelaskan cara menerapkan properti terlampir sebagai properti dependensi dan cara menentukan konvensi aksesor yang diperlukan agar properti terlampir Anda dapat digunakan di XAML.

Nota

C# dan C++/WinRT didukung untuk aplikasi WinUI 3 / SDK Aplikasi Windows dan UWP. C++/CX hanya berlaku untuk UWP, dan tidak didukung untuk WinUI 3.

Prasyarat

Kami berasumsi bahwa Anda memahami properti dependensi dari perspektif konsumen properti dependensi yang ada, dan Anda telah membaca gambaran umum properti Dependensi. Anda juga harus membaca Gambaran umum properti Terlampir. Untuk mengikuti contoh dalam topik ini, Anda juga harus memahami XAML dan tahu cara menulis aplikasi WinUI dasar menggunakan C++ atau C#.

Skenario untuk properti terlampir

Anda mungkin membuat properti terlampir ketika ada alasan untuk memiliki mekanisme penyetelan properti yang tersedia untuk kelas selain dari kelas yang mendefinisikannya. Skenario yang paling umum untuk ini adalah tata letak dan dukungan layanan. Contoh properti tata letak yang ada adalah Canvas.ZIndex dan Canvas.Top. Dalam skenario tata letak, elemen yang ada sebagai elemen turunan dari elemen pengontrol tata letak dapat menyatakan persyaratan tata letak ke elemen induknya satu per satu, masing-masing mengatur nilai properti yang didefinisikan induk sebagai properti terlampir. Contoh skenario dukungan layanan di Windows Runtime API adalah kumpulan properti terlampir dari ScrollViewer, seperti ScrollViewer.IsZoomChainingEnabled.

Peringatan

Batasan implementasi aplikasi WinUI yang ada adalah Anda tidak dapat menganimasikan properti terlampir kustom Anda.

Mendaftarkan properti terlampir kustom

Jika Anda mendefinisikan properti terlampir secara ketat untuk digunakan pada jenis lain, kelas tempat properti terdaftar tidak harus berasal dari DependencyObject. Tetapi Anda harus memiliki parameter target untuk penggunaan aksesor dengan menggunakan DependencyObject jika Anda mengikuti model umum yang menjadikan properti terlampir Anda juga sebagai properti dependensi, sehingga Anda dapat menggunakan penyimpanan properti yang mendukung.

Tentukan properti terlampir Anda sebagai properti dependensi dengan mendeklarasikan properti publikstatisreadonly dari jenis DependencyProperty. Anda menentukan properti ini dengan menggunakan nilai pengembalian metode RegisterAttached . Nama properti harus cocok dengan nama properti terlampir yang Anda tentukan sebagai parameter namaRegisterAttached, dengan string "Property" ditambahkan ke akhir. Ini adalah konvensi yang ditetapkan untuk memberi nama pengidentifikasi properti dependensi sehubungan dengan properti yang diwakilinya.

Area utama di mana menentukan properti terlampir kustom berbeda dari properti dependensi kustom adalah bagaimana Anda menentukan aksesor atau pembungkus. Alih-alih menggunakan teknik pembungkus yang dijelaskan dalam Properti dependensi kustom, Anda juga harus menyediakan metode GetPropertyName dan SetPropertyName statis sebagai aksesor untuk properti terlampir. Pengakses sebagian besar digunakan oleh pengurai XAML, meskipun pemanggil lain juga dapat menggunakannya untuk mengatur nilai dalam skenario non-XAML.

Penting

Jika Anda tidak menentukan aksesor dengan benar, prosesor XAML tidak dapat mengakses properti terlampir Anda dan siapa pun yang mencoba menggunakannya mungkin akan mendapatkan kesalahan pengurai XAML. Selain itu, alat desain dan pengkodean sering mengandalkan konvensi "*Property" untuk penamaan pengidentifikasi ketika mereka menemukan properti ketergantungan kustom dalam rakitan yang direferensikan.

Accessors

Tanda tangan untuk aksesor GetPropertyName harus ini.

public static valueTypeGetPropertyName(DependencyObject target)

Objek target dapat memiliki jenis yang lebih spesifik dalam implementasi Anda, tetapi harus berasal dari DependencyObject. Nilai yang dikembalikan ValueType juga dapat berupa jenis yang lebih spesifik dalam implementasi Anda. Jenis Objek dasar dapat diterima, tetapi seringkali Anda ingin properti terlampir Anda memberlakukan keamanan jenis. Penggunaan pengetikan dalam tanda tangan getter dan setter adalah teknik keamanan jenis yang direkomendasikan.

Tanda tangan untuk aksesor SetPropertyName harus ini.

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

Objek target dapat memiliki jenis yang lebih spesifik dalam implementasi Anda, tetapi harus berasal dari DependencyObject. Objek nilai dan valueType-nya dapat berupa jenis yang lebih spesifik dalam implementasi Anda. Ingatlah bahwa nilai untuk metode ini adalah input yang berasal dari prosesor XAML ketika menemui properti terlampir dalam markup. Harus ada konversi jenis atau dukungan ekstensi markup yang ada untuk jenis yang Anda gunakan, sehingga jenis yang sesuai dapat dibuat dari nilai atribut (yang pada akhirnya hanya string). Jenis Objek dasar dapat diterima, tetapi seringkali Anda menginginkan keamanan jenis lebih lanjut. Untuk mencapainya, letakkan penegakan jenis di aksesor.

Nota

Anda juga dapat menentukan properti terlampir di mana penggunaan yang dimaksudkan adalah melalui sintaks elemen properti. Dalam hal ini Anda tidak memerlukan konversi jenis untuk nilai, tetapi Anda perlu memastikan bahwa nilai yang Anda inginkan dapat dibangun di XAML. VisualStateManager.VisualStateGroups adalah contoh properti terlampir yang ada yang hanya mendukung penggunaan elemen properti.

Contoh kode

Contoh ini menunjukkan pendaftaran properti dependensi (menggunakan metode RegisterAttached), serta aksesor Get dan Set, untuk properti terlampir yang ter-kustom. Dalam contoh, nama properti yang dilampirkan adalah IsMovable. Oleh karena itu, aksesor harus diberi nama GetIsMovable dan SetIsMovable. Pemilik properti terlampir adalah kelas layanan bernama GameService yang tidak memiliki UI sendiri; tujuannya hanya untuk menyediakan layanan properti terlampir ketika properti terlampir GameService.IsMovable digunakan.

Menentukan properti terlampir di C++/CX sedikit lebih kompleks. Anda harus memutuskan cara memilah antara header dan berkas kode. Selain itu, Anda harus mengekspos pengidentifikasi sebagai properti hanya dengan akssesor get, karena alasan yang dibahas dalam Properti dependensi kustom. Di C++/CX Anda harus menentukan relasi properti-bidang ini secara eksplisit daripada mengandalkan kata kunci .NET readonly dan dukungan implisit dari properti sederhana. Anda juga perlu melakukan pendaftaran properti terlampir dalam fungsi pembantu yang hanya dijalankan sekali, ketika aplikasi pertama kali dimulai tetapi sebelum halaman XAML yang memerlukan properti terlampir dimuat. Tempat yang lazim untuk memanggil fungsi pembantu pendaftaran properti Anda bagi setiap dan semua dependensi atau properti terlampir adalah dari dalam constructor App / Application dalam kode untuk file app.xaml Anda.

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);
    }
}
// GameService.idl
namespace UserAndCustomControls
{
    [default_interface]
    runtimeclass GameService : Microsoft.UI.Xaml.DependencyObject
    {
        GameService();
        static Microsoft.UI.Xaml.DependencyProperty IsMovableProperty{ get; };
        static Boolean GetIsMovable(Microsoft.UI.Xaml.DependencyObject target);
        static void SetIsMovable(Microsoft.UI.Xaml.DependencyObject target, Boolean value);
    }
}

// GameService.h
...
    static Microsoft::UI::Xaml::DependencyProperty IsMovableProperty() { return m_IsMovableProperty; }
    static bool GetIsMovable(Microsoft::UI::Xaml::DependencyObject const& target) { return winrt::unbox_value<bool>(target.GetValue(m_IsMovableProperty)); }
    static void SetIsMovable(Microsoft::UI::Xaml::DependencyObject const& target, bool value) { target.SetValue(m_IsMovableProperty, winrt::box_value(value)); }

private:
    static Microsoft::UI::Xaml::DependencyProperty m_IsMovableProperty;
...

// GameService.cpp
...
Microsoft::UI::Xaml::DependencyProperty GameService::m_IsMovableProperty =
    Microsoft::UI::Xaml::DependencyProperty::RegisterAttached(
        L"IsMovable",
        winrt::xaml_typename<bool>(),
        winrt::xaml_typename<UserAndCustomControls::GameService>(),
        Microsoft::UI::Xaml::PropertyMetadata{ winrt::box_value(false) }
);
...
// GameService.h
#pragma once

#include "pch.h"
//namespace WUX = Microsoft::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 Microsoft::UI::Xaml;
using namespace Microsoft::UI::Xaml::Controls;
using namespace Microsoft::UI::Xaml::Data;
using namespace Microsoft::UI::Xaml::Documents;
using namespace Microsoft::UI::Xaml::Input;
using namespace Microsoft::UI::Xaml::Interop;
using namespace Microsoft::UI::Xaml::Media;

GameService::GameService() {};

GameService::RegisterDependencyProperties() {
    DependencyProperty^ GameService::_IsMovableProperty = DependencyProperty::RegisterAttached(
         "IsMovable", Platform::Boolean::typeid, GameService::typeid, ref new PropertyMetadata(false));
}

Menetapkan properti lampiran kustom Anda dari markup XAML

Setelah Anda menentukan properti terlampir dan menyertakan anggota dukungannya sebagai bagian dari jenis kustom, Anda kemudian harus membuat definisi tersedia untuk penggunaan XAML. Untuk melakukan ini, Anda harus memetakan namespace XAML yang akan mereferensikan namespace kode yang berisi kelas yang relevan. Dalam kasus di mana Anda telah menentukan properti terlampir sebagai bagian dari pustaka, Anda harus menyertakan pustaka tersebut sebagai bagian dari paket aplikasi untuk aplikasi.

Pemetaan namespace XML untuk XAML biasanya ditempatkan di elemen akar halaman XAML. Misalnya, untuk kelas bernama GameService di namespace UserAndCustomControls yang berisi definisi properti terlampir yang ditampilkan dalam cuplikan sebelumnya, pemetaan mungkin terlihat seperti ini.

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

Dengan menggunakan pemetaan, Anda dapat mengatur GameService.IsMovable properti terlampir pada elemen apa pun yang cocok dengan definisi target Anda, termasuk jenis yang sudah ditentukan oleh Windows Runtime.

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

Jika Anda mengatur properti pada elemen yang juga berada dalam namespace XML yang dipetakan yang sama, Anda masih harus menyertakan awalan pada nama properti terlampir. Ini karena awalan menentukan tipe pemilik. Atribut properti terlampir tidak dapat diasumsikan berada dalam namespace XML yang sama dengan elemen tempat atribut disertakan, meskipun, menurut aturan XML normal, atribut dapat mewarisi namespace dari elemen. Misalnya, jika Anda mengatur GameService.IsMovable pada jenis ImageWithLabelControl kustom (definisi tidak ditampilkan), dan bahkan jika keduanya didefinisikan dalam namespace kode yang sama yang dipetakan ke awalan yang sama, XAML akan tetap seperti ini.

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

Nota

Jika Anda menulis UI XAML dengan C++/CX, maka Anda harus menyertakan header untuk jenis kustom yang menentukan properti terlampir, kapan saja halaman XAML menggunakan jenis tersebut. Setiap halaman XAML memiliki header code-behind terkait (.xaml.h). Di sinilah Anda harus menyertakan (menggunakan #include) header untuk definisi jenis pemilik properti terlampir.

Mengatur properti terlampir kustom Anda secara imperatif

Anda juga dapat mengakses properti terlampir kustom dari kode imperatif. Kode di bawah ini menunjukkan caranya.

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

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

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

Jenis nilai properti terlampir kustom

Tipe yang digunakan sebagai tipe nilai dari properti terlampir kustom memengaruhi penggunaan, definisi, atau baik penggunaan maupun definisi. Jenis nilai properti terlampir dideklarasikan di beberapa tempat: dalam tanda tangan metode akses Get dan Set, serta sebagai parameter propertyType dari panggilan RegisterAttached.

Jenis nilai yang paling umum untuk properti terlampir (kustom atau sebaliknya) adalah string sederhana. Ini karena properti terlampir umumnya ditujukan untuk penggunaan atribut XAML, dan menggunakan string sebagai jenis nilai menjaga properti tetap ringan. Primitif lain yang memiliki konversi asli ke metode string, seperti bilangan bulat, ganda, atau nilai enumerasi, juga umum sebagai jenis nilai untuk properti terlampir. Anda dapat menggunakan jenis nilai lain—yang tidak mendukung konversi string asli—sebagai nilai properti terlampir. Namun, ini memerlukan membuat pilihan tentang penggunaan atau implementasi:

  • Anda dapat meninggalkan properti terlampir apa adanya, tetapi properti terlampir hanya dapat mendukung penggunaan di mana properti terlampir adalah elemen properti, dan nilai dinyatakan sebagai elemen objek. Dalam hal ini, jenis properti memang harus mendukung penggunaan XAML sebagai elemen objek. Untuk kelas referensi Windows Runtime yang ada, periksa sintaks XAML untuk memastikan bahwa jenis mendukung penggunaan elemen objek XAML.
  • Anda dapat meninggalkan properti terlampir apa adanya, tetapi menggunakannya hanya dalam penggunaan atribut melalui teknik referensi XAML seperti Pengikatan atau StaticResource yang dapat diekspresikan sebagai string.

Lebih lanjut tentang contoh Canvas.Left

Dalam contoh penggunaan properti terlampir sebelumnya, kami menunjukkan berbagai cara untuk mengatur properti Terlampir Canvas.Left . Tetapi apa yang berubah tentang bagaimana Canvas berinteraksi dengan objek Anda, dan kapan itu terjadi? Kami akan memeriksa contoh khusus ini lebih lanjut, karena jika Anda menerapkan properti yang melekat, menarik untuk melihat apa yang biasanya dilakukan kelas pemilik properti yang melekat terhadap nilai properti tersebut jika menemukannya pada objek lain.

Fungsi utama Kanvas adalah menjadi kontainer tata letak yang diposisikan absolut di UI. Anak-anak Kanvas disimpan dalam properti kelas dasar yang didefinisikan Children. Dari semua panel Canvas adalah satu-satunya yang menggunakan posisi absolut. Ini akan membengkakkan model objek dari jenis UIElement umum jika menambahkan properti yang mungkin hanya menjadi perhatian Canvas dan kasus UIElement tertentu di mana mereka adalah elemen anak dari UIElement. Menentukan properti kontrol tata letak Kanvas menjadi properti terlampir yang dapat digunakan UIElement mana pun agar model objek tetap bersih.

Untuk menjadi panel yang praktis, Canvas memiliki perilaku yang mengesampingkan metode Measure dan Arrange tingkat framework. Di sinilah Canvas benar-benar memeriksa nilai properti terlampir pada anak-anaknya. Bagian dari pola Pengukuran dan Pengaturan adalah perulangan yang melakukan iterasi atas konten apa pun, dan panel memiliki properti Anak yang membuatnya eksplisit apa yang seharusnya dianggap sebagai anak dari panel. Jadi perilaku tata letak Kanvas berulang melalui anak-anak ini, dan membuat panggilan Canvas.GetLeft dan Canvas.GetTop statis pada setiap anak untuk melihat apakah properti terlampir tersebut berisi nilai non-default (defaultnya adalah 0). Nilai-nilai ini kemudian digunakan untuk benar-benar memposisikan setiap anak di ruang tata letak yang tersedia Kanvas sesuai dengan nilai tertentu yang disediakan oleh setiap anak, dan diterapkan menggunakan Atur.

Kodenya terlihat seperti pseudocode ini.

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

Untuk informasi selengkapnya tentang cara kerja panel, lihat Gambaran umum panel kustom XAML.