Koleksi dengan C++/WinRT

Secara internal, koleksi Windows Runtime melibatkan banyak komponen yang kompleks. Tetapi ketika Anda ingin meneruskan objek koleksi ke fungsi Windows Runtime, atau untuk mengimplementasikan properti koleksi dan jenis koleksi Anda sendiri, ada fungsi dan kelas dasar di C++/WinRT untuk mendukung Anda. Fitur-fitur ini menghilangkan kerumitan dari tangan Anda dan menghemat banyak beban tambahan dari segi waktu dan upaya.

IVector adalah antarmuka Windows Runtime yang diimplementasikan oleh kumpulan elemen akses acak apa pun. Jika Anda mengimplementasikan IVector sendiri, Anda juga perlu menerapkan IIterable, IVectorView, dan IIterator. Bahkan jika Anda memerlukan tipe koleksi khusus, itu tetap pekerjaan yang besar. Tetapi jika Anda memiliki data di std::vector (atau std::map, atau std::unordered_map) dan yang ingin Anda lakukan adalah meneruskannya ke API Windows Runtime, maka Anda ingin menghindari melakukan tingkat pekerjaan itu, jika memungkinkan. Dan hal itu dapat dihindari, karena C++/WinRT membantu Anda membuat koleksi secara efisien dan dengan upaya minimal.

Lihat juga kontrol item XAML; ikat ke koleksi C++/WinRT.

Fungsi pembantu untuk koleksi

Koleksi tujuan umum, kosong

Bagian ini membahas skenario ketika Anda ingin membuat koleksi yang awalnya kosong, lalu mengisinya setelah dibuat.

Untuk mendapatkan objek baru dengan jenis yang mengimplementasikan koleksi serbaguna, Anda dapat memanggil templat fungsi winrt::single_threaded_vector. Objek dikembalikan sebagai IVector, dan itulah antarmuka tempat Anda memanggil fungsi dan properti objek yang dikembalikan.

Jika Anda ingin menyalin-tempel contoh kode berikut langsung ke file kode sumber utama proyek aplikasi konsol Windows (C++/WinRT), maka pertama-tama atur Tidak Menggunakan Header yang Telah Dikompilasi sebelumnya di properti proyek.

// main.cpp
#include <winrt/Windows.Foundation.Collections.h>
#include <iostream>
using namespace winrt;

int main()
{
    winrt::init_apartment();

    Windows::Foundation::Collections::IVector<int> coll{ winrt::single_threaded_vector<int>() };
    coll.Append(1);
    coll.Append(2);
    coll.Append(3);

    for (auto const& el : coll)
    {
        std::cout << el << std::endl;
    }

    Windows::Foundation::Collections::IVectorView<int> view{ coll.GetView() };
}

Seperti yang dapat Anda lihat pada contoh kode di atas, setelah membuat koleksi, Anda dapat menambahkan elemen, mengiterasinya, dan secara umum memperlakukan objek tersebut sebagaimana Anda memperlakukan objek koleksi Windows Runtime lainnya yang mungkin Anda terima dari API. Jika Anda memerlukan tampilan yang tidak dapat diubah atas koleksi, maka Anda dapat memanggil IVector::GetView, seperti yang ditunjukkan. Pola yang ditunjukkan di atas—membuat dan mengkonsumsi koleksi—sesuai untuk skenario sederhana tempat Anda ingin meneruskan data, atau mengeluarkan data dari API. Anda dapat memberikan IVector atau IVectorView di mana pun IIterable diperlukan.

Dalam contoh kode di atas, panggilan ke winrt::init_apartment menginisialisasi utas di Windows Runtime; secara default, di apartemen multithreaded. Pemanggilan ini juga menginisialisasi COM.

Koleksi serbaguna, dibangun berdasarkan data

Bagian ini mencakup skenario di mana Anda ingin membuat koleksi dan mengisinya secara bersamaan.

Anda dapat menghindari overhead dari pemanggilan Append dalam contoh kode sebelumnya. Anda mungkin sudah memiliki data sumber, atau Anda mungkin lebih suka mengisi data sumber sebelum membuat objek pengumpulan Windows Runtime. Berikut adalah cara melakukannya.

auto coll1{ winrt::single_threaded_vector<int>({ 1,2,3 }) };

std::vector<int> values{ 1,2,3 };
auto coll2{ winrt::single_threaded_vector<int>(std::move(values)) };

for (auto const& el : coll2)
{
    std::cout << el << std::endl;
}

Anda dapat meneruskan objek sementara yang berisi data Anda ke winrt::single_threaded_vector, seperti halnya coll1, di atas. Atau Anda dapat memindahkan std::vector (dengan asumsi Anda tidak akan mengaksesnya lagi) ke dalam fungsi. Dalam kedua kasus, Anda meneruskan rvalue ke dalam fungsi. Itu memungkinkan pengkompilasi menjadi efisien dan untuk menghindari penyalinan data. Jika Anda ingin mengetahui selengkapnya tentang rvalue, lihat Kategori nilai, dan referensinya.

Jika Anda ingin mengikat kontrol item XAML ke koleksi Anda, maka Anda dapat. Tetapi ketahuilah bahwa untuk mengatur properti ItemsControl.ItemsSource dengan benar, Anda perlu mengaturnya ke nilai jenis IVectorIInspectable (atau jenis interoperabilitas seperti IBindableObservableVector).

Berikut adalah contoh kode yang menghasilkan kumpulan jenis yang cocok untuk pengikatan, dan menambahkan elemen ke dalamnya. Anda dapat menemukan konteks untuk contoh kode ini di kontrol item XAML; ikat ke koleksi C++/WinRT.

auto bookSkus{ winrt::single_threaded_vector<Windows::Foundation::IInspectable>() };
bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"Moby Dick"));

Anda dapat membuat koleksi Windows Runtime dari data, lalu mendapatkan tampilan atas data tersebut yang siap diberikan ke API, semuanya tanpa perlu menyalin apa pun.

std::vector<float> values{ 0.1f, 0.2f, 0.3f };
Windows::Foundation::Collections::IVectorView<float> view{ winrt::single_threaded_vector(std::move(values)).GetView() };

Dalam contoh di atas, koleksi yang kita buat dapat terikat ke kontrol item XAML; tetapi koleksinya tidak dapat diamati.

Koleksi yang dapat diamati

Untuk mendapatkan objek baru dari tipe yang mengimplementasikan koleksi observable, panggil templat fungsi winrt::single_threaded_observable_vector dengan tipe elemen apa pun. Tetapi untuk membuat koleksi yang dapat diamati cocok untuk mengikat kontrol item XAML, gunakan IInspectable sebagai jenis elemen.

Objek dikembalikan sebagai IObservableVector, dan itulah antarmuka tempat Anda (atau kontrol yang terikat) memanggil fungsi dan properti objek yang dikembalikan.

auto bookSkus{ winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>() };

Untuk detail selengkapnya, dan contoh kode, tentang mengikat kontrol antarmuka pengguna (UI) Anda ke koleksi yang dapat diamati, lihat kontrol item XAML; mengikat koleksi C++/WinRT.

Koleksi asosiatif (peta)

Ada versi koleksi asosiatif dari dua fungsi yang telah kita lihat.

Anda dapat secara opsional menginisialisasi koleksi ini dengan data dengan meneruskan rvalue bertipe std::map atau std::unordered_map ke fungsi tersebut.

auto coll1{
    winrt::single_threaded_map<winrt::hstring, int>(std::map<winrt::hstring, int>{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    })
};

std::map<winrt::hstring, int> values{
    { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
};
auto coll2{ winrt::single_threaded_map<winrt::hstring, int>(std::move(values)) };

Utas tunggal

Istilah "single-threaded" pada nama fungsi-fungsi ini menunjukkan bahwa fungsi-fungsi tersebut tidak mendukung konkurensi apa pun—dengan kata lain, fungsi-fungsi tersebut tidak thread-safe. Penyebutan utas tidak terkait dengan apartment, karena objek yang dikembalikan dari fungsi ini semuanya agile (lihat Objek agile di C++/WinRT). Hanya saja objeknya berutas tunggal. Dan itu sangat tepat jika Anda hanya ingin meneruskan data ke satu arah atau arah lainnya melalui antarmuka biner aplikasi (ABI).

Kelas dasar untuk koleksi

Jika, untuk fleksibilitas lengkap, Anda ingin menerapkan koleksi kustom Anda sendiri, maka Anda harus menghindari melakukan itu dengan cara yang sulit. Misalnya, seperti inilah tampilan vektor kustom tanpa bantuan kelas dasar C++/WinRT.

...
using namespace winrt;
using namespace Windows::Foundation::Collections;
...
struct MyVectorView :
    implements<MyVectorView, IVectorView<float>, IIterable<float>>
{
    // IVectorView
    float GetAt(uint32_t const) { ... };
    uint32_t GetMany(uint32_t, winrt::array_view<float>) const { ... };
    bool IndexOf(float, uint32_t&) { ... };
    uint32_t Size() { ... };

    // IIterable
    IIterator<float> First() const { ... };
};
...
IVectorView<float> view{ winrt::make<MyVectorView>() };

Sebaliknya, jauh lebih mudah untuk mendapatkan tampilan vektor kustom Anda dari templat struct winrt::vector_view_base , dan hanya menerapkan fungsi get_container untuk mengekspos kontainer yang menyimpan data Anda.

struct MyVectorView2 :
    implements<MyVectorView2, IVectorView<float>, IIterable<float>>,
    winrt::vector_view_base<MyVectorView2, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

Kontainer yang dikembalikan oleh get_container harus menyediakan antarmuka awal dan akhir yang diharapkan winrt::vector_view_base . Seperti yang ditunjukkan pada contoh di atas, std::vector menyediakannya. Tetapi Anda dapat mengembalikan kontainer apa pun yang memenuhi kontrak yang sama, termasuk kontainer kustom Anda sendiri.

struct MyVectorView3 :
    implements<MyVectorView3, IVectorView<float>, IIterable<float>>,
    winrt::vector_view_base<MyVectorView3, float>
{
    auto get_container() const noexcept
    {
        struct container
        {
            float const* const first;
            float const* const last;

            auto begin() const noexcept
            {
                return first;
            }

            auto end() const noexcept
            {
                return last;
            }
        };

        return container{ m_values.data(), m_values.data() + m_values.size() };
    }

private:
    std::array<float, 3> m_values{ 0.2f, 0.3f, 0.4f };
};

Ini adalah kelas dasar yang disediakan C++/WinRT untuk membantu Anda menerapkan koleksi kustom.

winrt::vector_view_base

Lihat contoh kode di atas.

winrt::vector_base

struct MyVector :
    implements<MyVector, IVector<float>, IVectorView<float>, IIterable<float>>,
    winrt::vector_base<MyVector, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

winrt::observable_vector_base

struct MyObservableVector :
    implements<MyObservableVector, IObservableVector<float>, IVector<float>, IVectorView<float>, IIterable<float>>,
    winrt::observable_vector_base<MyObservableVector, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

winrt::map_view_base

struct MyMapView :
    implements<MyMapView, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::map_view_base<MyMapView, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

winrt::map_base

struct MyMap :
    implements<MyMap, IMap<winrt::hstring, int>, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::map_base<MyMap, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

winrt::observable_map_base

struct MyObservableMap :
    implements<MyObservableMap, IObservableMap<winrt::hstring, int>, IMap<winrt::hstring, int>, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::observable_map_base<MyObservableMap, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

API penting