Kontrol XAML; ikat 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).

Penting

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.

Catatan

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 proyek Aplikasi Kosong (C++/WinRT), 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 Windows SDK.

Kita akan menulis kelas baru untuk mewakili buku yang memiliki properti judul yang dapat diamati. 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 untuk penulis dan mengonsumsinya.

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

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

Catatan

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 kendala di sekitar kelas yang dapat dikomposisikan. 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 namespace Windows.*. Jika Anda perlu memperoleh kelas runtime dari kelas dasar—misalnya, untuk menerapkan kelas BindableBase untuk semua model tampilan Anda untuk diperoleh—maka Anda dapat memperoleh dari Windows.UI.Xaml.DependencyObject.

Model tampilan adalah abstraksi tampilan, sehingga terikat langsung ke tampilan (markup XAML). Model data adalah abstraksi data, dan hanya digunakan dari model tampilan Anda, dan tidak terikat 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. Build belum berhasil (sepenuhnya), tetapi akan melakukan beberapa hal yang diperlukan bagi kita. Secara khusus, selama proses midl.exe build alat dijalankan untuk membuat file metadata Windows Runtime yang menjelaskan kelas runtime (file ditempatkan pada disk di \Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd). Kemudian, cppwinrt.exe alat ini dijalankan untuk menghasilkan file kode sumber untuk mendukung Anda dalam menulis dan mengonsumsi 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. Yang 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 Proyek.

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 anggota privat lain untuk peristiwa yang akan kami ajukan saat judul berubah.

Setelah membuat perubahan ini, Anda BookSku.h 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(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Windows::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, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

    winrt::event_token BookSku::PropertyChanged(Windows::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 judul, kita memeriksa apakah nilai sedang diatur yang berbeda dari nilai saat ini. Dan, jika demikian, maka kita memperbarui judul dan juga menaikkan peristiwa INotifyPropertyChanged::P ropertyChanged dengan argumen yang sama dengan 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 kami akan mengikat 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. Tetapi juga lihat Memperhitungkan kelas runtime ke dalam file Midl (.idl).

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

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

Simpan dan bangun (build belum sepenuhnya berhasil, tetapi alasan kami membangun adalah untuk menghasilkan 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.

Catatan

Dalam daftar di bawah ini untuk BookstoreViewModel.h dan BookstoreViewModel.cpp, kode menggambarkan 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;
    }
}

Catatan

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, buat pengembalian instans dari jenis 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 : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

Simpan file. 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 menghilangkan sertakan BookstoreViewModel.idl (lihat daftar MainPage.idl di atas), maka Anda akan melihat kesalahan yang diharapkan < di dekat "MainViewModel". Tips lain adalah memastikan bahwa Anda meninggalkan semua jenis di namespace yang sama—namespace yang ditampilkan dalam daftar kode.

Untuk mengatasi kesalahan yang kami harapkan untuk dilihat, Anda sekarang perlu menyalin stub aksesor untuk properti MainViewModel dari file yang dihasilkan (\Bookstore\Bookstore\Generated Files\sources\MainPage.h dan MainPage.cpp) dan ke dalam \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 dalam hal jenis yang diproyeksikan 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.

Catatan

Dalam pasangan daftar di bawah ini untuk MainPage.h dan MainPage.cpp, kode mengilustrasikan 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&, Windows::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".
  • Di penanganan aktivitas tombol (ClickHandler), perbarui judul buku ke judul yang diterbitkan.
  • 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 Windows::UI::Xaml;

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

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Windows::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 m_mainViewModel hanya dalam satu langkah, seperti yang ditunjukkan MainPage.h 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 yang diubah 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 harus mendeklarasikan entitas myTextBox XAML bernama sebagai properti baca-saja di file Midl Anda (.idl).

// MainPage.idl
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
    MainPage();
    Windows.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 terapkan, karena kode XAML yang dibuat secara otomatis menyediakan implementasi 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 : Windows.UI.Xaml.Controls.Page
{
    MyPage();

    // These members are consumed by binding.
    void ChangeColorButton_OnClick();
    Windows.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 Memberi tahu Properti dalam dokumentasi WIL.

API penting