Kontrol XAML; mengikat ke properti C++/WinRT

Properti yang dapat secara efektif terikat ke kontrol XAML dikenal sebagai properti yang dapat diamati . Ide ini didasarkan pada pola desain perangkat lunak yang dikenal sebagai pola pengamat. Topik ini menunjukkan cara menerapkan properti yang dapat diamati di C++/WinRT, dan cara mengikat kontrol XAML ke properti tersebut (untuk info latar belakang, lihat Pengikatan data).

Important

Untuk konsep dan istilah penting yang mendukung pemahaman Anda tentang cara menggunakan dan menulis kelas runtime dengan C++/WinRT, lihat Menggunakan API dengan C++/WinRT dan API Penulis dengan C++/WinRT.

Apa arti yang dapat diamati untuk properti?

Katakanlah kelas runtime bernama BookSku memiliki properti bernama Judul. Jika BookSku menaikkan peristiwa INotifyPropertyChanged::P ropertyChanged setiap kali nilai Judul berubah, maka itu berarti bahwa Judul adalah properti yang dapat diamati. Ini adalah perilaku BookSku (menaikkan atau tidak meningkatkan peristiwa) yang menentukan mana, jika ada, dari propertinya yang dapat diamati.

Elemen teks XAML, atau kontrol, dapat mengikat ke, dan menangani, peristiwa ini. Elemen atau kontrol tersebut menangani peristiwa dengan mengambil nilai yang diperbarui, lalu memperbarui dirinya sendiri untuk menunjukkan nilai baru.

Note

Untuk informasi tentang menginstal dan menggunakan C++/WinRT Visual Studio Extension (VSIX) dan paket NuGet (yang bersama-sama menyediakan templat proyek dan dukungan build), lihat dukungan Visual Studio untuk C++/WinRT.

Membuat Aplikasi Kosong (Bookstore)

Mulailah dengan membuat proyek baru di Microsoft Visual Studio. Buat Blank App, Packaged (WinUI 3 in Desktop) untuk proyek C++, dan beri nama Bookstore. Pastikan bahwa Tempatkan solusi dan proyek dalam direktori yang sama tidak dicentang. Targetkan versi terbaru yang tersedia secara umum (yaitu, bukan pratinjau) dari SDK Windows.

Kita akan membuat kelas baru yang merepresentasikan sebuah buku dengan properti judul yang dapat diobservasi. Kami menulis dan mengonsumsi kelas dalam unit kompilasi yang sama. Tapi kita ingin dapat mengikat ke kelas ini dari XAML, dan untuk alasan itu itu akan menjadi kelas runtime. Dan kita akan menggunakan C++/WinRT baik untuk membuat maupun menggunakannya.

Langkah pertama dalam menulis kelas runtime baru adalah menambahkan item Midl File (.idl) baru ke proyek. Beri nama item BookSku.idlbaru . Hapus konten default dari BookSku.idl, lalu tempelkan deklarasi kelas runtime ini.

// BookSku.idl
namespace Bookstore
{
    runtimeclass BookSku : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
        BookSku(String title);
        String Title;
    }
}

Note

Kelas model tampilan Anda—pada kenyataannya, kelas runtime apa pun yang Anda deklarasikan dalam aplikasi Anda—tidak perlu berasal dari kelas dasar. Kelas BookSku yang dinyatakan di atas adalah contohnya. Ini mengimplementasikan antarmuka, tetapi tidak berasal dari kelas dasar apa pun.

Setiap kelas runtime yang Anda deklarasikan dalam aplikasi yang berasal dari kelas dasar dikenal sebagai kelas yang dapat disusun . Dan ada batasan terkait kelas komposabel. Agar aplikasi lulus tes Windows App Certification Kit yang digunakan oleh Visual Studio dan oleh Microsoft Store untuk memvalidasi pengiriman (dan oleh karena itu agar aplikasi berhasil diserap ke Microsoft Store), kelas yang dapat disusun pada akhirnya harus berasal dari kelas dasar Windows. Artinya kelas di akar hierarki warisan harus merupakan jenis yang berasal dari Windows.* atau Microsoft.* namespace. Jika Anda perlu memperoleh kelas runtime dari kelas dasar—misalnya, untuk mengimplementasikan kelas BindableBase untuk semua model tampilan Anda—maka Anda dapat memperoleh dari Microsoft. UI. Xaml.DependencyObject.

Model tampilan adalah abstraksi tampilan, sehingga terikat langsung ke tampilan (markup XAML). Model data merupakan abstraksi dari data dan hanya digunakan melalui model tampilan Anda, serta tidak diikat secara langsung ke XAML. Jadi Anda dapat mendeklarasikan model data Anda bukan sebagai kelas runtime, tetapi sebagai struct atau kelas C++. Mereka tidak perlu dideklarasikan di MIDL, dan Anda bebas menggunakan hierarki warisan apa pun yang Anda suka.

Simpan file, dan buat proyek. Proses build belum akan sepenuhnya berhasil, tetapi akan melakukan beberapa hal yang perlu untuk kita. Secara khusus, selama proses build, alat midl.exe dijalankan untuk membuat file metadata Windows Runtime yang mendeskripsikan kelas runtime (file tersebut ditempatkan di disk pada \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). Kemudian, alat cppwinrt.exe dijalankan untuk menghasilkan berkas kode sumber guna membantu Anda menulis dan menggunakan kelas runtime Anda. File-file tersebut termasuk stub untuk membuat Anda mulai menerapkan kelas runtime BookSku yang Anda deklarasikan di IDL Anda. Kita akan menemukannya di disk dalam sesaat, tetapi stub tersebut adalah \Bookstore\Bookstore\Generated Files\sources\BookSku.h dan BookSku.cpp.

Jadi sekarang klik kanan simpul proyek di Visual Studio, dan klik Buka Folder di File Explorer. Ini akan membuka folder proyek di File Explorer. Anda sekarang harus melihat isi \Bookstore\Bookstore\ folder. Dari sana, navigasikan ke \Generated Files\sources\ folder, dan salin file BookSku.h stub dan BookSku.cpp ke clipboard. Navigasikan cadangan ke folder proyek (\Bookstore\Bookstore\), dan tempelkan dua file yang baru saja Anda salin. Terakhir, di Penjelajah Solusi dengan simpul proyek dipilih, pastikan Tampilkan Semua File diaktifkan. Klik kanan file stub yang Anda salin, dan klik Sertakan Dalam Project.

Menerapkan BookSku

Sekarang mari kita buka \Bookstore\Bookstore\BookSku.h dan BookSku.cpp dan terapkan kelas runtime kami. Pertama, Anda akan melihat static_assert di bagian BookSku.h atas dan BookSku.cpp, yang harus Anda hapus.

Selanjutnya, di BookSku.h, buat perubahan ini.

  • Pada konstruktor default, ubah = default ke = delete. Itu karena kita tidak ingin konstruktor default.
  • Tambahkan anggota privat untuk menyimpan string judul. Perhatikan bahwa kita memiliki konstruktor yang mengambil nilai winrt::hstring . Nilai tersebut adalah string judul.
  • Tambahkan satu member privat lagi untuk event yang akan dipicu ketika judul berubah.

Setelah membuat perubahan ini, BookSku.h Anda akan terlihat seperti ini.

// BookSku.h
#pragma once
#include "BookSku.g.h"

namespace winrt::Bookstore::implementation
{
    struct BookSku : BookSkuT<BookSku>
    {
        BookSku() = delete;
        BookSku(winrt::hstring const& title);

        winrt::hstring Title();
        void Title(winrt::hstring const& value);
        winrt::event_token PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookSku : BookSkuT<BookSku, implementation::BookSku>
    {
    };
}

Dalam BookSku.cpp, terapkan fungsi seperti ini.

// BookSku.cpp
#include "pch.h"
#include "BookSku.h"
#include "BookSku.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookSku::BookSku(winrt::hstring const& title) : m_title{ title }
    {
    }

    winrt::hstring BookSku::Title()
    {
        return m_title;
    }

    void BookSku::Title(winrt::hstring const& value)
    {
        if (m_title != value)
        {
            m_title = value;
            m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

    winrt::event_token BookSku::PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return m_propertyChanged.add(handler);
    }

    void BookSku::PropertyChanged(winrt::event_token const& token)
    {
        m_propertyChanged.remove(token);
    }
}

Dalam fungsi mutator Title, kita memeriksa apakah nilai yang sedang ditetapkan berbeda dari nilai saat ini. Dan jika demikian, kita memperbarui judul dan juga memicu event INotifyPropertyChanged::PropertyChanged dengan argumen berupa nama properti yang telah berubah. Hal ini agar antarmuka pengguna (UI) akan mengetahui nilai properti mana yang akan dikueri ulang.

Proyek akan dibangun lagi sekarang, jika Anda ingin memeriksanya.

Mendeklarasikan dan mengimplementasikan BookstoreViewModel

Halaman XAML utama kita akan diikatkan ke sebuah model tampilan utama. Dan model tampilan itu akan memiliki beberapa properti, termasuk salah satu jenis BookSku. Dalam langkah ini, kami akan mendeklarasikan dan mengimplementasikan kelas runtime model tampilan utama kami.

Tambahkan item Midl File (.idl) baru bernama BookstoreViewModel.idl. Namun, lihat juga Memisahkan kelas runtime ke dalam file Midl (.idl).

// BookstoreViewModel.idl
import "BookSku.idl";

namespace Bookstore
{
    runtimeclass BookstoreViewModel
    {
        BookstoreViewModel();
        BookSku BookSku{ get; };
    }
}

Simpan dan kompilasi (proses kompilasi belum akan sepenuhnya berhasil, tetapi alasan kita melakukannya adalah untuk membuat file stub lagi).

Salin BookstoreViewModel.h dan BookstoreViewModel.cpp dari Generated Files\sources folder ke folder proyek, dan sertakan dalam proyek. Buka file tersebut (menghapus static_assert lagi), dan terapkan kelas runtime seperti yang ditunjukkan di bawah ini. Perhatikan bagaimana, dalam BookstoreViewModel.h, kami termasuk BookSku.h, yang mendeklarasikan jenis implementasi untuk BookSku (yaitu winrt::Bookstore::implementation::BookSku). Dan kami menghapus = default dari konstruktor default.

Note

Dalam daftar di bawah ini untuk BookstoreViewModel.h dan BookstoreViewModel.cpp, kode mengilustrasikan cara default membangun anggota data m_bookSku . Itulah metode yang dimulai kembali ke rilis pertama C++/WinRT, dan ada baiknya setidaknya terbiasa dengan pola tersebut. Dengan C++/WinRT versi 2.0 dan yang lebih baru, ada bentuk konstruksi yang dioptimalkan yang tersedia untuk Anda yang dikenal sebagai konstruksi seragam (lihat Berita, dan perubahan, di C++/WinRT 2.0). Nanti dalam topik ini, kita akan menunjukkan contoh konstruksi seragam.

// BookstoreViewModel.h
#pragma once
#include "BookstoreViewModel.g.h"
#include "BookSku.h"

namespace winrt::Bookstore::implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
    {
        BookstoreViewModel();

        Bookstore::BookSku BookSku();

    private:
        Bookstore::BookSku m_bookSku{ nullptr };
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel, implementation::BookstoreViewModel>
    {
    };
}
// BookstoreViewModel.cpp
#include "pch.h"
#include "BookstoreViewModel.h"
#include "BookstoreViewModel.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookstoreViewModel::BookstoreViewModel()
    {
        m_bookSku = winrt::make<Bookstore::implementation::BookSku>(L"Atticus");
    }

    Bookstore::BookSku BookstoreViewModel::BookSku()
    {
        return m_bookSku;
    }
}

Note

Jenisnya m_bookSku adalah jenis yang diproyeksikan (winrt::Bookstore::BookSku), dan parameter templat yang Anda gunakan dengan winrt::make adalah jenis implementasi (winrt::Bookstore::implementation::BookSku). Meskipun demikian, make mengembalikan sebuah instans dari tipe yang diproyeksikan.

Proyek akan dibangun lagi sekarang.

Menambahkan properti jenis BookstoreViewModel ke MainPage

Buka MainPage.idl, yang mendeklarasikan kelas runtime yang mewakili halaman UI utama kami.

  • Tambahkan direktif import untuk mengimpor BookstoreViewModel.idl.
  • Tambahkan properti baca-saja bernama MainViewModel, dari jenis BookstoreViewModel.
  • Hapus properti MyProperty.
// MainPage.idl
import "BookstoreViewModel.idl";

namespace Bookstore
{
    runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

Simpan file tersebut. Proyek ini belum sepenuhnya berhasil dalam membangun, tetapi membangun sekarang adalah hal yang berguna untuk dilakukan karena meregenerasi file kode sumber tempat kelas runtime MainPage diimplementasikan (\Bookstore\Bookstore\Generated Files\sources\MainPage.h dan MainPage.cpp). Jadi lanjutkan dan bangun sekarang. Kesalahan build yang dapat Anda harapkan untuk dilihat pada tahap ini adalah 'MainViewModel': bukan anggota 'winrt::Bookstore::implementation::MainPage'.

Jika Anda tidak menyertakan BookstoreViewModel.idl (lihat listing MainPage.idl di atas), Anda akan melihat kesalahan mengharapkan < di dekat "MainViewModel". Tip lainnya adalah memastikan bahwa semua tipe tetap berada dalam namespace yang sama—namespace yang ditampilkan dalam cuplikan kode.

Untuk mengatasi kesalahan yang diperkirakan akan muncul, Anda sekarang perlu menyalin stub pengakses untuk properti MainViewModel dari file yang dihasilkan (\Bookstore\Bookstore\Generated Files\sources\MainPage.h dan MainPage.cpp) ke \Bookstore\Bookstore\MainPage.h dan MainPage.cpp. Langkah-langkah untuk melakukannya dijelaskan selanjutnya.

Dalam \Bookstore\Bookstore\MainPage.h, lakukan langkah-langkah ini.

  • Sertakan BookstoreViewModel.h, yang menyatakan jenis implementasi untuk BookstoreViewModel (yaitu winrt::Bookstore::implementation::BookstoreViewModel).
  • Tambahkan anggota privat untuk menyimpan model tampilan. Perhatikan bahwa fungsi pengakses properti (dan anggota m_mainViewModel) diimplementasikan menggunakan tipe terproyeksi untuk BookstoreViewModel (yaitu Bookstore::BookstoreViewModel).
  • Jenis implementasi berada dalam proyek yang sama (unit kompilasi) dengan aplikasi, jadi kami membangun m_mainViewModel melalui overload konstruktor yang mengambil std::nullptr_t.
  • Hapus properti MyProperty.

Note

Dalam pasangan daftar di bawah ini untuk MainPage.h dan MainPage.cpp, kode menggambarkan cara default membangun anggota data m_mainViewModel . Di bagian berikut, kita akan menampilkan versi yang menggunakan konstruksi seragam sebagai gantinya.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
namespace winrt::Bookstore::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        Bookstore::BookstoreViewModel MainViewModel();

        void ClickHandler(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::RoutedEventArgs const&);

    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
    };
}
...

Dalam \Bookstore\Bookstore\MainPage.cpp, seperti yang ditunjukkan pada daftar di bawah ini, buat perubahan berikut.

  • Panggil winrt::make (dengan jenis implementasi BookstoreViewModel ) untuk menetapkan instans baru dari jenis BookstoreViewModel yang diproyeksikan ke m_mainViewModel. Seperti yang kita lihat di atas, konstruktor BookstoreViewModel membuat objek BookSku baru sebagai anggota data pribadi, mengatur judulnya awalnya ke L"Atticus".
  • Dalam pengendali event tombol (ClickHandler), perbarui judul buku menjadi judul terbitnya.
  • Terapkan aksesor untuk properti MainViewModel .
  • Hapus properti MyProperty.
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

using namespace winrt;
using namespace Microsoft::UI::Xaml;

namespace winrt::Bookstore::implementation
{
    MainPage::MainPage()
    {
        m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
        InitializeComponent();
    }

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* args */)
    {
        MainViewModel().BookSku().Title(L"To Kill a Mockingbird");
    }

    Bookstore::BookstoreViewModel MainPage::MainViewModel()
    {
        return m_mainViewModel;
    }
}

Konstruksi seragam

Untuk menggunakan konstruksi seragam alih-alih winrt::make, dalam mendeklarasikan dan menginisialisasi MainPage.h hanya dalam satu langkah, seperti yang ditunjukkan di bawah ini.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
struct MainPage : MainPageT<MainPage>
{
    ...
private:
    Bookstore::BookstoreViewModel m_mainViewModel;
};
...

Dan kemudian, di konstruktor MainPage di MainPage.cpp, tidak perlu kode m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.

Untuk informasi selengkapnya tentang konstruksi seragam, dan contoh kode, lihat Ikut serta dalam konstruksi seragam, dan akses implementasi langsung.

Mengikat tombol ke properti Judul

Buka MainPage.xaml, yang berisi markup XAML untuk halaman UI utama kami. Seperti yang ditunjukkan dalam daftar di bawah ini, hapus nama dari tombol , dan ubah nilai properti Kontennya dari harfiah menjadi ekspresi pengikatan. Mode=OneWay Perhatikan properti pada ekspresi pengikatan (satu arah dari model tampilan ke UI). Tanpa properti tersebut, UI tidak akan merespons peristiwa perubahan properti.

<Button Click="ClickHandler" Content="{x:Bind MainViewModel.BookSku.Title, Mode=OneWay}"/>

Sekarang bangun dan jalankan proyek. Klik tombol untuk menjalankan penanganan aktivitas Klik . Handler itu memanggil fungsi mutator judul buku; mutator tersebut memunculkan peristiwa untuk memberi tahu UI bahwa properti Judul telah berubah; dan tombol mengkueri ulang nilai properti tersebut untuk memperbarui nilai Kontennya sendiri.

Menggunakan ekstensi markup {Binding} dengan C++/WinRT

Untuk versi C++/WinRT yang saat ini dirilis, agar dapat menggunakan ekstensi markup {Binding}, Anda harus menerapkan antarmuka ICustomPropertyProvider dan ICustomProperty .

Pengikatan elemen-ke-elemen

Anda dapat mengikat properti dari satu elemen XAML ke properti elemen XAML lain. Berikut adalah contoh tampilannya dalam markup.

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

Anda perlu mendeklarasikan entitas XAML bernama myTextBox sebagai properti hanya-baca dalam file MIDL (.idl) Anda.

// MainPage.idl
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
    MainPage();
    Microsoft.UI.Xaml.Controls.TextBox myTextBox{ get; };
}

Inilah alasan untuk kebutuhan ini. Semua jenis yang perlu divalidasi oleh pengkompilasi XAML (termasuk yang digunakan dalam {x:Bind}) dibaca dari Metadata Windows (WinMD). Yang perlu Anda lakukan adalah menambahkan properti baca-saja ke file Midl Anda. Jangan mengimplementasikannya, karena kode code-behind XAML yang dibuat secara otomatis sudah menyediakan implementasinya untuk Anda.

Mengkonsumsi objek dari markup XAML

Semua entitas yang digunakan dengan menggunakan ekstensi markup XAML {x:Bind} harus diekspos secara publik di IDL. Selain itu, jika markup XAML berisi referensi ke elemen lain yang juga dalam markup, maka getter untuk markup tersebut harus ada di IDL.

<Page x:Name="MyPage">
    <StackPanel>
        <CheckBox x:Name="UseCustomColorCheckBox" Content="Use custom color"
             Click="UseCustomColorCheckBox_Click" />
        <Button x:Name="ChangeColorButton" Content="Change color"
            Click="{x:Bind ChangeColorButton_OnClick}"
            IsEnabled="{x:Bind UseCustomColorCheckBox.IsChecked.Value, Mode=OneWay}"/>
    </StackPanel>
</Page>

Elemen ChangeColorButton mengacu pada elemen UseCustomColorCheckBox melalui pengikatan. Jadi IDL untuk halaman ini harus mendeklarasikan properti baca-saja bernama UseCustomColorCheckBox agar dapat diakses oleh pengikatan.

Delegasi penanganan aktivitas klik untuk UseCustomColorCheckBox menggunakan sintaks delegasi XAML klasik, sehingga tidak memerlukan entri di IDL; itu hanya perlu publik di kelas implementasi Anda. Di sisi lain, ChangeColorButton juga memiliki {x:Bind} penanganan aktivitas klik, yang juga harus masuk ke IDL.

runtimeclass MyPage : Microsoft.UI.Xaml.Controls.Page
{
    MyPage();

    // These members are consumed by binding.
    void ChangeColorButton_OnClick();
    Microsoft.UI.Xaml.Controls.CheckBox UseCustomColorCheckBox{ get; };
}

Anda tidak perlu memberikan implementasi untuk properti UseCustomColorCheckBox . Generator kode XAML melakukannya untuk Anda.

Pengikatan ke Boolean

Anda mungkin melakukan ini dalam mode diagnostik:

<TextBlock Text="{Binding CanPair}"/>

Itu menampilkan true atau false di C++/CX; tetapi ditampilkan Windows.Foundation.IReference`1<Boolean> di C++/WinRT.

Sebagai gantinya, gunakan x:Bind saat mengikat ke Boolean.

<TextBlock Text="{x:Bind CanPair}"/>

Menggunakan Pustaka Implementasi Windows (WIL)

Windows Implementation Libraries (WIL) menyediakan pembantu untuk memudahkan penulisan properti yang dapat diikat. Lihat Notifying Properties di dokumentasi WIL.

API penting