使用 C++/WinRT 的集合

在內部,Windows 執行階段 集合包含許多複雜的運作部分。 但當你想把集合物件傳給 Windows 執行階段 函式,或實作自己的集合屬性和集合類型時,C++/WinRT 裡有函式和基底類別可以支援你。 這些功能可讓你免於處理複雜性,並為你省下大量時間與精力上的額外負擔。

IVector 是由任何隨機存取元素集合實作的 Windows 執行階段 介面。 如果你要自己實作 IVector ,還需要實作 IIterableIVectorViewIIterator。 即使 你需要 自訂的集合類型,那也是一項大量的工作。 但如果你的資料是 std::vector(或 std::map,或 std::unordered_map),而你只想把它傳給 Windows 執行階段 API,那你最好盡量避免這種工作。 而且避免這點 可能的,因為 C++/WinRT 幫助你高效且輕鬆地建立集合。

另見 XAML 項目控制項;綁定到 C++/WinRT 集合

集合的輔助函式

通用集合,空白

本節說明當你想建立一個一開始為空的集合,並在 建立後 再加入內容的情況。

若要取得實作通用集合的新物件類型,可以呼叫 winrt::single_threaded_vector 函式模板。 物件會以 IVector 的形式回傳,這是你呼叫回傳物件函式和屬性的介面。

如果你想直接複製貼上以下程式碼範例到 Windows 主控台應用程式(C++/WinRT)專案的主原始碼檔案,請先在專案屬性中設定「不使用預編譯標頭」。

// 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() };
}

如你在上面的程式碼範例中所見,建立集合後,你可以附加元素、反覆修改,並且通常將物件視為你從 API 收到的任何 Windows 執行階段 集合物件。 如果你需要對集合有不可變的檢視,可以呼叫 IVector::GetView,如圖所示。 上述建立並使用集合的模式,適用於簡單情境,例如你想將資料傳遞到或從 API 取得資料。 你可以在任何預期使用 IIterable 的地方傳遞 IVectorIVectorView

在上述程式碼範例中,呼叫 winrt::init_apartment 會初始化 Windows 執行階段 中的執行緒;預設是在多執行緒公寓中。 通話同時初始化 COM。

通用集合,由資料預先建立

本節說明您希望建立一個集合並同時填充該集合的情況。

你可以避免前述程式碼範例中呼叫 Append 的額外負擔。 你可能已經擁有原始資料,或者你可能偏好在建立 Windows 執行階段 集合物件之前先填充原始資料。 方法如下所示。

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;
}

你可以將包含你的資料的暫時物件傳遞給 winrt::single_threaded_vector,如同上面的 coll1 一樣。 或者,你也可以將 std::vector(假設你之後不會再存取它)移入函式。 在這兩種情況下,你都是把 rvalue 傳入函數。 這讓編譯器能更有效率,避免複製資料。 如果你想了解更多關於 r值的資訊,請參閱 「值類別」及其相關參考資料。

如果你想把 XAML 的物品控制項綁定到你的收藏,那你可以這麼做。 但要正確設定 ItemsControl.ItemsSource 屬性,你需要將其設為 IInspectableIVector 類型(或像 IBindableObservableVector 這樣的互通型別)。

這裡有一個程式碼範例,產生一個適合綁定的型別集合,並在上面附加一個元素。 你可以在 XAML 項目控制項中找到此程式碼範例的上下文 ;綁定到 C++/WinRT 集合

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

你可以從資料建立 Windows 執行階段 集合,並取得視圖,準備傳給 API,完全不複製任何東西。

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

在上述範例中,我們建立的集合 可以 綁定到 XAML 項目控制;但這個收藏是無法觀察到的。

可觀察集合

若要擷取實作 observable 集合之類型的新物件,請使用任意元素類型呼叫 winrt::single_threaded_observable_vector 函式範本。 但若要讓可觀察集合適合綁定到 XAML 項目控制項,請使用 IInspectable 作為元素類型。

物件會以 IObservableVector 回傳,這是你(或綁定它的控制項)呼叫回傳物件函式和屬性的介面。

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

關於如何將使用者介面(UI)控制項綁定到可觀察集合的更多細節及程式碼範例,請參見 XAML 項目控制項;綁定到 C++/WinRT 集合

關聯集合(地圖)

我們已經研究過這兩個函式的關聯集合版本。

你可以選擇性地將 rvalue 傳遞給函式 std::mapstd::unordered_map 來為這些集合加碼。

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)) };

單執行緒

這些函式名稱中的「單執行緒」表示它們不提供任何並行——換句話說,它們並非執行緒安全。 關於執行緒的提及與 apartments 無關,因為這些函式回傳的物件皆為敏捷(參見 C++/WinRT 中的敏捷物件)。 只是物件本身是單執行緒的而已。 如果你只是想透過應用程式二進位介面(ABI)傳遞資料,這完全是合適的。

集合的基底類別

如果你想要完全的彈性,想實作自己的自訂收藏,那你就要避免用困難的方式來做這件事。 例如,這就是 沒有 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>() };

相反地,從 winrt::vector_view_base struct 模板中衍生出自訂向量視圖會簡單得多,然後實作 get_container 函式來暴露存放資料的容器。

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 };
};

get_container 回傳的容器必須提供 winrt::vector_view_base 期望的開始結束介面。 如上例所示, std::vector 提供了這個功能。 但你可以退回任何符合相同合約的容器,包括你自己訂製的容器。

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 };
};

這些是 C++/WinRT 提供的基礎類別,幫助你實作自訂集合。

Winrt::vector_view_base

請參考上述程式碼範例。

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