Properti dependensi kustom

Di sini kami menjelaskan cara menentukan dan menerapkan properti dependensi Anda sendiri untuk aplikasi Windows Runtime menggunakan C++, C#, atau Visual Basic. Kami mencantumkan alasan mengapa pengembang aplikasi dan penulis komponen mungkin ingin membuat properti dependensi kustom. Kami menjelaskan langkah-langkah implementasi untuk properti dependensi kustom, serta beberapa praktik terbaik yang dapat meningkatkan performa, kegunaan, atau fleksibilitas properti dependensi.

Prasyarat

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

Apa itu properti dependensi?

Untuk mendukung gaya, pengikatan data, animasi, dan nilai default untuk properti, maka harus diimplementasikan sebagai properti dependensi. Nilai properti dependensi tidak disimpan sebagai bidang di kelas , nilai tersebut disimpan oleh kerangka kerja xaml, dan direferensikan menggunakan kunci, yang diambil ketika properti terdaftar dengan sistem properti Windows Runtime dengan memanggil metode DependencyProperty.Register . Properti dependensi hanya dapat digunakan oleh jenis yang berasal dari DependencyObject. Tetapi DependencyObject cukup tinggi dalam hierarki kelas, sehingga sebagian besar kelas yang ditujukan untuk UI dan dukungan presentasi dapat mendukung properti dependensi. Untuk informasi selengkapnya tentang properti dependensi dan beberapa terminologi dan konvensi yang digunakan untuk menjelaskannya dalam dokumentasi ini, lihat Gambaran umum properti dependensi.

Contoh properti dependensi di Windows Runtime adalah: Control.Background, FrameworkElement.Width, dan TextBox.Text, di antara banyak lainnya.

Konvensi adalah bahwa setiap properti dependensi yang diekspos oleh kelas memiliki properti baca-saja statis publik yang sesuai dari jenis DependencyProperty yang diekspos pada kelas yang sama menyediakan pengidentifikasi untuk properti dependensi. Nama pengidentifikasi mengikuti konvensi ini: nama properti dependensi, dengan string "Properti" ditambahkan ke akhir nama. Misalnya, pengidentifikasi DependencyProperty yang sesuai untuk properti Control.Background adalah Control.BackgroundProperty. Pengidentifikasi menyimpan informasi tentang properti dependensi seperti yang terdaftar, dan kemudian dapat digunakan untuk operasi lain yang melibatkan properti dependensi, seperti memanggil SetValue.

Pembungkus properti

Properti dependensi biasanya memiliki implementasi pembungkus. Tanpa pembungkus, satu-satunya cara untuk mendapatkan atau mengatur properti adalah dengan menggunakan metode utilitas properti dependensi GetValue dan SetValue dan untuk meneruskan pengidentifikasi kepada mereka sebagai parameter. Ini adalah penggunaan yang agak tidak wajar untuk sesuatu yang secara ostensib properti. Tetapi dengan pembungkus, kode Anda dan kode lain yang mereferensikan properti dependensi dapat menggunakan sintaks properti objek langsung yang alami untuk bahasa yang Anda gunakan.

Jika Anda menerapkan properti dependensi kustom sendiri dan ingin menjadi publik dan mudah dipanggil, tentukan pembungkus properti juga. Pembungkus properti juga berguna untuk melaporkan informasi dasar tentang properti dependensi ke proses refleksi atau analisis statis. Secara khusus, pembungkus adalah tempat Anda menempatkan atribut seperti ContentPropertyAttribute.

Kapan menerapkan properti sebagai properti dependensi

Setiap kali Anda menerapkan properti baca/tulis publik di kelas, selama kelas Anda berasal dari DependencyObject, Anda memiliki opsi untuk membuat properti Anda berfungsi sebagai properti dependensi. Terkadang teknik khas mendukung properti Anda dengan bidang privat memadai. Menentukan properti kustom Anda sebagai properti dependensi tidak selalu diperlukan atau sesuai. Pilihannya akan bergantung pada skenario yang ingin Anda dukung properti Anda.

Anda mungkin mempertimbangkan untuk menerapkan properti Anda sebagai properti dependensi saat Anda ingin properti tersebut mendukung satu atau beberapa fitur Windows Runtime atau aplikasi Windows Runtime ini:

  • Mengatur properti melalui Gaya
  • Bertindak sebagai properti target yang valid untuk pengikatan data dengan {Binding}
  • Mendukung nilai animasi melalui Storyboard
  • Melaporkan kapan nilai properti telah diubah oleh:
    • Tindakan yang diambil oleh sistem properti itu sendiri
    • Lingkungan
    • Tindakan pengguna
    • Gaya membaca dan menulis

Daftar periksa untuk menentukan properti dependensi

Menentukan properti dependensi dapat dianggap sebagai serangkaian konsep. Konsep-konsep ini belum tentu merupakan langkah prosedural, karena beberapa konsep dapat diatasi dalam satu baris kode dalam implementasi. Daftar ini hanya memberikan gambaran umum singkat. Kami akan menjelaskan setiap konsep secara lebih rinci nanti dalam topik ini, dan kami akan menunjukkan contoh kode dalam beberapa bahasa.

  • Daftarkan nama properti dengan sistem properti (panggil Daftar), menentukan jenis pemilik dan jenis nilai properti.
    • Ada parameter yang diperlukan untuk Daftar yang mengharapkan metadata properti. Tentukan null untuk ini, atau jika Anda menginginkan perilaku yang diubah properti, atau nilai default berbasis metadata yang dapat dipulihkan dengan memanggil ClearValue, tentukan instans PropertyMetadata.
  • Tentukan pengidentifikasi DependencyProperty sebagai anggota properti baca-saja statis publik pada jenis pemilik.
  • Tentukan properti pembungkus, mengikuti model pengakses properti yang digunakan dalam bahasa yang Anda terapkan. Nama properti pembungkus harus cocok dengan string nama yang Anda gunakan di Daftar. Terapkan aksesor dapatkan dan atur untuk menghubungkan pembungkus dengan properti dependensi yang dibungkusnya, dengan memanggil GetValue dan SetValue dan meneruskan pengidentifikasi properti Anda sendiri sebagai parameter.
  • (Opsional) Tempatkan atribut seperti ContentPropertyAttribute pada pembungkus.

Catatan

Jika Anda menentukan properti terlampir kustom, Anda umumnya menghilangkan pembungkus. Sebagai gantinya, Anda menulis gaya aksesor yang berbeda yang dapat digunakan prosesor XAML. Lihat Properti terlampir kustom

Mendaftarkan properti

Agar properti Anda menjadi properti dependensi, Anda harus mendaftarkan properti ke penyimpanan properti yang dikelola oleh sistem properti Windows Runtime. Untuk mendaftarkan properti , Anda memanggil metode Register .

Untuk bahasa Microsoft .NET (C# dan Microsoft Visual Basic) Anda memanggil Daftar dalam isi kelas Anda (di dalam kelas, tetapi di luar definisi anggota apa pun). Pengidentifikasi disediakan oleh panggilan metode Register , sebagai nilai yang dikembalikan. Panggilan Daftar biasanya dilakukan sebagai konstruktor statis atau sebagai bagian dari inisialisasi properti baca-saja publik dari jenis DependencyProperty sebagai bagian dari kelas Anda. Properti ini mengekspos pengidentifikasi untuk properti dependensi Anda. Berikut adalah contoh panggilan Daftar .

Catatan

Mendaftarkan properti dependensi sebagai bagian dari definisi properti pengidentifikasi adalah implementasi umum, tetapi Anda juga dapat mendaftarkan properti dependensi di konstruktor statis kelas. Pendekatan ini mungkin masuk akal jika Anda membutuhkan lebih dari satu baris kode untuk menginisialisasi properti dependensi.

Untuk C++/CX, Anda memiliki opsi tentang cara Anda membagi implementasi antara header dan file kode. Pemisahan umum adalah mendeklarasikan pengidentifikasi itu sendiri sebagai properti statis publik di header, dengan implementasi get tetapi tidak ada yang ditetapkan. Implementasi get mengacu pada bidang privat, yang merupakan instans DependencyProperty yang tidak diinisialisasi. Anda juga dapat mendeklarasikan pembungkus dan mendapatkan dan menetapkan implementasi pembungkus. Dalam hal ini header mencakup beberapa implementasi minimal. Jika pembungkus memerlukan atribusi Windows Runtime, atribut di header juga. Masukkan panggilan Daftar ke dalam file kode, dalam fungsi pembantu yang hanya dijalankan saat aplikasi menginisialisasi pertama kalinya. Gunakan nilai pengembalian Register untuk mengisi pengidentifikasi statis tetapi tidak diinisialisasi yang Anda deklarasikan di header, yang awalnya Anda atur ke nullptr pada cakupan akar file implementasi.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty = 
    DependencyProperty.Register("Label", 
      GetType(String), 
      GetType(ImageWithLabelControl), 
      New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
    runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
    {
        ImageWithLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

// ImageWithLabelControl.h
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
    static Windows::UI::Xaml::DependencyProperty LabelProperty()
    {
        return m_labelProperty;
    }

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...
};

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;

public ref class ImageWithLabelControl sealed : public Control
{
private:
    static DependencyProperty^ _LabelProperty;
...
public:
    static void RegisterDependencyProperties();
    static property DependencyProperty^ LabelProperty
    {
        DependencyProperty^ get() {return _LabelProperty;}
    }
...
};

//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;

DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;

// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{ 
    if (_LabelProperty == nullptr)
    { 
        _LabelProperty = DependencyProperty::Register(
          "Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
    } 
}

Catatan

Untuk kode C++/CX, alasan mengapa Anda memiliki bidang privat dan properti baca-saja publik yang menampilkan DependencyProperty adalah agar penelepon lain yang menggunakan properti dependensi Anda juga dapat menggunakan API utilitas sistem properti yang mengharuskan pengidentifikasi untuk menjadi publik. Jika Anda menjaga pengidentifikasi tetap privat, orang tidak dapat menggunakan API utilitas ini. Contoh API dan skenario tersebut termasuk GetValue atau SetValue berdasarkan pilihan, ClearValue, GetAnimationBaseValue, SetBinding, dan Setter.Property. Anda tidak dapat menggunakan bidang publik untuk ini, karena aturan metadata Windows Runtime tidak memperbolehkan bidang publik.

Konvensi nama properti dependensi

Ada konvensi penamaan untuk properti dependensi; Ikuti mereka dalam keadaan yang luar biasa. Properti dependensi itu sendiri memiliki nama dasar ("Label" dalam contoh sebelumnya) yang diberikan sebagai parameter pertama Register. Nama harus unik dalam setiap jenis pendaftaran, dan persyaratan keunikan juga berlaku untuk setiap anggota yang diwariskan. Properti dependensi yang diwarisi melalui jenis dasar dianggap sebagai bagian dari jenis pendaftaran yang sudah ada; nama properti yang diwariskan tidak dapat didaftarkan lagi.

Peringatan

Meskipun nama yang Anda berikan di sini dapat menjadi pengidentifikasi string apa pun yang valid dalam pemrograman untuk bahasa pilihan Anda, Anda biasanya ingin dapat mengatur properti dependensi Anda di XAML juga. Untuk diatur dalam XAML, nama properti yang Anda pilih harus merupakan nama XAML yang valid. Untuk informasi selengkapnya, lihat Gambaran umum XAML.

Saat Anda membuat properti pengidentifikasi, gabungkan nama properti saat Anda mendaftarkannya dengan akhiran "Properti" ("LabelProperty", misalnya). Properti ini adalah pengidentifikasi Anda untuk properti dependensi, dan digunakan sebagai input untuk panggilan SetValue dan GetValue yang Anda lakukan di pembungkus properti Anda sendiri. Ini juga digunakan oleh sistem properti dan prosesor XAML lainnya seperti {x:Bind}

Menerapkan pembungkus

Pembungkus properti Anda harus memanggil GetValue dalam implementasi get , dan SetValue dalam implementasi yang ditetapkan .

Peringatan

Dalam semua keadaan kecuali keadaan yang luar biasa, implementasi pembungkus Anda hanya boleh melakukan operasi GetValue dan SetValue . Jika tidak, Anda akan mendapatkan perilaku yang berbeda saat properti diatur melalui XAML versus saat diatur melalui kode. Untuk efisiensi, pengurai XAML melewati pembungkus saat mengatur properti dependensi; dan berbicara dengan backing store melalui SetValue.

public String Label
{
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
    Get
        Return DirectCast(GetValue(LabelProperty), String) 
    End Get 
    Set(ByVal value As String)
        SetValue(LabelProperty, value)
    End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
    return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}

void Label(winrt::hstring const& value)
{
    SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
  property String^ Label
  {
    String^ get() {
      return (String^)GetValue(LabelProperty);
    }
    void set(String^ value) {
      SetValue(LabelProperty, value);
    }
  }

Metadata properti untuk properti dependensi kustom

Saat metadata properti ditetapkan ke properti dependensi, metadata yang sama diterapkan ke properti tersebut untuk setiap instans jenis pemilik properti atau subkelasnya. Dalam metadata properti, Anda dapat menentukan dua perilaku:

  • Nilai default yang ditetapkan sistem properti ke semua kasus properti.
  • Metode panggilan balik statis yang secara otomatis dipanggil dalam sistem properti setiap kali perubahan nilai properti terdeteksi.

Memanggil Daftar dengan metadata properti

Dalam contoh sebelumnya dari memanggil DependencyProperty.Register, kami meneruskan nilai null untuk parameter propertyMetadata . Untuk mengaktifkan properti dependensi untuk memberikan nilai default atau menggunakan panggilan balik yang diubah properti, Anda harus menentukan instans PropertyMetadata yang menyediakan satu atau kedua kemampuan ini.

Biasanya Anda menyediakan PropertyMetadata sebagai instans yang dibuat sebaris, dalam parameter untuk DependencyProperty.Register.

Catatan

Jika Anda mendefinisikan implementasi CreateDefaultValueCallback , Anda harus menggunakan metode utilitas PropertyMetadata.Create daripada memanggil konstruktor PropertyMetadata untuk menentukan instans PropertyMetadata .

Contoh berikutnya ini memodifikasi contoh DependencyProperty.Register yang ditampilkan sebelumnya dengan mereferensikan instans PropertyMetadata dengan nilai PropertyChangedCallback . Implementasi panggilan balik "OnLabelChanged" akan ditampilkan nanti di bagian ini.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(
        Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
    DependencyProperty::Register("Label",
    Platform::String::typeid,
    ImageWithLabelControl::typeid,
    ref new PropertyMetadata(nullptr,
      ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
    );

Nilai default

Anda dapat menentukan nilai default untuk properti dependensi sehingga properti selalu mengembalikan nilai default tertentu saat tidak diatur. Nilai ini dapat berbeda dari nilai default yang melekat untuk jenis properti tersebut.

Jika nilai default tidak ditentukan, nilai default untuk properti dependensi null untuk jenis referensi, atau default jenis untuk jenis nilai atau primitif bahasa (misalnya, 0 untuk bilangan bulat atau string kosong untuk string). Alasan utama untuk menetapkan nilai default adalah bahwa nilai ini dipulihkan saat Anda memanggil ClearValue pada properti . Menetapkan nilai default berdasarkan per properti mungkin lebih nyaman daripada menetapkan nilai default dalam konstruktor, terutama untuk jenis nilai. Namun, untuk jenis referensi, pastikan bahwa menetapkan nilai default tidak membuat pola singleton yang tidak disengaja. Untuk informasi selengkapnya, lihat Praktik terbaik nanti dalam topik ini

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...

Catatan

Jangan mendaftar dengan nilai default UnsetValue. Jika Anda melakukannya, itu akan membingungkan konsumen properti dan akan memiliki konsekuensi yang tidak diinginkan dalam sistem properti.

CreateDefaultValueCallback

Dalam beberapa skenario, Anda menentukan properti dependensi untuk objek yang digunakan pada lebih dari satu utas UI. Ini mungkin terjadi jika Anda menentukan objek data yang digunakan oleh beberapa aplikasi, atau kontrol yang Anda gunakan di lebih dari satu aplikasi. Anda dapat mengaktifkan pertukaran objek antara utas UI yang berbeda dengan menyediakan implementasi CreateDefaultValueCallback daripada instans nilai default, yang terkait dengan utas yang mendaftarkan properti. Pada dasarnya CreateDefaultValueCallback mendefinisikan pabrik untuk nilai default. Nilai yang dikembalikan oleh CreateDefaultValueCallback selalu dikaitkan dengan utas CreateDefaultValueCallback UI saat ini yang menggunakan objek .

Untuk menentukan metadata yang menentukan CreateDefaultValueCallback, Anda harus memanggil PropertyMetadata.Create untuk mengembalikan instans metadata; konstruktor PropertyMetadata tidak memiliki tanda tangan yang menyertakan parameter CreateDefaultValueCallback .

Pola implementasi umum untuk CreateDefaultValueCallback adalah membuat kelas DependencyObject baru, mengatur nilai properti tertentu dari setiap properti DependencyObject ke default yang dimaksudkan, lalu mengembalikan kelas baru sebagai referensi Objek melalui nilai pengembalian metode CreateDefaultValueCallback .

Metode panggilan balik yang diubah properti

Anda dapat menentukan metode panggilan balik yang diubah properti untuk menentukan interaksi properti Anda dengan properti dependensi lain, atau untuk memperbarui properti internal atau status objek Anda setiap kali properti berubah. Jika panggilan balik Anda dipanggil, sistem properti telah menentukan bahwa ada perubahan nilai properti yang efektif. Karena metode panggilan balik statis, parameter d dari panggilan balik penting karena memberi tahu Anda instans kelas mana yang telah melaporkan perubahan. Implementasi umum menggunakan properti NewValue dari data peristiwa dan memproses nilai tersebut dengan cara tertentu, biasanya dengan melakukan beberapa perubahan lain pada objek yang diteruskan sebagai d. Respons tambahan terhadap perubahan properti adalah menolak nilai yang dilaporkan oleh NewValue, untuk memulihkan OldValue, atau untuk mengatur nilai ke batasan terprogram yang diterapkan ke NewValue.

Contoh berikutnya ini menunjukkan implementasi PropertyChangedCallback . Ini mengimplementasikan metode yang Anda lihat yang dirujuk dalam contoh Register sebelumnya, sebagai bagian dari argumen konstruksi untuk PropertyMetadata. Skenario yang ditangani oleh panggilan balik ini adalah bahwa kelas juga memiliki properti baca-saja terhitung bernama "HasLabelValue" (implementasi tidak ditampilkan). Setiap kali properti "Label" dievaluasi ulang, metode panggilan balik ini dipanggil, dan panggilan balik memungkinkan nilai terhitung dependen untuk tetap dalam sinkronisasi dengan perubahan pada properti dependensi.

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        iwlc.HasLabelValue = false;
    } else {
        iwlc.HasLabelValue = true;
    }
}
    Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
        Dim s As String = CType(e.NewValue,String) ' null checks omitted
        If s Is String.Empty Then
            iwlc.HasLabelValue = False
        Else
            iwlc.HasLabelValue = True
        End If
    End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
    auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
    Platform::String^ s = (Platform::String^)(e->NewValue);
    if (s->IsEmpty()) {
        iwlc->HasLabelValue=false;
    }
}

Properti mengubah perilaku untuk struktur dan enumerasi

Jika jenis DependencyProperty adalah enumerasi atau struktur, panggilan balik dapat dipanggil bahkan jika nilai internal struktur atau nilai enumerasi tidak berubah. Ini berbeda dari primitif sistem seperti string di mana hanya dipanggil jika nilai berubah. Ini adalah efek samping dari operasi kotak dan buka kotak pada nilai-nilai ini yang dilakukan secara internal. Jika Anda memiliki metode PropertyChangedCallback untuk properti di mana nilai Anda adalah enumerasi atau struktur, Anda perlu membandingkan OldValue dan NewValue dengan mentransmisikan nilai sendiri dan menggunakan operator perbandingan kelebihan beban yang tersedia untuk nilai yang sekarang ditransmisikan. Atau, jika tidak ada operator yang tersedia (yang mungkin terjadi untuk struktur kustom), Anda mungkin perlu membandingkan nilai individual. Anda biasanya akan memilih untuk tidak melakukan apa pun jika hasilnya adalah nilainya tidak berubah.

private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    if ((Visibility)e.NewValue != (Visibility)e.OldValue)
    {
        //value really changed, invoke your changed logic here
    } // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
        '  value really changed, invoke your changed logic here
    End If
    '  else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
    auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };

    if (newVisibility != oldVisibility)
    {
        // The value really changed; invoke your property-changed logic here.
    }
    // Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    if ((Visibility)e->NewValue != (Visibility)e->OldValue)
    {
        //value really changed, invoke your changed logic here
    } 
    // else this was invoked because of boxing, do nothing
    }
}

Praktik terbaik

Ingatlah pertimbangan berikut sebagai praktik terbaik saat Anda menentukan properti dependensi kustom Anda.

DependencyObject dan threading

Semua instans DependencyObject harus dibuat pada utas UI yang terkait dengan Jendela saat ini yang ditampilkan oleh aplikasi Windows Runtime. Meskipun setiap DependencyObject harus dibuat pada utas UI utama, objek dapat diakses menggunakan referensi dispatcher dari utas lain, dengan memanggil Dispatcher.

Aspek utas DependencyObject relevan karena umumnya berarti bahwa hanya kode yang berjalan pada utas UI yang dapat berubah atau bahkan membaca nilai properti dependensi. Masalah utas biasanya dapat dihindari dalam kode UI khas yang memanfaatkan pola asinkron dan utas pekerja latar belakang dengan benar. Anda biasanya hanya mengalami masalah utas terkait DependencyObject jika Anda mendefinisikan jenis DependencyObject Anda sendiri dan Anda mencoba menggunakannya untuk sumber data atau skenario lain di mana DependencyObject belum tentu sesuai.

Menghindari singleton yang tidak disengaja

Singleton yang tidak disengaja dapat terjadi jika Anda mendeklarasikan properti dependensi yang mengambil jenis referensi, dan Anda memanggil konstruktor untuk jenis referensi tersebut sebagai bagian dari kode yang menetapkan PropertyMetadata Anda. Apa yang terjadi adalah bahwa semua penggunaan properti dependensi hanya berbagi satu instans PropertyMetadata dan dengan demikian mencoba berbagi jenis referensi tunggal yang Anda buat. Setiap subproperti dari jenis nilai yang Anda tetapkan melalui properti dependensi Anda kemudian disebarluaskan ke objek lain dengan cara yang mungkin tidak Anda inginkan.

Anda dapat menggunakan konstruktor kelas untuk mengatur nilai awal untuk properti dependensi jenis referensi jika Anda menginginkan nilai non-null, tetapi ketahuilah bahwa ini akan dianggap sebagai nilai lokal untuk tujuan gambaran umum properti Dependensi. Mungkin lebih tepat untuk menggunakan templat untuk tujuan ini, jika kelas Anda mendukung templat. Cara lain untuk menghindari pola singleton, tetapi masih memberikan default yang berguna, adalah dengan mengekspos properti statis pada jenis referensi yang menyediakan default yang sesuai untuk nilai kelas tersebut.

Properti dependensi jenis kumpulan

Properti dependensi jenis koleksi memiliki beberapa masalah implementasi tambahan yang perlu dipertimbangkan.

Properti dependensi jenis koleksi relatif jarang ada di Windows Runtime API. Dalam kebanyakan kasus, Anda dapat menggunakan koleksi di mana item adalah subkelas DependencyObject , tetapi properti koleksi itu sendiri diimplementasikan sebagai properti CLR atau C++ konvensional. Ini karena koleksi tidak selalu sesuai dengan beberapa skenario umum di mana properti dependensi terlibat. Contohnya:

  • Anda biasanya tidak menganimasikan koleksi.
  • Anda biasanya tidak mengisi item dalam koleksi dengan gaya atau templat.
  • Meskipun mengikat koleksi adalah skenario utama, koleksi tidak perlu menjadi properti dependensi untuk menjadi sumber pengikatan. Untuk target pengikatan, lebih umum menggunakan subkelas ItemsControl atau DataTemplate untuk mendukung item koleksi, atau menggunakan pola model tampilan. Untuk informasi selengkapnya tentang pengikatan ke dan dari koleksi, lihat Pengikatan data secara mendalam.
  • Pemberitahuan untuk perubahan pengumpulan lebih baik diatasi melalui antarmuka seperti INotifyPropertyChanged atau INotifyCollectionChanged, atau dengan mengambil jenis koleksi dari ObservableCollection<T>.

Namun demikian, skenario untuk properti dependensi jenis koleksi memang ada. Tiga bagian berikutnya memberikan beberapa panduan tentang cara mengimplementasikan properti dependensi jenis koleksi.

Menginisialisasi koleksi

Saat membuat properti dependensi, Anda dapat menetapkan nilai default dengan metadata properti dependensi. Tetapi berhati-hatilah untuk tidak menggunakan koleksi statis singleton sebagai nilai default. Sebagai gantinya, Anda harus dengan sengaja mengatur nilai koleksi ke koleksi (instans) unik sebagai bagian dari logika konstruktor kelas untuk kelas pemilik properti koleksi.

Mengubah pemberitahuan

Menentukan koleksi sebagai properti dependensi tidak secara otomatis memberikan pemberitahuan perubahan untuk item dalam koleksi berdasarkan sistem properti yang memanggil metode panggilan balik "PropertyChanged". Jika Anda menginginkan pemberitahuan untuk koleksi atau item koleksi—misalnya, untuk skenario pengikatan data— terapkan antarmuka INotifyPropertyChanged atau INotifyCollectionChanged . Untuk informasi selengkapnya, lihat Pengikatan data secara mendalam.

Pertimbangan keamanan properti dependensi

Nyatakan properti dependensi sebagai properti publik. Nyatakan pengidentifikasi properti dependensi sebagai anggota readonly statis publik . Bahkan jika Anda mencoba mendeklarasikan tingkat akses lain yang diizinkan oleh bahasa (seperti dilindungi), properti dependensi selalu dapat diakses melalui pengidentifikasi dalam kombinasi dengan API sistem properti. Menyatakan pengidentifikasi properti dependensi sebagai internal atau privat tidak akan berfungsi, karena sistem properti tidak dapat beroperasi dengan baik.

Properti pembungkus benar-benar hanya untuk kenyamanan, Mekanisme keamanan yang diterapkan pada pembungkus dapat dilewati dengan memanggil GetValue atau SetValue sebagai gantinya. Jadi, pertahankan properti pembungkus publik; jika tidak, Anda hanya membuat properti Anda lebih sulit untuk digunakan penelepon yang sah tanpa memberikan manfaat keamanan nyata.

Windows Runtime tidak menyediakan cara untuk mendaftarkan properti dependensi kustom sebagai baca-saja.

Properti dependensi dan konstruktor kelas

Ada prinsip umum bahwa konstruktor kelas tidak boleh memanggil metode virtual. Ini karena konstruktor dapat dipanggil untuk mencapai inisialisasi dasar konstruktor kelas turunan, dan memasukkan metode virtual melalui konstruktor mungkin terjadi ketika instans objek yang dibangun belum sepenuhnya diinisialisasi. Ketika Anda berasal dari kelas apa pun yang sudah berasal dari DependencyObject, ingatlah bahwa sistem properti itu sendiri memanggil dan mengekspos metode virtual secara internal sebagai bagian dari layanannya. Untuk menghindari potensi masalah dengan inisialisasi run-time, jangan atur nilai properti dependensi dalam konstruktor kelas.

Mendaftarkan properti dependensi untuk aplikasi C++/CX

Implementasi untuk mendaftarkan properti di C++/CX lebih rumit daripada C#, baik karena pemisahan ke dalam file header dan implementasi dan juga karena inisialisasi pada cakupan akar file implementasi adalah praktik yang buruk. (Ekstensi komponen Visual C++ (C++/CX) menempatkan kode penginisialisasi statis dari cakupan akar langsung ke DllMain, sedangkan pengkompilasi C# menetapkan penginisialisasi statis ke kelas dan dengan demikian menghindari masalah kunci beban DllMain .). Praktik terbaik di sini adalah mendeklarasikan fungsi pembantu yang melakukan semua pendaftaran properti dependensi Anda untuk kelas, satu fungsi per kelas. Kemudian untuk setiap kelas kustom yang digunakan aplikasi, Anda harus mereferensikan fungsi pendaftaran pembantu yang diekspos oleh setiap kelas kustom yang ingin Anda gunakan. Panggil setiap fungsi pendaftaran pembantu sekali sebagai bagian dari konstruktor Aplikasi (App::App()), sebelum InitializeComponent. Konstruktor itu hanya berjalan ketika aplikasi benar-benar direferensikan untuk pertama kalinya, konstruktor tersebut tidak akan berjalan lagi jika aplikasi yang ditangguhkan dilanjutkan, misalnya. Selain itu, seperti yang terlihat dalam contoh pendaftaran C++sebelumnya, pemeriksaan nullptr di sekitar setiap panggilan Register penting: itu adalah asuransi bahwa tidak ada pemanggil fungsi yang dapat mendaftarkan properti dua kali. Panggilan pendaftaran kedua mungkin akan merusak aplikasi Anda tanpa pemeriksaan seperti itu karena nama properti akan menjadi duplikat. Anda dapat melihat pola implementasi ini dalam sampel pengguna XAML dan kontrol kustom jika Anda melihat kode untuk versi C++/CX sampel.