Properti dependensi kustom

Di sini kami menjelaskan cara menentukan dan menerapkan properti dependensi Anda sendiri untuk aplikasi runtime Windows 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 kinerja, kegunaan, atau fleksibilitas properti dependensi.

Prasyarat

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

Apa itu properti dependensi?

Untuk mendukung styling, pengikatan data, animasi, dan nilai default untuk properti, maka itu harus diimplementasikan sebagai properti dependensi. Nilai properti dependensi tidak disimpan sebagai bidang pada kelas, nilai tersebut disimpan oleh kerangka xaml, dan direferensikan menggunakan kunci, yang diambil saat properti didaftarkan ke sistem properti runtime Windows dengan memanggil metode DependencyProperty.Register. Properti dependensi hanya dapat digunakan berdasarkan 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 serta konvensi yang digunakan untuk menjelaskannya dalam dokumentasi ini, lihat Gambaran umum properti dependensi.

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

Konvensi adalah bahwa setiap properti dependensi yang diekspos oleh kelas memiliki properti readonly statis publik yang sesuai dari jenis DependencyProperty yang diekspos pada kelas yang sama yang menyediakan pengenal untuk properti dependensi. Nama pengenal mengikuti konvensi ini: nama properti dependensi, dengan string "Properti" ditambahkan ke akhir nama. Misalnya, pengenal DependencyProperty yang sesuai untuk properti Control.Background adalah Control.BackgroundProperty. Pengidentifikasi menyimpan informasi tentang properti dependensi saat didaftarkan, 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 meneruskan pengidentifikasi kepada mereka sebagai parameter. Ini adalah penggunaan yang agak tidak wajar untuk sesuatu yang seolah-olah 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 khusus sendiri dan ingin itu menjadi publik dan mudah disebut, 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 pribadi sudah memadai. Mendefinisikan properti kustom Anda sebagai properti dependensi tidak selalu diperlukan atau sesuai. Pilihannya akan tergantung pada skenario yang Anda rencanakan untuk didukung oleh properti Anda.

Anda dapat mempertimbangkan untuk menerapkan properti Anda sebagai properti dependensi saat Anda ingin mendukung satu atau beberapa fitur Windows Runtime atau aplikasi runtime Windows:

  • 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

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

  • Daftarkan nama properti dengan sistem properti (panggil Daftar), tentukan jenis pemilik dan jenis nilai properti.
    • Ada parameter yang diperlukan untuk Register yang mengharapkan metadata properti. Tentukan null untuk ini, atau jika Anda ingin perilaku yang diubah properti, atau nilai default berbasis metadata yang dapat dipulihkan dengan memanggil ClearValue, tentukan instans PropertyMetadata.
  • Tentukan pengenal DependencyProperty sebagai anggota properti readonly statis publik pada tipe pemilik.
  • Tentukan properti pembungkus, mengikuti model aksesori properti yang digunakan dalam bahasa yang Anda terapkan. Nama properti pembungkus harus sesuai dengan string nama yang Anda gunakan dalam Daftar. Terapkan aksesori get and set untuk menghubungkan pembungkus dengan properti dependensi yang dibungkusnya, dengan memanggil GetValue dan SetValue dan meneruskan pengenal properti Anda sendiri sebagai parameter.
  • (Opsional) Tempatkan atribut seperti ContentPropertyAttribute pada pembungkus.

Catatan

Jika Anda mendefinisikan properti terlampir khusus, Anda biasanya menghilangkan pembungkusnya. Sebagai gantinya, Anda menulis gaya aksesori yang berbeda yang dapat digunakan prosesor XAML. Lihat Properti terlampir kustom

Mendaftarkan properti

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

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 pengembalian. Panggilan Register biasanya dibuat sebagai konstruktor statis atau sebagai bagian dari inisialisasi properti readonly statis publik dari jenis DependencyProperty sebagai bagian dari kelas Anda. Properti ini memperlihatkan pengidentifikasi untuk properti dependensi Anda. Berikut adalah contoh dari panggilan Register .

Catatan

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

Untuk C ++/CX, Anda memiliki opsi untuk cara membagi implementasi antara header dan file kode. Perpecahan yang khas adalah mendeklarasikan pengidentifikasi itu sendiri sebagai properti statis publik di header, dengan implementasi get tetapi tidak ada set. Implementasi get mengacu pada bidang pribadi, yang merupakan instance DependencyProperty yang belum diinsiti. Anda juga dapat mendeklarasikan pembungkus dan implementasi get and set dari pembungkus. Dalam hal ini header mencakup beberapa implementasi minimal. Jika pembungkus perlu Windows atribusi Runtime, atribut di header juga. Masukkan panggilan Daftar dalam file kode, dalam fungsi pembantu yang hanya akan dijalankan saat aplikasi menginisialisasi pertama kali. Gunakan nilai pengembalian Register untuk mengisi pengidentifikasi statis tetapi tidak diketahui yang Anda nyatakan di header, yang awalnya Anda atur ke nullptr pada cakupan root 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
...
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 pribadi 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 menjadi publik. Jika Anda menjaga pengidentifikasi tetap pribadi, 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 runtime Windows tidak mengizinkan bidang publik.

Konvensi nama properti dependensi

Ada konvensi penamaan untuk properti dependensi; ikuti mereka dalam semua kecuali keadaan 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 tipe dasar dianggap sebagai bagian dari tipe registering; nama properti yang diwariskan tidak dapat didaftarkan lagi.

Peringatan

Meskipun nama yang Anda berikan di sini dapat berupa pengenal 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 berupa nama XAML yang valid. Untuk info selengkapnya, lihat gambaran umum XAML.

Saat Anda membuat properti pengenal, gabungkan nama properti saat Anda mendaftarkannya dengan akhiran "Properti" ("LabelProperty", misalnya). Properti ini adalah pengenal Anda untuk properti dependensi, dan digunakan sebagai input untuk panggilan SetValue dan GetValue yang Anda buat 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 kecuali keadaan luar biasa, implementasi pembungkus Anda hanya akan melakukan operasi GetValue dan SetValue . Jika tidak, Anda akan mendapatkan perilaku yang berbeda ketika properti Anda diatur melalui XAML versus ketika diatur melalui kode. Untuk efisiensi, parser XAML melewati pembungkus saat mengatur properti dependensi; dan berbicara ke toko latar 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

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

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

Daftar Panggilan dengan metadata properti

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

Biasanya Anda menyediakan PropertyMetadata sebagai instance 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 callback "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 belumset. Nilai ini bisa berbeda dari nilai default yang melekat untuk jenis properti itu.

Jika nilai default tidak ditentukan, nilai default untuk properti dependensi adalah null untuk tipe referensi, atau default tipe untuk tipe nilai atau bahasa primitif (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 di properti. Menetapkan nilai default berdasarkan per properti mungkin lebih nyaman daripada menetapkan nilai default di konstruktor, terutama untuk tipe nilai. Namun, untuk jenis referensi, pastikan bahwa menetapkan nilai default tidak membuat pola singleton yang tidak disengaja. Untuk info 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 mendefinisikan properti dependensi untuk objek yang digunakan pada lebih dari satu utas UI. Ini mungkin terjadi jika Anda mendefinisikan 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 UI CreateDefaultValueCallback 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 khas untuk CreateDefaultValueCallback adalah membuat kelas DependencyObject baru, mengatur nilai properti spesifik dari setiap properti DependencyObject ke default yang dimaksudkan, dan kemudian mengembalikan kelas baru sebagai referensi Objek melalui nilai pengembalian metode CreateDefaultValueCallback .

Metode callback yang diubah properti

Anda dapat menentukan metode callback yang diubah properti untuk menentukan interaksi properti Anda dengan properti dependensi lainnya, 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 callback statis, parameter d callback penting karena memberi tahu Anda instans kelas mana yang telah melaporkan perubahan. Implementasi tipikal menggunakan properti NewValue dari data peristiwa dan memproses nilai tersebut dalam beberapa cara, biasanya dengan melakukan beberapa perubahan lain pada objek yang diteruskan sebagai d. Tanggapan tambahan terhadap perubahan properti adalah menolak nilai yang dilaporkan oleh NewValue, untuk memulihkan OldValue, atau untuk menetapkan nilai ke batasan terprogram yang diterapkan ke NewValue.

Contoh berikutnya ini menunjukkan implementasi PropertyChangedCallback . Ini menerapkan metode yang Anda lihat direferensikan dalam contoh Register sebelumnya, sebagai bagian dari argumen konstruksi untuk PropertyMetadata. Skenario yang ditangani oleh callback ini adalah bahwa kelas juga memiliki properti baca-saja yang dihitung bernama "HasLabelValue" (implementasi tidak ditampilkan). Setiap kali properti "Label" dievaluasi kembali, metode callback ini dipanggil, dan callback memungkinkan nilai terhitung dependen tetap sinkron 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 pencacahan

Jika jenis DependencyProperty adalah pencacahan atau struktur, callback dapat dipanggil bahkan jika nilai internal struktur atau nilai pencacahan tidak berubah. Ini berbeda dari sistem primitif seperti string di mana ia hanya dipanggil jika nilainya berubah. Ini adalah efek samping dari operasi kotak dan unbox pada nilai-nilai ini yang dilakukan secara internal. Jika Anda memiliki metode PropertyChangedCallback untuk properti di mana nilai Anda adalah pencacahan atau struktur, Anda perlu membandingkan OldValue dan NewValue dengan memberikan nilai sendiri dan menggunakan operator perbandingan kelebihan beban yang tersedia untuk nilai yang sekarang dilemparkan. Atau, jika tidak ada operator tersebut yang tersedia (yang mungkin terjadi untuk struktur kustom), Anda mungkin perlu membandingkan nilai individu. Anda biasanya akan memilih untuk tidak melakukan apa-apa 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 dikaitkan dengan Jendela saat ini yang ditampilkan oleh aplikasi runtime Windows. Meskipun setiap DependencyObject harus dibuat pada utas UI utama, objek dapat diakses menggunakan referensi dispatcher dari utas lain, dengan memanggil Dispatcher.

Aspek threading DependencyObject relevan karena umumnya berarti bahwa hanya kode yang berjalan pada utas UI yang dapat mengubah atau bahkan membaca nilai properti dependensi. Masalah threading biasanya dapat dihindari dalam kode UI khas yang memanfaatkan pola async dan utas pekerja latar belakang dengan benar. Anda biasanya hanya mengalami masalah threading terkait DependencyObject jika Anda menentukan 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 itu sebagai bagian dari kode yang menetapkan PropertyMetadata Anda. Apa yang terjadi adalah bahwa semua penggunaan properti dependensi hanya berbagi satu contoh PropertyMetadata dan dengan demikian mencoba untuk berbagi jenis referensi tunggal yang Anda buat. Setiap subproperti dari jenis nilai yang Anda tetapkan melalui properti dependensi Anda kemudian menyebar ke objek lain dengan cara yang mungkin tidak Anda maksudkan.

Anda dapat menggunakan konstruktor kelas untuk menetapkan nilai awal untuk properti dependensi tipe 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 template 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-nilai kelas itu.

Properti dependensi tipe koleksi

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

Properti dependensi tipe koleksi relatif jarang terjadi di api runtime Windows. Dalam kebanyakan kasus, Anda dapat menggunakan koleksi di mana item tersebut adalah subkelas DependencyObject , tetapi properti koleksi itu sendiri diimplementasikan sebagai properti CLR atau C ++ konvensional. Ini karena koleksi tidak selalu sesuai dengan beberapa skenario khas di mana properti dependensi terlibat. Contohnya:

  • Anda biasanya tidak menganimasikan koleksi.
  • Anda biasanya tidak pra-populasi item dalam koleksi dengan gaya atau template.
  • Meskipun mengikat koleksi adalah skenario utama, koleksi tidak perlu menjadi properti dependensi untuk menjadi sumber yang mengikat. Untuk mengikat target, lebih khas menggunakan subkelas ItemsControl atau DataTemplate untuk mendukung item pengumpulan, atau menggunakan pola model tampilan. Untuk info selengkapnya tentang pengikatan ke dan dari koleksi, lihat Pengikatan data secara mendalam.
  • Pemberitahuan untuk perubahan koleksi lebih baik ditangani melalui antarmuka seperti INotifyPropertyChanged atau INotifyCollectionChanged, atau dengan menurunkan jenis koleksi dari ObservableCollectionT<>.

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

Menginsialisasi koleksi

Saat membuat properti dependensi, Anda dapat menetapkan nilai default melalui 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 (instance) unik sebagai bagian dari logika konstruktor kelas untuk kelas pemilik properti koleksi.

Mengubah pemberitahuan

Mendefinisikan koleksi sebagai properti dependensi tidak secara otomatis memberikan pemberitahuan perubahan untuk item dalam koleksi berdasarkan sistem properti yang menggunakan metode callback "PropertyChanged". Jika Anda ingin notifikasi untuk koleksi atau item koleksi—misalnya, untuk skenario pengikatan data—terapkan antarmuka INotifyPropertyChanged atau INotifyCollectionChanged . Untuk info selengkapnya, lihat Pengikatan data secara mendalam.

Pertimbangan keamanan properti dependensi

Mendeklarasikan properti dependensi sebagai properti publik. Mendeklarasikan 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. Mendeklarasikan pengenal properti dependensi sebagai internal atau pribadi 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 jaga properti pembungkus tetap publik; jika tidak, Anda hanya membuat properti Anda lebih sulit bagi penelepon yang sah untuk digunakan tanpa memberikan manfaat keamanan nyata.

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

Properti dependensi dan konstruktor kelas

Ada prinsip umum bahwa konstruktor kelas tidak boleh menyebut metode virtual. Ini karena konstruktor dapat dipanggil untuk menyelesaikan inisialisasi dasar konstruktor kelas turunan, dan memasuki metode virtual melalui konstruktor dapat terjadi ketika instance objek yang sedang dibangun belum sepenuhnya diinsialisasi. 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 menetapkan 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 menjadi header dan file implementasi dan juga karena inisialisasi pada cakupan root file implementasi adalah praktik yang buruk. (Ekstensi komponen Visual C ++ (C ++/CX) menempatkan kode inisialisasi statis dari cakupan root langsung ke DllMain, sedangkan kompiler C # menetapkan inisialisasi 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 dikonsumsi aplikasi Anda, 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, itu tidak akan berjalan lagi jika aplikasi yang ditangguhkan dilanjutkan, misalnya. Juga, seperti yang terlihat pada contoh pendaftaran C ++ sebelumnya, pemeriksaan nullptr di sekitar setiap panggilan Register adalah penting: asuransi bahwa tidak ada penelepon 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 di sampel pengguna XAML dan kontrol kustom jika Anda melihat kode untuk sampel versi C ++/CX.