Coleções com C++/WinRT

Internamente, uma coleção do Windows Runtime tem muitos componentes complexos. Mas quando quiseres passar um objeto de coleção para uma função do Windows Runtime, ou implementar as tuas próprias propriedades e tipos de coleção, existem funções e classes base em C++/WinRT para te apoiar. Estas funcionalidades tiram-lhe a complexidade das mãos e poupam-lhe muito trabalho em tempo e esforço.

IVector é a interface Windows Runtime implementada por qualquer coleção de elementos de acesso aleatório. Se fosses implementar o IVector tu próprio, também terias de implementar o IIterable, o IVectorView e o IIterator. Mesmo que precises de um tipo de coleção personalizada, isso dá muito trabalho. Mas se tiveres dados num std::vector (ou num std::map, ou num std::unordered_map) e tudo o que queres é passar isso para uma API Windows Runtime, então deves evitar fazer esse nível de trabalho, se possível. E evitá-lo é possível, porque o C++/WinRT ajuda-te a criar coleções de forma eficiente e com pouco esforço.

Veja também controlos de itens em XAML; associe-os a uma coleção C++/WinRT.

Funções auxiliares para coleções

Coleção de uso geral, vazia

Esta secção aborda o cenário em que pretende criar uma coleção inicialmente vazia; e depois preenchê-la depois da criação.

Para obter um novo objeto de um tipo que implementa uma coleção de finalidade geral, pode chamar o modelo de função winrt::single_threaded_vector. O objeto é devolvido como um IVector, e essa é a interface através da qual chamas as funções e propriedades do objeto devolvido.

Se quiser copiar e colar os seguintes exemplos de código diretamente no ficheiro principal de código-fonte de um projeto de Aplicação de Consola Windows (C++/WinRT), então primeiro defina Não Usar Cabeçalhos Pré-Compilados nas propriedades do projeto.

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

Como podes ver no exemplo de código acima, depois de criares a coleção podes adicionar elementos, iterar sobre eles e, de um modo geral, tratar o objeto como tratarias qualquer objeto de coleção do Windows Runtime que possas ter recebido de uma API. Se precisares de uma vista imutável da coleção, podes chamar IVector::GetView, como mostrado. O padrão mostrado acima — de criar e consumir uma coleção — é adequado para cenários simples em que se pretende passar dados para uma API, ou obter dados. Pode passar um IVector ou um IVectorView, em qualquer lugar que se espere um IIterable .

No exemplo de código acima, a chamada a winrt::init_apartment inicializa a thread no Windows Runtime; por predefinição, num apartamento multithreaded. A chamada também inicia o COM.

Recolha de uso geral, preparada a partir de dados

Esta secção cobre o cenário em que deseja criar uma coleção e preenchê-la ao mesmo tempo.

Podes evitar a sobrecarga das chamadas para Append no exemplo de código anterior. Pode já ter os dados de origem, ou pode preferir preencher os dados de origem antes de criar o objeto de coleção do Windows Runtime. Veja como fazer isso.

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

Pode passar um objeto temporário contendo os seus dados para winrt::single_threaded_vector, tal como com coll1, acima. Ou pode mover um std::vector (partindo do princípio de que não voltará a aceder-lhe) para a função. Em ambos os casos, estás a passar um valor r para a função. Isso permite ao compilador ser eficiente e evitar copiar os dados. Se quiseres saber mais sobre rvalores, vê Categorias de valores e referências a elas.

Se quiseres associar um controlo de itens XAML à tua coleção, então podes. Mas tenha em atenção que, para definir corretamente a propriedade ItemsControl.ItemsSource, tem de a definir para um valor do tipo IVector de IInspectable (ou de um tipo de interoperabilidade, como IBindableObservableVector).

Aqui está um exemplo de código que gera uma coleção de um tipo adequado para associação e adiciona-lhe um elemento. Pode encontrar o contexto para este exemplo de código em controlos de itens em XAML; vincule a uma coleção C++/WinRT.

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

Podes criar uma coleção Windows Runtime a partir de dados e ter uma visualização pronta para passar para uma API, tudo isto sem copiar nada.

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

Nos exemplos acima, a coleção que criamos pode ser associada a um controlo XAML items; Mas a coleção não é observável.

Coleção observável

Para obter um novo objeto de um tipo que implemente uma coleção observável, chame o modelo de função winrt::single_threaded_observable_vector com qualquer tipo de elemento. Mas para criar uma coleção observável adequada para ligação a um controlo de itens XAML, use IInspectable como tipo de elemento.

O objeto é devolvido como um IObservableVector, e essa é a interface através da qual tu (ou o controlo ao qual está vinculado) chamas as funções e propriedades do objeto devolvido.

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

Para mais detalhes, e exemplos de código, sobre como associar os controlos da interface de utilizador (UI) a uma coleção observável, veja controlos de itens XAML; associar a uma coleção C++/WinRT.

Coleção associativa (mapa)

Existem versões de coleção associativa das duas funções que analisámos.

Pode, opcionalmente, inicializar estas coleções com dados ao passar à função um rvalue do tipo std::map ou std::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)) };

De rosca única

A designação "single-threaded" nos nomes destas funções indica que não oferecem qualquer concorrência — por outras palavras, não são seguras para threads. A menção a threads não está relacionada com os apartamentos, porque os objetos devolvidos por estas funções são todos ágeis (ver Objetos ágeis no C++/WinRT). É só que os objetos são single-threaded. E isso é perfeitamente apropriado se quiser apenas passar dados para um lado ou para o outro através da interface binária da aplicação (ABI).

Classes base para coleções

Se, para ter total flexibilidade, quiser implementar a sua própria coleção, convém evitar fazê-lo pela via mais difícil. Por exemplo, é assim que uma vista vetorial personalizada seria sem a ajuda das classes base do 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>() };

Em vez disso, é muito mais fácil derivar a tua vista vetorial personalizada a partir do modelo de struct winrt::vector_view_base e simplesmente implementar a função get_container para expor o contentor que contém os teus dados.

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

O contentor devolvido por get_container tem de fornecer a interface begin e end que winrt::vector_view_base espera. Como mostrado no exemplo acima, std::vector fornece isso. Mas pode devolver qualquer contentor que cumpra o mesmo contrato, incluindo o seu próprio contentor personalizado.

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

Estas são as classes base que o C++/WinRT fornece para ajudar a implementar coleções personalizadas.

winrt::vector_view_base

Veja os exemplos de código acima.

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

APIs importantes