Collections avec C++/WinRT

De nombreux éléments complexes interagissent au sein d’une collection Windows Runtime. Toutefois, lorsque vous souhaitez passer un objet de collection à une fonction Windows Runtime, ou pour implémenter vos propres propriétés de collection et types de collection, il existe des fonctions et des classes de base dans C++/WinRT pour vous prendre en charge. Ces fonctionnalités prennent la complexité de vos mains et vous permettent de gagner beaucoup de temps et d’effort.

IVector est l’interface Windows Runtime implémentée par n’importe quelle collection d’éléments à accès aléatoire. Si vous implémentez IVector vous-même, vous devrez également implémenter IIterable, IVectorView et IIterator. Même si vous avez besoin d’un type de collection personnalisé, c’est beaucoup de travail. Toutefois, si vous avez des données dans un vecteur std ::vector (ou un std ::map ou un std ::unordered_map) et que tout ce que vous souhaitez faire est de passer cela à une API Windows Runtime, alors vous souhaitez éviter de faire ce niveau de travail, si possible. Et il est possible de l’éviter, car C++/WinRT vous aide à créer des collections efficacement et avec peu d’effort.

Consultez également les contrôles d’éléments XAML ; liaison à une collection C++/WinRT.

Fonctions utilitaires pour les collections

Collection à usage général, vide

Cette section décrit le scénario dans lequel vous souhaitez créer une collection initialement vide ; puis le remplir après la création.

Pour récupérer un nouvel objet d’un type qui implémente une collection à usage général, vous pouvez appeler le modèle de fonction winrt ::single_threaded_vector . L’objet est renvoyé sous la forme d’un IVector, et c’est l’interface par laquelle vous appelez les fonctions et propriétés de l’objet renvoyé.

Si vous souhaitez copier-coller les exemples de code suivants directement dans le fichier de code source principal d’un projet d’application console Windows (C++/WinRT), commencez par définir Not Using Not Using Precompiled Headers in project properties.

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

Comme vous pouvez le voir dans l’exemple de code ci-dessus, après avoir créé la collection, vous pouvez ajouter des éléments, effectuer une itération sur eux et traiter l’objet comme vous le feriez pour n’importe quel objet de collection Windows Runtime que vous avez peut-être reçu d’une API. Si vous avez besoin d’une vue immuable sur la collection, vous pouvez appeler IVector ::GetView, comme indiqué. Le modèle ci-dessus , de création et de consommation d’une collection, convient aux scénarios simples dans lesquels vous souhaitez transmettre des données, ou extraire des données d’une API. Vous pouvez passer un IVector ou un IVectorView, partout où un IIterable est attendu.

Dans l’exemple de code ci-dessus, l’appel à winrt::init_apartment initialise le thread au sein du runtime Windows ; par défaut, dans un appartement multithreadé. L’appel initialise également COM.

Collection à usage général, pré-entraînée à partir de données

Cette section décrit le scénario dans lequel vous souhaitez créer une collection et la remplir en même temps.

Vous pouvez éviter la surcharge des appels à Append dans l’exemple de code précédent. Vous avez peut-être déjà les données sources ou vous préférez remplir les données sources avant de créer l’objet de collection Windows Runtime. Voici comment procéder :

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

Vous pouvez transmettre un objet temporaire contenant vos données à winrt ::single_threaded_vector, comme avec coll1, ci-dessus. Vous pouvez également déplacer un std ::vector (en supposant que vous ne l’accéderez plus à nouveau) dans la fonction. Dans les deux cas, vous passez une rvalue dans la fonction. Cela permet au compilateur d’être efficace et d’éviter de copier les données. Si vous souhaitez en savoir plus sur les valeurs rvalues, consultez les catégories valeur et les références à celles-ci.

Si vous souhaitez lier un contrôle d’éléments XAML à votre collection, vous pouvez. Toutefois, n’oubliez pas que pour définir correctement la propriété ItemsControl.ItemsSource, vous devez la définir sur une valeur de type IVector d’IInspectable (ou d’un type d’interopérabilité tel que IBindableObservableVector).

Voici un exemple de code qui génère une collection d’un type compatible avec la liaison de données, et y ajoute un élément. Vous trouverez le contexte de cet exemple de code dans les contrôles d’éléments XAML ; liez-vous à une collection C++/WinRT.

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

Vous pouvez créer une collection de Windows Runtime à partir de données et obtenir une vue sur celle-ci prête à être passée à une API, sans copier quoi que ce soit.

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

Dans les exemples ci-dessus, la collection que nous créons peut être liée à un contrôle d’éléments XAML ; mais la collection n’est pas observable.

Collection observable

Pour récupérer un nouvel objet d’un type qui implémente une collection observable , appelez le modèle de fonction winrt ::single_threaded_observable_vector avec n’importe quel type d’élément. Mais pour rendre une collection observable adaptée à la liaison à un contrôle d’éléments XAML, utilisez IInspectable comme type d’élément.

L’objet est retourné sous la forme d’un IObservableVector, et c’est l’interface via laquelle vous (ou le contrôle auquel il est lié) appelez les fonctions et propriétés de l’objet retourné.

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

Pour plus d’informations et des exemples de code, sur la liaison de contrôles d’interface utilisateur à une collection observable, consultez les contrôles d’éléments XAML ; liaison à une collection C++/WinRT.

Collection associatif (carte)

Il existe des versions de collection associative des deux fonctions que nous avons examinées.

Vous pouvez éventuellement primer ces collections avec des données en passant à la fonction une rvalue de type 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)) };

Monothread

Le terme « monothread » dans le nom de ces fonctions indique qu’elles ne prennent pas en charge l’exécution concurrente ; en d’autres termes, elles ne sont pas sûres dans un environnement multithread. La mention des threads n’est pas liée aux appartements, car les objets retournés par ces fonctions sont tous agiles (voir Les objets Agiles en C++/WinRT). C’est simplement que les objets sont monothread. Et c’est tout à fait approprié si vous souhaitez simplement transmettre des données d’une manière ou de l’autre dans l’interface binaire d’application (ABI).

Classes de base pour les collections

Si, pour une flexibilité complète, vous souhaitez implémenter votre propre collection personnalisée, vous voudrez éviter de le faire de façon difficile. Par exemple, c’est à quoi ressemblerait une vue vectorielle personnalisée sans l’aide des classes de base de 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>() };

Au lieu de cela, il est beaucoup plus facile de dériver votre vue vectorielle personnalisée à partir du modèle de struct winrt ::vector_view_base , et il vous suffit d’implémenter la fonction get_container pour exposer le conteneur contenant vos données.

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

Le conteneur retourné par get_container doit fournir l’interface de début et de fin attendue par winrt ::vector_view_base . Comme illustré dans l’exemple ci-dessus, std ::vector fournit cela. Mais vous pouvez retourner n’importe quel conteneur qui remplit le même contrat, y compris votre propre conteneur personnalisé.

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

Il s’agit des classes de base que C++/WinRT fournit pour vous aider à implémenter des collections personnalisées.

winrt ::vector_view_base

Consultez les exemples de code ci-dessus.

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 importantes