Coleções com C++/WinRT

Internamente, uma coleção do Windows Runtime tem muitas partes móveis complicadas. Entretanto, quando você deseja passar um objeto de coleção para uma função do Windows Runtime ou implementar seus próprios tipos de coleção e propriedades de coleção, há funções e classes base no C++/WinRT para ajudá-lo. Esses recursos eliminam a complexidade para você e diminuem a sobrecarga de tempo e esforço.

IVector é a interface do Windows Runtime implementada por qualquer coleção de acesso aleatório de elementos. Se você implementar IVector por conta própria, também precisará implementar IIterable, IVectorView e IIterator. Mesmo se você precisar de um tipo de coleção personalizada, isso envolverá muito trabalho. Porém, se você tiver dados em um std::vector (ou em um std::map ou std::unordered_map) e quiser apenas passá-lo para uma API do Windows Runtime, será melhor evitar esse nível de trabalho. E evitar isso é possível, porque o C++/WinRT ajuda você a criar coleções de forma eficiente e com pouco esforço.

Confira também Controles de itens XAML; associar a uma coleção C++/WinRT.

Funções auxiliares para coleções

Coleção de uso geral, vazia

Esta seção aborda o cenário em que você deseja criar uma coleção que está inicialmente vazia e, em seguida, preenchê-la após a criação.

Para recuperar um novo objeto de um tipo que implementa uma coleção de uso geral, chame o modelo de função winrt::single_threaded_vector. O objeto retorna como um IVector, e essa é a interface por meio da qual você chama as funções e as propriedades do objeto retornado.

Se quiser copiar e colar os exemplos de código a seguir diretamente no arquivo de código-fonte principal de um projeto de Aplicativo de Console do Windows (C++/WinRT) , 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 você pode ver no exemplo de código anterior, após a criação da coleção é possível acrescentar elementos, iterá-los e geralmente tratar o objeto como você faria com qualquer objeto de coleção do Windows Runtime, que você pode ter recebido de uma API. Se precisar de uma exibição imutável da coleção, você poderá chamar IVector::GetView, conforme mostrado. O padrão apresentado anteriormente, de criar e consumir uma coleção, é apropriado para cenários simples em que você deseje passar dados para uma API ou extrair dados dela. Você pode passar um IVector ou um IVectorView em qualquer lugar em que um IIterable é esperado.

No exemplo de código anterior, a chamada para winrt::init_apartment inicializa o thread no Windows Runtime; por padrão, em um Multi-Threaded Apartment. A chamada também inicializa o COM.

Coleção de uso geral, preenchida com dados

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

Você pode evitar a sobrecarga de chamadas para Append no exemplo de código anterior. Talvez você já tenha os dados de origem ou prefira preencher os dados de origem antes de criar o objeto de coleção do Windows Runtime. Veja a seguir 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;
}

Você pode passar um objeto temporário que contenha seus dados para winrt::single_threaded_vector, assim como acontece com coll1, acima. Ou pode mover um std::vector (supondo que você não o acessará novamente) para uma função. Em ambos os casos, você estará passando um rvalue para uma função. Isso permite que o compilador seja eficiente e evite a cópia dos dados. Se quiser saber mais sobre rvalues, confira Categorias de valor e as referências a elas.

Se quiser associar um controle de itens XAML à sua coleção, você poderá. Mas esteja ciente de que, para definir corretamente a propriedade ItemsControl.ItemsSource, será necessário defini-la como um valor de tipo IVector de IInspectable (ou de um tipo de interoperabilidade como IBindableObservableVector).

Veja a seguir um exemplo de código que produz uma coleção de um tipo adequado para associação e acrescenta um elemento a ele. Você pode encontrar o contexto para esse exemplo de código em Controles de itens XAML; associar 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"));

É possível criar uma coleção do Windows Runtime a partir de dados e obter uma exibição dela pronta para passar para uma API, tudo 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 controle de itens XAML; mas a coleção não é observável.

Coleção observável

Para recuperar um novo objeto de um tipo que implementa uma coleção observável, chame o modelo de função winrt::single_threaded_observable_vector com qualquer tipo de elemento. Porém, para tornar uma coleção observável adequada para associação a um controle de itens XAML, use IInspectable como o tipo de elemento.

O objeto retorna como um IObservableVector, e essa é a interface por meio da qual você (ou o controle ao qual ela está associada) chama as funções e as propriedades do objeto retornado.

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

Para obter mais detalhes e exemplos de código sobre a associação de controles da interface do usuário (IU) a uma coleção observável, confira Controles de itens de XAML; associar a uma coleção C++/WinRT.

Coleção associativa (mapa)

Há versões de coleção associativa das duas funções que vimos.

É possível preencher essas coleções com dados, passando para a 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)) };

Single-Thread

O "single-threaded" nos nomes dessas funções indica que elas não fornecem nenhuma simultaneidade, em outras palavras, elas não são thread-safe. A menção a threads não está relacionada a apartments, porque os objetos retornados dessas funções são todos agile (confira Objetos agile no C++/WinRT). Os objetos são single-threaded. Caso você queira apenas passar os dados de uma forma ou de outra pela interface binária do aplicativo (ABI), isso é totalmente apropriado.

Classes base para coleções

Se, para total flexibilidade, você quiser implementar sua própria coleção personalizada, evite fazer isso da maneira mais difícil. Por exemplo, esta é a aparência de uma exibição de vetor personalizada sem a assistência 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 exibição personalizada de vetor do modelo de struct winrt::vector_view_base e apenas implementar a função get_container para expor o contêiner que mantém seus 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 contêiner retornado por get_container deve fornecer o início e o fim da interface que winrt::vector_view_base espera. Conforme mostrado no exemplo anterior, std::vector oferece isso. Porém, é possível retornar qualquer contêiner que atenda ao mesmo contrato, incluindo seu próprio contêiner 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 as coleções personalizadas.

winrt::vector_view_base

Confira 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